Skip to content

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

PropTypeDefaultDescription
modelValuestring''Current query (v-model).
placeholderstringundefinedPlaceholder shown when empty.
loadingbooleanfalseCross-fades the leading search icon into a spinner.
disabledbooleanfalseDisables the field.
inputmode'text' | 'numeric' | 'decimal' | 'tel' | 'search' | 'email' | 'url' | 'none'undefinedPassed 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

EventPayloadFired when
update:modelValuestringStandard v-model, on every input.
clearThe clear button is clicked (also emits update:modelValue with '').

Behavior notes

  • No search/debounce/fetch logic lives here — the consumer still owns querying (see SearchResultList for the matching result rows) and just flips loading while a request is in flight.
  • The clear (×) button only renders once modelValue is 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 -inset token.
  • prefers-reduced-motion: reduce keeps the icon cross-fade but drops the spinner to a slower, simpler rotation and removes the clear-button scale-in.