A navigation-menu component in neobrutalism style.
1"use client"
2
3import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
4import { cva } from "class-variance-authority"
5import { ChevronDown } from "lucide-react"
6
7import * as React from "react"
8
9import { cn } from "@/lib/utils"
10
11function NavigationMenu({
12 className,
13 children,
14 viewport = true,
15 ...props
16}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
17 viewport?: boolean
18}) {
19 return (
20 <NavigationMenuPrimitive.Root
21 data-slot="navigation-menu"
22 data-viewport={viewport}
23 className={cn(
24 "relative z-10 flex max-w-max rounded-base font-heading border-border border-2 p-1 bg-main flex-1 items-center justify-center",
25 className,
26 )}
27 {...props}
28 >
29 {children}
30 {viewport && <NavigationMenuViewport />}
31 </NavigationMenuPrimitive.Root>
32 )
33}
34
35function NavigationMenuList({
36 className,
37 ...props
38}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
39 return (
40 <NavigationMenuPrimitive.List
41 data-slot="navigation-menu-list"
42 className={cn(
43 "group flex flex-1 list-none items-center font-heading justify-center space-x-1",
44 className,
45 )}
46 {...props}
47 />
48 )
49}
50
51function NavigationMenuItem({
52 className,
53 ...props
54}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
55 return (
56 <NavigationMenuPrimitive.Item
57 data-slot="navigation-menu-item"
58 className={cn("relative", className)}
59 {...props}
60 />
61 )
62}
63
64const navigationMenuTriggerStyle = cva(
65 "group inline-flex h-10 w-max items-center justify-center text-main-foreground rounded-base bg-main px-4 py-2 text-sm font-heading transition-colors focus:outline-none disabled:pointer-events-none disabled:opacity-50",
66)
67
68function NavigationMenuTrigger({
69 className,
70 children,
71 ...props
72}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
73 return (
74 <NavigationMenuPrimitive.Trigger
75 data-slot="navigation-menu-trigger"
76 className={cn(navigationMenuTriggerStyle(), "group", className)}
77 {...props}
78 >
79 {children}{" "}
80 <ChevronDown
81 className="relative top-[1px] ml-2 size-4 font-heading transition duration-200 group-data-[state=open]:rotate-180"
82 aria-hidden="true"
83 />
84 </NavigationMenuPrimitive.Trigger>
85 )
86}
87
88function NavigationMenuContent({
89 className,
90 ...props
91}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
92 return (
93 <NavigationMenuPrimitive.Content
94 data-slot="navigation-menu-content"
95 className={cn(
96 "data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
97 "group-data-[viewport=false]/navigation-menu:bg-main group-data-[viewport=false]/navigation-menu:text-main-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
98 className,
99 )}
100 {...props}
101 />
102 )
103}
104
105function NavigationMenuLink({
106 className,
107 ...props
108}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
109 return (
110 <NavigationMenuPrimitive.Link
111 data-slot="navigation-menu-link"
112 className={cn(
113 "block select-none space-y-1 rounded-base p-2 leading-none no-underline outline-none transition-colors focus-visible:ring-4 focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
114 className,
115 )}
116 {...props}
117 />
118 )
119}
120
121function NavigationMenuViewport({
122 className,
123 ...props
124}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
125 return (
126 <div
127 className={cn(
128 "absolute top-full left-0 isolate z-50 flex justify-center",
129 )}
130 >
131 <NavigationMenuPrimitive.Viewport
132 data-slot="navigation-menu-viewport"
133 className={cn(
134 "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full 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]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
135 className,
136 )}
137 {...props}
138 />
139 </div>
140 )
141}
142
143function NavigationMenuIndicator({
144 className,
145 ...props
146}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
147 return (
148 <NavigationMenuPrimitive.Indicator
149 data-slot="navigation-menu-indicator"
150 className={cn(
151 "top-full z-[1] flex h-1.5 items-end font-heading justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
152 className,
153 )}
154 {...props}
155 >
156 <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-white" />
157 </NavigationMenuPrimitive.Indicator>
158 )
159}
160
161export {
162 navigationMenuTriggerStyle,
163 NavigationMenu,
164 NavigationMenuList,
165 NavigationMenuItem,
166 NavigationMenuContent,
167 NavigationMenuTrigger,
168 NavigationMenuLink,
169 NavigationMenuIndicator,
170 NavigationMenuViewport,
171}
172