HextaUI

Combobox

Çipler, gruplar ve asenkron sonuçlar içeren, siz yazdıkça boyutu değişen bir açılır pencerede filtrelenebilir bir select.

"use client"

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"

const fruits = [
  "Apple",
  "Apricot",
  "Banana",
  "Blackberry",
  "Blueberry",
  "Cherry",
  "Grape",
  "Grapefruit",
  "Kiwi",
  "Lychee",
  "Mango",
  "Orange",
  "Papaya",
  "Peach",
  "Pear",
  "Pineapple",
  "Plum",
  "Raspberry",
  "Strawberry",
  "Watermelon",
]

export function ComboboxDemo() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Label htmlFor="combobox-demo">Fruit</Label>
      <Combobox items={fruits}>
        <ComboboxInput id="combobox-demo" placeholder="Select a fruit" />
        <ComboboxContent>
          <ComboboxEmpty>No fruit found.</ComboboxEmpty>
          <ComboboxList>
            {(item: string) => (
              <ComboboxItem key={item} value={item}>
                {item}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/combobox.json

Bileşeni, HextaUI tema token'larını ve bileşenin bağımlı olduğu tüm HextaUI bileşenlerini ekler.

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@/components/ui/combobox"
const fruits = ["Apple", "Banana", "Cherry"]

<Combobox items={fruits}>
  <ComboboxInput placeholder="Select a fruit" />
  <ComboboxContent>
    <ComboboxEmpty>No fruit found.</ComboboxEmpty>
    <ComboboxList>
      {(item: string) => (
        <ComboboxItem key={item} value={item}>
          {item}
        </ComboboxItem>
      )}
    </ComboboxList>
  </ComboboxContent>
</Combobox>

Seçenekleri items'a geçin ve her birini <ComboboxList /> içindeki bir fonksiyonla render edin. Combobox, siz yazdıkça onları filtreler ve yalnızca eşleşmeleri render eder. Nesneler de çalışır: label değerleri girdide gösterilir, value değerleri gönderilir.

Listeyi filtrelemek için alana yazın.

Combobox
├── ComboboxInput
└── ComboboxContent
    ├── ComboboxEmpty
    ├── ComboboxStatus
    └── ComboboxList
        ├── ComboboxItem
        ├── ComboboxGroup
        │   ├── ComboboxLabel
        │   └── ComboboxCollection
        │       └── ComboboxItem
        └── ComboboxSeparator

Bir düğme değeri gösterir ve arama alanı popup'ın içine taşınır.

Combobox
├── ComboboxTrigger
│   └── ComboboxValue
└── ComboboxContent
    ├── ComboboxInput
    ├── ComboboxEmpty
    └── ComboboxList
        └── ComboboxItem

multiple ile her seçili öğe girdiden önce bir chip olur.

Combobox
├── ComboboxChips
│   └── ComboboxValue
│       ├── ComboboxChip
│       └── ComboboxChipsInput
└── ComboboxContent
    ├── ComboboxEmpty
    └── ComboboxList
        └── ComboboxItem

Temizle düğmesi

showClear bir değer varken okun yerini alan bir temizle düğmesi ekler; böylece alan asla büyümez.

"use client"

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"

const fruits = [
  "Apple",
  "Apricot",
  "Banana",
  "Blackberry",
  "Blueberry",
  "Cherry",
  "Grape",
  "Grapefruit",
  "Kiwi",
  "Lychee",
  "Mango",
  "Orange",
  "Papaya",
  "Peach",
  "Pear",
  "Pineapple",
  "Plum",
  "Raspberry",
  "Strawberry",
  "Watermelon",
]

export function ComboboxWithClear() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Label htmlFor="combobox-clear">Fruit</Label>
      <Combobox items={fruits} defaultValue="Mango">
        <ComboboxInput
          id="combobox-clear"
          placeholder="Select a fruit"
          showClear
        />
        <ComboboxContent>
          <ComboboxEmpty>No fruit found.</ComboboxEmpty>
          <ComboboxList>
            {(item: string) => (
              <ComboboxItem key={item} value={item}>
                {item}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  )
}

Simgelerle

Bir öğenin içindeki simgeler sizin için boyutlandırılır ve soluklaştırılır. autoHighlight yazarken ilk eşleşmeyi vurgular; böylece Enter onu seçer.

"use client"

import type * as React from "react"
import {
  IconBrandAngular,
  IconBrandNextjs,
  IconBrandReact,
  IconBrandSvelte,
  IconBrandVue,
} from "@tabler/icons-react"

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"

type Framework = {
  value: string
  label: string
  icon: React.ComponentType<{ className?: string }>
}

const frameworks: Framework[] = [
  { value: "next", label: "Next.js", icon: IconBrandNextjs },
  { value: "react", label: "React", icon: IconBrandReact },
  { value: "vue", label: "Vue", icon: IconBrandVue },
  { value: "svelte", label: "Svelte", icon: IconBrandSvelte },
  { value: "angular", label: "Angular", icon: IconBrandAngular },
]

export function ComboboxWithIcons() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Label htmlFor="combobox-icons">Framework</Label>
      <Combobox items={frameworks} autoHighlight>
        <ComboboxInput id="combobox-icons" placeholder="Select a framework" />
        <ComboboxContent>
          <ComboboxEmpty>No framework found.</ComboboxEmpty>
          <ComboboxList>
            {(item: Framework) => (
              <ComboboxItem key={item.value} value={item}>
                <item.icon />
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  )
}

Gruplar ve ayırıcılar

{ value, items } biçiminde gruplar geçin ve her birini <ComboboxGroup />, <ComboboxLabel /> ve <ComboboxCollection /> ile render edin. Boş gruplar filtreleme sırasında gizlenir.

"use client"

import * as React from "react"

import {
  Combobox,
  ComboboxCollection,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxGroup,
  ComboboxInput,
  ComboboxItem,
  ComboboxLabel,
  ComboboxList,
  ComboboxSeparator,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"

type Timezone = { value: string; label: string }
type TimezoneGroup = { value: string; items: Timezone[] }

const timezones: TimezoneGroup[] = [
  {
    value: "Americas",
    items: [
      { value: "America/New_York", label: "New York (GMT-4)" },
      { value: "America/Chicago", label: "Chicago (GMT-5)" },
      { value: "America/Los_Angeles", label: "Los Angeles (GMT-7)" },
      { value: "America/Sao_Paulo", label: "São Paulo (GMT-3)" },
    ],
  },
  {
    value: "Europe",
    items: [
      { value: "Europe/London", label: "London (GMT+1)" },
      { value: "Europe/Paris", label: "Paris (GMT+2)" },
      { value: "Europe/Berlin", label: "Berlin (GMT+2)" },
    ],
  },
  {
    value: "Asia",
    items: [
      { value: "Asia/Kolkata", label: "Kolkata (GMT+5:30)" },
      { value: "Asia/Tokyo", label: "Tokyo (GMT+9)" },
      { value: "Asia/Singapore", label: "Singapore (GMT+8)" },
    ],
  },
]

export function ComboboxGroups() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Label htmlFor="combobox-groups">Timezone</Label>
      <Combobox items={timezones} autoHighlight>
        <ComboboxInput id="combobox-groups" placeholder="Select a timezone" />
        <ComboboxContent>
          <ComboboxEmpty>No timezone found.</ComboboxEmpty>
          <ComboboxList>
            {(group: TimezoneGroup, index: number) => (
              <React.Fragment key={group.value}>
                {index > 0 ? <ComboboxSeparator /> : null}
                <ComboboxGroup items={group.items}>
                  <ComboboxLabel>{group.value}</ComboboxLabel>
                  <ComboboxCollection>
                    {(item: Timezone) => (
                      <ComboboxItem key={item.value} value={item}>
                        {item.label}
                      </ComboboxItem>
                    )}
                  </ComboboxCollection>
                </ComboboxGroup>
              </React.Fragment>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  )
}

Çoklu

multiple ile seçimler <ComboboxChips /> içinde chip olur. Siz seçerken popup açık kalır, boş girdide Backspace son chip'i kaldırır ve ok tuşları chip'ler arasında gezinir.

"use client"

import type * as React from "react"
import {
  IconBrandAngular,
  IconBrandNextjs,
  IconBrandReact,
  IconBrandSvelte,
  IconBrandVue,
} from "@tabler/icons-react"

import {
  Combobox,
  ComboboxChip,
  ComboboxChips,
  ComboboxChipsInput,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxItem,
  ComboboxList,
  ComboboxValue,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"

type Framework = {
  value: string
  label: string
  icon: React.ComponentType<{ className?: string }>
}

const frameworks: Framework[] = [
  { value: "next", label: "Next.js", icon: IconBrandNextjs },
  { value: "react", label: "React", icon: IconBrandReact },
  { value: "vue", label: "Vue", icon: IconBrandVue },
  { value: "svelte", label: "Svelte", icon: IconBrandSvelte },
  { value: "angular", label: "Angular", icon: IconBrandAngular },
]

export function ComboboxMultiple() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Label htmlFor="combobox-multiple">Frameworks</Label>
      <Combobox
        items={frameworks}
        multiple
        autoHighlight
        defaultValue={[frameworks[0], frameworks[1]]}
      >
        <ComboboxChips>
          <ComboboxValue>
            {(values: Framework[]) => (
              <>
                {values.map((value) => (
                  <ComboboxChip key={value.value}>{value.label}</ComboboxChip>
                ))}
                <ComboboxChipsInput
                  id="combobox-multiple"
                  placeholder={values.length > 0 ? "" : "Add frameworks"}
                />
              </>
            )}
          </ComboboxValue>
        </ComboboxChips>
        <ComboboxContent>
          <ComboboxEmpty>No framework found.</ComboboxEmpty>
          <ComboboxList>
            {(item: Framework) => (
              <ComboboxItem key={item.value} value={item}>
                <item.icon />
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  )
}

Popup içinde ara

Select benzeri bir alan için <ComboboxTrigger /> kullanın. Girdiyi <ComboboxContent /> içine koyarsanız simgeli bir arama kutusuna dönüşür ve popup en az 15rem'e genişler.

"use client"

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxTrigger,
  ComboboxValue,
} from "@/components/ui/combobox"

const countries = [
  { value: "ar", label: "Argentina" },
  { value: "au", label: "Australia" },
  { value: "br", label: "Brazil" },
  { value: "ca", label: "Canada" },
  { value: "de", label: "Germany" },
  { value: "fr", label: "France" },
  { value: "in", label: "India" },
  { value: "jp", label: "Japan" },
  { value: "mx", label: "Mexico" },
  { value: "nl", label: "Netherlands" },
  { value: "uk", label: "United Kingdom" },
  { value: "us", label: "United States" },
]

export function ComboboxPopupSearch() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Combobox items={countries}>
        <ComboboxTrigger aria-label="Country">
          <ComboboxValue placeholder="Select a country" />
        </ComboboxTrigger>
        <ComboboxContent>
          <ComboboxInput placeholder="Search countries" />
          <ComboboxEmpty>No country found.</ComboboxEmpty>
          <ComboboxList>
            {(item: (typeof countries)[number]) => (
              <ComboboxItem key={item.value} value={item}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  )
}

Button olarak render edilen trigger

Herhangi bir düğmeyi kullanmak için trigger'a render geçin. Popup ona sabitlenir ve en az onun genişliğini korur.

"use client"

import { Button } from "@/components/ui/button"
import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxTrigger,
  ComboboxValue,
} from "@/components/ui/combobox"

const countries = [
  { value: "ar", label: "Argentina" },
  { value: "au", label: "Australia" },
  { value: "br", label: "Brazil" },
  { value: "ca", label: "Canada" },
  { value: "de", label: "Germany" },
  { value: "fr", label: "France" },
  { value: "in", label: "India" },
  { value: "jp", label: "Japan" },
  { value: "mx", label: "Mexico" },
  { value: "nl", label: "Netherlands" },
  { value: "uk", label: "United Kingdom" },
  { value: "us", label: "United States" },
]

export function ComboboxButtonTrigger() {
  return (
    <Combobox items={countries} defaultValue={countries[6]}>
      <ComboboxTrigger
        aria-label="Country"
        render={<Button variant="outline" />}
      >
        <ComboboxValue />
      </ComboboxTrigger>
      <ComboboxContent>
        <ComboboxInput placeholder="Search countries" />
        <ComboboxEmpty>No country found.</ComboboxEmpty>
        <ComboboxList>
          {(item: (typeof countries)[number]) => (
            <ComboboxItem key={item.value} value={item}>
              {item.label}
            </ComboboxItem>
          )}
        </ComboboxList>
      </ComboboxContent>
    </Combobox>
  )
}

Kontrollü

Seçimi value ve onValueChange ile, popup'ı open ve onOpenChange ile kontrol edin. Temizlemek değeri null yapar.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"

const fruits = [
  "Apple",
  "Apricot",
  "Banana",
  "Blackberry",
  "Blueberry",
  "Cherry",
  "Grape",
  "Grapefruit",
  "Kiwi",
  "Lychee",
  "Mango",
  "Orange",
  "Papaya",
  "Peach",
  "Pear",
  "Pineapple",
  "Plum",
  "Raspberry",
  "Strawberry",
  "Watermelon",
]

export function ComboboxControlled() {
  const [value, setValue] = React.useState<string | null>("Peach")
  const [open, setOpen] = React.useState(false)

  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Label htmlFor="combobox-controlled">Fruit</Label>
      <Combobox
        items={fruits}
        value={value}
        onValueChange={setValue}
        open={open}
        onOpenChange={setOpen}
      >
        <ComboboxInput
          id="combobox-controlled"
          placeholder="Select a fruit"
          showClear
        />
        <ComboboxContent>
          <ComboboxEmpty>No fruit found.</ComboboxEmpty>
          <ComboboxList>
            {(item: string) => (
              <ComboboxItem key={item} value={item}>
                {item}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
      <div className="flex items-center gap-2">
        <Button size="sm" variant="outline" onClick={() => setValue("Kiwi")}>
          Pick Kiwi
        </Button>
        <Button size="sm" variant="outline" onClick={() => setOpen(!open)}>
          {open ? "Close" : "Open"}
        </Button>
      </div>
      <p className="text-sm text-muted-foreground">Value: {value ?? "none"}</p>
    </div>
  )
}

Devre dışı, geçersiz ve devre dışı öğeler

Kökteki disabled alanı ve düğmelerini soluklaştırır. Girdideki aria-invalid hata halkasını çizer. Devre dışı öğeler ok tuşlarıyla atlanır.

"use client"

import {
  Combobox,
  ComboboxContent,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"

const fruits = [
  "Apple",
  "Apricot",
  "Banana",
  "Blackberry",
  "Blueberry",
  "Cherry",
  "Grape",
  "Grapefruit",
  "Kiwi",
  "Lychee",
  "Mango",
  "Orange",
  "Papaya",
  "Peach",
  "Pear",
  "Pineapple",
  "Plum",
  "Raspberry",
  "Strawberry",
  "Watermelon",
]

export function ComboboxStates() {
  return (
    <div className="grid w-full max-w-xl gap-6 sm:grid-cols-2">
      <div className="flex flex-col gap-2">
        <Label htmlFor="combobox-disabled">Disabled</Label>
        <Combobox items={fruits} disabled defaultValue="Apple">
          <ComboboxInput id="combobox-disabled" showClear />
          <ComboboxContent>
            <ComboboxList>
              {(item: string) => (
                <ComboboxItem key={item} value={item}>
                  {item}
                </ComboboxItem>
              )}
            </ComboboxList>
          </ComboboxContent>
        </Combobox>
      </div>
      <div className="flex flex-col gap-2">
        <Label htmlFor="combobox-invalid">Invalid</Label>
        <Combobox items={fruits}>
          <ComboboxInput
            id="combobox-invalid"
            aria-invalid
            placeholder="Required"
          />
          <ComboboxContent>
            <ComboboxList>
              {(item: string) => (
                <ComboboxItem
                  key={item}
                  value={item}
                  disabled={item.startsWith("B")}
                >
                  {item}
                </ComboboxItem>
              )}
            </ComboboxList>
          </ComboboxContent>
        </Combobox>
        <p className="text-sm text-muted-foreground">
          Items starting with B are disabled.
        </p>
      </div>
    </div>
  )
}

Uzun içerik ve büyük listeler

Uzun ve kırılmayan etiketler popup'ı genişletmek yerine sarar. limit kaç eşleşmenin render edileceğini sınırlar; bu, 500 öğelik bir listeyi hızlı tutar.

"use client"

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"

const longItems = [
  "A very long option label that wraps onto a second line instead of pushing the popup wider than its input",
  "supercalifragilisticexpialidocious-unbroken-string-without-any-spaces-at-all-anywhere",
  "olivia.martin+newsletter-subscriptions@a-very-long-company-domain.example.com",
  "👩‍👩‍👧‍👦 Family 🧑🏽‍💻 Developer 🏳️‍🌈",
  "東京都千代田区丸の内一丁目",
  "",
  "Short",
]

const manyItems = Array.from({ length: 500 }, (_, index) => `Item ${index + 1}`)

export function ComboboxLongContent() {
  return (
    <div className="grid w-full max-w-xl gap-6 sm:grid-cols-2">
      <div className="flex w-full max-w-60 flex-col gap-2">
        <Label htmlFor="combobox-long">Long labels</Label>
        <Combobox items={longItems} defaultValue={longItems[1]}>
          <ComboboxInput id="combobox-long" placeholder="Pick one" showClear />
          <ComboboxContent>
            <ComboboxEmpty>
              Nothing matches this unusually long query, try something else.
            </ComboboxEmpty>
            <ComboboxList>
              {(item: string) => (
                <ComboboxItem key={item} value={item}>
                  {item || "(empty)"}
                </ComboboxItem>
              )}
            </ComboboxList>
          </ComboboxContent>
        </Combobox>
      </div>
      <div className="flex flex-col gap-2">
        <Label htmlFor="combobox-many">500 items</Label>
        <Combobox items={manyItems} limit={100}>
          <ComboboxInput id="combobox-many" placeholder="Search items" />
          <ComboboxContent>
            <ComboboxEmpty>No item found.</ComboboxEmpty>
            <ComboboxList>
              {(item: string) => (
                <ComboboxItem key={item} value={item}>
                  {item}
                </ComboboxItem>
              )}
            </ComboboxList>
          </ComboboxContent>
        </Combobox>
        <p className="text-sm text-muted-foreground">
          Shows the first 100 matches.
        </p>
      </div>
    </div>
  )
}

Yerleşik filtrelemeyi filter={null} ile kapatın, onInputValueChange üzerinde veri çekin ve ilerlemeyi ekran okuyuculara duyuran <ComboboxStatus /> içinde gösterin. Sonuçlar değiştikçe popup yüksekliği animasyonlanır.

"use client"

import * as React from "react"
import { IconLoader2 } from "@tabler/icons-react"

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxStatus,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"

const countries = [
  { value: "ar", label: "Argentina" },
  { value: "au", label: "Australia" },
  { value: "br", label: "Brazil" },
  { value: "ca", label: "Canada" },
  { value: "de", label: "Germany" },
  { value: "fr", label: "France" },
  { value: "in", label: "India" },
  { value: "jp", label: "Japan" },
  { value: "mx", label: "Mexico" },
  { value: "nl", label: "Netherlands" },
  { value: "es", label: "Spain" },
  { value: "se", label: "Sweden" },
  { value: "ch", label: "Switzerland" },
  { value: "za", label: "South Africa" },
  { value: "kr", label: "South Korea" },
  { value: "uk", label: "United Kingdom" },
  { value: "us", label: "United States" },
]

export function ComboboxAsync() {
  const [query, setQuery] = React.useState("")
  const [results, setResults] = React.useState(countries.slice(0, 5))
  const [loading, setLoading] = React.useState(false)
  const runRef = React.useRef(0)

  React.useEffect(() => {
    const run = ++runRef.current
    const timer = setTimeout(() => {
      setLoading(true)
      setTimeout(() => {
        if (run !== runRef.current) {
          return
        }
        const needle = query.trim().toLowerCase()
        setResults(
          countries.filter((country) =>
            country.label.toLowerCase().includes(needle)
          )
        )
        setLoading(false)
      }, 600)
    }, 150)
    return () => {
      clearTimeout(timer)
      runRef.current += 1
    }
  }, [query])

  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Label htmlFor="combobox-async">Country</Label>
      <Combobox
        items={results}
        filter={null}
        inputValue={query}
        onInputValueChange={setQuery}
      >
        <ComboboxInput id="combobox-async" placeholder="Search countries" />
        <ComboboxContent>
          <ComboboxStatus>
            {loading ? (
              <>
                <IconLoader2 className="animate-spin" />
                Searching…
              </>
            ) : null}
          </ComboboxStatus>
          {loading ? null : (
            <ComboboxEmpty>No country matches “{query}”.</ComboboxEmpty>
          )}
          <ComboboxList>
            {(item: (typeof countries)[number]) => (
              <ComboboxItem key={item.value} value={item}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  )
}

Sheet içinde

Popup sheet'in üstünde katmanlanır ve Escape, sheet'ten önce popup'ı kapatır.

"use client"

import { Button } from "@/components/ui/button"
import {
  Combobox,
  ComboboxCollection,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxGroup,
  ComboboxInput,
  ComboboxItem,
  ComboboxLabel,
  ComboboxList,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"
import {
  Sheet,
  SheetBody,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"

type Timezone = { value: string; label: string }
type TimezoneGroup = { value: string; items: Timezone[] }

const timezones: TimezoneGroup[] = [
  {
    value: "Americas",
    items: [
      { value: "America/New_York", label: "New York (GMT-4)" },
      { value: "America/Chicago", label: "Chicago (GMT-5)" },
      { value: "America/Los_Angeles", label: "Los Angeles (GMT-7)" },
      { value: "America/Sao_Paulo", label: "São Paulo (GMT-3)" },
    ],
  },
  {
    value: "Europe",
    items: [
      { value: "Europe/London", label: "London (GMT+1)" },
      { value: "Europe/Paris", label: "Paris (GMT+2)" },
      { value: "Europe/Berlin", label: "Berlin (GMT+2)" },
    ],
  },
  {
    value: "Asia",
    items: [
      { value: "Asia/Kolkata", label: "Kolkata (GMT+5:30)" },
      { value: "Asia/Tokyo", label: "Tokyo (GMT+9)" },
      { value: "Asia/Singapore", label: "Singapore (GMT+8)" },
    ],
  },
]

export function ComboboxInSheet() {
  return (
    <Sheet>
      <SheetTrigger render={<Button variant="outline" />}>
        Open sheet
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetTitle>Preferences</SheetTitle>
          <SheetDescription>
            Choose the timezone for your reports.
          </SheetDescription>
        </SheetHeader>
        <SheetBody>
          <div className="flex flex-col gap-2">
            <Label htmlFor="combobox-sheet">Timezone</Label>
            <Combobox items={timezones} autoHighlight>
              <ComboboxInput
                id="combobox-sheet"
                placeholder="Select a timezone"
              />
              <ComboboxContent>
                <ComboboxEmpty>No timezone found.</ComboboxEmpty>
                <ComboboxList>
                  {(group: TimezoneGroup) => (
                    <ComboboxGroup key={group.value} items={group.items}>
                      <ComboboxLabel>{group.value}</ComboboxLabel>
                      <ComboboxCollection>
                        {(item: Timezone) => (
                          <ComboboxItem key={item.value} value={item}>
                            {item.label}
                          </ComboboxItem>
                        )}
                      </ComboboxCollection>
                    </ComboboxGroup>
                  )}
                </ComboboxList>
              </ComboboxContent>
            </Combobox>
          </div>
        </SheetBody>
      </SheetContent>
    </Sheet>
  )
}

Sağdan sola

Popup alanın yönünü alır; böylece temizle düğmesi, chip'ler ve öğeler ek prop olmadan yansıtılır.

"use client"

import { DirectionProvider } from "@base-ui/react/direction-provider"

import {
  Combobox,
  ComboboxChip,
  ComboboxChips,
  ComboboxChipsInput,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxValue,
} from "@/components/ui/combobox"
import { Label } from "@/components/ui/label"

const cities = ["القاهرة", "الرياض", "دبي", "بيروت", "عمّان", "الدوحة"]

export function ComboboxRtl() {
  return (
    <DirectionProvider direction="rtl">
      <div dir="rtl" className="flex w-full max-w-xs flex-col gap-4">
        <div className="flex flex-col gap-2">
          <Label htmlFor="combobox-rtl">المدينة</Label>
          <Combobox items={cities} defaultValue={cities[2]}>
            <ComboboxInput
              id="combobox-rtl"
              placeholder="اختر مدينة"
              showClear
            />
            <ComboboxContent>
              <ComboboxEmpty>لا توجد نتائج.</ComboboxEmpty>
              <ComboboxList>
                {(item: string) => (
                  <ComboboxItem key={item} value={item}>
                    {item}
                  </ComboboxItem>
                )}
              </ComboboxList>
            </ComboboxContent>
          </Combobox>
        </div>
        <div className="flex flex-col gap-2">
          <Label htmlFor="combobox-rtl-chips">المدن</Label>
          <Combobox items={cities} multiple defaultValue={[cities[0]]}>
            <ComboboxChips>
              <ComboboxValue>
                {(values: string[]) => (
                  <>
                    {values.map((value) => (
                      <ComboboxChip key={value}>{value}</ComboboxChip>
                    ))}
                    <ComboboxChipsInput id="combobox-rtl-chips" />
                  </>
                )}
              </ComboboxValue>
            </ComboboxChips>
            <ComboboxContent>
              <ComboboxList>
                {(item: string) => (
                  <ComboboxItem key={item} value={item}>
                    {item}
                  </ComboboxItem>
                )}
              </ComboboxList>
            </ComboboxContent>
          </Combobox>
        </div>
      </div>
    </DirectionProvider>
  )
}
TuşAction
↓↑Popup'ı açar ve vurguyu eşleşmeler arasında taşır. Devre dışı öğeler atlanır.
EnterVurgulanan öğeyi seçer. Hiçbir şey vurgulanmamışsa popup'ı kapatır ve formun gönderilmesine izin verir.
EscapePopup'ı kapatır. Zaten kapalıysa değeri ve girdiyi temizler.
HomeEndMetin imlecini girdinin başına veya sonuna taşır.
BackspaceBoş bir chip girdisinde son chip'i kaldırır. Odaktaki bir chip'te onu kaldırır.
←→Chip'lerle, odağı chip'ler arasında ve girdiye geri taşır. Sağdan sola düzenlerde yansıtılır.
TabPopup'ı kapatır ve odağı ilerletir.
  • Girdiye id ve htmlFor ile görünür bir <label> veya bir aria-label verin. Görünür metni olmayan bir <ComboboxTrigger /> bileşeni de bir aria-label gerektirir.
  • Ok düğmesi “Show options”, temizle düğmesi “Clear selection” ve her chip'in kaldır düğmesi “Remove” olarak etiketlenir.
  • Vurgu aria-activedescendant ile hareket eder; böylece gezinirken odak girdide kalır.
  • Girdiler dokunmatik ekranlarda 16px yazı tipi kullanır; böylece iOS yakınlaştırmaz ve öğeler 44px'lik bir dokunma hedefine büyür.

Base UI combobox üzerine kuruludur. Her parça, sardığı primitive'in prop'larını kabul eder; tablolar en çok kullanacaklarınızı listeler.

PropTürVarsayılan
itemsSeçenekler. Yazdıkça filtrelenir ve listenin render fonksiyonuna geçirilir.
Item[] | Group[]–
multipleChip olarak gösterilen birden fazla değer seçin.
booleanfalse
value
Value | Value[] | null–
defaultValue
Value | Value[] | null–
onValueChange
(value, details) => void–
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean, details) => void–
inputValue
string–
defaultInputValue
string–
onInputValueChange
(inputValue: string, details) => void–
filterÖzel eşleştirme. null, sunucu tarafı arama için filtrelemeyi kapatır.
((item, query, itemToString) => boolean) | null–
limitRender edilecek en fazla eşleşme sayısı. -1 tümü demektir.
number-1
autoHighlightYazarken ilk eşleşmeyi vurgulayın.
booleanfalse
highlightItemOnHover
booleantrue
openOnInputClick
booleantrue
loopFocusVurguyu son öğeden ilkine sarın.
booleantrue
itemToStringLabelBir nesne öğesi için girdide gösterilen metin.
(item) => string–
itemToStringValueBir nesne öğesi için formla birlikte gönderilen değer.
(item) => string–
isItemEqualToValue
(item, value) => boolean–
name
string–
required
booleanfalse
disabled
booleanfalse
readOnly
booleanfalse
modalAçıkken sayfa kaydırmasını ve dış tıklamaları kilitler.
booleanfalse
virtualizedÖğeler bir virtualizer ile render edilirken ayarlayın.
booleanfalse
localeEşleştirme için kullanılan yerel ayar (locale).
Intl.LocalesArgument–

Popup'ın dışında tam alanı render eder. <ComboboxContent /> içinde kompakt bir arama kutusuna dönüşür.

PropTürVarsayılan
showTriggerOk düğmesini gösterin. Ayarlanmadıkça popup içinde her zaman kapalıdır.
booleantrue outside the popup
showClearBir değer varken ok yerine bir temizle düğmesi gösterin.
booleanfalse
classNameGirdinin çevresindeki girdi grubuna uygulanır.
string–
disabled
booleanfalse
placeholder
string–
ÖznitelikAçıklama
data-slot="combobox-input-group"Girdinin çevresindeki alan.
data-slot="combobox-input"Metin girdisi.
data-slot="combobox-input-actions"Ok ve temizle düğmelerini tek bir üst üste hücrede tutar.
data-popup-openPopup açıkken girdide bulunur.
data-popup-sidePopup'ın açıldığı taraf.
data-list-emptyHiçbir şey eşleşmediğinde bulunur.
data-disabledDevre dışıyken bulunur.
data-invalidBir Base UI Field içinde geçersiz olduğunda bulunur.
PropTürVarsayılan
childrenGenellikle bir <ComboboxValue />. Ok, ondan sonra eklenir.
ReactNode–
renderAyarlandığında yerleşik alan stilleri atlanır.
ReactElement | (props, state) => ReactElement<button>
ÖznitelikAçıklama
data-slot="combobox-trigger"Trigger düğmesi.
data-slot="combobox-trigger-value"Kısaltılmış değeri sarar.
data-slot="combobox-trigger-icon"Ok. Açıkken ters döner.
data-popup-openPopup açıkken bulunur.
data-placeholderHiçbir değer seçili değilken bulunur.
PropTürVarsayılan
childrenSeçili değeri kendiniz render edin; örneğin chip olarak.
ReactNode | (value) => ReactNode–
placeholderHiçbir şey seçili değilken gösterilir.
ReactNode–
PropTürVarsayılan
side
"top" | "bottom" | "left" | "right" | "inline-start" | "inline-end""bottom"
align
"start" | "center" | "end""start"
sideOffset
number6
alignOffset
number0
anchorBaşka bir öğeye göre konumlandırın. Varsayılan olarak alandır. useComboboxAnchor'a bakın.
Element | RefObject<Element | null> | VirtualElement | (() => Element | VirtualElement | null) | null–
dirVarsayılan olarak alanın yönüdür.
"ltr" | "rtl"–
ÖznitelikAçıklama
data-slot="combobox-positioner"Popup'ı konumlandırır.
data-slot="combobox-content"Popup yüzeyi.
data-slot="combobox-content-sizer"Eşleşmeler değiştikçe popup yüksekliğini animasyonlamak için ölçülür.
data-openAçıkken bulunur.
data-sideAçıldığı taraf.
data-alignHizalaması.
data-emptyHiçbir şey eşleşmediğinde bulunur.
data-starting-styleGiriş animasyonu sırasında bulunur.
data-ending-styleÇıkış animasyonu sırasında bulunur.
--combobox-item-radiusPopup yarıçapından dolgusu çıkarılarak türetilen öğe yarıçapı.
PropTürVarsayılan
childrenitems içindeki her eşleşme için çağrılır.
ReactNode | (item, index) => ReactNode–
ÖznitelikAçıklama
data-slot="combobox-list"Kayan liste.
PropTürVarsayılan
valueBu satırın temsil ettiği öğe.
Item–
disabled
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
ÖznitelikAçıklama
data-slot="combobox-item"Bir seçenek.
data-slot="combobox-item-indicator"Seçildiğinde ölçeklenerek gelen onay işareti.
data-highlightedVurgulanmışken bulunur.
data-selectedSeçiliyken bulunur.
data-disabledDevre dışıyken bulunur.
PropTürVarsayılan
itemsComboboxGroup üzerinde: grubun kendi öğeleri.
Item[]–
childrenComboboxCollection üzerinde: her eşleşmeyi render eder.
(item, index) => ReactNode–
ÖznitelikAçıklama
data-slot="combobox-group"Bir öğe grubu.
data-slot="combobox-label"Grup başlığı.

<ComboboxEmpty /> alt öğelerini yalnızca hiçbir şey eşleşmediğinde gösterir. <ComboboxStatus /> yükleme ve sonuç mesajları için bir canlı bölgedir. İkisi de boşken hiçbir şeye daralır.

ÖznitelikAçıklama
data-slot="combobox-empty"Sonuç yok mesajı.
data-slot="combobox-status"Canlı durum mesajı.
data-slot="combobox-separator"Gruplar arasındaki ayırıcı.
PropTürVarsayılan
children
ReactNode<IconX />
ÖznitelikAçıklama
data-slot="combobox-clear"“Clear selection” olarak etiketlenir.
data-visibleTemizlenecek bir şey olduğunda bulunur.
PropTürVarsayılan
classNameChip'leri saran alana uygulanır.
string–
ÖznitelikAçıklama
data-slot="combobox-chips"Chip'leri ve girdiyi tutan alan.
PropTürVarsayılan
showRemoveKaldır düğmesini gösterin.
booleantrue
ÖznitelikAçıklama
data-slot="combobox-chip"Seçili bir değer.
data-slot="combobox-chip-label"Kısaltılmış etiketi.
data-slot="combobox-chip-remove"“Remove” olarak etiketlenir.

Chip'lerden sonra gelen metin girdisi. Base UI input ile aynı prop'ları kabul eder.

ÖznitelikAçıklama
data-slot="combobox-chips-input"Chip girdisi.
  • useComboboxAnchor() bir öğeye ve içerikteki anchor'a geçirilecek bir ref döndürür.
  • useComboboxFilter() filter için yerel ayara duyarlı contains, startsWith ve endsWith eşleştiricilerini döndürür.
  • useComboboxFilteredItems() sayımlar veya sanallaştırılmış listeler için geçerli eşleşmeleri okur.
  • createComboboxItems(data, { getValue }) seçim değeri tüm nesne yerine bir veritabanı anahtarı gibi ilkel bir id olan bir öğe koleksiyonu oluşturur.
  • comboboxFieldVariants özel alanlar oluşturmak için alan stillerini sunar.