Skip to content

Commit 39feb09

Browse files
committed
Improve menus for mobile
1 parent ce673d5 commit 39feb09

2 files changed

Lines changed: 113 additions & 21 deletions

File tree

components/gallery/ArticleCTA.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function ArticleCTA({ article }: { article: Article }) {
99
const [collapsed, setCollapsed] = useState(false)
1010

1111
return (
12-
<div className="pointer-events-none fixed bottom-4 left-4 z-[60] sm:bottom-8 sm:left-8">
12+
<div className="pointer-events-none fixed bottom-4 left-4 z-40 sm:bottom-8 sm:left-8">
1313
<div className="relative">
1414
{collapsed ? (
1515
<button

components/gallery/NavPill.tsx

Lines changed: 112 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client'
22

3+
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
34
import Link from '@/components/Link'
45
import Logo from '@/data/logo.svg'
56
import ThemeSwitch from '@/components/ThemeSwitch'
@@ -8,6 +9,7 @@ import { Github, Discord, Twitter } from '@/components/social-icons/icons'
89
import siteMetadata from '@/data/siteMetadata'
910
import headerNavLinks from '@/data/headerNavLinks'
1011
import { usePathname } from 'next/navigation'
12+
import { useTheme } from 'next-themes'
1113

1214
const 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+
73165
function 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

Comments
 (0)