mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-31 20:58:04 +00:00
feat(ui): add Classic, Smart, and Compact desktop navigation styles (#1642)
* feat(ui): add Classic, Smart, and Compact desktop navigation styles Introduce a shared app-nav registry and reachable model so TopBar, the command palette, and mobile menus share one destination source. Smart bar is the default; Appearance gains a Navigation subsection with quick links. * fix(e2e): stop clearing top-nav prefs on every reload The desktop-navigation suite used addInitScript to wipe mode storage, which re-ran on reload and undid the classic/compact values under test. * fix(ui): harden nav PR docs and Compact quick-link coverage Drop the partial docs-refresh import that left missing image assets and a stale Display screenshot, keep navigation-scoped operator docs against main, and add an E2E path that proves Compact quick-link add, persist, and render after reload. * fix(ui): polish Compact quick links and menu mastheads Align Smart/Compact menus with Theme chrome, keep pin labels always visible, and drive add capacity from persisted pins (max five) with a trailing + picker and per-pin remove. * test(e2e): exact-match Compact Networking pin locator Avoid Playwright strict-mode clash with Actions for Networking. * fix(ui): simplify Compact quick link removal and fix + button trailing Remove the (...) dropdown per quick link in Compact mode. Right-click context menu remains as the sole on-bar removal affordance. Fix the + add-button to trail quick links rather than pinning to the far right by removing flex-1 from the quick-link rail.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { SENCHO_SETTINGS_CHANGED } from '@/lib/events';
|
||||
|
||||
export const TOP_NAV_MODE_KEY = 'sencho.appearance.topNavMode';
|
||||
|
||||
export type TopNavMode = 'classic' | 'smart' | 'compact';
|
||||
|
||||
const VALID: ReadonlySet<string> = new Set(['classic', 'smart', 'compact']);
|
||||
|
||||
/** Missing or invalid storage resolves to Smart (recommended default). */
|
||||
export function parseTopNavMode(raw: string | null): TopNavMode {
|
||||
if (raw && VALID.has(raw)) return raw as TopNavMode;
|
||||
return 'smart';
|
||||
}
|
||||
|
||||
function readStored(): TopNavMode {
|
||||
if (typeof window === 'undefined') return 'smart';
|
||||
try {
|
||||
return parseTopNavMode(window.localStorage.getItem(TOP_NAV_MODE_KEY));
|
||||
} catch {
|
||||
return 'smart';
|
||||
}
|
||||
}
|
||||
|
||||
export function useTopNavMode(): [TopNavMode, (next: TopNavMode) => void] {
|
||||
const [mode, setModeState] = useState<TopNavMode>(readStored);
|
||||
|
||||
useEffect(() => {
|
||||
function onSettingsChanged() {
|
||||
setModeState(readStored());
|
||||
}
|
||||
window.addEventListener(SENCHO_SETTINGS_CHANGED, onSettingsChanged);
|
||||
return () => window.removeEventListener(SENCHO_SETTINGS_CHANGED, onSettingsChanged);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
function onStorage(event: StorageEvent) {
|
||||
if (event.key !== TOP_NAV_MODE_KEY) return;
|
||||
setModeState(parseTopNavMode(event.newValue));
|
||||
}
|
||||
window.addEventListener('storage', onStorage);
|
||||
return () => window.removeEventListener('storage', onStorage);
|
||||
}, []);
|
||||
|
||||
const setMode = useCallback((next: TopNavMode) => {
|
||||
try {
|
||||
window.localStorage.setItem(TOP_NAV_MODE_KEY, next);
|
||||
} catch {
|
||||
// ignore; localStorage may be unavailable
|
||||
}
|
||||
setModeState(next);
|
||||
window.dispatchEvent(new CustomEvent(SENCHO_SETTINGS_CHANGED));
|
||||
}, []);
|
||||
|
||||
return [mode, setMode];
|
||||
}
|
||||
Reference in New Issue
Block a user