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:
BOHA
2026-07-04 03:22:06 +02:00
parent 87e644eef8
commit 4d0ec53514
7 changed files with 322 additions and 223 deletions

View File

@@ -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" },