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