select

PreviousNext
Docs
takiui

Preview

Loading preview…
registry/new-york/ui/select.tsx
"use client"

import React from "react"
import { ChevronDown } from "lucide-react"
import {
  Select as AriaSelect,
  SelectProps as AriaSelectProps,
  composeRenderProps,
  SelectValue,
  ValidationResult,
  type ListBoxItemProps,
} from "react-aria-components"

import { cn } from "../lib/utils"
import { Button, ButtonProps } from "./button"
import { FieldDescription, FieldError, FieldLabel } from "./field"
import {
  DropdownItem,
  DropdownSection,
  DropdownSectionProps,
  ListBox,
} from "./list-box"
import { Popover } from "./popover"

export interface SelectProps<T extends object>
  extends Omit<AriaSelectProps<T>, "children"> {
  label?: string
  description?: string
  errorMessage?: string | ((validation: ValidationResult) => string)
  items?: Iterable<T>
  children: React.ReactNode | ((item: T) => React.ReactNode)
  size?: ButtonProps["size"]
  enableSearch?: boolean
  filterBehavior?: "contains" | "startsWith"
  variant?: ButtonProps["variant"]
}

export function SelectRoot<T extends object>(props: AriaSelectProps<T>) {
  return (
    <AriaSelect<T>
      {...props}
      className={composeRenderProps(props.className, (className) =>
        cn("group relative flex flex-col gap-1", className)
      )}
    >
      {props.children}
    </AriaSelect>
  )
}

export function Select<T extends object>({
  label,
  description,
  errorMessage,
  children,
  items,
  size = "default",
  variant = "outline",
  ...props
}: SelectProps<T>) {
  return (
    <SelectRoot {...props}>
      {label && <FieldLabel>{label}</FieldLabel>}
      <Button
        size={size}
        variant={variant}
        data-slot="select-trigger"
        className="text-start"
      >
        <SelectValue
          data-slot="select-value"
          className="placeholder-shown:text-muted-foreground flex-1 text-sm"
        >
          {({ selectedText, defaultChildren }) =>
            selectedText || defaultChildren
          }
        </SelectValue>
        <ChevronDown aria-hidden />
      </Button>
      {description && <FieldDescription>{description}</FieldDescription>}
      <FieldError>{errorMessage}</FieldError>
      <Popover className="w-56 min-w-(--trigger-width)">
        <ListBox
          items={items}
          className="max-h-[inherit] overflow-auto border-none p-1 outline-hidden [clip-path:inset(0_0_0_0_round_.75rem)]"
        >
          {children}
        </ListBox>
      </Popover>
    </SelectRoot>
  )
}

export function SelectItem(props: ListBoxItemProps) {
  return <DropdownItem {...props} />
}

export function SelectSection<T extends object>(
  props: DropdownSectionProps<T>
) {
  return <DropdownSection {...props} />
}

Installation

npx shadcn@latest add @taki/select

Usage

import { Select } from "@/components/ui/select"
<Select />