Add pane show/hide toggle strip with persisted visibility (M6, §01A)

A persistent left-rail toolbar (APG toolbar + roving tabindex) toggles the
library / editor / preview panes; a hidden pane frees its space and the rest
redistribute proportionally to their remembered widths. Visibility persists to
astrolabe:ux-prefs (separate key from widths, so a hidden pane keeps its width).
Adds the positional pane-* glyph sub-family to Icon and a Datasets shortcut
button in the strip.
This commit is contained in:
2026-06-07 17:15:58 +03:00
parent f92118712c
commit 8c0a6b9239
11 changed files with 601 additions and 45 deletions
+45
View File
@@ -93,3 +93,48 @@ describe('usePanesStore', () => {
expect(store().previewWidth).toBe(PANE_DEFAULT.preview);
});
});
describe('pane visibility (§01A)', () => {
test('all panes are visible by default', () => {
store().hydrate({}, undefined);
expect(store().libraryVisible).toBe(true);
expect(store().editorVisible).toBe(true);
expect(store().previewVisible).toBe(true);
});
test('togglePane flips only the targeted pane', () => {
store().hydrate({}, {});
store().togglePane('editor');
expect(store().editorVisible).toBe(false);
expect(store().libraryVisible).toBe(true);
expect(store().previewVisible).toBe(true);
store().togglePane('editor');
expect(store().editorVisible).toBe(true);
});
test('hiding all panes is permitted', () => {
store().hydrate({}, {});
store().togglePane('library');
store().togglePane('editor');
store().togglePane('preview');
expect(store().libraryVisible).toBe(false);
expect(store().editorVisible).toBe(false);
expect(store().previewVisible).toBe(false);
});
test('hydrate restores explicit visibility; missing flags default to shown', () => {
store().hydrate({}, { library: false });
expect(store().libraryVisible).toBe(false);
expect(store().editorVisible).toBe(true);
expect(store().previewVisible).toBe(true);
});
test('a hidden pane keeps its remembered width (re-shows at it)', () => {
store().hydrate({ libraryWidth: 250 }, {});
store().togglePane('library'); // hide
expect(store().libraryWidth).toBe(250);
store().togglePane('library'); // re-show
expect(store().libraryWidth).toBe(250);
});
});
+37 -3
View File
@@ -22,6 +22,9 @@ import { create } from 'zustand';
/** Which side pane a drag handle controls. */
export type PaneSide = 'library' | 'preview';
/** Every pane that can be shown/hidden via the toggle strip (spec §01A). */
export type PaneName = 'library' | 'editor' | 'preview';
/** Minimum widths (px) enforced while resizing, so no pane collapses (§01A). */
export const PANE_MIN = { library: 180, preview: 240, editor: 320 } as const;
@@ -79,25 +82,56 @@ export function sideWidthValue(
return Math.round(Math.min(100, Math.max(0, pct)));
}
/** Per-pane visibility (spec §01A). Widths are kept *independently* of visibility,
* so a hidden pane keeps its remembered width and re-shows at it (not a default). */
export interface PaneVisibility {
library?: boolean;
editor?: boolean;
preview?: boolean;
}
export interface PanesState {
libraryWidth: number;
previewWidth: number;
/** Whether each pane is currently shown. All visible by default (§01A). */
libraryVisible: boolean;
editorVisible: boolean;
previewVisible: boolean;
/** Set a side pane's width (already clamped by the caller). */
setWidth: (side: PaneSide, width: number) => void;
/** Restore persisted widths on startup; missing values keep their defaults. */
hydrate: (layout: { libraryWidth?: number; previewWidth?: number }) => void;
/** Show/hide one pane. Hiding all panes is permitted (§01A) — the strip stays. */
togglePane: (pane: PaneName) => void;
/** Restore persisted widths + visibility on startup; missing values keep defaults. */
hydrate: (
layout: { libraryWidth?: number; previewWidth?: number },
visibility?: PaneVisibility,
) => void;
}
const VISIBLE_KEY = {
library: 'libraryVisible',
editor: 'editorVisible',
preview: 'previewVisible',
} as const;
export const usePanesStore = create<PanesState>((set) => ({
libraryWidth: PANE_DEFAULT.library,
previewWidth: PANE_DEFAULT.preview,
libraryVisible: true,
editorVisible: true,
previewVisible: true,
setWidth: (side, width) =>
set(side === 'library' ? { libraryWidth: width } : { previewWidth: width }),
hydrate: (layout) =>
togglePane: (pane) => set((s) => ({ [VISIBLE_KEY[pane]]: !s[VISIBLE_KEY[pane]] })),
hydrate: (layout, visibility) =>
set({
libraryWidth: layout.libraryWidth ?? PANE_DEFAULT.library,
previewWidth: layout.previewWidth ?? PANE_DEFAULT.preview,
libraryVisible: visibility?.library ?? true,
editorVisible: visibility?.editor ?? true,
previewVisible: visibility?.preview ?? true,
}),
}));