271 lines · 9.9 KB · TypeScript
RawDownload
"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 [&_svg:not([class*='text-'])]:text-muted-foreground",        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,};