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