Add resizable panes with drag handles, min widths, and persistence

This commit is contained in:
2026-06-05 10:46:07 +03:00
parent 411bfbc6c2
commit c50f141d57
11 changed files with 482 additions and 25 deletions
+55
View File
@@ -0,0 +1,55 @@
import { beforeEach, describe, expect, test } from 'vitest';
import { clampSideWidth, HANDLES_TOTAL, PANE_DEFAULT, PANE_MIN, usePanesStore } from './PanesStore';
const store = () => usePanesStore.getState();
describe('clampSideWidth', () => {
// A roomy container where nothing is constrained.
const W = 1400;
test('passes a comfortable width through unchanged', () => {
expect(clampSideWidth('library', 300, W, PANE_DEFAULT.preview)).toBe(300);
expect(clampSideWidth('preview', 400, W, PANE_DEFAULT.library)).toBe(400);
});
test('never goes below the pane minimum', () => {
expect(clampSideWidth('library', 50, W, PANE_DEFAULT.preview)).toBe(PANE_MIN.library);
expect(clampSideWidth('preview', 10, W, PANE_DEFAULT.library)).toBe(PANE_MIN.preview);
});
test('caps the width so the editor keeps at least its minimum', () => {
const other = PANE_DEFAULT.preview;
const max = W - other - HANDLES_TOTAL - PANE_MIN.editor;
// Asking for far more than the editor can spare clamps to that maximum.
expect(clampSideWidth('library', W, W, other)).toBe(max);
// The editor sits exactly at its minimum at that point.
expect(W - max - other - HANDLES_TOTAL).toBe(PANE_MIN.editor);
});
test('a container too narrow for all minimums still never drops below the min', () => {
// 500px total can't fit library(180)+preview(240)+editor(320)+handles.
expect(clampSideWidth('library', 300, 500, PANE_MIN.preview)).toBe(PANE_MIN.library);
});
});
describe('usePanesStore', () => {
beforeEach(() =>
store().hydrate({ libraryWidth: PANE_DEFAULT.library, previewWidth: PANE_DEFAULT.preview }),
);
test('setWidth updates the targeted side only', () => {
store().setWidth('library', 320);
expect(store().libraryWidth).toBe(320);
expect(store().previewWidth).toBe(PANE_DEFAULT.preview);
store().setWidth('preview', 420);
expect(store().previewWidth).toBe(420);
expect(store().libraryWidth).toBe(320);
});
test('hydrate fills missing values from defaults', () => {
store().hydrate({ libraryWidth: 250 });
expect(store().libraryWidth).toBe(250);
expect(store().previewWidth).toBe(PANE_DEFAULT.preview);
});
});