Sections across the top, and a choice between the two shells
The old web UI put everything in a sidebar, and a reskin of it would still read as the old web UI. The modern shell splits navigation in two instead: the layout switcher already in the top bar is tier one, and a new bar under it carries that layout's own top-level items, each group opening its children in a menu. Nothing on the left, so a queue table or a dashboard gets the whole window. - Management's nine entries and Account's eleven suit a menu bar; the bar measures its items once per layout and folds whatever doesn't fit into "More", recomputing on resize from the cached widths. - Settings keeps the sidebar. Nineteen groups is a configuration browser, not a set of destinations, and a menu bar stops helping however wide the window; the threshold is on the count, not on the name, so a changed schema can't strand anyone. - Which shell to use is the reader's, beside the theme: user menu, Layout, Modern or Legacy. Modern is the default, and the choice is remembered in the browser rather than with the account — settings.json is shared with the webmail, which has no notion of this. - A phone is unchanged. The section bar is hidden below md and the sidebar stays as the slide-over behind the hamburger. - The tree walking both shells need moves to lib/navTree.ts, so the sidebar and the section bar agree on what is visible, what is locked and what is active.
This commit is contained in:
@@ -12,10 +12,26 @@ import { persist } from 'zustand/middleware';
|
||||
|
||||
type Theme = 'light' | 'dark';
|
||||
|
||||
/**
|
||||
* INBUXA: which shell draws the navigation. "modern" is the two-tier shell —
|
||||
* the layout switcher in the top bar over a section bar carrying that layout's
|
||||
* own items, and no sidebar. "legacy" is the sidebar the old web UI had. A
|
||||
* layout too deep for a menu bar keeps the sidebar under either setting.
|
||||
*/
|
||||
export type AdminLayout = 'modern' | 'legacy';
|
||||
|
||||
export const DEFAULT_ADMIN_LAYOUT: AdminLayout = 'modern';
|
||||
|
||||
export function isAdminLayout(value: unknown): value is AdminLayout {
|
||||
return value === 'modern' || value === 'legacy';
|
||||
}
|
||||
|
||||
interface UIState {
|
||||
theme: Theme;
|
||||
/** INBUXA: the color palette, the same set INBUXA webmail offers. */
|
||||
palette: PaletteId;
|
||||
/** INBUXA: the navigation shell, the reader's own choice. */
|
||||
adminLayout: AdminLayout;
|
||||
sidebarOpen: boolean;
|
||||
/** INBUXA: the sidebar folded to a rail of icon tiles, on wide screens. */
|
||||
sidebarCollapsed: boolean;
|
||||
@@ -24,6 +40,7 @@ interface UIState {
|
||||
toggleTheme: () => void;
|
||||
setTheme: (theme: Theme) => void;
|
||||
setPalette: (palette: PaletteId) => void;
|
||||
setAdminLayout: (layout: AdminLayout) => void;
|
||||
/** INBUXA: take the theme stored with the account, without writing it back. */
|
||||
applyAccountTheme: (palette: PaletteId | null, mode: 'system' | 'light' | 'dark' | null) => void;
|
||||
toggleSidebar: () => void;
|
||||
@@ -48,6 +65,7 @@ export const useUIStore = create<UIState>()(
|
||||
sidebarOpen: typeof window !== 'undefined' ? (window.matchMedia?.('(min-width: 768px)').matches ?? true) : true,
|
||||
sidebarCollapsed: false,
|
||||
palette: DEFAULT_PALETTE,
|
||||
adminLayout: DEFAULT_ADMIN_LAYOUT,
|
||||
activeSection: '',
|
||||
|
||||
toggleTheme: () => {
|
||||
@@ -77,6 +95,10 @@ export const useUIStore = create<UIState>()(
|
||||
queueAccountTheme(palette, null, get().theme);
|
||||
},
|
||||
|
||||
setAdminLayout: (layout) => {
|
||||
set({ adminLayout: layout });
|
||||
},
|
||||
|
||||
applyAccountTheme: (palette, mode) => {
|
||||
const next: Partial<UIState> = {};
|
||||
if (palette) {
|
||||
@@ -105,6 +127,7 @@ export const useUIStore = create<UIState>()(
|
||||
partialize: (state) => ({
|
||||
theme: state.theme,
|
||||
palette: state.palette,
|
||||
adminLayout: state.adminLayout,
|
||||
sidebarCollapsed: state.sidebarCollapsed,
|
||||
}),
|
||||
onRehydrateStorage: () => {
|
||||
@@ -112,6 +135,7 @@ export const useUIStore = create<UIState>()(
|
||||
if (state) {
|
||||
applyThemeClass(state.theme);
|
||||
applyPalette(isPaletteId(state.palette) ? state.palette : DEFAULT_PALETTE);
|
||||
if (!isAdminLayout(state.adminLayout)) state.adminLayout = DEFAULT_ADMIN_LAYOUT;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user