This commit is contained in:
Fred KISSIE
2026-04-23 06:33:57 +02:00
parent 53c48e6f04
commit b9bee2836b
4 changed files with 135 additions and 54 deletions

View File

@@ -0,0 +1,61 @@
import {
Popover,
PopoverContent,
PopoverTrigger
} from "@app/components/ui/popover";
import { Button } from "@app/components/ui/button";
import { cn } from "@app/lib/cn";
import { ChevronDownIcon } from "lucide-react";
import {
type TagValue,
type MultiSelectTagsProps,
MultiSelectTags
} from "./multi-select-tags";
export interface MultiSelectInputProps<
T extends TagValue
> extends MultiSelectTagsProps<T> {
buttonText?: string;
}
export function MultiSelectInput<T extends TagValue>({
buttonText,
...props
}: MultiSelectInputProps<T>) {
return (
<Popover>
<PopoverTrigger>
<div
className={cn(
"justify-between w-full",
"text-muted-foreground pl-1.5 cursor-text"
)}
>
<span
className={cn(
"inline-flex items-center gap-1",
"overflow-x-auto"
)}
>
{/* {(field.value ?? []).map((client) => (
<span
key={client.clientId}
className={cn(
"bg-muted-foreground/20 font-normal text-foreground rounded-sm",
"py-1 px-1.5 text-xs"
)}
>
{client.name}
</span>
))} */}
<span className="pl-1 font-normal">{buttonText}</span>
</span>
<ChevronDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</div>
</PopoverTrigger>
<PopoverContent className="p-0">
<MultiSelectTags {...props} />
</PopoverContent>
</Popover>
);
}

View File

@@ -0,0 +1,77 @@
import type { Ref } from "react";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList
} from "../ui/command";
import { cn } from "@app/lib/cn";
import { CheckIcon } from "lucide-react";
export type TagValue = { text: string; id: string };
export type MultiSelectTagsProps<T extends TagValue> = {
emptyPlaceholder: string;
searchPlaceholder: string;
searchQuery?: string;
options: Array<T>;
value: Array<T>;
onChange: (newValue: Array<T>) => void;
onSearch: (query: string) => void;
ref?: Ref<HTMLButtonElement>;
};
export function MultiSelectTags<T extends TagValue>({
emptyPlaceholder,
searchPlaceholder,
searchQuery,
value,
options,
onSearch,
onChange
}: MultiSelectTagsProps<T>) {
const selectedValues = new Set(value.map((v) => v.id));
return (
<Command shouldFilter={false}>
<CommandInput
placeholder={searchPlaceholder}
value={searchQuery}
onValueChange={onSearch}
/>
<CommandList>
<CommandEmpty>{emptyPlaceholder}</CommandEmpty>
<CommandGroup>
{options.map((option) => (
<CommandItem
value={option.id}
key={option.id}
onSelect={() => {
let newValues = [];
if (selectedValues.has(option.id)) {
newValues = value.filter(
(v) => v.id !== option.id
);
} else {
newValues = [...value, option];
}
onChange(newValues);
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
selectedValues.has(option.id)
? "opacity-100"
: "opacity-0"
)}
/>
{`${option.text}`}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
);
}