{}
"use client"
// import { SearchField } from "@/components/ui/preskok-ui/search-field"
import { useEffect, useState } from "react"
import type { Selection } from "react-aria-components"
import { MultipleSelectTest } from "@/components/ui/preskok-ui/multiple-select-test"
export function SearchableMultiSelectPreskokDemo() {
const [selectedItems, setSelectedItems] = useState<Selection>(new Set())
const [items, setItems] = useState<typeof technologies>([])
const [isLoading, setIsLoading] = useState(false)
// Simulate an async fetch for items and initial selection
useEffect(() => {
let cancelled = false
setIsLoading(true)
const timer = setTimeout(() => {
if (cancelled) return
setItems(technologies)
// Selected items coming from the "API"
setSelectedItems(new Set(["react", "vue", "angular"]))
setIsLoading(false)
}, 1000)
return () => {
cancelled = true
clearTimeout(timer)
}
}, [])
return (
<div className="flex justify-center gap-4 rounded-lg p-8 sm:h-[350px]">
{/* <ListBox
items={technologies}
aria-label="Fruits"
selectionMode="multiple"
>
{(item) => (
<ListBox.Item id={item.id} textValue={item.label}>
hello
{item.label}
</ListBox.Item>
)}
</ListBox> */}
<MultipleSelectTest
description={JSON.stringify(selectedItems, null, 2)}
label="Select Technologies"
items={items}
selectedKeys={selectedItems}
onSelectionChange={(s) => {
console.log(s, "outside")
setSelectedItems(s)
}}
placeholder="Choose technologies..."
className="max-w-sm"
renderEmptyState={(q) => (
<div className="text-muted-foreground p-3 text-sm">
{isLoading ? (
"Loading…"
) : q ? (
<>
No results found for: <strong>{q}</strong>
</>
) : (
"No options"
)}
</div>
)}
// renderValue={(items) => {
// return items?.map((item) => item.label).join(" | ")
// }}
>
{(fruit) => (
<MultipleSelectTest.Item
id={fruit.id}
textValue={fruit.label}
key={fruit.id}
>
{fruit.label}
</MultipleSelectTest.Item>
)}
</MultipleSelectTest>
</div>
)
}
const technologies = [
{ id: "react", label: "React", bla: "bla" },
{ id: "vue", label: "Vue.js", bla: "bla" },
{ id: "angular", label: "Angular" },
{ id: "svelte", label: "Svelte" },
{ id: "nextjs", label: "Next.js", bla: "bla" },
{ id: "nuxt", label: "Nuxt.js", bla: "bla" },
{ id: "gatsby", label: "Gatsby", bla: "bla" },
{ id: "remix", label: "Remix" },
{ id: "astro", label: "Astro" },
{ id: "solid", label: "SolidJS" },
{ id: "preact", label: "Preact" },
{ id: "qwik", label: "Qwik" },
{ id: "alpine", label: "Alpine.js" },
{ id: "lit", label: "Lit" },
{ id: "stencil", label: "Stencil" },
{ id: "ember", label: "Ember.js" },
{ id: "backbone", label: "Backbone.js" },
{ id: "jquery", label: "jQuery" },
{ id: "stimulus", label: "Stimulus" },
{ id: "htmx", label: "HTMX" },
]
Installation
CLI
Manual
pnpmnpmyarnbunpnpm dlx @preskok-org/ui@latest add searchable-multi-select
Usage
import { SearchableMultiSelect } from "@/registry/preskok/ui/preskok-ui/searchable-multi-select"
const items = [
{ id: "1", label: "React" },
{ id: "2", label: "Vue" },
{ id: "3", label: "Angular" },
{ id: "4", label: "Svelte" },
]
export function SearchableMultiSelectDemo() {
const [selected, setSelected] = useState(new Set())
return (
<SearchableMultiSelect
label="Select Frameworks"
items={items}
selectedKeys={selected}
onSelectionChange={setSelected}
placeholder="Choose frameworks..."
searchPlaceholder="Search frameworks..."
/>
)
}
Examples
Basic Usage
The most basic form of the component with default props:
<SearchableMultiSelect
items={items}
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
/>
With Custom Labels
Use custom functions to extract key and label from your data:
const users = [
{ userId: "1", name: "John Doe", email: "john@example.com" },
{ userId: "2", name: "Jane Smith", email: "jane@example.com" }
]
<SearchableMultiSelect
items={users}
getKey={(user) => user.userId}
getLabel={(user) => user.name}
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
/>
With Maximum Selection
Limit the number of items that can be selected:
<SearchableMultiSelect
items={items}
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
maxSelection={3}
placeholder="Select up to 3 items..."
/>
Show Selection Count
Display selection count instead of tags in the trigger:
<SearchableMultiSelect
items={items}
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
showSelectedAsMessage={true}
/>
Custom Render
Use a custom render function for list items:
<SearchableMultiSelect
items={users}
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
{(user) => (
<div className="flex items-center gap-2">
<div className="bg-primary text-primary-foreground flex h-8 w-8 items-center justify-center rounded-full text-sm">
{user.name.charAt(0)}
</div>
<div>
<div className="font-medium">{user.name}</div>
<div className="text-muted-foreground text-sm">{user.email}</div>
</div>
</div>
)}
</SearchableMultiSelect>
Props
Prop | Type | Default | Description |
---|---|---|---|
items | T[] | [] | Array of items to display in the list |
selectedKeys | Selection | new Set() | Currently selected keys |
onSelectionChange | (keys: Selection) => void | - | Callback when selection changes |
selectionMode | "single" | "multiple" | "multiple" | Selection mode |
placeholder | string | "Select items..." | Placeholder text for the trigger |
searchPlaceholder | string | "Search items..." | Placeholder text for the search field |
getKey | (item: T) => Key | (item) => item.id | Function to extract key from item |
getLabel | (item: T) => string | (item) => item.label | Function to extract label from item |
maxSelection | number | - | Maximum number of items that can be selected |
showSelectedAsMessage | boolean | false | Show selection count instead of tags |
emptyMessage | string | "No items found" | Message to show when no items match search |
label | string | - | Label for the component |
description | string | - | Description text |
errorMessage | string | - | Error message |
children | React.ReactNode | ((item: T) => React.ReactNode) | - | Custom render function for items |