feat(fe): Add form field navigation - Enter key moves to next field

- Create useFormFieldNavigation composable for Tab-like Enter behavior
- KsTextField: Enter -> next field
- KsTextArea: Ctrl+Enter for newline, Enter -> next field
- KsSelect: Enter -> next field after selection
- Implements standard form navigation pattern across input components
This commit is contained in:
2026-08-16 21:50:50 +09:00
parent 5213ea142b
commit b12fc7411d
25 changed files with 1105 additions and 85 deletions
@@ -1,5 +1,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useFieldLayout } from '../contracts/layoutContext'
import { useFormFieldNavigation } from '../composables/useFormFieldNavigation'
export interface KsTextFieldProps {
modelValue: string
@@ -17,6 +19,7 @@ export interface KsTextFieldProps {
autocomplete?: string
inputId?: string
size?: 'sm' | 'md' | 'lg'
inline?: boolean
}
const props = withDefaults(defineProps<KsTextFieldProps>(), {
@@ -36,11 +39,14 @@ const emit = defineEmits<{
const isFocused = ref(false)
const id = computed(() => props.inputId || `input-${Math.random().toString(36).substr(2, 9)}`)
const { isInline } = useFieldLayout(props.inline)
const { inputRef, handleKeyDown } = useFormFieldNavigation()
const containerClass = computed(() => [
'ks-text-field',
`ks-text-field--${props.size}`,
{
'ks-text-field--inline': isInline.value,
'ks-text-field--focused': isFocused.value,
'ks-text-field--filled': props.modelValue,
'ks-text-field--disabled': props.disabled,
@@ -49,6 +55,8 @@ const containerClass = computed(() => [
},
])
const handleFocus = (event: FocusEvent) => {
isFocused.value = true
emit('focus', event)
@@ -64,6 +72,10 @@ const handleInput = (event: Event) => {
emit('update:modelValue', target.value)
emit('input', event)
}
const handleKeyPress = (event: KeyboardEvent) => {
handleKeyDown(event, false)
}
</script>
<template>
@@ -77,6 +89,7 @@ const handleInput = (event: Event) => {
<!-- Input container with focus ring -->
<div class="ks-text-field__container">
<input
ref="inputRef"
:id="id"
:type="type"
:value="modelValue"
@@ -94,6 +107,7 @@ const handleInput = (event: Event) => {
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@keydown="handleKeyPress"
/>
</div>
@@ -119,6 +133,32 @@ const handleInput = (event: Event) => {
gap: var(--spacing-2);
}
.ks-text-field--inline {
flex-direction: row;
align-items: center;
gap: var(--ks-space-2, 6px);
}
.ks-text-field--inline .ks-text-field__label {
width: var(--ks-field-label-width, 72px);
min-width: var(--ks-field-label-width, 72px);
max-width: var(--ks-field-label-width, 72px);
text-align: left;
white-space: nowrap;
flex-shrink: 0;
margin-bottom: 0;
}
.ks-text-field--inline .ks-text-field__container {
flex: 1 1 auto;
min-width: 0;
max-width: 100%;
width: 100%;
}
.ks-text-field--sm {
--input-height: 32px;
--font-size: var(--font-size-sm);