"use client"; import { Autocomplete as AutocompletePrimitive } from "@base-ui/react/autocomplete";import { ChevronsUpDownIcon, XIcon } from "lucide-react"; import { cn } from "~/lib/utils";import { Input } from "~/components/ui/input";import { ScrollArea } from "~/components/ui/scroll-area"; const Autocomplete = AutocompletePrimitive.Root; function AutocompleteInput({ className, showTrigger = false, showClear = false, startAddon, size, ...props}: Omit<AutocompletePrimitive.Input.Props, "size"> & { showTrigger?: boolean; showClear?: boolean; startAddon?: React.ReactNode; size?: "sm" | "default" | "lg" | number; ref?: React.Ref<HTMLInputElement>;}) { const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number; return ( <div className="relative not-has-[>*.w-full]:w-fit w-full text-foreground has-disabled:opacity-64"> {startAddon && ( <div aria-hidden="true" className="[&_svg]:-mx-0.5 pointer-events-none absolute inset-y-0 start-px z-10 flex items-center ps-[calc(--spacing(3)-1px)] opacity-80 has-[+[data-size=sm]]:ps-[calc(--spacing(2.5)-1px)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4" data-slot="autocomplete-start-addon" > {startAddon} </div> )} <AutocompletePrimitive.Input className={cn( startAddon && "data-[size=sm]:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(7.5)-1px)] *:data-[slot=autocomplete-input]:ps-[calc(--spacing(8.5)-1px)] sm:data-[size=sm]:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(7)-1px)] sm:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(8)-1px)]", sizeValue === "sm" ? "has-[+[data-slot=autocomplete-trigger],+[data-slot=autocomplete-clear]]:*:data-[slot=autocomplete-input]:pe-6.5" : "has-[+[data-slot=autocomplete-trigger],+[data-slot=autocomplete-clear]]:*:data-[slot=autocomplete-input]:pe-7", className, )} data-slot="autocomplete-input" render={<Input nativeInput size={sizeValue} />} {...props} /> {showTrigger && ( <AutocompleteTrigger className={cn( "-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=autocomplete-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", sizeValue === "sm" ? "end-0" : "end-0.5", )} > <AutocompletePrimitive.Icon data-slot="autocomplete-icon"> <ChevronsUpDownIcon /> </AutocompletePrimitive.Icon> </AutocompleteTrigger> )} {showClear && ( <AutocompleteClear className={cn( "-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=autocomplete-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", sizeValue === "sm" ? "end-0" : "end-0.5", )} > <XIcon /> </AutocompleteClear> )} </div> );} function AutocompletePopup({ className, children, side = "bottom", sideOffset = 4, alignOffset, align = "start", anchor, ...props}: AutocompletePrimitive.Popup.Props & { align?: AutocompletePrimitive.Positioner.Props["align"]; sideOffset?: AutocompletePrimitive.Positioner.Props["sideOffset"]; alignOffset?: AutocompletePrimitive.Positioner.Props["alignOffset"]; side?: AutocompletePrimitive.Positioner.Props["side"]; anchor?: AutocompletePrimitive.Positioner.Props["anchor"];}) { return ( <AutocompletePrimitive.Portal> <AutocompletePrimitive.Positioner align={align} alignOffset={alignOffset} anchor={anchor} className="z-50 select-none" data-slot="autocomplete-positioner" side={side} sideOffset={sideOffset} > <span className={cn( "relative flex max-h-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", className, )} > <AutocompletePrimitive.Popup className="flex max-h-[min(var(--available-height),23rem)] flex-1 flex-col text-foreground" data-slot="autocomplete-popup" {...props} > {children} </AutocompletePrimitive.Popup> </span> </AutocompletePrimitive.Positioner> </AutocompletePrimitive.Portal> );} function AutocompleteItem({ className, children, ...props }: AutocompletePrimitive.Item.Props) { return ( <AutocompletePrimitive.Item className={cn( "flex min-h-8 cursor-default select-none items-center rounded-sm px-2 py-1 text-base outline-none hover:bg-accent data-disabled:pointer-events-none data-selected:bg-accent/50 data-selected:text-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground [&[data-highlighted][data-selected]]:bg-accent [&[data-highlighted][data-selected]]:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm", className, )} data-slot="autocomplete-item" {...props} > {children} </AutocompletePrimitive.Item> );} function AutocompleteSeparator({ className, ...props }: AutocompletePrimitive.Separator.Props) { return ( <AutocompletePrimitive.Separator className={cn("mx-2 my-1 h-px bg-border last:hidden", className)} data-slot="autocomplete-separator" {...props} /> );} function AutocompleteGroup({ className, ...props }: AutocompletePrimitive.Group.Props) { return ( <AutocompletePrimitive.Group className={cn("[[role=group]+&]:mt-1.5", className)} data-slot="autocomplete-group" {...props} /> );} function AutocompleteGroupLabel({ className, ...props }: AutocompletePrimitive.GroupLabel.Props) { return ( <AutocompletePrimitive.GroupLabel className={cn("px-2 py-1.5 font-medium text-muted-foreground text-xs", className)} data-slot="autocomplete-group-label" {...props} /> );} function AutocompleteEmpty({ className, ...props }: AutocompletePrimitive.Empty.Props) { return ( <AutocompletePrimitive.Empty className={cn( "not-empty:p-2 text-center text-base text-muted-foreground sm:text-sm", className, )} data-slot="autocomplete-empty" {...props} /> );} function AutocompleteRow({ className, ...props }: AutocompletePrimitive.Row.Props) { return ( <AutocompletePrimitive.Row className={className} data-slot="autocomplete-row" {...props} /> );} function AutocompleteValue({ ...props }: AutocompletePrimitive.Value.Props) { return <AutocompletePrimitive.Value data-slot="autocomplete-value" {...props} />;} function AutocompleteList({ className, ...props }: AutocompletePrimitive.List.Props) { return ( <ScrollArea scrollbarGutter scrollFade> <AutocompletePrimitive.List className={cn("not-empty:scroll-py-1 not-empty:p-1", className)} data-slot="autocomplete-list" {...props} /> </ScrollArea> );} function AutocompleteClear({ className, ...props }: AutocompletePrimitive.Clear.Props) { return ( <AutocompletePrimitive.Clear className={cn( "-translate-y-1/2 absolute end-0.5 top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-[color,background-color,box-shadow,opacity] pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", className, )} data-slot="autocomplete-clear" {...props} > <XIcon /> </AutocompletePrimitive.Clear> );} function AutocompleteStatus({ className, ...props }: AutocompletePrimitive.Status.Props) { return ( <AutocompletePrimitive.Status className={cn( "px-3 py-2 font-medium text-muted-foreground text-xs empty:m-0 empty:p-0", className, )} data-slot="autocomplete-status" {...props} /> );} function AutocompleteCollection({ ...props }: AutocompletePrimitive.Collection.Props) { return <AutocompletePrimitive.Collection data-slot="autocomplete-collection" {...props} />;} function AutocompleteTrigger({ className, children, ...props}: AutocompletePrimitive.Trigger.Props) { return ( <AutocompletePrimitive.Trigger className={className} data-slot="autocomplete-trigger" {...props} > {children} </AutocompletePrimitive.Trigger> );} const useAutocompleteFilter = AutocompletePrimitive.useFilter; export { Autocomplete, AutocompleteInput, AutocompleteTrigger, AutocompletePopup, AutocompleteItem, AutocompleteSeparator, AutocompleteGroup, AutocompleteGroupLabel, AutocompleteEmpty, AutocompleteValue, AutocompleteList, AutocompleteClear, AutocompleteStatus, AutocompleteRow, AutocompleteCollection, useAutocompleteFilter,};
"use client";
import { Autocomplete as AutocompletePrimitive } from "@base-ui/react/autocomplete";
import { ChevronsUpDownIcon, XIcon } from "lucide-react";
import { cn } from "~/lib/utils";
import { Input } from "~/components/ui/input";
import { ScrollArea } from "~/components/ui/scroll-area";
const Autocomplete = AutocompletePrimitive.Root;
function AutocompleteInput({
className,
showTrigger = false,
showClear = false,
startAddon,
size,
...props
}: Omit<AutocompletePrimitive.Input.Props, "size"> & {
showTrigger?: boolean;
showClear?: boolean;
startAddon?: React.ReactNode;
size?: "sm" | "default" | "lg" | number;
ref?: React.Ref<HTMLInputElement>;
}) {
const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number;
return (
<div className="relative not-has-[>*.w-full]:w-fit w-full text-foreground has-disabled:opacity-64">
{startAddon && (
<div
aria-hidden="true"
className="[&_svg]:-mx-0.5 pointer-events-none absolute inset-y-0 start-px z-10 flex items-center ps-[calc(--spacing(3)-1px)] opacity-80 has-[+[data-size=sm]]:ps-[calc(--spacing(2.5)-1px)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4"
data-slot="autocomplete-start-addon"
>
{startAddon}
</div>
)}
<AutocompletePrimitive.Input
className={cn(
startAddon &&
"data-[size=sm]:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(7.5)-1px)] *:data-[slot=autocomplete-input]:ps-[calc(--spacing(8.5)-1px)] sm:data-[size=sm]:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(7)-1px)] sm:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(8)-1px)]",
sizeValue === "sm"
? "has-[+[data-slot=autocomplete-trigger],+[data-slot=autocomplete-clear]]:*:data-[slot=autocomplete-input]:pe-6.5"
: "has-[+[data-slot=autocomplete-trigger],+[data-slot=autocomplete-clear]]:*:data-[slot=autocomplete-input]:pe-7",
className,
)}
data-slot="autocomplete-input"
render={<Input nativeInput size={sizeValue} />}
{...props}
/>
{showTrigger && (
<AutocompleteTrigger
className={cn(
"-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=autocomplete-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
sizeValue === "sm" ? "end-0" : "end-0.5",
)}
>
<AutocompletePrimitive.Icon data-slot="autocomplete-icon">
<ChevronsUpDownIcon />
</AutocompletePrimitive.Icon>
</AutocompleteTrigger>
)}
{showClear && (
<AutocompleteClear
className={cn(
"-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=autocomplete-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
sizeValue === "sm" ? "end-0" : "end-0.5",
)}
>
<XIcon />
</AutocompleteClear>
)}
</div>
);
}
function AutocompletePopup({
className,
children,
side = "bottom",
sideOffset = 4,
alignOffset,
align = "start",
anchor,
...props
}: AutocompletePrimitive.Popup.Props & {
align?: AutocompletePrimitive.Positioner.Props["align"];
sideOffset?: AutocompletePrimitive.Positioner.Props["sideOffset"];
alignOffset?: AutocompletePrimitive.Positioner.Props["alignOffset"];
side?: AutocompletePrimitive.Positioner.Props["side"];
anchor?: AutocompletePrimitive.Positioner.Props["anchor"];
}) {
return (
<AutocompletePrimitive.Portal>
<AutocompletePrimitive.Positioner
align={align}
alignOffset={alignOffset}
anchor={anchor}
className="z-50 select-none"
data-slot="autocomplete-positioner"
side={side}
sideOffset={sideOffset}
>
<span
className={cn(
"relative flex max-h-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
className,
)}
>
<AutocompletePrimitive.Popup
className="flex max-h-[min(var(--available-height),23rem)] flex-1 flex-col text-foreground"
data-slot="autocomplete-popup"
{...props}
>
{children}
</AutocompletePrimitive.Popup>
</span>
</AutocompletePrimitive.Positioner>
</AutocompletePrimitive.Portal>
);
}
function AutocompleteItem({ className, children, ...props }: AutocompletePrimitive.Item.Props) {
return (
<AutocompletePrimitive.Item
className={cn(
"flex min-h-8 cursor-default select-none items-center rounded-sm px-2 py-1 text-base outline-none hover:bg-accent data-disabled:pointer-events-none data-selected:bg-accent/50 data-selected:text-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground [&[data-highlighted][data-selected]]:bg-accent [&[data-highlighted][data-selected]]:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm",
className,
)}
data-slot="autocomplete-item"
{...props}
>
{children}
</AutocompletePrimitive.Item>
);
}
function AutocompleteSeparator({ className, ...props }: AutocompletePrimitive.Separator.Props) {
return (
<AutocompletePrimitive.Separator
className={cn("mx-2 my-1 h-px bg-border last:hidden", className)}
data-slot="autocomplete-separator"
{...props}
/>
);
}
function AutocompleteGroup({ className, ...props }: AutocompletePrimitive.Group.Props) {
return (
<AutocompletePrimitive.Group
className={cn("[[role=group]+&]:mt-1.5", className)}
data-slot="autocomplete-group"
{...props}
/>
);
}
function AutocompleteGroupLabel({ className, ...props }: AutocompletePrimitive.GroupLabel.Props) {
return (
<AutocompletePrimitive.GroupLabel
className={cn("px-2 py-1.5 font-medium text-muted-foreground text-xs", className)}
data-slot="autocomplete-group-label"
{...props}
/>
);
}
function AutocompleteEmpty({ className, ...props }: AutocompletePrimitive.Empty.Props) {
return (
<AutocompletePrimitive.Empty
className={cn(
"not-empty:p-2 text-center text-base text-muted-foreground sm:text-sm",
className,
)}
data-slot="autocomplete-empty"
{...props}
/>
);
}
function AutocompleteRow({ className, ...props }: AutocompletePrimitive.Row.Props) {
return (
<AutocompletePrimitive.Row className={className} data-slot="autocomplete-row" {...props} />
);
}
function AutocompleteValue({ ...props }: AutocompletePrimitive.Value.Props) {
return <AutocompletePrimitive.Value data-slot="autocomplete-value" {...props} />;
}
function AutocompleteList({ className, ...props }: AutocompletePrimitive.List.Props) {
return (
<ScrollArea scrollbarGutter scrollFade>
<AutocompletePrimitive.List
className={cn("not-empty:scroll-py-1 not-empty:p-1", className)}
data-slot="autocomplete-list"
{...props}
/>
</ScrollArea>
);
}
function AutocompleteClear({ className, ...props }: AutocompletePrimitive.Clear.Props) {
return (
<AutocompletePrimitive.Clear
className={cn(
"-translate-y-1/2 absolute end-0.5 top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-[color,background-color,box-shadow,opacity] pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className,
)}
data-slot="autocomplete-clear"
{...props}
>
<XIcon />
</AutocompletePrimitive.Clear>
);
}
function AutocompleteStatus({ className, ...props }: AutocompletePrimitive.Status.Props) {
return (
<AutocompletePrimitive.Status
className={cn(
"px-3 py-2 font-medium text-muted-foreground text-xs empty:m-0 empty:p-0",
className,
)}
data-slot="autocomplete-status"
{...props}
/>
);
}
function AutocompleteCollection({ ...props }: AutocompletePrimitive.Collection.Props) {
return <AutocompletePrimitive.Collection data-slot="autocomplete-collection" {...props} />;
}
function AutocompleteTrigger({
className,
children,
...props
}: AutocompletePrimitive.Trigger.Props) {
return (
<AutocompletePrimitive.Trigger
className={className}
data-slot="autocomplete-trigger"
{...props}
>
{children}
</AutocompletePrimitive.Trigger>
);
}
const useAutocompleteFilter = AutocompletePrimitive.useFilter;
export {
Autocomplete,
AutocompleteInput,
AutocompleteTrigger,
AutocompletePopup,
AutocompleteItem,
AutocompleteSeparator,
AutocompleteGroup,
AutocompleteGroupLabel,
AutocompleteEmpty,
AutocompleteValue,
AutocompleteList,
AutocompleteClear,
AutocompleteStatus,
AutocompleteRow,
AutocompleteCollection,
useAutocompleteFilter,
};