11// src/App.tsx
2- import React , { useState } from 'react' ;
2+ import React , { useState , useEffect } from 'react' ;
33import { BrowserRouter as Router , Routes , Route , NavLink } from 'react-router-dom' ;
44import './App.css' ;
55import TopNav from './components/TopNav' ;
66import MyProfile from './components/MyProfile' ;
77import Policies from './components/Policies' ;
88import Achievements from './components/Achievements' ;
9- import Announcement from './components/Announcement' ;
109import Organization from './components/Organization' ;
1110import EmployeeInfo from './components/EmployeeInfo' ;
1211
13- // Extend CSSProperties so TS accepts our custom CSS variables
12+ // Syncfusion Sidebar
13+ import { SidebarComponent } from '@syncfusion/ej2-react-navigations' ;
14+
1415type LayoutCSSVars = React . CSSProperties & {
1516 [ '--sidebar-expanded' ] ?: string ;
1617 [ '--sidebar-collapsed' ] ?: string ;
1718 [ '--header-h' ] ?: string ;
1819 [ '--sidebar-current-width' ] ?: string ;
1920} ;
2021
22+ function useMediaQuery ( query : string ) {
23+ const [ matches , setMatches ] = useState < boolean > ( ( ) =>
24+ typeof window !== 'undefined' ? window . matchMedia ( query ) . matches : false
25+ ) ;
26+ useEffect ( ( ) => {
27+ if ( typeof window === 'undefined' ) return ;
28+ const mq = window . matchMedia ( query ) ;
29+ const onChange = ( ) => setMatches ( mq . matches ) ;
30+ mq . addEventListener ?.( 'change' , onChange ) ;
31+ mq . addListener ?.( onChange ) ;
32+ return ( ) => {
33+ mq . removeEventListener ?.( 'change' , onChange ) ;
34+ mq . removeListener ?.( onChange ) ;
35+ } ;
36+ } , [ query ] ) ;
37+ return matches ;
38+ }
39+
2140function App ( ) {
22- // Layout constants
2341 const SIDEBAR_WIDTH = 240 ;
2442 const SIDEBAR_WIDTH_COLLAPSED = 72 ;
2543 const HEADER_HEIGHT = 56 ;
2644
27- // UI state
28- const [ sidebarCollapsed , setSidebarCollapsed ] = useState ( false ) ;
29- const [ mobileSidebarOpen , setMobileSidebarOpen ] = useState ( false ) ;
45+ const [ sidebarCollapsed , setSidebarCollapsed ] = useState ( false ) ; // desktop dock
46+ const [ mobileSidebarOpen , setMobileSidebarOpen ] = useState ( false ) ; // mobile overlay
47+ const isDesktop = useMediaQuery ( '(min-width: 992px)' ) ;
48+
49+ useEffect ( ( ) => {
50+ if ( isDesktop ) setMobileSidebarOpen ( false ) ;
51+ } , [ isDesktop ] ) ;
3052
31- // CSS vars consumed by App.css
3253 const layoutVars : LayoutCSSVars = {
3354 '--sidebar-expanded' : `${ SIDEBAR_WIDTH } px` ,
3455 '--sidebar-collapsed' : `${ SIDEBAR_WIDTH_COLLAPSED } px` ,
3556 '--header-h' : `${ HEADER_HEIGHT } px` ,
36- '--sidebar-current-width' : sidebarCollapsed
37- ? `${ SIDEBAR_WIDTH_COLLAPSED } px`
38- : `${ SIDEBAR_WIDTH } px` ,
57+ '--sidebar-current-width' : isDesktop
58+ ? sidebarCollapsed
59+ ? `${ SIDEBAR_WIDTH_COLLAPSED } px`
60+ : `${ SIDEBAR_WIDTH } px`
61+ : '0px' ,
3962 } ;
4063
4164 const toggleDesktopSidebar = ( ) => setSidebarCollapsed ( ( v ) => ! v ) ;
4265 const toggleMobileSidebar = ( ) => setMobileSidebarOpen ( ( v ) => ! v ) ;
4366
67+ const sbIsOpen = isDesktop ? ! sidebarCollapsed : mobileSidebarOpen ;
68+ const sbEnableDock = isDesktop ;
69+ const sbType = isDesktop ? 'Push' : 'Over' ;
70+
71+ const collapsed = isDesktop && sidebarCollapsed ;
72+
4473 return (
4574 < div
4675 className = { `app-layout ${ sidebarCollapsed ? 'sidebar-is-collapsed' : '' } ${
@@ -50,49 +79,102 @@ function App() {
5079 >
5180 < Router >
5281 < TopNav
53- companyName = "Syncfusion Software"
82+ companyName = "NexGen7 Software"
5483 userFullName = "Test Person"
55- sidebarWidth = { SIDEBAR_WIDTH }
56- collapsedSidebarWidth = { SIDEBAR_WIDTH_COLLAPSED }
57- sidebarCollapsed = { sidebarCollapsed }
58- onToggleSidebar = { toggleDesktopSidebar }
84+ headerOffsetLeft = { isDesktop ? ( sidebarCollapsed ? SIDEBAR_WIDTH_COLLAPSED : SIDEBAR_WIDTH ) : 0 }
5985 onSearch = { ( q ) => console . log ( 'Search:' , q ) }
6086 />
6187
62- { /* Sidebar */ }
63- < aside className = "app-sidebar" role = "navigation" aria-label = "Main" >
64- < div className = "sidebar-brand" >
65- < div className = "title" > HR Portal</ div >
66- < span className = "e-icons e-line-very-small" > </ span >
67- </ div >
88+ { /* Syncfusion Sidebar */ }
89+ < SidebarComponent
90+ width = { `${ SIDEBAR_WIDTH } px` }
91+ dockSize = { `${ SIDEBAR_WIDTH_COLLAPSED } px` }
92+ enableDock = { sbEnableDock }
93+ isOpen = { sbIsOpen }
94+ type = { sbType as any }
95+ position = "Left"
96+ showBackdrop = { ! isDesktop }
97+ closeOnDocumentClick = { ! isDesktop }
98+ open = { ( ) => {
99+ if ( ! isDesktop ) setMobileSidebarOpen ( true ) ;
100+ } }
101+ close = { ( ) => {
102+ if ( ! isDesktop ) setMobileSidebarOpen ( false ) ;
103+ } }
104+ >
105+ < aside className = "app-sidebar" role = "navigation" aria-label = "Main" >
106+ { /* Sidebar header with toggle inside sidebar */ }
107+ < div className = "sidebar-brand" >
108+ < div className = "title" > { collapsed ? 'HR' : 'HR Portal' } </ div >
109+ < button
110+ type = "button"
111+ className = "sidebar-toggle"
112+ aria-label = { collapsed ? 'Expand menu' : 'Collapse menu' }
113+ title = { collapsed ? 'Expand' : 'Collapse' }
114+ onClick = { isDesktop ? toggleDesktopSidebar : toggleMobileSidebar }
115+ >
116+ { /* menu icon */ }
117+ < svg width = "18" height = "18" viewBox = "0 0 24 24" aria-hidden = "true" >
118+ < path fill = "currentColor" d = "M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z" />
119+ </ svg >
120+ </ button >
121+ </ div >
68122
69- < ul className = "nav flex-column" >
70- < li className = "nav-item" >
71- < NavLink className = "nav-link" to = "/" end > My Profile</ NavLink >
72- </ li >
73- < li className = "nav-item" >
74- < NavLink className = "nav-link" to = "/organization" > Organization</ NavLink >
75- </ li >
76- < li className = "nav-item" >
77- < NavLink className = "nav-link" to = "/policies" > Policies</ NavLink >
78- </ li >
79- < li className = "nav-item" >
80- < NavLink className = "nav-link" to = "/achievements" > Achievements</ NavLink >
81- </ li >
82- < li className = "nav-item" >
83- < NavLink className = "nav-link" to = "/announcement" > Announcement</ NavLink >
84- </ li >
85- </ ul >
86- </ aside >
123+ < ul className = "nav flex-column" >
124+ < li className = "nav-item" >
125+ < NavLink className = "nav-link" to = "/" end >
126+ < span className = "nav-icon" aria-hidden = "true" >
127+ { /* user/profile */ }
128+ < svg width = "18" height = "18" viewBox = "0 0 24 24" >
129+ < path fill = "currentColor" d = "M12 12a5 5 0 1 0-5-5a5 5 0 0 0 5 5zm0 2c-4.418 0-8 2.239-8 5v1h16v-1c0-2.761-3.582-5-8-5z" />
130+ </ svg >
131+ </ span >
132+ { ! collapsed && < span className = "nav-text" > My Profile</ span > }
133+ </ NavLink >
134+ </ li >
135+ < li className = "nav-item" >
136+ < NavLink className = "nav-link" to = "/organization" >
137+ < span className = "nav-icon" aria-hidden = "true" >
138+ { /* organization/people */ }
139+ < svg width = "18" height = "18" viewBox = "0 0 24 24" >
140+ < path fill = "currentColor" d = "M16 11a4 4 0 1 0-3.465-2H11V7H8V5H4v2H1v3h3v2h3v2h3v2h3.535A4 4 0 1 0 16 11zM6 7h2v2H6zm10-2a2 2 0 1 1 0 4a2 2 0 0 1 0-4zm0 10a2 2 0 1 1 0 4a2 2 0 0 1 0-4z" />
141+ </ svg >
142+ </ span >
143+ { ! collapsed && < span className = "nav-text" > Organization</ span > }
144+ </ NavLink >
145+ </ li >
146+ < li className = "nav-item" >
147+ < NavLink className = "nav-link" to = "/policies" >
148+ < span className = "nav-icon" aria-hidden = "true" >
149+ { /* policy/document */ }
150+ < svg width = "18" height = "18" viewBox = "0 0 24 24" >
151+ < path fill = "currentColor" d = "M6 2h9l5 5v15H6zM8 4v16h10V9h-5V4zm2 7h6v2h-6zm0 4h6v2h-6z" />
152+ </ svg >
153+ </ span >
154+ { ! collapsed && < span className = "nav-text" > Policies</ span > }
155+ </ NavLink >
156+ </ li >
157+ < li className = "nav-item" >
158+ < NavLink className = "nav-link" to = "/achievements" >
159+ < span className = "nav-icon" aria-hidden = "true" >
160+ { /* trophy */ }
161+ < svg width = "18" height = "18" viewBox = "0 0 24 24" >
162+ < path fill = "currentColor" d = "M17 3H7v4a5 5 0 0 0 4 4.9V14H8v2h8v-2h-3v-2.1A5 5 0 0 0 17 7zM7 7V5h10v2a3 3 0 0 1-6 0H9a3 3 0 0 1-2 3z" />
163+ </ svg >
164+ </ span >
165+ { ! collapsed && < span className = "nav-text" > Achievements</ span > }
166+ </ NavLink >
167+ </ li >
168+ </ ul >
169+ </ aside >
170+ </ SidebarComponent >
87171
88- { /* Main content */ }
89172 < main className = "app-main" >
90173 < Routes >
91174 < Route path = "/" element = { < MyProfile /> } />
92175 < Route path = "/organization" element = { < Organization /> } />
93176 < Route path = "/policies" element = { < Policies /> } />
94177 < Route path = "/achievements" element = { < Achievements /> } />
95- < Route path = "/announcement" element = { < Announcement /> } />
96178 < Route path = "/employeeinfo" element = { < EmployeeInfo /> } />
97179 </ Routes >
98180 </ main >
0 commit comments