| @@ -43,7 +43,7 @@ | |||
| color: #0C489E; | |||
| } | |||
| #navbar div li a:hover::after, | |||
| #navbar div li a:focus::after{ | |||
| #navbar div li a:focus-visible::after{ | |||
| content: ""; | |||
| width: 80%; | |||
| height: 3px; | |||
| @@ -73,6 +73,19 @@ | |||
| opacity: 1; | |||
| display: block | |||
| } | |||
| /* Navbar: don't show focus ring on mouse click */ | |||
| #navbar a:focus { | |||
| outline: none; | |||
| } | |||
| /* Navbar: show focus ring for keyboard navigation */ | |||
| #navbar a:focus-visible { | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: 2px; | |||
| border-radius: 10px; /* tweak to match your design */ | |||
| } | |||
| /* #navbar div li:focus-within > ul, | |||
| #navbar div li ul:hover, | |||
| #navbar div li ul:focus { | |||
| @@ -74,4 +74,60 @@ a:active { | |||
| text-decoration: none; | |||
| } | |||
| /* iframe#webpack-dev-server-client-overlay{display:none!important} */ | |||
| /* iframe#webpack-dev-server-client-overlay{display:none!important} */ | |||
| /* ===== WCAG 2.4.7 Focus Visible (Global) ===== */ | |||
| :where( | |||
| a, | |||
| button, | |||
| input, | |||
| select, | |||
| textarea, | |||
| summary, | |||
| [role="button"], | |||
| [role="link"], | |||
| [tabindex]:not([tabindex="-1"]) | |||
| ):focus-visible { | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: 2px; | |||
| border-radius: 4px; | |||
| } | |||
| /* Fallback for browsers that don't support :focus-visible */ | |||
| :where( | |||
| a, | |||
| button, | |||
| input, | |||
| select, | |||
| textarea, | |||
| summary, | |||
| [role="button"], | |||
| [role="link"], | |||
| [tabindex]:not([tabindex="-1"]) | |||
| ):focus { | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: 2px; | |||
| border-radius: 4px; | |||
| } | |||
| /* ===== MUI DataGrid focus visible (WCAG 2.4.7) ===== */ | |||
| /* Column headers */ | |||
| .MuiDataGrid-columnHeader:focus, | |||
| .MuiDataGrid-columnHeader:focus-within { | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: -2px; | |||
| } | |||
| /* Cells */ | |||
| .MuiDataGrid-cell:focus, | |||
| .MuiDataGrid-cell:focus-within { | |||
| outline: 3px solid #0C489E; | |||
| outline-offset: -2px; | |||
| } | |||
| /* If outline is clipped, add halo */ | |||
| .MuiDataGrid-columnHeader:focus-within, | |||
| .MuiDataGrid-cell:focus-within { | |||
| box-shadow: 0 0 0 3px rgba(12, 72, 158, 0.25); | |||
| } | |||
| @@ -23,14 +23,23 @@ const LogoSection = ({ sx, to }) => { | |||
| return ( | |||
| <Stack direction="column" justifyContent="center" alignItems="center" > | |||
| <ButtonBase | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| sx={sx} | |||
| > | |||
| <Logo /> | |||
| </ButtonBase> | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| sx={{ | |||
| ...sx, | |||
| /* ✅ WCAG 2.4.7 focus indicator */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| > | |||
| <Logo /> | |||
| </ButtonBase> | |||
| <span style={{ color: checkSysEnv()!=''?'red':'#0C489E'}} id="systemTitle">PNSPS</span> | |||
| </Stack> | |||
| @@ -17,14 +17,23 @@ const LogoSection = ({ sx, to }) => { | |||
| const dispatch = useDispatch(); | |||
| return ( | |||
| <ButtonBase | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| sx={sx} | |||
| > | |||
| <Logo /> | |||
| </ButtonBase> | |||
| disableRipple | |||
| component={Link} | |||
| onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
| to={!to ? config.defaultPath : to} | |||
| sx={{ | |||
| ...sx, | |||
| /* WCAG 2.4.7 – visible keyboard focus */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| > | |||
| <Logo /> | |||
| </ButtonBase> | |||
| ); | |||
| }; | |||
| @@ -47,16 +47,26 @@ const LocaleSelector = () => { | |||
| return ( | |||
| <Box sx={{ flexShrink: 0, ml: 0.75 }}> | |||
| <IconButton | |||
| disableRipple | |||
| color="secondary" | |||
| sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor }} | |||
| aria-label="open profile" | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'profile-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| disableRipple | |||
| color="secondary" | |||
| sx={{ | |||
| color: 'text.primary', | |||
| bgcolor: open ? iconBackColorOpen : iconBackColor, | |||
| /* ✅ WCAG 2.4.7 focus indicator */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| aria-label="open profile" | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'profile-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| > | |||
| <LanguageIcon /> | |||
| <LanguageIcon /> | |||
| </IconButton> | |||
| <Popper | |||
| placement={matchesXs ? 'bottom' : 'bottom-end'} | |||
| @@ -45,19 +45,26 @@ const MobileSection = () => { | |||
| <> | |||
| <Box sx={{ flexShrink: 0, ml: 0.75 }}> | |||
| <IconButton | |||
| component="span" | |||
| disableRipple | |||
| sx={{ | |||
| bgcolor: open ? 'grey.300' : 'grey.100' | |||
| }} | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'menu-list-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| color="inherit" | |||
| > | |||
| <MoreOutlined /> | |||
| </IconButton> | |||
| component="span" | |||
| disableRipple | |||
| sx={{ | |||
| bgcolor: open ? 'grey.300' : 'grey.100', | |||
| /* WCAG 2.4.7 – visible keyboard focus */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'menu-list-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| color="inherit" | |||
| > | |||
| <MoreOutlined /> | |||
| </IconButton> | |||
| </Box> | |||
| <Popper | |||
| placement="bottom-end" | |||
| @@ -71,17 +71,27 @@ const Notification = () => { | |||
| <IconButton | |||
| disableRipple | |||
| color="secondary" | |||
| sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor }} | |||
| sx={{ | |||
| color: 'text.primary', | |||
| bgcolor: open ? iconBackColorOpen : iconBackColor, | |||
| /* ✅ WCAG 2.4.7 focus indicator */ | |||
| '&:focus-visible': { | |||
| outline: '3px solid #0C489E', | |||
| outlineOffset: '2px', | |||
| borderRadius: '6px' | |||
| } | |||
| }} | |||
| aria-label="open profile" | |||
| ref={anchorRef} | |||
| aria-controls={open ? 'profile-grow' : undefined} | |||
| aria-haspopup="true" | |||
| onClick={handleToggle} | |||
| > | |||
| <Badge badgeContent={4} color="primary"> | |||
| <BellOutlined /> | |||
| </Badge> | |||
| </IconButton> | |||
| <Badge badgeContent={4} color="primary"> | |||
| <BellOutlined /> | |||
| </Badge> | |||
| </IconButton> | |||
| <Popper | |||
| placement={matchesXs ? 'bottom' : 'bottom-end'} | |||
| open={open} | |||