autocomplete

With search results

With search results

Загрузка превью…

src/autocomplete/c-autocomplete-9.vue

<!--
  Кейс проверяет только закрытое состояние (см. docs/PORTING.md §5):
  попап монтируется только когда `searchValue !== ""`, изначально пусто.
  `IconPlaceholder` (спиннер загрузки) заменён инлайновым `<svg>`
  (lucide "loader-circle") — виден только в статусе при непустом
  запросе, но адаптирован для полноты. Датасет разработчиков сокращён
  (3 записи вместо 25) и сетевые аватары (randomuser.me) заменены
  статичным 1x1 data-URI (PIXEL) — на закрытое состояние не влияет.
-->
<script setup lang="ts">
import { ref, watch } from "vue"
import { Autocomplete, AutocompleteContent, AutocompleteInput, AutocompleteItem, AutocompleteList, AutocompleteStatus } from "@/components/reui/autocomplete"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"

const PIXEL =
  "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="

interface Developer {
  id: string
  name: string
  role: string
  location: string
  avatar: string
}

const topDevelopers: Developer[] = [
  { id: "1", name: "Alex Chen", role: "Senior Full-Stack Developer", location: "San Francisco, CA", avatar: PIXEL },
  { id: "2", name: "Sarah Johnson", role: "Frontend Architect", location: "New York, NY", avatar: PIXEL },
  { id: "3", name: "Michael Rodriguez", role: "Backend Engineer", location: "Austin, TX", avatar: PIXEL },
]

const searchValue = ref("")
const isLoading = ref(false)
const searchResults = ref<Developer[]>([])
const error = ref<string | null>(null)

let timeoutId: ReturnType<typeof setTimeout> | undefined

function search(query: string) {
  const q = query.toLowerCase()
  return topDevelopers.filter(
    (d) => d.name.toLowerCase().includes(q) || d.role.toLowerCase().includes(q) || d.location.toLowerCase().includes(q)
  )
}

watch(searchValue, (v) => {
  clearTimeout(timeoutId)
  if (!v) {
    searchResults.value = []
    isLoading.value = false
    return
  }
  isLoading.value = true
  error.value = null
  timeoutId = setTimeout(() => {
    try {
      searchResults.value = search(v)
    } catch {
      error.value = "Failed to fetch developers. Please try again."
      searchResults.value = []
    } finally {
      isLoading.value = false
    }
  }, 300)
})

const shouldRenderPopup = () => searchValue.value !== ""
</script>

<template>
  <div class="w-full max-w-xs">
    <Autocomplete v-model="searchValue">
      <AutocompleteInput placeholder="e.g. John Smith, React, San Francisco" show-trigger show-clear />
      <AutocompleteContent v-if="shouldRenderPopup()">
        <AutocompleteStatus>
          <div v-if="isLoading" class="flex items-center gap-2">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="24"
              height="24"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
              aria-hidden="true"
              class="lucide lucide-loader-circle size-4 animate-spin"
            >
              <path d="M21 12a9 9 0 1 1-6.219-8.56" />
            </svg>
            Searching developers...
          </div>
          <template v-else-if="error">{{ error }}</template>
          <template v-else-if="searchResults.length === 0 && searchValue">{{ `No developers found for "${searchValue}"` }}</template>
          <template v-else-if="searchResults.length > 0">{{ `${searchResults.length} developer${searchResults.length === 1 ? "" : "s"} found` }}</template>
          <template v-else-if="!searchValue">Start typing to search developers...</template>
        </AutocompleteStatus>
        <AutocompleteList>
          <AutocompleteItem v-for="developer in searchResults" :key="developer.id" :value="developer.name" class="rounded-lg">
            <div class="flex items-center gap-2.5 truncate">
              <Avatar class="size-9">
                <AvatarImage :src="developer.avatar" :alt="developer.name" />
                <AvatarFallback>{{ developer.name.split(" ").map((n) => n[0]).join("") }}</AvatarFallback>
              </Avatar>
              <div class="min-w-0 flex-1">
                <div class="truncate font-medium">{{ developer.name }}</div>
                <div class="text-muted-foreground truncate text-sm">{{ `${developer.role} • ${developer.location}` }}</div>
              </div>
            </div>
          </AutocompleteItem>
        </AutocompleteList>
      </AutocompleteContent>
    </Autocomplete>
  </div>
</template>

Установка

npx shadcn-vue@latest add https://revueui.rootapi.dev/r/c-autocomplete-9.json

Зависимости реестра

Источник: порт из ReUI (Keenthemes, MIT)