Files
kaiser-natron/src/design-system/components/Input.vue
Dorian 7bd8e0a181 chore: initial commit
Vue 3 + Tailwind v4 frontend scaffold with living design system
at /design. Pinned dependencies, dev-only a11y toolbar with
colour-vision simulation, WCAG contrast checker, and axe-core audit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 15:09:27 +01:00

56 lines
2.0 KiB
Vue

<script setup>
import { computed, useId } from 'vue'
const props = defineProps({
modelValue: { type: [String, Number], default: '' },
label: { type: String, default: '' },
hint: { type: String, default: '' },
error: { type: String, default: '' },
type: { type: String, default: 'text' },
placeholder: { type: String, default: '' },
disabled: { type: Boolean, default: false },
required: { type: Boolean, default: false },
})
defineEmits(['update:modelValue'])
const uid = useId()
const inputId = computed(() => `in-${uid}`)
const hintId = computed(() => (props.hint ? `hint-${uid}` : undefined))
const errorId = computed(() => (props.error ? `err-${uid}` : undefined))
const describedBy = computed(
() => [hintId.value, errorId.value].filter(Boolean).join(' ') || undefined,
)
</script>
<template>
<div class="flex flex-col gap-2">
<label
v-if="label"
:for="inputId"
class="text-[11px] font-bold uppercase tracking-[var(--tracking-eyebrow)] text-[var(--color-muted)]"
>
{{ label }}<span v-if="required" class="text-[var(--color-danger)]"> *</span>
</label>
<input
:id="inputId"
:type="type"
:value="modelValue"
:placeholder="placeholder"
:disabled="disabled"
:required="required"
:aria-invalid="!!error"
:aria-describedby="describedBy"
class="w-full rounded-[var(--radius-sm)] border bg-[var(--color-paper)] px-4 py-3 text-[15px] text-[var(--color-ink)]
placeholder:text-[color:rgba(13,31,19,0.35)]
transition-colors duration-[var(--duration-base)]
focus:outline-none focus:border-[var(--color-brand)]
disabled:opacity-50 disabled:cursor-not-allowed"
:class="error ? 'border-[var(--color-danger)]' : 'border-[var(--color-line)]'"
@input="$emit('update:modelValue', $event.target.value)"
/>
<p v-if="hint && !error" :id="hintId" class="text-[13px] text-[var(--color-muted)]">{{ hint }}</p>
<p v-if="error" :id="errorId" class="text-[13px] text-[var(--color-danger)]">{{ error }}</p>
</div>
</template>