11'use client'
22
3+ import { Menu , MenuButton , MenuItem , MenuItems } from '@headlessui/react'
34import Link from '@/components/Link'
45import Logo from '@/data/logo.svg'
56import ThemeSwitch from '@/components/ThemeSwitch'
@@ -8,6 +9,7 @@ import { Github, Discord, Twitter } from '@/components/social-icons/icons'
89import siteMetadata from '@/data/siteMetadata'
910import headerNavLinks from '@/data/headerNavLinks'
1011import { usePathname } from 'next/navigation'
12+ import { useTheme } from 'next-themes'
1113
1214const NAV_LINKS = headerNavLinks . filter ( ( l ) => l . href !== '/' )
1315
@@ -33,7 +35,7 @@ export function NavPill() {
3335 ) }
3436
3537 < nav
36- className = "fixed top-3 left-[calc(100vw-1 .75rem)] z-50 flex -translate-x-full items-center gap-px rounded-full bg-white p-1.5 shadow-[0_1px_6px_rgb(0_0_0/0.08)] ring-1 ring-black/5 dark:bg-[#1a1a1a] dark:shadow-[0_1px_6px_rgb(0_0_0/0.3)] dark:ring-white/10"
38+ className = "fixed top-3 left-[calc(100vw-0 .75rem)] z-50 flex -translate-x-full items-center gap-px rounded-full bg-white p-1.5 shadow-[0_1px_6px_rgb(0_0_0/0.08)] ring-1 ring-black/5 sm:left-[calc(100vw-1.75rem)] dark:bg-[#1a1a1a] dark:shadow-[0_1px_6px_rgb(0_0_0/0.3)] dark:ring-white/10"
3739 aria-label = "Site navigation"
3840 >
3941 { NAV_LINKS . map ( ( { href, title } ) => (
@@ -46,30 +48,120 @@ export function NavPill() {
4648 </ Link >
4749 ) ) }
4850
49- < span className = "mx-0.5 h-4 w-px bg-black/10 dark:bg-white/10" aria-hidden />
50-
51- < PillIconButton label = "Search" >
52- < SearchButton />
53- </ PillIconButton >
54-
55- < PillIconButton label = "Theme" >
56- < ThemeSwitch />
57- </ PillIconButton >
58-
59- { iconLinks . map ( ( { href, Icon, label } ) =>
60- href ? (
61- < PillIconButton key = { label } label = { label } >
62- < a href = { href } target = "_blank" rel = "noopener noreferrer" aria-label = { label } >
63- < Icon className = "h-[0.95rem] w-[0.95rem] fill-current text-[#555] dark:text-[#aaa]" />
64- </ a >
65- </ PillIconButton >
66- ) : null
67- ) }
51+ < span className = "mx-0.5 hidden h-4 w-px bg-black/10 sm:block dark:bg-white/10" aria-hidden />
52+
53+ < div className = "hidden items-center gap-px sm:flex" >
54+ < PillIconButton label = "Search" >
55+ < SearchButton />
56+ </ PillIconButton >
57+
58+ < PillIconButton label = "Theme" >
59+ < ThemeSwitch />
60+ </ PillIconButton >
61+
62+ { iconLinks . map ( ( { href, Icon, label } ) =>
63+ href ? (
64+ < PillIconButton key = { label } label = { label } >
65+ < a href = { href } target = "_blank" rel = "noopener noreferrer" aria-label = { label } >
66+ < Icon className = "h-[0.95rem] w-[0.95rem] fill-current text-[#555] dark:text-[#aaa]" />
67+ </ a >
68+ </ PillIconButton >
69+ ) : null
70+ ) }
71+ </ div >
72+
73+ < MobileMoreMenu />
6874 </ nav >
6975 </ >
7076 )
7177}
7278
79+ function MobileMoreMenu ( ) {
80+ return (
81+ < Menu as = "div" className = "relative sm:hidden" >
82+ < MenuButton
83+ aria-label = "More actions"
84+ className = "grid h-[1.8rem] w-[1.8rem] place-items-center rounded-full text-[#555] transition-colors duration-150 hover:bg-black/6 hover:text-[#111] dark:text-[#aaa] dark:hover:bg-white/8 dark:hover:text-white"
85+ >
86+ < svg viewBox = "0 0 16 16" aria-hidden className = "h-[0.95rem] w-[0.95rem] fill-current" >
87+ < circle cx = "3" cy = "8" r = "1.25" />
88+ < circle cx = "8" cy = "8" r = "1.25" />
89+ < circle cx = "13" cy = "8" r = "1.25" />
90+ </ svg >
91+ </ MenuButton >
92+
93+ < MenuItems static = { false } className = "absolute top-full right-0 z-[60] mt-2 min-w-[12rem] rounded-2xl bg-white p-2 shadow-[0_10px_30px_rgb(0_0_0/0.12)] ring-1 ring-black/5 focus:outline-none dark:bg-[#1a1a1a] dark:ring-white/10" >
94+ < div className = "flex flex-col gap-1" >
95+ { /* Search — whole row triggers the nested SearchButton */ }
96+ < div
97+ className = "flex h-10 cursor-pointer items-center rounded-xl px-3 text-[#555] hover:bg-black/6 dark:text-[#aaa] dark:hover:bg-white/8"
98+ onClick = { ( e ) => {
99+ const btn = e . currentTarget . querySelector ( 'button' )
100+ if ( btn && e . target === e . currentTarget ) btn . click ( )
101+ } }
102+ >
103+ < span className = "text-[0.8rem] font-medium" > Search</ span >
104+ < div className = "ml-auto flex h-[0.95rem] w-[0.95rem] items-center justify-center [&_button]:!p-0 [&_svg]:h-[0.95rem] [&_svg]:w-[0.95rem]" >
105+ < SearchButton />
106+ </ div >
107+ </ div >
108+
109+ { /* Theme — inline buttons to avoid nested Headless UI Menu */ }
110+ < MobileThemeRow />
111+
112+ < div className = "my-1 h-px bg-black/10 dark:bg-white/10" />
113+
114+ { iconLinks . map ( ( { href, Icon, label } ) =>
115+ href ? (
116+ < MenuItem key = { label } >
117+ < a
118+ href = { href }
119+ target = "_blank"
120+ rel = "noopener noreferrer"
121+ className = "flex h-10 items-center rounded-xl px-3 text-[#555] transition-colors hover:bg-black/6 hover:text-[#111] dark:text-[#aaa] dark:hover:bg-white/8 dark:hover:text-white"
122+ >
123+ < span className = "text-[0.8rem] font-medium" > { label } </ span >
124+ < Icon className = "ml-auto h-[0.95rem] w-[0.95rem] fill-current" />
125+ </ a >
126+ </ MenuItem >
127+ ) : null
128+ ) }
129+ </ div >
130+ </ MenuItems >
131+ </ Menu >
132+ )
133+ }
134+
135+ const themeOptions = [
136+ { value : 'light' , label : 'Light' } ,
137+ { value : 'dark' , label : 'Dark' } ,
138+ { value : 'system' , label : 'Auto' } ,
139+ ] as const
140+
141+ function MobileThemeRow ( ) {
142+ const { theme, setTheme } = useTheme ( )
143+ return (
144+ < div className = "flex h-10 items-center rounded-xl px-3 text-[#555] dark:text-[#aaa]" >
145+ < span className = "text-[0.8rem] font-medium" > Theme</ span >
146+ < div className = "ml-auto flex gap-1" >
147+ { themeOptions . map ( ( opt ) => (
148+ < button
149+ key = { opt . value }
150+ onClick = { ( ) => setTheme ( opt . value ) }
151+ className = { `rounded-lg px-2 py-1 text-[0.7rem] font-medium transition-colors ${
152+ theme === opt . value
153+ ? 'bg-black/8 text-[#111] dark:bg-white/12 dark:text-white'
154+ : 'text-[#999] hover:text-[#555] dark:text-[#666] dark:hover:text-[#aaa]'
155+ } `}
156+ >
157+ { opt . label }
158+ </ button >
159+ ) ) }
160+ </ div >
161+ </ div >
162+ )
163+ }
164+
73165function PillIconButton ( { label, children } : { label : string ; children : React . ReactNode } ) {
74166 return (
75167 < div className = "group relative grid h-[1.8rem] w-[1.8rem] place-items-center rounded-full text-[#555] transition-colors duration-150 hover:bg-black/6 hover:text-[#111] dark:text-[#aaa] dark:hover:bg-white/8 dark:hover:text-white" >
0 commit comments