@@ -12,6 +12,7 @@ import {
1212 TargetIcon ,
1313 UsersIcon ,
1414 XIcon ,
15+ type Icon as PhosphorIcon
1516} from '@phosphor-icons/react' ;
1617import Link from 'next/link' ;
1718import { usePathname } from 'next/navigation' ;
@@ -24,32 +25,47 @@ import { Button } from '@/components/ui/button';
2425import { ScrollArea } from '@/components/ui/scroll-area' ;
2526import { cn } from '@/lib/utils' ;
2627
27- const demoNavigation = [
28+ interface DemoNavigationItem {
29+ title : string ;
30+ items : DemoNavigationItemItem [ ] ;
31+ }
32+
33+ interface DemoNavigationItemItem {
34+ name : string ;
35+ icon : PhosphorIcon ;
36+ href : string ;
37+ highlight : boolean ;
38+ }
39+
40+ const DEMO_WEBSITE_ID = 'OXmNQsViBT-FOS_wZCTHc' ;
41+ const DEMO_WEBSITE_URL = 'https://www.databuddy.cc' ;
42+
43+ const demoNavigation : DemoNavigationItem [ ] = [
2844 {
2945 title : 'Web Analytics' ,
3046 items : [
3147 {
3248 name : 'Overview' ,
3349 icon : HouseIcon ,
34- href : ' /demo/OXmNQsViBT-FOS_wZCTHc' ,
50+ href : ` /demo/${ DEMO_WEBSITE_ID } ` ,
3551 highlight : true ,
3652 } ,
3753 {
3854 name : 'Sessions' ,
3955 icon : ClockIcon ,
40- href : ' /demo/OXmNQsViBT-FOS_wZCTHc /sessions' ,
56+ href : ` /demo/${ DEMO_WEBSITE_ID } /sessions` ,
4157 highlight : true ,
4258 } ,
4359 {
4460 name : 'Errors' ,
4561 icon : BugIcon ,
46- href : ' /demo/OXmNQsViBT-FOS_wZCTHc /errors' ,
62+ href : ` /demo/${ DEMO_WEBSITE_ID } /errors` ,
4763 highlight : true ,
4864 } ,
4965 {
5066 name : 'Map' ,
5167 icon : MapPinIcon ,
52- href : ' /demo/OXmNQsViBT-FOS_wZCTHc /map' ,
68+ href : ` /demo/${ DEMO_WEBSITE_ID } /map` ,
5369 highlight : true ,
5470 } ,
5571 ] ,
@@ -60,198 +76,192 @@ const demoNavigation = [
6076 {
6177 name : 'Profiles' ,
6278 icon : UsersIcon ,
63- href : ' /demo/OXmNQsViBT-FOS_wZCTHc /profiles' ,
79+ href : ` /demo/${ DEMO_WEBSITE_ID } /profiles` ,
6480 highlight : true ,
6581 } ,
6682 {
6783 name : 'Funnels' ,
6884 icon : FunnelIcon ,
69- href : ' /demo/OXmNQsViBT-FOS_wZCTHc /funnels' ,
85+ href : ` /demo/${ DEMO_WEBSITE_ID } /funnels` ,
7086 highlight : true ,
7187 } ,
7288 {
7389 name : 'Goals' ,
7490 icon : TargetIcon ,
75- href : ' /demo/OXmNQsViBT-FOS_wZCTHc /goals' ,
91+ href : ` /demo/${ DEMO_WEBSITE_ID } /goals` ,
7692 highlight : true ,
7793 } ,
7894 ] ,
7995 } ,
8096] ;
8197
82- export function Sidebar ( ) {
98+ function Sidebar ( ) {
8399 const pathname = usePathname ( ) ;
84100 const [ isMobileOpen , setIsMobileOpen ] = useState ( false ) ;
85101
86102 const closeSidebar = useCallback ( ( ) => {
87103 setIsMobileOpen ( false ) ;
88104 } , [ ] ) ;
89105
90- // Handle keyboard navigation
91- useEffect ( ( ) => {
92- const handleKeyDown = ( e : KeyboardEvent ) => {
93- if ( e . key === 'Escape' && isMobileOpen ) {
94- closeSidebar ( ) ;
95- }
96- } ;
106+ const handleKeyDown = useCallback ( ( e : KeyboardEvent ) => {
107+ if ( e . key === 'Escape' && isMobileOpen ) {
108+ closeSidebar ( ) ;
109+ }
110+ } , [ isMobileOpen , closeSidebar ] ) ;
97111
112+ useEffect ( ( ) => {
98113 document . addEventListener ( 'keydown' , handleKeyDown ) ;
99114 return ( ) => document . removeEventListener ( 'keydown' , handleKeyDown ) ;
100- } , [ isMobileOpen , closeSidebar ] ) ;
115+ } , [ handleKeyDown ] ) ;
101116
102117 return (
103118 < >
104- { /* Top Header */ }
105119 < header className = "fixed top-0 right-0 left-0 z-50 h-16 w-full border-b bg-background/95 backdrop-blur-md" >
106120 < div className = "flex h-full items-center px-4 md:px-6" >
107- { /* Left side: Logo + Mobile menu */ }
108121 < div className = "flex items-center gap-4" >
109122 < Button
123+ aria-label = "Toggle menu"
110124 className = "md:hidden"
111125 onClick = { ( ) => setIsMobileOpen ( true ) }
112126 size = "icon"
113127 variant = "ghost"
114128 >
115- < ListIcon className = "h-5 w-5" size = { 32 } weight = "duotone" />
116- < span className = "sr-only" > Toggle menu</ span >
129+ < ListIcon className = "h-5 w-5" weight = "duotone" />
117130 </ Button >
118131
119132 < div className = "flex items-center gap-3" >
120- < div className = "flex flex-row items-center gap-3" >
121- < Logo />
122- </ div >
133+ < Logo />
123134 </ div >
124135 </ div >
125136
126- { /* Right Side - User Controls */ }
127137 < div className = "ml-auto flex items-center gap-2" >
128138 < ThemeToggle />
129139
130- { /* Help */ }
131140 < Button
141+ aria-label = "Help"
132142 className = "hidden h-8 w-8 md:flex"
133143 size = "icon"
134144 variant = "ghost"
135145 >
136- < InfoIcon className = "h-6 w-6" size = { 32 } weight = "duotone" />
137- < span className = "sr-only" > Help</ span >
146+ < InfoIcon className = "h-6 w-6" weight = "duotone" />
138147 </ Button >
139148
140- { /* Notifications */ }
141149 < NotificationsPopover />
142-
143- { /* User Menu */ }
144150 < UserMenu />
145151 </ div >
146152 </ div >
147153 </ header >
148154
149- { /* Mobile backdrop */ }
150155 { isMobileOpen && (
151156 < div
152157 aria-hidden = "true"
153158 className = "fixed inset-0 z-30 bg-black/20 md:hidden"
154159 onClick = { closeSidebar }
155- onKeyDown = { closeSidebar }
156- onKeyPress = { closeSidebar }
157- onKeyUp = { closeSidebar }
158160 />
159161 ) }
160162
161- { /* Sidebar */ }
162- < div
163+ < aside
164+ aria-label = "Demo navigation"
163165 className = { cn (
164166 'fixed inset-y-0 left-0 z-40 w-64 bg-background' ,
165167 'border-r pt-16 transition-transform duration-200 ease-out md:translate-x-0' ,
166168 isMobileOpen ? 'translate-x-0' : '-translate-x-full'
167169 ) }
168170 >
169- { /* Mobile close button */ }
170171 < Button
172+ aria-label = "Close sidebar"
171173 className = "absolute top-3 right-3 z-50 h-8 w-8 p-0 md:hidden"
172174 onClick = { closeSidebar }
173175 size = "sm"
174176 variant = "ghost"
175177 >
176- < XIcon className = "h-4 w-4" size = { 32 } weight = "duotone" />
177- < span className = "sr-only" > Close sidebar</ span >
178+ < XIcon className = "h-4 w-4" weight = "duotone" />
178179 </ Button >
179180
180181 < ScrollArea className = "h-[calc(100vh-4rem)]" >
181- < div className = "space-y-4 p-3" >
182- { /* Demo Website Header */ }
182+ < nav className = "space-y-4 p-3" >
183183 < div className = "flex items-center gap-3 rounded border bg-muted/50 p-3" >
184184 < div className = "rounded border border-primary/20 bg-primary/10 p-2" >
185185 < GlobeIcon
186+ aria-hidden = "true"
186187 className = "h-5 w-5 text-primary"
187- size = { 32 }
188188 weight = "duotone"
189189 />
190190 </ div >
191191 < div className = "min-w-0 flex-1" >
192192 < h2 className = "truncate font-semibold text-sm" > Landing Page</ h2 >
193193 < Link
194194 className = "truncate text-muted-foreground text-xs"
195- href = "https://www.databuddy.cc"
195+ href = { DEMO_WEBSITE_URL }
196+ rel = "noopener"
196197 target = "_blank"
197198 >
198199 www.databuddy.cc
199200 </ Link >
200201 </ div >
201202 </ div >
202203
203- { /* Demo Navigation */ }
204204 { demoNavigation . map ( ( section ) => (
205205 < div key = { section . title } >
206206 < h3 className = "mb-2 px-2 font-semibold text-muted-foreground text-xs uppercase tracking-wider" >
207207 { section . title }
208208 </ h3 >
209- < div className = "ml-1 space-y-1" >
209+ < ul className = "ml-1 space-y-1" >
210210 { section . items . map ( ( item ) => {
211211 const isActive = pathname === item . href ;
212212 const Icon = item . icon ;
213213
214214 return (
215- < Link
216- className = { cn (
217- 'flex cursor-pointer items-center gap-3 rounded px-3 py-2 text-sm transition-all' ,
218- isActive
219- ? 'bg-primary/15 font-medium text-primary'
220- : 'text-foreground hover:bg-accent/70'
221- ) }
222- href = { item . href }
223- key = { item . name }
224- >
225- < Icon
226- className = { cn ( 'h-4 w-4' , isActive && 'text-primary' ) }
227- size = { 32 }
228- weight = "duotone"
229- />
230- < span className = "truncate" > { item . name } </ span >
231- </ Link >
215+ < li key = { item . name } >
216+ < Link
217+ className = { cn (
218+ 'group flex items-center gap-x-3 rounded px-3 py-2 text-sm transition-all duration-200' ,
219+ 'focus:outline-none focus:ring-2 focus:ring-primary/20 focus:ring-offset-1' ,
220+ isActive
221+ ? 'bg-accent font-medium text-foreground shadow-sm'
222+ : 'text-muted-foreground hover:bg-accent/50 hover:text-foreground'
223+ ) }
224+ href = { item . href }
225+ >
226+ < span className = "flex-shrink-0" >
227+ < Icon
228+ aria-hidden = "true"
229+ className = { cn (
230+ 'h-5 w-5 transition-colors duration-200' ,
231+ isActive
232+ ? 'text-primary'
233+ : 'not-dark:text-primary group-hover:text-primary'
234+ ) }
235+ size = { 32 }
236+ weight = "duotone"
237+ />
238+ </ span >
239+ < span className = "flex-grow truncate" > { item . name } </ span >
240+ </ Link >
241+ </ li >
232242 ) ;
233243 } ) }
234- </ div >
244+ </ ul >
235245 </ div >
236246 ) ) }
237- </ div >
247+ </ nav >
238248 </ ScrollArea >
239- </ div >
249+ </ aside >
240250 </ >
241251 ) ;
242252}
243253
244- export default function MainLayout ( {
245- children,
246- } : {
254+ interface MainLayoutProps {
247255 children : React . ReactNode ;
248- } ) {
256+ }
257+
258+ export default function MainLayout ( { children } : MainLayoutProps ) {
249259 return (
250260 < div className = "h-screen overflow-hidden bg-gradient-to-br from-background to-muted/20 text-foreground" >
251261 < Sidebar />
252- < div className = "relative h-screen pt-16 md:pl-64" >
262+ < main className = "relative h-screen pt-16 md:pl-64" >
253263 < div className = "h-[calc(100vh-4rem)] overflow-y-scroll" > { children } </ div >
254- </ div >
264+ </ main >
255265 </ div >
256266 ) ;
257267}
0 commit comments