/ src / components / ui / select.tsx
select.tsx
  1  "use client"
  2  
  3  import * as React from "react"
  4  import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
  5  import { Select as SelectPrimitive } from "radix-ui"
  6  
  7  import { cn } from "@/lib/utils"
  8  
  9  function Select({
 10    ...props
 11  }: React.ComponentProps<typeof SelectPrimitive.Root>) {
 12    return <SelectPrimitive.Root data-slot="select" {...props} />
 13  }
 14  
 15  function SelectGroup({
 16    ...props
 17  }: React.ComponentProps<typeof SelectPrimitive.Group>) {
 18    return <SelectPrimitive.Group data-slot="select-group" {...props} />
 19  }
 20  
 21  function SelectValue({
 22    ...props
 23  }: React.ComponentProps<typeof SelectPrimitive.Value>) {
 24    return <SelectPrimitive.Value data-slot="select-value" {...props} />
 25  }
 26  
 27  function SelectTrigger({
 28    className,
 29    size = "default",
 30    children,
 31    ...props
 32  }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
 33    size?: "sm" | "default"
 34  }) {
 35    return (
 36      <SelectPrimitive.Trigger
 37        data-slot="select-trigger"
 38        data-size={size}
 39        className={cn(
 40          "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
 41          className
 42        )}
 43        {...props}
 44      >
 45        {children}
 46        <SelectPrimitive.Icon asChild>
 47          <ChevronDownIcon className="size-4 opacity-50" />
 48        </SelectPrimitive.Icon>
 49      </SelectPrimitive.Trigger>
 50    )
 51  }
 52  
 53  function SelectContent({
 54    className,
 55    children,
 56    position = "item-aligned",
 57    align = "center",
 58    ...props
 59  }: React.ComponentProps<typeof SelectPrimitive.Content>) {
 60    return (
 61      <SelectPrimitive.Portal>
 62        <SelectPrimitive.Content
 63          data-slot="select-content"
 64          className={cn(
 65            "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
 66            position === "popper" &&
 67              "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
 68            className
 69          )}
 70          position={position}
 71          align={align}
 72          {...props}
 73        >
 74          <SelectScrollUpButton />
 75          <SelectPrimitive.Viewport
 76            className={cn(
 77              "p-1",
 78              position === "popper" &&
 79                "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
 80            )}
 81          >
 82            {children}
 83          </SelectPrimitive.Viewport>
 84          <SelectScrollDownButton />
 85        </SelectPrimitive.Content>
 86      </SelectPrimitive.Portal>
 87    )
 88  }
 89  
 90  function SelectLabel({
 91    className,
 92    ...props
 93  }: React.ComponentProps<typeof SelectPrimitive.Label>) {
 94    return (
 95      <SelectPrimitive.Label
 96        data-slot="select-label"
 97        className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
 98        {...props}
 99      />
100    )
101  }
102  
103  function SelectItem({
104    className,
105    children,
106    ...props
107  }: React.ComponentProps<typeof SelectPrimitive.Item>) {
108    return (
109      <SelectPrimitive.Item
110        data-slot="select-item"
111        className={cn(
112          "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
113          className
114        )}
115        {...props}
116      >
117        <span
118          data-slot="select-item-indicator"
119          className="absolute right-2 flex size-3.5 items-center justify-center"
120        >
121          <SelectPrimitive.ItemIndicator>
122            <CheckIcon className="size-4" />
123          </SelectPrimitive.ItemIndicator>
124        </span>
125        <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
126      </SelectPrimitive.Item>
127    )
128  }
129  
130  function SelectSeparator({
131    className,
132    ...props
133  }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
134    return (
135      <SelectPrimitive.Separator
136        data-slot="select-separator"
137        className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
138        {...props}
139      />
140    )
141  }
142  
143  function SelectScrollUpButton({
144    className,
145    ...props
146  }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
147    return (
148      <SelectPrimitive.ScrollUpButton
149        data-slot="select-scroll-up-button"
150        className={cn(
151          "flex cursor-default items-center justify-center py-1",
152          className
153        )}
154        {...props}
155      >
156        <ChevronUpIcon className="size-4" />
157      </SelectPrimitive.ScrollUpButton>
158    )
159  }
160  
161  function SelectScrollDownButton({
162    className,
163    ...props
164  }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
165    return (
166      <SelectPrimitive.ScrollDownButton
167        data-slot="select-scroll-down-button"
168        className={cn(
169          "flex cursor-default items-center justify-center py-1",
170          className
171        )}
172        {...props}
173      >
174        <ChevronDownIcon className="size-4" />
175      </SelectPrimitive.ScrollDownButton>
176    )
177  }
178  
179  export {
180    Select,
181    SelectContent,
182    SelectGroup,
183    SelectItem,
184    SelectLabel,
185    SelectScrollDownButton,
186    SelectScrollUpButton,
187    SelectSeparator,
188    SelectTrigger,
189    SelectValue,
190  }