diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3fafb59d..27a543ca 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -16,6 +16,8 @@ "title": "Laputa", "width": 1400, "height": 900, + "minWidth": 800, + "minHeight": 400, "resizable": true, "fullscreen": false, "titleBarStyle": "Overlay", diff --git a/src/App.css b/src/App.css index 71c33ce1..de568d63 100644 --- a/src/App.css +++ b/src/App.css @@ -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; diff --git a/src/components/EditorRightPanel.tsx b/src/components/EditorRightPanel.tsx index 68153b13..2dd8bf0d 100644 --- a/src/components/EditorRightPanel.tsx +++ b/src/components/EditorRightPanel.tsx @@ -40,7 +40,7 @@ export function EditorRightPanel({ return (
onToggleAIChat?.()} diff --git a/src/hooks/useLayoutPanels.test.ts b/src/hooks/useLayoutPanels.test.ts new file mode 100644 index 00000000..9aae1c9a --- /dev/null +++ b/src/hooks/useLayoutPanels.test.ts @@ -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) + }) +}) diff --git a/src/hooks/useLayoutPanels.ts b/src/hooks/useLayoutPanels.ts index 192c276f..d2b15b25 100644 --- a/src/hooks/useLayoutPanels.ts +++ b/src/hooks/useLayoutPanels.ts @@ -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 } }