feat(shell): scrollable status tabs, per-page tab titles, skip-to-content, Czech MUI locale
- Shared Tabs: variant=scrollable + auto scroll buttons — the 5 status-filter
tabs no longer clip unreachable at ~360px (desktop rendering unchanged).
- TitleSync: browser tab shows the active page ('Faktury · BOHA'; ambiguous
labels qualified with their section, e.g. 'Záznam – Docházka · BOHA');
index.html fallback now 'BOHA Admin'.
- Skip-to-content link (visually hidden, visible on focus) + id/tabIndex on
<main> — keyboard users skip the 248px sidebar.
- MUI csCZ locale merged into the theme (kills built-in English strings like
'No options'); Czech noOptionsText on Customer/Supplier pickers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import { setLogoutAlert } from "../utils/api";
|
||||
import SidebarNav from "./SidebarNav";
|
||||
import LoadingState from "./LoadingState";
|
||||
import ShortcutsHelp from "../components/ShortcutsHelp";
|
||||
import TitleSync from "../components/TitleSync";
|
||||
|
||||
const DRAWER_WIDTH = 248;
|
||||
|
||||
@@ -98,6 +99,7 @@ export default function AppShell() {
|
||||
|
||||
return (
|
||||
<ScopedCssBaseline>
|
||||
<TitleSync />
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.98 }}
|
||||
animate={
|
||||
@@ -110,6 +112,38 @@ export default function AppShell() {
|
||||
ease: [0.4, 0, 0.2, 1],
|
||||
}}
|
||||
>
|
||||
{/* Skip link: first focusable element; visually hidden (clipped)
|
||||
until keyboard focus reveals it above the drawer. */}
|
||||
<Box
|
||||
component="a"
|
||||
href="#main-content"
|
||||
sx={(theme) => ({
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
left: 8,
|
||||
zIndex: theme.zIndex.drawer + 2,
|
||||
width: "1px",
|
||||
height: "1px",
|
||||
overflow: "hidden",
|
||||
clipPath: "inset(50%)",
|
||||
whiteSpace: "nowrap",
|
||||
px: 2,
|
||||
py: 1,
|
||||
borderRadius: 1,
|
||||
bgcolor: theme.vars!.palette.primary.main,
|
||||
color: theme.vars!.palette.primary.contrastText,
|
||||
textDecoration: "none",
|
||||
fontWeight: 600,
|
||||
"&:focus": {
|
||||
width: "auto",
|
||||
height: "auto",
|
||||
overflow: "visible",
|
||||
clipPath: "none",
|
||||
},
|
||||
})}
|
||||
>
|
||||
Přeskočit na obsah
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -209,10 +243,15 @@ export default function AppShell() {
|
||||
</Box>
|
||||
<Box
|
||||
component="main"
|
||||
id="main-content"
|
||||
tabIndex={-1}
|
||||
sx={{
|
||||
flex: 1,
|
||||
px: immersiveOnMobile ? { xs: 0, md: 3 } : { xs: 2, md: 3 },
|
||||
pb: immersiveOnMobile ? { xs: 0, md: 4 } : 4,
|
||||
// Skip-link target: focus lands here programmatically — the
|
||||
// region-wide focus ring would be noise, not a signal.
|
||||
"&:focus": { outline: "none" },
|
||||
...(immersiveOnMobile && {
|
||||
minHeight: 0,
|
||||
overflow: { xs: "hidden", md: "visible" },
|
||||
|
||||
Reference in New Issue
Block a user