1"use client";
2import { mergeProps } from "@base-ui/react/merge-props";
3import { Select as SelectPrimitive } from "@base-ui/react/select";
4import { useRender } from "@base-ui/react/use-render";
5import { cva, type VariantProps } from "class-variance-authority";
6import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
7import type * as React from "react";
8
9import { cn } from "~/lib/utils";
10
11const Select = SelectPrimitive.Root;
12
13const selectTriggerVariants = cva(
14 "relative inline-flex cursor-pointer select-none items-center justify-between gap-2 border rounded-lg text-left text-base outline-none transition-[color,box-shadow,background-color] data-disabled:pointer-events-none data-disabled:opacity-64 sm:text-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4",
15 {
16 defaultVariants: {
17 size: "default",
18 variant: "default",
19 },
20 variants: {
21 variant: {
22 default:
23 "w-full min-w-36 border-input bg-background not-dark:bg-clip-padding text-foreground shadow-xs/5 ring-ring/24 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-inset aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/16 dark:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='text-'])]:text-icon-muted [[data-disabled],:focus-visible,[aria-invalid],[data-pressed]]:shadow-none",
24 ghost:
25 "border-transparent text-secondary-label focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent [:hover,[data-pressed]]:text-foreground",
26 },
27 size: {
28 compact:
29 "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-xs before:rounded-[calc(var(--radius-md)-1px)] [&_svg:not([class*='size-'])]:size-3.5",
30 default: "min-h-9 px-[calc(--spacing(3)-1px)] sm:min-h-8",
31 lg: "min-h-10 px-[calc(--spacing(3)-1px)] sm:min-h-9",
32 sm: "min-h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:min-h-7",
33 xs: "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-sm before:rounded-[calc(var(--radius-md)-1px)] sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5",
34 },
35 },
36 },
37);
38
39function SelectTrigger({
40 className,
41 size = "default",
42 variant = "default",
43 children,
44 icon,
45 ...props
46}: SelectPrimitive.Trigger.Props &
47 VariantProps<typeof selectTriggerVariants> & { icon?: React.ReactNode }) {
48 return (
49 <SelectPrimitive.Trigger
50 className={cn(selectTriggerVariants({ size, variant }), className)}
51 data-slot="select-trigger"
52 {...props}
53 >
54 {children}
55 <SelectPrimitive.Icon data-slot="select-icon">
56 {icon ?? <ChevronDownIcon className="-me-1 size-3 opacity-50" />}
57 </SelectPrimitive.Icon>
58 </SelectPrimitive.Trigger>
59 );
60}
61
62/**
63 * The select-field look for a picker that is not a Select, such as a Menu or
64 * Combobox trigger. Render it as that trigger: `<MenuTrigger render={<SelectButton />}>`.
65 */
66function SelectButton({
67 className,
68 size = "default",
69 children,
70 render,
71 ...props
72}: useRender.ComponentProps<"button"> & Pick<VariantProps<typeof selectTriggerVariants>, "size">) {
73 const defaultProps = {
74 className: cn(selectTriggerVariants({ size }), className),
75 "data-slot": "select-trigger",
76 type: render ? undefined : ("button" as const),
77 };
78 return useRender({
79 defaultTagName: "button",
80 props: {
81 ...mergeProps<"button">(defaultProps, props),
82 children: (
83 <>
84 <span className="min-w-0 flex-1 truncate text-left">{children}</span>
85 <ChevronDownIcon aria-hidden className="-me-1 size-3 shrink-0 opacity-50" />
86 </>
87 ),
88 },
89 render,
90 });
91}
92
93function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
94 return (
95 <SelectPrimitive.Value
96 className={cn("flex-1 truncate data-placeholder:text-placeholder", className)}
97 data-slot="select-value"
98 {...props}
99 />
100 );
101}
102
103function SelectPopup({
104 className,
105 children,
106 side = "bottom",
107 sideOffset = 4,
108 align = "start",
109 alignOffset = 0,
110 alignItemWithTrigger = true,
111 matchTriggerWidth = true,
112 anchor,
113 ...props
114}: SelectPrimitive.Popup.Props & {
115 side?: SelectPrimitive.Positioner.Props["side"];
116 sideOffset?: SelectPrimitive.Positioner.Props["sideOffset"];
117 align?: SelectPrimitive.Positioner.Props["align"];
118 alignOffset?: SelectPrimitive.Positioner.Props["alignOffset"];
119 alignItemWithTrigger?: SelectPrimitive.Positioner.Props["alignItemWithTrigger"];
120 matchTriggerWidth?: boolean;
121 anchor?: SelectPrimitive.Positioner.Props["anchor"];
122}) {
123 return (
124 <SelectPrimitive.Portal>
125 <SelectPrimitive.Positioner
126 align={align}
127 alignItemWithTrigger={alignItemWithTrigger}
128 alignOffset={alignOffset}
129 anchor={anchor}
130 className="z-[130] select-none"
131 data-slot="select-positioner"
132 side={side}
133 sideOffset={sideOffset}
134 >
135 <SelectPrimitive.Popup
136 className="origin-(--transform-origin) rounded-lg text-foreground outline-none"
137 data-slot="select-popup"
138 {...props}
139 >
140 <SelectPrimitive.ScrollUpArrow
141 className="top-0 z-50 flex h-6 w-full cursor-default items-center justify-center before:pointer-events-none before:absolute before:inset-x-px before:top-px before:h-[200%] before:rounded-t-[calc(var(--radius-lg)-1px)] before:bg-linear-to-b before:from-50% before:from-popover"
142 data-slot="select-scroll-up-arrow"
143 >
144 <ChevronUpIcon className="relative size-4.5 sm:size-4" />
145 </SelectPrimitive.ScrollUpArrow>
146 <div
147 className={cn(
148 "dropdown-glass relative h-full rounded-lg shadow-[0_16px_40px_-18px_rgb(0_0_0/55%)] dark:shadow-[0_18px_44px_-18px_rgb(0_0_0/80%)]",
149 matchTriggerWidth && "min-w-(--anchor-width)",
150 )}
151 >
152 <SelectPrimitive.List
153 className={cn("max-h-(--available-height) overflow-y-auto p-1", className)}
154 data-slot="select-list"
155 >
156 {children}
157 </SelectPrimitive.List>
158 </div>
159 <SelectPrimitive.ScrollDownArrow
160 className="bottom-0 z-50 flex h-6 w-full cursor-default items-center justify-center before:pointer-events-none before:absolute before:inset-x-px before:bottom-px before:h-[200%] before:rounded-b-[calc(var(--radius-lg)-1px)] before:bg-linear-to-t before:from-50% before:from-popover"
161 data-slot="select-scroll-down-arrow"
162 >
163 <ChevronDownIcon className="relative size-4.5 sm:size-4" />
164 </SelectPrimitive.ScrollDownArrow>
165 </SelectPrimitive.Popup>
166 </SelectPrimitive.Positioner>
167 </SelectPrimitive.Portal>
168 );
169}
170
171function SelectItem({
172 className,
173 children,
174 hideIndicator: _hideIndicator = false,
175 ...props
176}: SelectPrimitive.Item.Props & {
177 hideIndicator?: boolean;
178}) {
179 return (
180 <SelectPrimitive.Item
181 className={cn(
182 "flex min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-pointer items-center rounded-sm px-2 py-1 text-base outline-none data-selected:bg-foreground/[0.08] data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
183 className,
184 )}
185 data-slot="select-item"
186 {...props}
187 >
188 <SelectPrimitive.ItemText
189 className="min-w-0 flex-1 [&_svg:not([class*='text-'])]:text-muted-foreground"
190 data-slot="select-item-text"
191 >
192 {children}
193 </SelectPrimitive.ItemText>
194 </SelectPrimitive.Item>
195 );
196}
197
198function SelectGroup(props: SelectPrimitive.Group.Props) {
199 return <SelectPrimitive.Group data-slot="select-group" {...props} />;
200}
201
202function SelectGroupLabel(props: SelectPrimitive.GroupLabel.Props) {
203 return (
204 <SelectPrimitive.GroupLabel
205 className="px-2 py-1.5 font-medium text-muted-foreground text-xs"
206 data-slot="select-group-label"
207 {...props}
208 />
209 );
210}
211
212export {
213 Select,
214 SelectTrigger,
215 SelectButton,
216 SelectValue,
217 SelectPopup,
218 SelectPopup as SelectContent,
219 SelectItem,
220 SelectGroup,
221 SelectGroupLabel,
222};