fix: enforce min-width per column with cascade shrink on window resize
Editor (400px), note list (220px), sidebar (180px), and inspector (240px) now have CSS min-width constraints. Window minWidth set to 800px in Tauri config. Flex-shrink ratios create cascade order: editor shrinks first, then note list, then sidebar. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
"title": "Laputa",
|
||||
"width": 1400,
|
||||
"height": 900,
|
||||
"minWidth": 800,
|
||||
"minHeight": 400,
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
"titleBarStyle": "Overlay",
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
}
|
||||
|
||||
.app__sidebar {
|
||||
flex-shrink: 0;
|
||||
flex-shrink: 1;
|
||||
min-width: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -26,7 +27,8 @@
|
||||
}
|
||||
|
||||
.app__note-list {
|
||||
flex-shrink: 0;
|
||||
flex-shrink: 10;
|
||||
min-width: 220px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -37,7 +39,7 @@
|
||||
|
||||
.app__editor {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-width: 400px;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
|
||||
@@ -40,7 +40,7 @@ export function EditorRightPanel({
|
||||
return (
|
||||
<div
|
||||
className="shrink-0 flex flex-col min-h-0"
|
||||
style={{ width: inspectorWidth, height: '100%' }}
|
||||
style={{ width: inspectorWidth, minWidth: 240, height: '100%' }}
|
||||
>
|
||||
<AiPanel
|
||||
onClose={() => onToggleAIChat?.()}
|
||||
|
||||
55
src/hooks/useLayoutPanels.test.ts
Normal file
55
src/hooks/useLayoutPanels.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { useLayoutPanels, COLUMN_MIN_WIDTHS } from './useLayoutPanels'
|
||||
|
||||
describe('useLayoutPanels', () => {
|
||||
it('exports column minimum widths', () => {
|
||||
expect(COLUMN_MIN_WIDTHS.sidebar).toBe(180)
|
||||
expect(COLUMN_MIN_WIDTHS.noteList).toBe(220)
|
||||
expect(COLUMN_MIN_WIDTHS.editor).toBe(400)
|
||||
expect(COLUMN_MIN_WIDTHS.inspector).toBe(240)
|
||||
})
|
||||
|
||||
it('returns default widths', () => {
|
||||
const { result } = renderHook(() => useLayoutPanels())
|
||||
expect(result.current.sidebarWidth).toBe(250)
|
||||
expect(result.current.noteListWidth).toBe(300)
|
||||
expect(result.current.inspectorWidth).toBe(280)
|
||||
})
|
||||
|
||||
it('clamps sidebar resize to minimum', () => {
|
||||
const { result } = renderHook(() => useLayoutPanels())
|
||||
act(() => result.current.handleSidebarResize(-500))
|
||||
expect(result.current.sidebarWidth).toBe(COLUMN_MIN_WIDTHS.sidebar)
|
||||
})
|
||||
|
||||
it('clamps note list resize to minimum', () => {
|
||||
const { result } = renderHook(() => useLayoutPanels())
|
||||
act(() => result.current.handleNoteListResize(-500))
|
||||
expect(result.current.noteListWidth).toBe(COLUMN_MIN_WIDTHS.noteList)
|
||||
})
|
||||
|
||||
it('clamps inspector resize to minimum', () => {
|
||||
const { result } = renderHook(() => useLayoutPanels())
|
||||
act(() => result.current.handleInspectorResize(500))
|
||||
expect(result.current.inspectorWidth).toBe(COLUMN_MIN_WIDTHS.inspector)
|
||||
})
|
||||
|
||||
it('clamps sidebar resize to maximum', () => {
|
||||
const { result } = renderHook(() => useLayoutPanels())
|
||||
act(() => result.current.handleSidebarResize(500))
|
||||
expect(result.current.sidebarWidth).toBe(400)
|
||||
})
|
||||
|
||||
it('clamps note list resize to maximum', () => {
|
||||
const { result } = renderHook(() => useLayoutPanels())
|
||||
act(() => result.current.handleNoteListResize(500))
|
||||
expect(result.current.noteListWidth).toBe(500)
|
||||
})
|
||||
|
||||
it('clamps inspector resize to maximum', () => {
|
||||
const { result } = renderHook(() => useLayoutPanels())
|
||||
act(() => result.current.handleInspectorResize(-500))
|
||||
expect(result.current.inspectorWidth).toBe(500)
|
||||
})
|
||||
})
|
||||
@@ -1,12 +1,19 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export const COLUMN_MIN_WIDTHS = {
|
||||
sidebar: 180,
|
||||
noteList: 220,
|
||||
editor: 400,
|
||||
inspector: 240,
|
||||
} as const
|
||||
|
||||
export function useLayoutPanels() {
|
||||
const [sidebarWidth, setSidebarWidth] = useState(250)
|
||||
const [noteListWidth, setNoteListWidth] = useState(300)
|
||||
const [inspectorWidth, setInspectorWidth] = useState(280)
|
||||
const [inspectorCollapsed, setInspectorCollapsed] = useState(false)
|
||||
const handleSidebarResize = useCallback((delta: number) => setSidebarWidth((w) => Math.max(150, Math.min(400, w + delta))), [])
|
||||
const handleNoteListResize = useCallback((delta: number) => setNoteListWidth((w) => Math.max(200, Math.min(500, w + delta))), [])
|
||||
const handleInspectorResize = useCallback((delta: number) => setInspectorWidth((w) => Math.max(200, Math.min(500, w - delta))), [])
|
||||
const handleSidebarResize = useCallback((delta: number) => setSidebarWidth((w) => Math.max(COLUMN_MIN_WIDTHS.sidebar, Math.min(400, w + delta))), [])
|
||||
const handleNoteListResize = useCallback((delta: number) => setNoteListWidth((w) => Math.max(COLUMN_MIN_WIDTHS.noteList, Math.min(500, w + delta))), [])
|
||||
const handleInspectorResize = useCallback((delta: number) => setInspectorWidth((w) => Math.max(COLUMN_MIN_WIDTHS.inspector, Math.min(500, w - delta))), [])
|
||||
return { sidebarWidth, noteListWidth, inspectorWidth, inspectorCollapsed, setInspectorCollapsed, handleSidebarResize, handleNoteListResize, handleInspectorResize }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user