| @@ -43,7 +43,7 @@ | |||||
| color: #0C489E; | color: #0C489E; | ||||
| } | } | ||||
| #navbar div li a:hover::after, | #navbar div li a:hover::after, | ||||
| #navbar div li a:focus::after{ | |||||
| #navbar div li a:focus-visible::after{ | |||||
| content: ""; | content: ""; | ||||
| width: 80%; | width: 80%; | ||||
| height: 3px; | height: 3px; | ||||
| @@ -73,6 +73,19 @@ | |||||
| opacity: 1; | opacity: 1; | ||||
| display: block | 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:focus-within > ul, | ||||
| #navbar div li ul:hover, | #navbar div li ul:hover, | ||||
| #navbar div li ul:focus { | #navbar div li ul:focus { | ||||
| @@ -74,4 +74,60 @@ a:active { | |||||
| text-decoration: none; | 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 ( | return ( | ||||
| <Stack direction="column" justifyContent="center" alignItems="center" > | <Stack direction="column" justifyContent="center" alignItems="center" > | ||||
| <ButtonBase | <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> | <span style={{ color: checkSysEnv()!=''?'red':'#0C489E'}} id="systemTitle">PNSPS</span> | ||||
| </Stack> | </Stack> | ||||
| @@ -17,14 +17,23 @@ const LogoSection = ({ sx, to }) => { | |||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| return ( | return ( | ||||
| <ButtonBase | <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 ( | return ( | ||||
| <Box sx={{ flexShrink: 0, ml: 0.75 }}> | <Box sx={{ flexShrink: 0, ml: 0.75 }}> | ||||
| <IconButton | <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> | </IconButton> | ||||
| <Popper | <Popper | ||||
| placement={matchesXs ? 'bottom' : 'bottom-end'} | placement={matchesXs ? 'bottom' : 'bottom-end'} | ||||
| @@ -45,19 +45,26 @@ const MobileSection = () => { | |||||
| <> | <> | ||||
| <Box sx={{ flexShrink: 0, ml: 0.75 }}> | <Box sx={{ flexShrink: 0, ml: 0.75 }}> | ||||
| <IconButton | <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> | </Box> | ||||
| <Popper | <Popper | ||||
| placement="bottom-end" | placement="bottom-end" | ||||
| @@ -71,17 +71,27 @@ const Notification = () => { | |||||
| <IconButton | <IconButton | ||||
| disableRipple | disableRipple | ||||
| color="secondary" | 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" | aria-label="open profile" | ||||
| ref={anchorRef} | ref={anchorRef} | ||||
| aria-controls={open ? 'profile-grow' : undefined} | aria-controls={open ? 'profile-grow' : undefined} | ||||
| aria-haspopup="true" | aria-haspopup="true" | ||||
| onClick={handleToggle} | onClick={handleToggle} | ||||
| > | > | ||||
| <Badge badgeContent={4} color="primary"> | |||||
| <BellOutlined /> | |||||
| </Badge> | |||||
| </IconButton> | |||||
| <Badge badgeContent={4} color="primary"> | |||||
| <BellOutlined /> | |||||
| </Badge> | |||||
| </IconButton> | |||||
| <Popper | <Popper | ||||
| placement={matchesXs ? 'bottom' : 'bottom-end'} | placement={matchesXs ? 'bottom' : 'bottom-end'} | ||||
| open={open} | open={open} | ||||