mirror of
https://github.com/soconnor0919/personal-website.git
synced 2025-12-15 16:24:44 -05:00
Add system theme toggler
This commit is contained in:
@@ -32,7 +32,7 @@
|
||||
"fs": "0.0.1-security",
|
||||
"geist": "^1.3.1",
|
||||
"lucide-react": "^0.454.0",
|
||||
"next": "^15.0.1",
|
||||
"next": "^15.0.2",
|
||||
"next-themes": "^0.3.0",
|
||||
"radix-ui": "^1.0.1",
|
||||
"react": "^18.3.1",
|
||||
|
||||
104
pnpm-lock.yaml
generated
104
pnpm-lock.yaml
generated
@@ -52,10 +52,10 @@ importers:
|
||||
version: 0.11.1(typescript@5.6.3)(zod@3.23.8)
|
||||
'@vercel/analytics':
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2(next@15.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
version: 1.3.2(next@15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
'@vercel/speed-insights':
|
||||
specifier: ^1.0.14
|
||||
version: 1.0.14(next@15.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
version: 1.0.14(next@15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0
|
||||
@@ -70,13 +70,13 @@ importers:
|
||||
version: 0.0.1-security
|
||||
geist:
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1(next@15.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
||||
version: 1.3.1(next@15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
||||
lucide-react:
|
||||
specifier: ^0.454.0
|
||||
version: 0.454.0(react@18.3.1)
|
||||
next:
|
||||
specifier: ^15.0.1
|
||||
version: 15.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: ^15.0.2
|
||||
version: 15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next-themes:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -350,56 +350,56 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@next/env@15.0.1':
|
||||
resolution: {integrity: sha512-lc4HeDUKO9gxxlM5G2knTRifqhsY6yYpwuHspBZdboZe0Gp+rZHBNNSIjmQKDJIdRXiXGyVnSD6gafrbQPvILQ==}
|
||||
'@next/env@15.0.2':
|
||||
resolution: {integrity: sha512-c0Zr0ModK5OX7D4ZV8Jt/wqoXtitLNPwUfG9zElCZztdaZyNVnN40rDXVZ/+FGuR4CcNV5AEfM6N8f+Ener7Dg==}
|
||||
|
||||
'@next/eslint-plugin-next@15.0.1':
|
||||
resolution: {integrity: sha512-bKWsMaGPbiFAaGqrDJvbE8b4Z0uKicGVcgOI77YM2ui3UfjHMr4emFPrZTLeZVchi7fT1mooG2LxREfUUClIKw==}
|
||||
|
||||
'@next/swc-darwin-arm64@15.0.1':
|
||||
resolution: {integrity: sha512-C9k/Xv4sxkQRTA37Z6MzNq3Yb1BJMmSqjmwowoWEpbXTkAdfOwnoKOpAb71ItSzoA26yUTIo6ZhN8rKGu4ExQw==}
|
||||
'@next/swc-darwin-arm64@15.0.2':
|
||||
resolution: {integrity: sha512-GK+8w88z+AFlmt+ondytZo2xpwlfAR8U6CRwXancHImh6EdGfHMIrTSCcx5sOSBei00GyLVL0ioo1JLKTfprgg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@15.0.1':
|
||||
resolution: {integrity: sha512-uHl13HXOuq1G7ovWFxCACDJHTSDVbn/sbLv8V1p+7KIvTrYQ5HNoSmKBdYeEKRRCbEmd+OohOgg9YOp8Ux3MBg==}
|
||||
'@next/swc-darwin-x64@15.0.2':
|
||||
resolution: {integrity: sha512-KUpBVxIbjzFiUZhiLIpJiBoelqzQtVZbdNNsehhUn36e2YzKHphnK8eTUW1s/4aPy5kH/UTid8IuVbaOpedhpw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.0.1':
|
||||
resolution: {integrity: sha512-LvyhvxHOihFTEIbb35KxOc3q8w8G4xAAAH/AQnsYDEnOvwawjL2eawsB59AX02ki6LJdgDaHoTEnC54Gw+82xw==}
|
||||
'@next/swc-linux-arm64-gnu@15.0.2':
|
||||
resolution: {integrity: sha512-9J7TPEcHNAZvwxXRzOtiUvwtTD+fmuY0l7RErf8Yyc7kMpE47MIQakl+3jecmkhOoIyi/Rp+ddq7j4wG6JDskQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.0.1':
|
||||
resolution: {integrity: sha512-vFmCGUFNyk/A5/BYcQNhAQqPIw01RJaK6dRO+ZEhz0DncoW+hJW1kZ8aH2UvTX27zPq3m85zN5waMSbZEmANcQ==}
|
||||
'@next/swc-linux-arm64-musl@15.0.2':
|
||||
resolution: {integrity: sha512-BjH4ZSzJIoTTZRh6rG+a/Ry4SW0HlizcPorqNBixBWc3wtQtj4Sn9FnRZe22QqrPnzoaW0ctvSz4FaH4eGKMww==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.0.1':
|
||||
resolution: {integrity: sha512-5by7IYq0NCF8rouz6Qg9T97jYU68kaClHPfGpQG2lCZpSYHtSPQF1kjnqBTd34RIqPKMbCa4DqCufirgr8HM5w==}
|
||||
'@next/swc-linux-x64-gnu@15.0.2':
|
||||
resolution: {integrity: sha512-i3U2TcHgo26sIhcwX/Rshz6avM6nizrZPvrDVDY1bXcLH1ndjbO8zuC7RoHp0NSK7wjJMPYzm7NYL1ksSKFreA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-musl@15.0.1':
|
||||
resolution: {integrity: sha512-lmYr6H3JyDNBJLzklGXLfbehU3ay78a+b6UmBGlHls4xhDXBNZfgb0aI67sflrX+cGBnv1LgmWzFlYrAYxS1Qw==}
|
||||
'@next/swc-linux-x64-musl@15.0.2':
|
||||
resolution: {integrity: sha512-AMfZfSVOIR8fa+TXlAooByEF4OB00wqnms1sJ1v+iu8ivwvtPvnkwdzzFMpsK5jA2S9oNeeQ04egIWVb4QWmtQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@15.0.1':
|
||||
resolution: {integrity: sha512-DS8wQtl6diAj0eZTdH0sefykm4iXMbHT4MOvLwqZiIkeezKpkgPFcEdFlz3vKvXa2R/2UEgMh48z1nEpNhjeOQ==}
|
||||
'@next/swc-win32-arm64-msvc@15.0.2':
|
||||
resolution: {integrity: sha512-JkXysDT0/hEY47O+Hvs8PbZAeiCQVxKfGtr4GUpNAhlG2E0Mkjibuo8ryGD29Qb5a3IOnKYNoZlh/MyKd2Nbww==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@15.0.1':
|
||||
resolution: {integrity: sha512-4Ho2ggvDdMKlZ/0e9HNdZ9ngeaBwtc+2VS5oCeqrbXqOgutX6I4U2X/42VBw0o+M5evn4/7v3zKgGHo+9v/VjA==}
|
||||
'@next/swc-win32-x64-msvc@15.0.2':
|
||||
resolution: {integrity: sha512-foaUL0NqJY/dX0Pi/UcZm5zsmSk5MtP/gxx3xOPyREkMFN+CTjctPfu3QaqrQHinaKdPnMWPJDKt4VjDfTBe/Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -2183,16 +2183,16 @@ packages:
|
||||
react: ^16.8 || ^17 || ^18
|
||||
react-dom: ^16.8 || ^17 || ^18
|
||||
|
||||
next@15.0.1:
|
||||
resolution: {integrity: sha512-PSkFkr/w7UnFWm+EP8y/QpHrJXMqpZzAXpergB/EqLPOh4SGPJXv1wj4mslr2hUZBAS9pX7/9YLIdxTv6fwytw==}
|
||||
next@15.0.2:
|
||||
resolution: {integrity: sha512-rxIWHcAu4gGSDmwsELXacqAPUk+j8dV/A9cDF5fsiCMpkBDYkO2AEaL1dfD+nNmDiU6QMCFN8Q30VEKapT9UHQ==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.1.0
|
||||
'@playwright/test': ^1.41.2
|
||||
babel-plugin-react-compiler: '*'
|
||||
react: ^18.2.0 || 19.0.0-rc-69d4b800-20241021
|
||||
react-dom: ^18.2.0 || 19.0.0-rc-69d4b800-20241021
|
||||
react: ^18.2.0 || 19.0.0-rc-02c0e824-20241028
|
||||
react-dom: ^18.2.0 || 19.0.0-rc-02c0e824-20241028
|
||||
sass: ^1.3.0
|
||||
peerDependenciesMeta:
|
||||
'@opentelemetry/api':
|
||||
@@ -3069,34 +3069,34 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
'@next/env@15.0.1': {}
|
||||
'@next/env@15.0.2': {}
|
||||
|
||||
'@next/eslint-plugin-next@15.0.1':
|
||||
dependencies:
|
||||
fast-glob: 3.3.1
|
||||
|
||||
'@next/swc-darwin-arm64@15.0.1':
|
||||
'@next/swc-darwin-arm64@15.0.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@15.0.1':
|
||||
'@next/swc-darwin-x64@15.0.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.0.1':
|
||||
'@next/swc-linux-arm64-gnu@15.0.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.0.1':
|
||||
'@next/swc-linux-arm64-musl@15.0.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.0.1':
|
||||
'@next/swc-linux-x64-gnu@15.0.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@15.0.1':
|
||||
'@next/swc-linux-x64-musl@15.0.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@15.0.1':
|
||||
'@next/swc-win32-arm64-msvc@15.0.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@15.0.1':
|
||||
'@next/swc-win32-x64-msvc@15.0.2':
|
||||
optional: true
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
@@ -4026,16 +4026,16 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.12.1
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@vercel/analytics@1.3.2(next@15.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
||||
'@vercel/analytics@1.3.2(next@15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
server-only: 0.0.1
|
||||
optionalDependencies:
|
||||
next: 15.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next: 15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
|
||||
'@vercel/speed-insights@1.0.14(next@15.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
||||
'@vercel/speed-insights@1.0.14(next@15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
||||
optionalDependencies:
|
||||
next: 15.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next: 15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
|
||||
abs-svg-path@0.1.1: {}
|
||||
@@ -4759,9 +4759,9 @@ snapshots:
|
||||
|
||||
functions-have-names@1.2.3: {}
|
||||
|
||||
geist@1.3.1(next@15.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
|
||||
geist@1.3.1(next@15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
|
||||
dependencies:
|
||||
next: 15.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next: 15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
||||
get-intrinsic@1.2.4:
|
||||
dependencies:
|
||||
@@ -5111,9 +5111,9 @@ snapshots:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
next@15.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
next@15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@next/env': 15.0.1
|
||||
'@next/env': 15.0.2
|
||||
'@swc/counter': 0.1.3
|
||||
'@swc/helpers': 0.5.13
|
||||
busboy: 1.6.0
|
||||
@@ -5123,14 +5123,14 @@ snapshots:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
styled-jsx: 5.1.6(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 15.0.1
|
||||
'@next/swc-darwin-x64': 15.0.1
|
||||
'@next/swc-linux-arm64-gnu': 15.0.1
|
||||
'@next/swc-linux-arm64-musl': 15.0.1
|
||||
'@next/swc-linux-x64-gnu': 15.0.1
|
||||
'@next/swc-linux-x64-musl': 15.0.1
|
||||
'@next/swc-win32-arm64-msvc': 15.0.1
|
||||
'@next/swc-win32-x64-msvc': 15.0.1
|
||||
'@next/swc-darwin-arm64': 15.0.2
|
||||
'@next/swc-darwin-x64': 15.0.2
|
||||
'@next/swc-linux-arm64-gnu': 15.0.2
|
||||
'@next/swc-linux-arm64-musl': 15.0.2
|
||||
'@next/swc-linux-x64-gnu': 15.0.2
|
||||
'@next/swc-linux-x64-musl': 15.0.2
|
||||
'@next/swc-win32-arm64-msvc': 15.0.2
|
||||
'@next/swc-win32-x64-msvc': 15.0.2
|
||||
sharp: 0.33.5
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
|
||||
@@ -30,25 +30,12 @@ export function Navigation() {
|
||||
// Update the document title based on the current pathname
|
||||
useEffect(() => {
|
||||
const currentItem = navItems.find(item => item.href === pathname);
|
||||
document.title = currentItem ? `${currentItem.label} - Sean O'Connor` : 'Sean O\'Connor'; // Default title
|
||||
document.title = currentItem ? `${currentItem.label} - Sean O'Connor` : 'Sean O\'Connor';
|
||||
}, [pathname]);
|
||||
|
||||
// Determine the icon to show based on the theme
|
||||
const themeIcon = theme === 'dark' ? <Sun size={20} /> : <Moon size={20} />;
|
||||
const defaultThemeIcon = <SunMoon size={20} />; // Default icon for server-side rendering
|
||||
|
||||
// Always render a consistent initial state for SSR
|
||||
if (!mounted) {
|
||||
return (
|
||||
<>
|
||||
{/* Backdrop overlay - faster fade */}
|
||||
<div
|
||||
className={`fixed inset-0 bg-background/80 backdrop-blur-sm lg:hidden transition-opacity duration-200 ${
|
||||
isOpen ? 'opacity-100 z-50' : 'opacity-0 pointer-events-none'
|
||||
}`}
|
||||
onClick={() => setIsOpen(false)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* Existing nav component */}
|
||||
<nav className="sticky top-0 z-[51] bg-background border-b border-border shadow-sm">
|
||||
<div className="relative max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex h-16 items-center justify-between">
|
||||
@@ -56,13 +43,11 @@ export function Navigation() {
|
||||
<span className="text-lg font-bold">Sean O'Connor</span>
|
||||
</Link>
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* Render the theme icon as a placeholder */}
|
||||
<button
|
||||
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
||||
className="text-sm font-medium text-muted-foreground hover:text-primary flex items-center lg:hidden"
|
||||
className="ml-4 text-sm font-medium text-muted-foreground hover:text-primary flex items-center"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{mounted ? themeIcon : defaultThemeIcon} {/* Use the default icon for server-side rendering */}
|
||||
<SunMoon size={20} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
@@ -92,11 +77,137 @@ export function Navigation() {
|
||||
);
|
||||
})}
|
||||
<button
|
||||
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
||||
className="ml-4 text-sm font-medium text-muted-foreground hover:text-primary flex items-center"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{mounted ? themeIcon : defaultThemeIcon} {/* Use the default icon for server-side rendering */}
|
||||
<SunMoon size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile menu - delayed fade */}
|
||||
<div
|
||||
className={`
|
||||
absolute
|
||||
top-full
|
||||
left-0
|
||||
right-0
|
||||
z-40
|
||||
bg-background
|
||||
border-b
|
||||
border-border
|
||||
shadow-sm
|
||||
lg:hidden
|
||||
overflow-hidden
|
||||
transition-all
|
||||
duration-300
|
||||
delay-50
|
||||
${isOpen ? 'h-auto opacity-100' : 'h-0 opacity-0'}
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col p-4 space-y-2 bg-background">
|
||||
{navItems.map((item) => {
|
||||
const isActive = pathname === item.href;
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`
|
||||
flex items-center
|
||||
text-sm font-medium
|
||||
transition-colors
|
||||
${isActive ? 'text-primary' : 'text-muted-foreground'}
|
||||
hover:text-primary
|
||||
gap-2
|
||||
`}
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
<item.icon size={16} />
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
// Client-side only code
|
||||
const themeIcon = theme === 'dark' ? <Sun size={20} /> :
|
||||
theme === 'light' ? <Moon size={20} /> :
|
||||
<SunMoon size={20} />;
|
||||
|
||||
const cycleTheme = () => {
|
||||
if (theme === 'dark') {
|
||||
setTheme('light');
|
||||
} else if (theme === 'light') {
|
||||
setTheme('system');
|
||||
} else {
|
||||
setTheme('dark');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Backdrop overlay - faster fade */}
|
||||
<div
|
||||
className={`fixed inset-0 bg-background/80 backdrop-blur-sm lg:hidden transition-opacity duration-200 ${
|
||||
isOpen ? 'opacity-100 z-50' : 'opacity-0 pointer-events-none'
|
||||
}`}
|
||||
onClick={() => setIsOpen(false)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* Existing nav component */}
|
||||
<nav className="sticky top-0 z-[51] bg-background border-b border-border shadow-sm">
|
||||
<div className="relative max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex h-16 items-center justify-between">
|
||||
<Link href="/">
|
||||
<span className="text-lg font-bold">Sean O'Connor</span>
|
||||
</Link>
|
||||
<div className="flex items-center space-x-4">
|
||||
<button
|
||||
onClick={cycleTheme}
|
||||
className="text-sm font-medium text-muted-foreground hover:text-primary flex items-center lg:hidden"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{themeIcon}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="text-gray-500 hover:text-primary focus:outline-none relative h-6 w-6 lg:hidden"
|
||||
aria-label={isOpen ? 'Close menu' : 'Open menu'}
|
||||
>
|
||||
<span className={`absolute inset-0 transition-opacity duration-300 ${isOpen ? 'opacity-0' : 'opacity-100'}`}>
|
||||
<Menu size={24} />
|
||||
</span>
|
||||
<span className={`absolute inset-0 transition-opacity duration-300 ${isOpen ? 'opacity-100' : 'opacity-0'}`}>
|
||||
<X size={24} />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="hidden lg:flex lg:space-x-4 lg:justify-end">
|
||||
{navItems.map((item) => {
|
||||
const isActive = pathname === item.href;
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`text-sm font-medium transition-colors ${isActive ? 'text-primary' : 'text-muted-foreground'} hover:text-primary flex items-center gap-2`}
|
||||
>
|
||||
<item.icon size={16} />
|
||||
{item.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
<button
|
||||
onClick={cycleTheme}
|
||||
className="ml-4 text-sm font-medium text-muted-foreground hover:text-primary flex items-center"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{themeIcon}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user