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",
|
"fs": "0.0.1-security",
|
||||||
"geist": "^1.3.1",
|
"geist": "^1.3.1",
|
||||||
"lucide-react": "^0.454.0",
|
"lucide-react": "^0.454.0",
|
||||||
"next": "^15.0.1",
|
"next": "^15.0.2",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"radix-ui": "^1.0.1",
|
"radix-ui": "^1.0.1",
|
||||||
"react": "^18.3.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)
|
version: 0.11.1(typescript@5.6.3)(zod@3.23.8)
|
||||||
'@vercel/analytics':
|
'@vercel/analytics':
|
||||||
specifier: ^1.3.2
|
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':
|
'@vercel/speed-insights':
|
||||||
specifier: ^1.0.14
|
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:
|
class-variance-authority:
|
||||||
specifier: ^0.7.0
|
specifier: ^0.7.0
|
||||||
version: 0.7.0
|
version: 0.7.0
|
||||||
@@ -70,13 +70,13 @@ importers:
|
|||||||
version: 0.0.1-security
|
version: 0.0.1-security
|
||||||
geist:
|
geist:
|
||||||
specifier: ^1.3.1
|
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:
|
lucide-react:
|
||||||
specifier: ^0.454.0
|
specifier: ^0.454.0
|
||||||
version: 0.454.0(react@18.3.1)
|
version: 0.454.0(react@18.3.1)
|
||||||
next:
|
next:
|
||||||
specifier: ^15.0.1
|
specifier: ^15.0.2
|
||||||
version: 15.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
next-themes:
|
next-themes:
|
||||||
specifier: ^0.3.0
|
specifier: ^0.3.0
|
||||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
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':
|
'@jridgewell/trace-mapping@0.3.25':
|
||||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||||
|
|
||||||
'@next/env@15.0.1':
|
'@next/env@15.0.2':
|
||||||
resolution: {integrity: sha512-lc4HeDUKO9gxxlM5G2knTRifqhsY6yYpwuHspBZdboZe0Gp+rZHBNNSIjmQKDJIdRXiXGyVnSD6gafrbQPvILQ==}
|
resolution: {integrity: sha512-c0Zr0ModK5OX7D4ZV8Jt/wqoXtitLNPwUfG9zElCZztdaZyNVnN40rDXVZ/+FGuR4CcNV5AEfM6N8f+Ener7Dg==}
|
||||||
|
|
||||||
'@next/eslint-plugin-next@15.0.1':
|
'@next/eslint-plugin-next@15.0.1':
|
||||||
resolution: {integrity: sha512-bKWsMaGPbiFAaGqrDJvbE8b4Z0uKicGVcgOI77YM2ui3UfjHMr4emFPrZTLeZVchi7fT1mooG2LxREfUUClIKw==}
|
resolution: {integrity: sha512-bKWsMaGPbiFAaGqrDJvbE8b4Z0uKicGVcgOI77YM2ui3UfjHMr4emFPrZTLeZVchi7fT1mooG2LxREfUUClIKw==}
|
||||||
|
|
||||||
'@next/swc-darwin-arm64@15.0.1':
|
'@next/swc-darwin-arm64@15.0.2':
|
||||||
resolution: {integrity: sha512-C9k/Xv4sxkQRTA37Z6MzNq3Yb1BJMmSqjmwowoWEpbXTkAdfOwnoKOpAb71ItSzoA26yUTIo6ZhN8rKGu4ExQw==}
|
resolution: {integrity: sha512-GK+8w88z+AFlmt+ondytZo2xpwlfAR8U6CRwXancHImh6EdGfHMIrTSCcx5sOSBei00GyLVL0ioo1JLKTfprgg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@next/swc-darwin-x64@15.0.1':
|
'@next/swc-darwin-x64@15.0.2':
|
||||||
resolution: {integrity: sha512-uHl13HXOuq1G7ovWFxCACDJHTSDVbn/sbLv8V1p+7KIvTrYQ5HNoSmKBdYeEKRRCbEmd+OohOgg9YOp8Ux3MBg==}
|
resolution: {integrity: sha512-KUpBVxIbjzFiUZhiLIpJiBoelqzQtVZbdNNsehhUn36e2YzKHphnK8eTUW1s/4aPy5kH/UTid8IuVbaOpedhpw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@next/swc-linux-arm64-gnu@15.0.1':
|
'@next/swc-linux-arm64-gnu@15.0.2':
|
||||||
resolution: {integrity: sha512-LvyhvxHOihFTEIbb35KxOc3q8w8G4xAAAH/AQnsYDEnOvwawjL2eawsB59AX02ki6LJdgDaHoTEnC54Gw+82xw==}
|
resolution: {integrity: sha512-9J7TPEcHNAZvwxXRzOtiUvwtTD+fmuY0l7RErf8Yyc7kMpE47MIQakl+3jecmkhOoIyi/Rp+ddq7j4wG6JDskQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-linux-arm64-musl@15.0.1':
|
'@next/swc-linux-arm64-musl@15.0.2':
|
||||||
resolution: {integrity: sha512-vFmCGUFNyk/A5/BYcQNhAQqPIw01RJaK6dRO+ZEhz0DncoW+hJW1kZ8aH2UvTX27zPq3m85zN5waMSbZEmANcQ==}
|
resolution: {integrity: sha512-BjH4ZSzJIoTTZRh6rG+a/Ry4SW0HlizcPorqNBixBWc3wtQtj4Sn9FnRZe22QqrPnzoaW0ctvSz4FaH4eGKMww==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-linux-x64-gnu@15.0.1':
|
'@next/swc-linux-x64-gnu@15.0.2':
|
||||||
resolution: {integrity: sha512-5by7IYq0NCF8rouz6Qg9T97jYU68kaClHPfGpQG2lCZpSYHtSPQF1kjnqBTd34RIqPKMbCa4DqCufirgr8HM5w==}
|
resolution: {integrity: sha512-i3U2TcHgo26sIhcwX/Rshz6avM6nizrZPvrDVDY1bXcLH1ndjbO8zuC7RoHp0NSK7wjJMPYzm7NYL1ksSKFreA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-linux-x64-musl@15.0.1':
|
'@next/swc-linux-x64-musl@15.0.2':
|
||||||
resolution: {integrity: sha512-lmYr6H3JyDNBJLzklGXLfbehU3ay78a+b6UmBGlHls4xhDXBNZfgb0aI67sflrX+cGBnv1LgmWzFlYrAYxS1Qw==}
|
resolution: {integrity: sha512-AMfZfSVOIR8fa+TXlAooByEF4OB00wqnms1sJ1v+iu8ivwvtPvnkwdzzFMpsK5jA2S9oNeeQ04egIWVb4QWmtQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-win32-arm64-msvc@15.0.1':
|
'@next/swc-win32-arm64-msvc@15.0.2':
|
||||||
resolution: {integrity: sha512-DS8wQtl6diAj0eZTdH0sefykm4iXMbHT4MOvLwqZiIkeezKpkgPFcEdFlz3vKvXa2R/2UEgMh48z1nEpNhjeOQ==}
|
resolution: {integrity: sha512-JkXysDT0/hEY47O+Hvs8PbZAeiCQVxKfGtr4GUpNAhlG2E0Mkjibuo8ryGD29Qb5a3IOnKYNoZlh/MyKd2Nbww==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@next/swc-win32-x64-msvc@15.0.1':
|
'@next/swc-win32-x64-msvc@15.0.2':
|
||||||
resolution: {integrity: sha512-4Ho2ggvDdMKlZ/0e9HNdZ9ngeaBwtc+2VS5oCeqrbXqOgutX6I4U2X/42VBw0o+M5evn4/7v3zKgGHo+9v/VjA==}
|
resolution: {integrity: sha512-foaUL0NqJY/dX0Pi/UcZm5zsmSk5MtP/gxx3xOPyREkMFN+CTjctPfu3QaqrQHinaKdPnMWPJDKt4VjDfTBe/Q==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -2183,16 +2183,16 @@ packages:
|
|||||||
react: ^16.8 || ^17 || ^18
|
react: ^16.8 || ^17 || ^18
|
||||||
react-dom: ^16.8 || ^17 || ^18
|
react-dom: ^16.8 || ^17 || ^18
|
||||||
|
|
||||||
next@15.0.1:
|
next@15.0.2:
|
||||||
resolution: {integrity: sha512-PSkFkr/w7UnFWm+EP8y/QpHrJXMqpZzAXpergB/EqLPOh4SGPJXv1wj4mslr2hUZBAS9pX7/9YLIdxTv6fwytw==}
|
resolution: {integrity: sha512-rxIWHcAu4gGSDmwsELXacqAPUk+j8dV/A9cDF5fsiCMpkBDYkO2AEaL1dfD+nNmDiU6QMCFN8Q30VEKapT9UHQ==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@opentelemetry/api': ^1.1.0
|
'@opentelemetry/api': ^1.1.0
|
||||||
'@playwright/test': ^1.41.2
|
'@playwright/test': ^1.41.2
|
||||||
babel-plugin-react-compiler: '*'
|
babel-plugin-react-compiler: '*'
|
||||||
react: ^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-69d4b800-20241021
|
react-dom: ^18.2.0 || 19.0.0-rc-02c0e824-20241028
|
||||||
sass: ^1.3.0
|
sass: ^1.3.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@opentelemetry/api':
|
'@opentelemetry/api':
|
||||||
@@ -3069,34 +3069,34 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
'@next/env@15.0.1': {}
|
'@next/env@15.0.2': {}
|
||||||
|
|
||||||
'@next/eslint-plugin-next@15.0.1':
|
'@next/eslint-plugin-next@15.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-glob: 3.3.1
|
fast-glob: 3.3.1
|
||||||
|
|
||||||
'@next/swc-darwin-arm64@15.0.1':
|
'@next/swc-darwin-arm64@15.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-darwin-x64@15.0.1':
|
'@next/swc-darwin-x64@15.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-arm64-gnu@15.0.1':
|
'@next/swc-linux-arm64-gnu@15.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-arm64-musl@15.0.1':
|
'@next/swc-linux-arm64-musl@15.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-x64-gnu@15.0.1':
|
'@next/swc-linux-x64-gnu@15.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-x64-musl@15.0.1':
|
'@next/swc-linux-x64-musl@15.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-win32-arm64-msvc@15.0.1':
|
'@next/swc-win32-arm64-msvc@15.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-win32-x64-msvc@15.0.1':
|
'@next/swc-win32-x64-msvc@15.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
@@ -4026,16 +4026,16 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.12.1
|
'@typescript-eslint/types': 8.12.1
|
||||||
eslint-visitor-keys: 3.4.3
|
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:
|
dependencies:
|
||||||
server-only: 0.0.1
|
server-only: 0.0.1
|
||||||
optionalDependencies:
|
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
|
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:
|
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
|
react: 18.3.1
|
||||||
|
|
||||||
abs-svg-path@0.1.1: {}
|
abs-svg-path@0.1.1: {}
|
||||||
@@ -4759,9 +4759,9 @@ snapshots:
|
|||||||
|
|
||||||
functions-have-names@1.2.3: {}
|
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:
|
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:
|
get-intrinsic@1.2.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5111,9 +5111,9 @@ snapshots:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(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:
|
dependencies:
|
||||||
'@next/env': 15.0.1
|
'@next/env': 15.0.2
|
||||||
'@swc/counter': 0.1.3
|
'@swc/counter': 0.1.3
|
||||||
'@swc/helpers': 0.5.13
|
'@swc/helpers': 0.5.13
|
||||||
busboy: 1.6.0
|
busboy: 1.6.0
|
||||||
@@ -5123,14 +5123,14 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
styled-jsx: 5.1.6(react@18.3.1)
|
styled-jsx: 5.1.6(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-darwin-arm64': 15.0.1
|
'@next/swc-darwin-arm64': 15.0.2
|
||||||
'@next/swc-darwin-x64': 15.0.1
|
'@next/swc-darwin-x64': 15.0.2
|
||||||
'@next/swc-linux-arm64-gnu': 15.0.1
|
'@next/swc-linux-arm64-gnu': 15.0.2
|
||||||
'@next/swc-linux-arm64-musl': 15.0.1
|
'@next/swc-linux-arm64-musl': 15.0.2
|
||||||
'@next/swc-linux-x64-gnu': 15.0.1
|
'@next/swc-linux-x64-gnu': 15.0.2
|
||||||
'@next/swc-linux-x64-musl': 15.0.1
|
'@next/swc-linux-x64-musl': 15.0.2
|
||||||
'@next/swc-win32-arm64-msvc': 15.0.1
|
'@next/swc-win32-arm64-msvc': 15.0.2
|
||||||
'@next/swc-win32-x64-msvc': 15.0.1
|
'@next/swc-win32-x64-msvc': 15.0.2
|
||||||
sharp: 0.33.5
|
sharp: 0.33.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
|
|||||||
@@ -30,25 +30,12 @@ export function Navigation() {
|
|||||||
// Update the document title based on the current pathname
|
// Update the document title based on the current pathname
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentItem = navItems.find(item => item.href === pathname);
|
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]);
|
}, [pathname]);
|
||||||
|
|
||||||
// Determine the icon to show based on the theme
|
// Always render a consistent initial state for SSR
|
||||||
const themeIcon = theme === 'dark' ? <Sun size={20} /> : <Moon size={20} />;
|
if (!mounted) {
|
||||||
const defaultThemeIcon = <SunMoon size={20} />; // Default icon for server-side rendering
|
return (
|
||||||
|
|
||||||
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">
|
<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="relative max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="flex h-16 items-center justify-between">
|
<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>
|
<span className="text-lg font-bold">Sean O'Connor</span>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
{/* Render the theme icon as a placeholder */}
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
className="ml-4 text-sm font-medium text-muted-foreground hover:text-primary flex items-center"
|
||||||
className="text-sm font-medium text-muted-foreground hover:text-primary flex items-center lg:hidden"
|
|
||||||
aria-label="Toggle theme"
|
aria-label="Toggle theme"
|
||||||
>
|
>
|
||||||
{mounted ? themeIcon : defaultThemeIcon} {/* Use the default icon for server-side rendering */}
|
<SunMoon size={20} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
@@ -92,11 +77,137 @@ export function Navigation() {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<button
|
<button
|
||||||
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
|
||||||
className="ml-4 text-sm font-medium text-muted-foreground hover:text-primary flex items-center"
|
className="ml-4 text-sm font-medium text-muted-foreground hover:text-primary flex items-center"
|
||||||
aria-label="Toggle theme"
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user