base-autocomplete

PreviousNext
Docs
reuiui

Preview

Loading preview…
registry/default/ui/base-autocomplete.tsx
'use client';

import * as React from 'react';
import { cn } from '@/registry/default/lib/utils';
import { Autocomplete as AutocompletePrimitive } from '@base-ui-components/react/autocomplete';
import { cva, type VariantProps } from 'class-variance-authority';
import { X } from 'lucide-react';

// Define input size variants (without file: part)
const inputVariants = cva(
  `
    flex w-full bg-background border border-input shadow-xs shadow-black/5 transition-[color,box-shadow] text-foreground placeholder:text-muted-foreground/80 
    focus-visible:ring-ring/30 focus-visible:border-ring focus-visible:outline-none focus-visible:ring-[3px]     
    disabled:cursor-not-allowed disabled:opacity-60 
    [&[readonly]]:bg-muted/80 [&[readonly]]:cursor-not-allowed
    aria-invalid:border-destructive/60 aria-invalid:ring-destructive/10 dark:aria-invalid:border-destructive dark:aria-invalid:ring-destructive/20
  `,
  {
    variants: {
      variant: {
        lg: 'h-10 px-4 text-sm rounded-md [&~[data-slot=autocomplete-clear]]:end-2.5',
        md: 'h-9 px-3 text-sm rounded-md [&~[data-slot=autocomplete-clear]]:end-2',
        sm: 'h-8 px-2.5 text-xs rounded-md [&~[data-slot=autocomplete-clear]]:end-1.75',
      },
    },
    defaultVariants: {
      variant: 'md',
    },
  },
);

// Root - Groups all parts of the autocomplete
function Autocomplete({ ...props }: React.ComponentProps<typeof AutocompletePrimitive.Root>) {
  return <AutocompletePrimitive.Root data-slot="autocomplete" {...props} />;
}

// Value - Displays the selected value
function AutocompleteValue({ ...props }: React.ComponentProps<typeof AutocompletePrimitive.Value>) {
  return <AutocompletePrimitive.Value data-slot="autocomplete-value" {...props} />;
}

// Input and Clear controls
function AutocompleteControl({ className, ...props }: React.ComponentProps<'div'>) {
  return <span data-slot="autocomplete-control" className={cn('relative', className)} {...props} />;
}

// Trigger - A button that opens the autocomplete
function AutocompleteTrigger({ ...props }: React.ComponentProps<typeof AutocompletePrimitive.Trigger>) {
  return <AutocompletePrimitive.Trigger data-slot="autocomplete-trigger" {...props} />;
}

// Input - The input element for typing
function AutocompleteInput({
  className,
  variant = 'md',
  ...props
}: React.ComponentProps<typeof AutocompletePrimitive.Input> & VariantProps<typeof inputVariants>) {
  return (
    <AutocompletePrimitive.Input
      data-slot="autocomplete-input"
      data-variant={variant}
      className={cn(inputVariants({ variant }), className)}
      {...props}
    />
  );
}

// Icon - An icon element for the autocomplete
function AutocompleteIcon({ className, ...props }: React.ComponentProps<typeof AutocompletePrimitive.Icon>) {
  return (
    <AutocompletePrimitive.Icon
      data-slot="autocomplete-icon"
      className={cn('h-4 w-4 opacity-50', className)}
      {...props}
    />
  );
}

// Status - Displays the status of the autocomplete
function AutocompleteStatus({ className, ...props }: React.ComponentProps<typeof AutocompletePrimitive.Status>) {
  return (
    <AutocompletePrimitive.Status
      data-slot="autocomplete-status"
      className={cn('px-2 py-1.5 text-sm text-muted-foreground empty:m-0 empty:p-0', className)}
      {...props}
    />
  );
}

// Portal - A portal element that moves the popup to a different part of the DOM
function AutocompletePortal({ ...props }: React.ComponentProps<typeof AutocompletePrimitive.Portal>) {
  return <AutocompletePrimitive.Portal data-slot="autocomplete-portal" {...props} />;
}

// Backdrop - An overlay displayed beneath the autocomplete popup
function AutocompleteBackdrop({ ...props }: React.ComponentProps<typeof AutocompletePrimitive.Backdrop>) {
  return <AutocompletePrimitive.Backdrop data-slot="autocomplete-backdrop" {...props} />;
}

// Positioner - Positions the autocomplete popup against the input
function AutocompletePositioner({
  className,
  ...props
}: React.ComponentProps<typeof AutocompletePrimitive.Positioner>) {
  return (
    <AutocompletePrimitive.Positioner
      data-slot="autocomplete-positioner"
      className={cn('z-50 outline-none', className)}
      {...props}
    />
  );
}

// Popup - A container for the autocomplete options
function AutocompletePopup({ className, ...props }: React.ComponentProps<typeof AutocompletePrimitive.Popup>) {
  return (
    <AutocompletePrimitive.Popup
      data-slot="autocomplete-popup"
      className={cn(
        'py-1 w-[var(--anchor-width)] max-h-[min(var(--available-height),23rem)] max-w-[var(--available-width)]',
        'overflow-y-auto scroll-pt-2 scroll-pb-2 overscroll-contain bg-[canvas]',
        'rounded-md border border-border bg-popover text-popover-foreground shadow-md shadow-black/5',
        'origin-[var(--transform-origin)] transition-[transform,scale,opacity] data-[ending-style]:scale-90',
        'data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0',
        className,
      )}
      {...props}
    />
  );
}

// List - A container for the autocomplete options
function AutocompleteList({ className, ...props }: React.ComponentProps<typeof AutocompletePrimitive.List>) {
  return (
    <AutocompletePrimitive.List data-slot="autocomplete-list" className={cn('space-y-0.5', className)} {...props} />
  );
}

