A dropdown-menu component in neobrutalism style.
1"use client"
2
3import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
4import { Check, ChevronRight, Circle } from "lucide-react"
5
6import * as React from "react"
7
8import { cn } from "@/lib/utils"
9
10function DropdownMenu({
11 ...props
12}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
13 return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
14}
15
16function DropdownMenuTrigger({
17 ...props
18}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
19 return (
20 <DropdownMenuPrimitive.Trigger
21 data-slot="dropdown-menu-trigger"
22 {...props}
23 />
24 )
25}
26
27function DropdownMenuGroup({
28 ...props
29}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
30 return <DropdownMenuPrimitive.Group {...props} />
31}
32
33function DropdownMenuPortal({
34 ...props
35}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
36 return <DropdownMenuPrimitive.Portal {...props} />
37}
38
39function DropdownMenuSub({
40 ...props
41}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
42 return <DropdownMenuPrimitive.Sub {...props} />
43}
44
45function DropdownMenuRadioGroup({
46 ...props
47}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
48 return <DropdownMenuPrimitive.RadioGroup {...props} />
49}
50
51function DropdownMenuSubTrigger({
52 className,
53 inset,
54 children,
55 ...props
56}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
57 inset?: boolean
58}) {
59 return (
60 <DropdownMenuPrimitive.SubTrigger
61 data-slot="dropdown-menu-sub-trigger"
62 data-inset={inset}
63 className={cn(
64 "flex cursor-default select-none items-center rounded-base border-2 border-transparent bg-main px-2 py-1.5 text-sm font-base outline-hidden focus:border-border gap-2 data-[inset=true]:pl-8 [&_svg]:pointer-events-none [&_svg]:w-4 [&_svg]:h-4 [&_svg]:shrink-0",
65 className,
66 )}
67 {...props}
68 >
69 {children}
70 <ChevronRight className="ml-auto" />
71 </DropdownMenuPrimitive.SubTrigger>
72 )
73}
74
75function DropdownMenuSubContent({
76 className,
77 ...props
78}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
79 return (
80 <DropdownMenuPrimitive.SubContent
81 className={cn(
82 "z-50 min-w-[8rem] overflow-hidden rounded-base border-2 border-border bg-main p-1 font-base 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-dropdown-menu-content-transform-origin)",
83 className,
84 )}
85 {...props}
86 />
87 )
88}
89
90function DropdownMenuContent({
91 className,
92 sideOffset = 4,
93 ...props
94}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
95 return (
96 <DropdownMenuPrimitive.Portal>
97 <DropdownMenuPrimitive.Content
98 data-slot="dropdown-menu-content"
99 sideOffset={sideOffset}
100 className={cn(
101 "z-50 min-w-[8rem] overflow-hidden rounded-base border-2 border-border bg-main p-1 font-base 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-dropdown-menu-content-transform-origin)",
102 className,
103 )}
104 {...props}
105 />
106 </DropdownMenuPrimitive.Portal>
107 )
108}
109
110function DropdownMenuItem({
111 className,
112 inset,
113 ...props
114}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
115 inset?: boolean
116}) {
117 return (
118 <DropdownMenuPrimitive.Item
119 data-slot="dropdown-menu-item"
120 data-inset={inset}
121 className={cn(
122 "relative gap-2 [&_svg]:pointer-events-none [&_svg]:w-4 [&_svg]:h-4 [&_svg]:shrink-0 flex cursor-default select-none items-center rounded-base border-2 border-transparent data-[inset=true]:pl-8 bg-main px-2 py-1.5 text-sm font-base outline-hidden transition-colors focus:border-border data-disabled:pointer-events-none data-disabled:opacity-50",
123 className,
124 )}
125 {...props}
126 />
127 )
128}
129
130function DropdownMenuCheckboxItem({
131 className,
132 children,
133 checked,
134 ...props
135}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
136 return (
137 <DropdownMenuPrimitive.CheckboxItem
138 className={cn(
139 "relative flex cursor-default select-none items-center rounded-base border-2 border-transparent gap-2 py-1.5 pl-8 pr-2 text-sm font-base text-main-foreground outline-hidden transition-colors 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",
140 className,
141 )}
142 checked={checked}
143 {...props}
144 >
145 <span className="absolute left-2 flex size-3.5 items-center justify-center">
146 <DropdownMenuPrimitive.ItemIndicator>
147 <Check />
148 </DropdownMenuPrimitive.ItemIndicator>
149 </span>
150 {children}
151 </DropdownMenuPrimitive.CheckboxItem>
152 )
153}
154
155function DropdownMenuRadioItem({
156 className,
157 children,
158 ...props
159}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
160 return (
161 <DropdownMenuPrimitive.RadioItem
162 className={cn(
163 "relative flex cursor-default select-none items-center rounded-base border-2 border-transparent gap-2 py-1.5 pl-8 pr-2 text-sm font-base outline-hidden transition-colors focus:border-border data-disabled:pointer-events-none data-disabled:opacity-50",
164 className,
165 )}
166 {...props}
167 >
168 <span className="absolute left-2 flex size-3.5 items-center justify-center">
169 <DropdownMenuPrimitive.ItemIndicator>
170 <Circle className="size-2 fill-current" />
171 </DropdownMenuPrimitive.ItemIndicator>
172 </span>
173 {children}
174 </DropdownMenuPrimitive.RadioItem>
175 )
176}
177
178function DropdownMenuLabel({
179 className,
180 inset,
181 ...props
182}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
183 inset?: boolean
184}) {
185 return (
186 <DropdownMenuPrimitive.Label
187 data-slot="dropdown-menu-label"
188 data-inset={inset}
189 className={cn(
190 "px-2 py-1.5 text-sm font-heading data-[inset]:pl-8",
191 className,
192 )}
193 {...props}
194 />
195 )
196}
197
198function DropdownMenuSeparator({
199 className,
200 ...props
201}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
202 return (
203 <DropdownMenuPrimitive.Separator
204 className={cn("-mx-1 my-1 h-0.5 bg-border", className)}
205 {...props}
206 />
207 )
208}
209
210function DropdownMenuShortcut({
211 className,
212 ...props
213}: React.HTMLAttributes<HTMLSpanElement>) {
214 return (
215 <span
216 data-slot="dropdown-menu-shortcut"
217 className={cn("ml-auto text-xs font-base tracking-widest", className)}
218 {...props}
219 />
220 )
221}
222
223export {
224 DropdownMenu,
225 DropdownMenuTrigger,
226 DropdownMenuContent,
227 DropdownMenuItem,
228 DropdownMenuCheckboxItem,
229 DropdownMenuRadioItem,
230 DropdownMenuLabel,
231 DropdownMenuSeparator,
232 DropdownMenuShortcut,
233 DropdownMenuGroup,
234 DropdownMenuPortal,
235 DropdownMenuSub,
236 DropdownMenuSubContent,
237 DropdownMenuSubTrigger,
238 DropdownMenuRadioGroup,
239}
240