diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index b30360d..09f4db8 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -19,12 +19,13 @@ import { } from '@tabler/icons-react' import { openProjectAdmin } from '../lib/employeeAdminApi' import { serverpulsApi } from '../lib/serverpulsApi' +import { startsCollapsed } from '../lib/sidebar' export default function Sidebar() { const { user, logout, isAdmin } = useAuth() const navigate = useNavigate() const location = useLocation() - const [collapsed, setCollapsed] = useState(false) + const [collapsed, setCollapsed] = useState(() => startsCollapsed()) const [switching, setSwitching] = useState(false) const [pulseLevel, setPulseLevel] = useState('ok') diff --git a/src/lib/sidebar.js b/src/lib/sidebar.js new file mode 100644 index 0000000..5ab119b --- /dev/null +++ b/src/lib/sidebar.js @@ -0,0 +1,12 @@ +// Seitenmenue: auf schmalen Bildschirmen (Handy) startet es eingeklappt, sonst bleiben vom Inhalt bei 390 px nur +// etwa 140 px und jede Seite scrollt waagerecht. Ausklappen geht weiterhin per Knopf. +export const AUTO_COLLAPSE_MAX_WIDTH = 720 + +/** true, wenn das Menue eingeklappt starten soll (Fensterbreite bis 720 px); ohne window (Test, Server) false. */ +export function startsCollapsed(win = typeof window !== 'undefined' ? window : null) { + try { + return Boolean(win?.matchMedia?.(`(max-width: ${AUTO_COLLAPSE_MAX_WIDTH}px)`)?.matches) + } catch { + return false + } +} diff --git a/tests/unit/sidebar.test.mjs b/tests/unit/sidebar.test.mjs new file mode 100644 index 0000000..7f832ce --- /dev/null +++ b/tests/unit/sidebar.test.mjs @@ -0,0 +1,27 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { loadModule } from './load.mjs' + +const { startsCollapsed, AUTO_COLLAPSE_MAX_WIDTH } = await loadModule('src/lib/sidebar.js') + +// Fenster mit matchMedia, das "(max-width: Npx)" gegen eine feste Breite prueft +const windowOf = (width) => ({ + matchMedia: (query) => ({ matches: width <= Number(/max-width:\s*(\d+)px/.exec(query)?.[1]) }), +}) + +test('startsCollapsed: Handy (390 px) und Grenze (720 px) starten eingeklappt', () => { + assert.equal(AUTO_COLLAPSE_MAX_WIDTH, 720) + assert.equal(startsCollapsed(windowOf(390)), true) + assert.equal(startsCollapsed(windowOf(720)), true) +}) + +test('startsCollapsed: breitere Fenster starten ausgeklappt', () => { + assert.equal(startsCollapsed(windowOf(721)), false) + assert.equal(startsCollapsed(windowOf(1280)), false) +}) + +test('startsCollapsed: ohne window oder matchMedia (oder bei Fehler) ausgeklappt', () => { + assert.equal(startsCollapsed(null), false) + assert.equal(startsCollapsed({}), false) + assert.equal(startsCollapsed({ matchMedia: () => { throw new Error('kaputt') } }), false) +})