Skip to content

Commit 4153a1f

Browse files
committed
style desktop nav menu using tailwind classes
1 parent c350ea3 commit 4153a1f

15 files changed

+200
-120
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"reading-time": "^1.5.0",
8484
"remark-gfm": "^3.0.1",
8585
"tailwind-merge": "^2.3.0",
86+
"tailwind-variants": "^0.2.1",
8687
"tailwindcss-animate": "^1.0.7",
8788
"usehooks-ts": "^3.1.0",
8889
"yaml-loader": "^0.8.0"

src/components/Nav/Menu/MenuContent.tsx

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,42 @@
11
import { motion } from "framer-motion"
2-
import { Box } from "@chakra-ui/react"
2+
import { tv } from "tailwind-variants"
33
import { Content } from "@radix-ui/react-navigation-menu"
44

5+
import { cn } from "@/lib/utils/cn"
6+
57
import { NavItem, NavSections } from "../types"
68

79
import SubMenu from "./SubMenu"
810
import { useNavMenu } from "./useNavMenu"
911

12+
export const navMenu = tv({
13+
slots: {
14+
base: "text-body",
15+
item: "w-full relative -me-4 py-4 hover:text-menu-active [&:hover_p]:text-menu-active focus-visible:text-menu-active [&:focus-visible_p]:text-menu-active hover:outline-0 hover:rounded-md hover:shadow-none focus-visible:outline-0 focus-visible:rounded-md focus-visible:shadow-none",
16+
submenu: "grid h-full w-full grid-cols-1",
17+
},
18+
variants: {
19+
level: {
20+
1: {
21+
submenu: "grid-cols-3 bg-menu-1-background",
22+
item: "data-[active=true]:bg-menu-1-active-background hover:bg-menu-1-active-background focus-visible:bg-menu-1-active-background",
23+
},
24+
2: {
25+
submenu: "grid-cols-2 bg-menu-2-background",
26+
item: "hover:bg-menu-2-active-background focus-visible:bg-menu-2-active-background data-[active=true]:bg-menu-2-active-background",
27+
},
28+
3: {
29+
submenu: "grid-cols-1 bg-menu-3-background",
30+
item: "data-[active=true]:bg-menu-3-active-background hover:bg-menu-3-active-background",
31+
},
32+
4: {
33+
submenu: "grid-cols-1 bg-menu-4-background",
34+
item: "data-[active=true]:bg-menu-4-active-background hover:bg-menu-4-active-background",
35+
},
36+
},
37+
},
38+
})
39+
1040
type MenuContentProps = {
1141
items: NavItem[]
1242
isOpen: boolean
@@ -15,31 +45,27 @@ type MenuContentProps = {
1545

1646
// Desktop Menu content
1747
const MenuContent = ({ items, isOpen, sections }: MenuContentProps) => {
18-
const { activeSection, containerVariants, menuColors, onClose } =
19-
useNavMenu(sections)
48+
const { activeSection, containerVariants, onClose } = useNavMenu(sections)
49+
const { base } = navMenu()
2050

2151
return (
2252
<Content asChild>
23-
<Box
24-
as={motion.div}
53+
<motion.div
54+
className={cn(
55+
"absolute inset-x-0 top-19 border border-body-light shadow-md",
56+
base()
57+
)}
2558
variants={containerVariants}
2659
initial={false}
2760
animate={isOpen ? "open" : "closed"}
28-
position="absolute"
29-
top="19"
30-
insetInline="0"
31-
shadow="md"
32-
border="1px"
33-
borderColor={menuColors.stroke}
34-
bg={menuColors.lvl[1].background}
3561
>
3662
<SubMenu
3763
lvl={1}
3864
items={items}
3965
activeSection={activeSection}
4066
onClose={onClose}
4167
/>
42-
</Box>
68+
</motion.div>
4369
</Content>
4470
)
4571
}

src/components/Nav/Menu/SubMenu.tsx

Lines changed: 48 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
import { AnimatePresence, motion } from "framer-motion"
22
import NextLink from "next/link"
3-
import {
4-
Box,
5-
Button,
6-
Grid,
7-
Icon,
8-
ListItem,
9-
UnorderedList,
10-
} from "@chakra-ui/react"
113
import {
124
Content,
135
Item,
@@ -18,15 +10,18 @@ import {
1810
Viewport,
1911
} from "@radix-ui/react-navigation-menu"
2012

21-
import { ButtonProps } from "@/components/Buttons"
22-
import Link from "@/components/Link"
13+
import { Button } from "@/components/ui/buttons/Button"
14+
import { BaseLink } from "@/components/ui/Link"
15+
import { ListItem, UnorderedList } from "@/components/ui/list"
2316

17+
import { cn } from "@/lib/utils/cn"
2418
import { trackCustomEvent } from "@/lib/utils/matomo"
2519
import { cleanPath } from "@/lib/utils/url"
2620

2721
import type { Level, NavItem, NavSectionKey } from "../types"
2822

2923
import ItemContent from "./ItemContent"
24+
import { navMenu } from "./MenuContent"
3025
import NextChevron from "./NextChevron"
3126
import { useSubMenu } from "./useSubMenu"
3227

@@ -47,77 +42,56 @@ type LvlContentProps = {
4742
* @returns The JSX element representing the menu content.
4843
*/
4944
const SubMenu = ({ lvl, items, activeSection, onClose }: LvlContentProps) => {
50-
const { asPath, locale, menuColors, menuVariants, PADDING } = useSubMenu()
45+
const { asPath, locale, menuVariants } = useSubMenu()
46+
const { submenu, item: itemClasses } = navMenu({ level: lvl })
5147

5248
if (lvl > 3) return null
5349

54-
const templateColumns = `repeat(${4 - lvl}, 1fr)`
55-
5650
return (
5751
<Sub orientation="vertical" asChild>
5852
<AnimatePresence>
59-
<Grid
60-
as={motion.div}
53+
<motion.div
54+
className={submenu()}
6155
variants={menuVariants}
6256
initial="closed"
6357
animate="open"
6458
exit="closed"
65-
w="full"
66-
h="full"
67-
gridTemplateColumns={templateColumns}
6859
>
6960
<List asChild>
70-
<UnorderedList listStyleType="none" p={PADDING / 2} m="0">
61+
<UnorderedList className="m-0 list-none p-2">
7162
{items.map((item) => {
72-
const { label, icon, ...action } = item
63+
const { label, icon: Icon, ...action } = item
7364
const subItems = action.items || []
7465
const isLink = "href" in action
7566
const isActivePage = isLink && cleanPath(asPath) === action.href
76-
const activeStyles = {
77-
outline: "none",
78-
rounded: "md",
79-
"p, svg": { color: menuColors.highlight },
80-
bg: menuColors.lvl[lvl].activeBackground,
81-
boxShadow: "none",
82-
}
83-
const buttonProps: ButtonProps = {
84-
color: menuColors.body,
85-
leftIcon: lvl === 1 && icon ? <Icon as={icon} /> : undefined,
86-
rightIcon: isLink ? undefined : <NextChevron />,
87-
position: "relative",
88-
w: "full",
89-
me: -PADDING,
90-
sx: {
91-
"span:first-of-type": { m: 0, me: 4 }, // Spacing for icon
92-
},
93-
py: PADDING,
94-
bg: isActivePage
95-
? menuColors.lvl[lvl].activeBackground
96-
: "none",
97-
_hover: activeStyles,
98-
_focus: activeStyles,
99-
variant: "ghost",
100-
}
67+
68+
const buttonClasses = cn(
69+
"no-underline text-body",
70+
itemClasses()
71+
)
72+
10173
return (
10274
<Item key={label} asChild>
10375
<ListItem
104-
mb={PADDING / 2}
105-
_last={{ mb: 0 }}
106-
sx={{
107-
'&:has(button[data-state="open"])': {
108-
roundedStart: "md",
109-
roundedEnd: "none",
110-
bg: menuColors.lvl[lvl].activeBackground,
111-
me: -PADDING,
112-
pe: PADDING,
113-
},
114-
}}
76+
className="mb-2 last:mb-0"
77+
// TODO
78+
// sx={{
79+
// '&:has(button[data-state="open"])': {
80+
// roundedStart: "md",
81+
// roundedEnd: "none",
82+
// bg: menuColors.lvl[lvl].activeBackground,
83+
// me: -PADDING,
84+
// pe: PADDING,
85+
// },
86+
// }}
11587
>
11688
{isLink ? (
11789
<NextLink href={action.href!} passHref legacyBehavior>
11890
<NavigationMenuLink asChild>
11991
<Button
120-
as={Link}
92+
variant="ghost"
93+
className={buttonClasses}
94+
data-active={isActivePage}
12195
onClick={() => {
12296
onClose()
12397
trackCustomEvent({
@@ -126,31 +100,39 @@ const SubMenu = ({ lvl, items, activeSection, onClose }: LvlContentProps) => {
126100
eventName: action.href!,
127101
})
128102
}}
129-
{...buttonProps}
103+
asChild
130104
>
131-
<ItemContent item={item} lvl={lvl} />
105+
<BaseLink>
106+
{lvl === 1 && Icon ? (
107+
<Icon className="me-4 h-6 w-6" />
108+
) : null}
109+
110+
<ItemContent item={item} lvl={lvl} />
111+
</BaseLink>
132112
</Button>
133113
</NavigationMenuLink>
134114
</NextLink>
135115
) : (
136116
<>
137117
<Trigger asChild>
138-
<Button {...buttonProps}>
118+
<Button variant="ghost" className={buttonClasses}>
119+
{lvl === 1 && Icon ? (
120+
<Icon className="me-4 h-6 w-6" />
121+
) : null}
122+
139123
<ItemContent item={item} lvl={lvl} />
124+
<NextChevron />
140125
</Button>
141126
</Trigger>
142127
<Content asChild>
143-
<Box
144-
bg={menuColors.lvl[lvl + 1].background}
145-
h="full"
146-
>
128+
<div className="h-full">
147129
<SubMenu
148130
lvl={(lvl + 1) as Level}
149131
items={subItems}
150132
activeSection={activeSection}
151133
onClose={onClose}
152134
/>
153-
</Box>
135+
</div>
154136
</Content>
155137
</>
156138
)}
@@ -161,7 +143,7 @@ const SubMenu = ({ lvl, items, activeSection, onClose }: LvlContentProps) => {
161143
</UnorderedList>
162144
</List>
163145
<Viewport style={{ gridColumn: "2/4" }} />
164-
</Grid>
146+
</motion.div>
165147
</AnimatePresence>
166148
</Sub>
167149
)

src/components/Nav/Menu/useNavMenu.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
import { useState } from "react"
22
import type { MotionProps } from "framer-motion"
3-
import { useEventListener } from "@chakra-ui/react"
43

54
import { isModified } from "@/lib/utils/keyboard"
65

76
import { MAIN_NAV_ID, SECTION_LABELS } from "@/lib/constants"
87

98
import type { NavSectionKey, NavSections } from "../types"
109

11-
import { useNavMenuColors } from "@/hooks/useNavMenuColors"
10+
import { useEventListener } from "@/hooks/useEventListener"
1211
import { useRtlFlip } from "@/hooks/useRtlFlip"
1312

1413
export const useNavMenu = (sections: NavSections) => {
1514
const { direction } = useRtlFlip()
16-
const menuColors = useNavMenuColors()
1715
const [activeSection, setActiveSection] = useState<NavSectionKey | null>(null)
1816

1917
// Focus corresponding nav section when number keys pressed
@@ -72,7 +70,6 @@ export const useNavMenu = (sections: NavSections) => {
7270
direction,
7371
handleSectionChange,
7472
isOpen,
75-
menuColors,
7673
onClose,
7774
}
7875
}

src/components/Nav/Menu/useSubMenu.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import type { MotionProps } from "framer-motion"
22
import { useRouter } from "next/router"
33

4-
import { useNavMenuColors } from "@/hooks/useNavMenuColors"
54
import { useRtlFlip } from "@/hooks/useRtlFlip"
65

7-
const PADDING = 4 // Chakra-UI space token
8-
96
export const useSubMenu = () => {
107
const { asPath, locale } = useRouter()
11-
const menuColors = useNavMenuColors()
128
const { isRtl } = useRtlFlip()
139

1410
const menuVariants: MotionProps["variants"] = {
@@ -19,8 +15,6 @@ export const useSubMenu = () => {
1915
return {
2016
asPath,
2117
locale,
22-
menuColors,
2318
menuVariants,
24-
PADDING,
2519
}
2620
}

src/components/Nav/types.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { RefObject } from "react"
1+
import type { FC, RefObject, SVGProps } from "react"
22
import type { IconType } from "react-icons"
33
import type { IconProps } from "@chakra-ui/react"
44

@@ -9,7 +9,10 @@ type LinkXorItems = LinkOnly | ItemsOnly
99
export type NavItem = {
1010
label: string
1111
description: string
12-
icon?: IconType | ((props: IconProps) => JSX.Element)
12+
icon?:
13+
| IconType
14+
| FC<SVGProps<SVGElement>>
15+
| ((props: IconProps) => JSX.Element)
1316
} & LinkXorItems
1417

1518
export type NavSectionKey =

src/components/Nav/useNav.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
import { PiFlask, PiUsersFourLight } from "react-icons/pi"
2121
import { useColorMode } from "@chakra-ui/react"
2222

23-
import { EthereumIcon } from "@/components/icons/EthereumIcon"
23+
import EthereumIcon from "@/components/icons/ethereum-icon.svg"
2424

2525
import { trackCustomEvent } from "@/lib/utils/matomo"
2626

src/components/icons/EthereumIcon.tsx

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/components/icons/Icons.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as React from "react"
22
import { Center, Flex, Icon, SimpleGrid } from "@chakra-ui/react"
33
import type { Meta, StoryObj } from "@storybook/react"
44

5-
import { EthHomeIcon } from "./EthHomeIcon"
5+
import EthHomeIcon from "./eth-home-icon.svg"
66
import FeedbackThumbsUpIcon from "./feedback-thumbs-up-icon.svg"
77
import { HighlightDarkIcon } from "./HighlightDarkIcon"
88
import { HighlightIcon } from "./HighlightIcon"

0 commit comments

Comments
 (0)