"use client"; import { useState } from "react"; import type { ReactNode } from "react"; import { Button, Center, Combobox, Group, Loader, ScrollArea, useCombobox } from "@mantine/core"; import type { ButtonProps } from "@mantine/core"; export interface AccessPickerOption { value: string; label: string; keywords?: readonly string[]; content: ReactNode; item: T; } export type AccessPickerTriggerProps = Omit; interface AccessPickerProps { label: string; searchPlaceholder: string; emptyMessage: string; triggerLabel: string; options: AccessPickerOption[]; isPending: boolean; onSelect: (option: AccessPickerOption) => void | Promise; triggerProps?: AccessPickerTriggerProps; } export const AccessPicker = ({ label, searchPlaceholder, emptyMessage, triggerLabel, options, isPending, onSelect, triggerProps, }: AccessPickerProps) => { const [search, setSearch] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const combobox = useCombobox({ onDropdownClose: () => { combobox.resetSelectedOption(); setSearch(""); requestAnimationFrame(() => combobox.focusTarget()); }, onDropdownOpen: () => { combobox.selectFirstOption(); requestAnimationFrame(() => combobox.focusSearchInput()); }, }); const normalizedSearch = search.trim().toLowerCase(); const filteredOptions = options.filter((option) => { if (!normalizedSearch) return true; const searchableText = [option.label, ...(option.keywords ?? [])].join(" ").toLowerCase(); return searchableText.includes(normalizedSearch); }); const visibleOptions = filteredOptions.slice(0, 5); const handleSelect = (value: string) => { const option = options.find((item) => item.value === value); if (!option || isSubmitting) return; combobox.closeDropdown(); setIsSubmitting(true); const submit = async () => { try { await onSelect(option); } finally { setIsSubmitting(false); } }; void submit().catch(() => undefined); }; return ( { setSearch(event.currentTarget.value); combobox.updateSelectedOptionIndex(); }} placeholder={searchPlaceholder} aria-label={label} /> {isPending && (
)} {!isPending && filteredOptions.length === 0 && {emptyMessage}} {!isPending && visibleOptions.map((option) => ( {option.content} ))}
); }; const LoaderPlaceholder = () => ;