V13-FE-006: consolidate approved UI and contract hardening
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { UiSeverity } from '../contracts'
|
||||
withDefaults(defineProps<{ value: string; severity?: UiSeverity }>(), { severity: 'info' })
|
||||
import type { UiGridStatusSemantic } from '../../gridStatus'
|
||||
withDefaults(defineProps<{ value: string; severity?: UiSeverity; semantic?: UiGridStatusSemantic; unknown?: boolean }>(), { severity: 'info', unknown: false })
|
||||
</script>
|
||||
<template><span class="ks-native-tag" :class="`is-${severity}`">{{ value }}</span></template>
|
||||
<template><span class="ks-native-tag" :class="[`is-${severity}`, { 'is-unknown': unknown }]" :data-semantic="semantic" :aria-label="unknown ? `정의되지 않음: ${value}` : value"><span aria-hidden="true">{{ unknown ? '?' : '●' }}</span> {{ value }}</span></template>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{ modelValue: string; inputId?: string; disabled?: boolean; invalid?: boolean; placeholder?: string }>()
|
||||
import type { UiTextFieldType } from '../contracts'
|
||||
defineProps<{ modelValue: string; inputId?: string; type?: UiTextFieldType; disabled?: boolean; invalid?: boolean; placeholder?: string }>()
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: string]; blur: [event: FocusEvent] }>()
|
||||
</script>
|
||||
<template><input :id="inputId" class="ks-native-input" type="text" :value="modelValue" :disabled="disabled" :aria-invalid="invalid || undefined" :placeholder="placeholder" @input="emit('update:modelValue', ($event.target as HTMLInputElement).value)" @blur="emit('blur', $event)" /></template>
|
||||
<template><input :id="inputId" class="ks-native-input" :type="type ?? 'text'" :value="modelValue" :disabled="disabled" :aria-invalid="invalid || undefined" :placeholder="placeholder" @input="emit('update:modelValue', ($event.target as HTMLInputElement).value)" @blur="emit('blur', $event)" /></template>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import Tag from 'primevue/tag'
|
||||
import type { UiSeverity } from '../contracts'
|
||||
import type { UiGridStatusSemantic } from '../../gridStatus'
|
||||
|
||||
defineProps<{ value: string; severity?: UiSeverity; iconLabel?: string }>()
|
||||
defineProps<{ value: string; severity?: UiSeverity; iconLabel?: string; semantic?: UiGridStatusSemantic; unknown?: boolean }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Tag class="ks-status-tag" :severity="severity ?? 'info'">
|
||||
<span v-if="iconLabel" aria-hidden="true">{{ iconLabel }}</span>
|
||||
<Tag class="ks-status-tag" :severity="severity ?? 'info'" :data-semantic="semantic" :data-unknown="unknown || undefined" :aria-label="unknown ? `정의되지 않음: ${value}` : value">
|
||||
<span aria-hidden="true">{{ iconLabel ?? (unknown ? '?' : '●') }}</span>
|
||||
<span>{{ value }}</span>
|
||||
</Tag>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import InputText from 'primevue/inputtext'
|
||||
import type { UiTextFieldType } from '../contracts'
|
||||
|
||||
defineProps<{ modelValue: string; inputId?: string; disabled?: boolean; invalid?: boolean; placeholder?: string }>()
|
||||
defineProps<{ modelValue: string; inputId?: string; type?: UiTextFieldType; disabled?: boolean; invalid?: boolean; placeholder?: string }>()
|
||||
defineEmits<{ 'update:modelValue': [value: string]; blur: [event: FocusEvent] }>()
|
||||
</script>
|
||||
|
||||
@@ -10,6 +11,7 @@ defineEmits<{ 'update:modelValue': [value: string]; blur: [event: FocusEvent] }>
|
||||
class="ks-input"
|
||||
:id="inputId"
|
||||
:model-value="modelValue"
|
||||
:type="type"
|
||||
:disabled="disabled"
|
||||
:invalid="invalid"
|
||||
:placeholder="placeholder"
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { defineAsyncComponent, type Component } from 'vue'
|
||||
import type { UiAdapter, UiAdapterCapability } from '../contracts'
|
||||
import Button from './PrimeButtonAdapter.vue'
|
||||
import TextField from './PrimeTextFieldAdapter.vue'
|
||||
import TextArea from './PrimeTextAreaAdapter.vue'
|
||||
import Select from './PrimeSelectAdapter.vue'
|
||||
import MultiSelect from './PrimeMultiSelectAdapter.vue'
|
||||
import Checkbox from './PrimeCheckboxAdapter.vue'
|
||||
import DateField from './PrimeDateFieldAdapter.vue'
|
||||
import NumberField from './PrimeNumberFieldAdapter.vue'
|
||||
import Dialog from './PrimeDialogAdapter.vue'
|
||||
import StatusTag from './PrimeStatusTagAdapter.vue'
|
||||
import InlineMessage from './PrimeInlineMessageAdapter.vue'
|
||||
import Paginator from './PrimePaginatorAdapter.vue'
|
||||
import Tabs from './PrimeTabsAdapter.vue'
|
||||
import DataGrid from './AgGridAdapter.vue'
|
||||
const lazy = (loader: () => Promise<{ default: Component }>) => defineAsyncComponent({ loader, suspensible: false })
|
||||
const Button = lazy(() => import('./PrimeButtonAdapter.vue'))
|
||||
const TextField = lazy(() => import('./PrimeTextFieldAdapter.vue'))
|
||||
const TextArea = lazy(() => import('./PrimeTextAreaAdapter.vue'))
|
||||
const Select = lazy(() => import('./PrimeSelectAdapter.vue'))
|
||||
const MultiSelect = lazy(() => import('./PrimeMultiSelectAdapter.vue'))
|
||||
const Checkbox = lazy(() => import('./PrimeCheckboxAdapter.vue'))
|
||||
const DateField = lazy(() => import('./PrimeDateFieldAdapter.vue'))
|
||||
const NumberField = lazy(() => import('./PrimeNumberFieldAdapter.vue'))
|
||||
const Dialog = lazy(() => import('./PrimeDialogAdapter.vue'))
|
||||
const StatusTag = lazy(() => import('./PrimeStatusTagAdapter.vue'))
|
||||
const InlineMessage = lazy(() => import('./PrimeInlineMessageAdapter.vue'))
|
||||
const Paginator = lazy(() => import('./PrimePaginatorAdapter.vue'))
|
||||
const Tabs = lazy(() => import('./PrimeTabsAdapter.vue'))
|
||||
const DataGrid = lazy(() => import('./AgGridAdapter.vue'))
|
||||
|
||||
const capabilities: ReadonlySet<UiAdapterCapability> = new Set([
|
||||
'button','text-field','text-area','select','multi-select','checkbox','date-field','number-field',
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { readdirSync, readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
function sourceFiles(root: string): string[] {
|
||||
return readdirSync(root, { withFileTypes: true }).flatMap(entry => {
|
||||
const path = join(root, entry.name)
|
||||
if (entry.isDirectory()) return sourceFiles(path)
|
||||
return /\.(ts|vue)$/.test(entry.name) ? [path] : []
|
||||
})
|
||||
}
|
||||
|
||||
describe('UI vendor boundary', () => {
|
||||
it('keeps PrimeVue and AG Grid imports inside the shared adapter', () => {
|
||||
const featureRoot = join(process.cwd(), 'src', 'features')
|
||||
const forbiddenImport = /(?:from|import\s*\()\s*["'](?:primevue|ag-grid)/
|
||||
const violations = sourceFiles(featureRoot).filter(path => forbiddenImport.test(readFileSync(path, 'utf8')))
|
||||
|
||||
expect(violations).toEqual([])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user