Files
kjh2064 3f293d8aa8
deploy / deploy (push) Successful in 1m52s
deploy / notify (push) Successful in 1s
V13-FE-006: consolidate approved UI and contract hardening
2026-08-13 02:41:00 +09:00

14 lines
3.0 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue'
import type { KbxFieldState } from '@kbx/contracts'
const props=withDefaults(defineProps<{modelValue?:string|null;label:string;required?:boolean;readonly?:boolean;disabled?:boolean;error?:string;warning?:string;helpText?:string;state?:KbxFieldState}>(),{state:'default'})
const emit=defineEmits<{ 'update:modelValue':[string] }>()
const uid=`kbx-date-${Math.random().toString(36).slice(2)}`
const messageId=`${uid}-message`
const effectiveState=computed(()=>props.error?'error':props.warning?'warning':props.state)
const message=computed(()=>props.error||props.warning||props.helpText||'')
function normalize(value:string){const digits=value.replace(/\D/g,'');return digits.length===8?`${digits.slice(0,4)}-${digits.slice(4,6)}-${digits.slice(6,8)}`:value}
</script>
<template><div class="kbx-field" :data-state="effectiveState"><label :for="uid" class="kbx-field__label">{{label}}<span v-if="required" aria-hidden="true"> *</span></label><input :id="uid" class="kbx-input" inputmode="numeric" :value="modelValue??''" :readonly="readonly" :disabled="disabled" :required="required" placeholder="YYYY-MM-DD" :aria-invalid="Boolean(error)" :aria-describedby="message?messageId:undefined" @change="emit('update:modelValue',normalize(($event.target as HTMLInputElement).value))"><span v-if="message" :id="messageId" class="kbx-field__message" :role="error?'alert':undefined">{{message}}</span></div></template>
<style scoped>.kbx-field{display:grid;grid-template-columns:var(--kbx-label-width) minmax(0,1fr);gap:var(--kbx-space-2);align-items:start;min-height:var(--kbx-control-height);font-size:var(--kbx-font-md)}.kbx-field__label{padding-top:var(--kbx-space-2);font-weight:500;white-space:nowrap}.kbx-input{height:var(--kbx-control-height);border:var(--kbx-border-width) solid var(--kbx-color-border-strong);border-radius:var(--kbx-radius-sm);padding:0 var(--kbx-space-3);font:inherit;background:var(--kbx-color-surface);color:var(--kbx-color-text)}.kbx-field[data-state="changed"] .kbx-input{border-color:var(--kbx-color-changed-border);background:var(--kbx-color-changed-surface)}.kbx-field[data-state="warning"] .kbx-input{border-color:var(--kbx-color-warning-border);background:var(--kbx-color-warning-surface)}.kbx-field[data-state="ai-suggested"] .kbx-input{border-color:var(--kbx-color-ai-border);background:var(--kbx-color-ai-surface)}.kbx-field[data-state="error"] .kbx-input{border-color:var(--kbx-color-danger);background:var(--kbx-color-danger-surface)}.kbx-input[readonly]{background:var(--kbx-color-surface-muted)}.kbx-input:disabled{background:var(--kbx-color-surface-subtle);color:var(--kbx-color-text-muted)}.kbx-field__message{grid-column:2;color:var(--kbx-color-text-muted);font-size:var(--kbx-font-xs);margin-top:calc(var(--kbx-space-1) * -1)}.kbx-field[data-state="warning"] .kbx-field__message{color:var(--kbx-color-warning-text)}.kbx-field[data-state="error"] .kbx-field__message{color:var(--kbx-color-danger)}.kbx-field[data-state="ai-suggested"] .kbx-field__message{color:var(--kbx-color-ai-text)}</style>