// Collection - A collection of autocomplete items
function AutocompleteCollection({ ...props }: React.ComponentProps<typeof AutocompletePrimitive.Collection>) {
  return <AutocompletePrimitive.Collection data-slot="autocomplete-collection" {...props} />;
}

// Row - A row container for autocomplete items
function AutocompleteRow({ className, ...props }: React.ComponentProps<typeof AutocompletePrimitive.Row>) {
  return (
    <AutocompletePrimitive.Row
      data-slot="autocomplete-row"
      className={cn('flex items-center gap-2', className)}
      {...props}
    />
  );
}

// Item - An individual selectable option in the autocomplete
function AutocompleteItem({ className, ...props }: React.ComponentProps<typeof AutocompletePrimitive.Item>) {
  return (
    <AutocompletePrimitive.Item
      data-slot="autocomplete-item"
      className={cn(
        'text-foreground relative flex cursor-default select-none items-center gap-2 rounded-md px-4 py-2 text-sm outline-hidden transition-colors',
        'data-disabled:pointer-events-none data-disabled:opacity-50',
        '[&_svg]:pointer-events-none [&_svg:not([role=img]):not([class*=text-])]:opacity-60 [&_svg:not([class*=size-])]:size-4 [&_svg]:shrink-0',
        'data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-foreground data-[highlighted]:before:absolute',
        'data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-accent',
        className,
      )}
      {...props}
    />
  );
}

export interface AutocompleteContentProps extends React.ComponentProps<typeof AutocompletePrimitive.Popup> {
  align?: AutocompletePrimitive.Positioner.Props['align'];
  sideOffset?: AutocompletePrimitive.Positioner.Props['sideOffset'];
  alignOffset?: AutocompletePrimitive.Positioner.Props['alignOffset'];
  side?: AutocompletePrimitive.Positioner.Props['side'];
  showBackdrop?: boolean;
}

function AutocompleteContent({
  className,
  children,
  showBackdrop = false,
  align = 'start',
  sideOffset = 4,
  alignOffset = 0,
  side = 'bottom',
  ...props
}: AutocompleteContentProps) {
  return (
    <AutocompletePortal>
      {showBackdrop && <AutocompleteBackdrop />}
      <AutocompletePositioner align={align} sideOffset={sideOffset} alignOffset={alignOffset} side={side}>
        <AutocompletePopup className={className} {...props}>
          {children}
        </AutocompletePopup>
      </AutocompletePositioner>
    </AutocompletePortal>
  );
}

// Group - Groups related autocomplete items with the corresponding label
function AutocompleteGroup({ ...props }: React.ComponentProps<typeof AutocompletePrimitive.Group>) {
  return <AutocompletePrimitive.Group data-slot="autocomplete-group" {...props} />;
}

// GroupLabel - An accessible label that is automatically associated with its parent group
function AutocompleteGroupLabel({
  className,
  ...props
}: React.ComponentProps<typeof AutocompletePrimitive.GroupLabel>) {
  return (
    <AutocompletePrimitive.GroupLabel
      data-slot="autocomplete-group-label"
      className={cn('px-2 py-1.5 text-xs text-muted-foreground font-medium', className)}
      {...props}
    />
  );
}

// Empty - A component to display when no options are available
function AutocompleteEmpty({ className, ...props }: React.ComponentProps<typeof AutocompletePrimitive.Empty>) {
  return (
    <AutocompletePrimitive.Empty
      data-slot="autocomplete-empty"
      className={cn('px-2 py-1.5 text-sm text-muted-foreground empty:m-0 empty:p-0', className)}
      {...props}
    />
  );
}

// Clear - A button to clear the input value
function AutocompleteClear({
  className,
  children,
  ...props
}: React.ComponentProps<typeof AutocompletePrimitive.Clear>) {
  return (
    <AutocompletePrimitive.Clear
      data-slot="autocomplete-clear"
      className={cn(
        'absolute cursor-pointer top-1/2 -translate-y-1/2 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100',
        'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none',
        'data-[disabled]:pointer-events-none',
        className,
      )}
      {...props}
    >
      {children ? children : <X className="size-3.5" />}
    </AutocompletePrimitive.Clear>
  );
}

// Arrow - Displays an element positioned against the autocomplete anchor
function AutocompleteArrow({ ...props }: React.ComponentProps<typeof AutocompletePrimitive.Arrow>) {
  return <AutocompletePrimitive.Arrow data-slot="autocomplete-arrow" {...props} />;
}

// Separator - A separator element accessible to screen readers
function AutocompleteSeparator({ className, ...props }: React.ComponentProps<typeof AutocompletePrimitive.Separator>) {
  return (
    <AutocompletePrimitive.Separator
      data-slot="autocomplete-separator"
      className={cn('my-1.5 h-px bg-muted', className)}
      {...props}
    />
  );
}

export {
  Autocomplete,
  AutocompleteControl,
  AutocompleteValue,
  AutocompleteTrigger,
  AutocompleteInput,
  AutocompleteIcon,
  AutocompleteStatus,
  AutocompletePortal,
  AutocompleteBackdrop,
  AutocompletePositioner,
  AutocompletePopup,
  AutocompleteList,
  AutocompleteCollection,
  AutocompleteRow,
  AutocompleteItem,
  AutocompleteContent,
  AutocompleteGroup,
  AutocompleteGroupLabel,
  AutocompleteEmpty,
  AutocompleteClear,
  AutocompleteArrow,
  AutocompleteSeparator,
};

Installation

npx shadcn@latest add @reui/base-autocomplete

Usage

import { BaseAutocomplete } from "@/components/ui/base-autocomplete"
<BaseAutocomplete />