SearchField
A themed search input — reworks meal-tracker's ad-hoc ErfassenTab search box (a plain .input-icon div wrapping a bare <input>) into a proper yemek component: a leading icon that cross-fades into a spinner while loading, and a clear button that only appears once there's something to clear.
Playground
Type something, or toggle loading.
vue
<script setup>
import { ref } from 'vue'
import { SearchField, SearchResultList } from 'yemek'
const query = ref('')
const loading = ref(false)
const results = ref([])
let timeout
function onSearch(value) {
query.value = value
if (!value.trim()) { results.value = []; return }
loading.value = true
clearTimeout(timeout)
timeout = setTimeout(async () => {
results.value = await searchFood(value)
loading.value = false
}, 400)
}
</script>
<template>
<SearchField :model-value="query" placeholder="Lebensmittel suchen..." :loading="loading" @update:model-value="onSearch" />
<SearchResultList v-if="results.length" :results="results" @select="pickResult" />
</template>Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | '' | Current query (v-model). |
placeholder | string | undefined | Placeholder shown when empty. |
loading | boolean | false | Cross-fades the leading search icon into a spinner. |
disabled | boolean | false | Disables the field. |
inputmode | 'text' | 'numeric' | 'decimal' | 'tel' | 'search' | 'email' | 'url' | 'none' | undefined | Passed straight through to the inner <input> (e.g. numeric for a barcode field's on-screen keypad). Not interpreted here — same "consumer decides" rule as the rest of the kit. |
Events
| Event | Payload | Fired when |
|---|---|---|
update:modelValue | string | Standard v-model, on every input. |
clear | — | The clear button is clicked (also emits update:modelValue with ''). |
Behavior notes
- No search/debounce/fetch logic lives here — the consumer still owns querying (see
SearchResultListfor the matching result rows) and just flipsloadingwhile a request is in flight. - The clear (×) button only renders once
modelValueis non-empty, and scales in rather than appearing instantly. - The leading icon and the spinner are cross-faded (opacity + scale), not swapped instantly — same "always animate state changes" rule as the rest of the kit (CLAUDE.md §1b).
- Background is derived relative to the surface it's actually placed on (same convention as
Textarea), not the separate-insettoken. prefers-reduced-motion: reducekeeps the icon cross-fade but drops the spinner to a slower, simpler rotation and removes the clear-button scale-in.