diff --git a/memento-note/app/(main)/layout.tsx b/memento-note/app/(main)/layout.tsx index 579b063..5949e7a 100644 --- a/memento-note/app/(main)/layout.tsx +++ b/memento-note/app/(main)/layout.tsx @@ -7,6 +7,7 @@ import { detectUserLanguage, parseAcceptLanguage } from "@/lib/i18n/detect-user- import { loadTranslations } from "@/lib/i18n/load-translations"; import { getAISettings } from "@/app/actions/ai-settings"; import { AIChatLayoutBridge } from "@/components/ai-chat-layout-bridge"; +import { SidebarResizeClient } from "@/components/sidebar-resize-client"; export default async function MainLayout({ @@ -36,8 +37,8 @@ export default async function MainLayout({ initialAiProcessingConsent={aiSettings?.aiProcessingConsent === true} > {/* No top-bar header — sidebar-only navigation (architectural-grid design) */} -
- }> + + }> @@ -47,9 +48,9 @@ export default async function MainLayout({ {children} + - {showAIAssistant && } -
+ {showAIAssistant && } ); } diff --git a/memento-note/components/sidebar-resize-client.tsx b/memento-note/components/sidebar-resize-client.tsx new file mode 100644 index 0000000..9329ef1 --- /dev/null +++ b/memento-note/components/sidebar-resize-client.tsx @@ -0,0 +1,19 @@ +'use client' + +import { type ReactNode } from 'react' +import { useSidebarResize, SidebarResizeHandle } from '@/components/sidebar-resize' + +export function SidebarResizeClient({ children }: { children: ReactNode }) { + const { width, isDragging, onPointerDown } = useSidebarResize() + + return ( +
+
+
+ {children} +
+
+ +
+ ) +} diff --git a/memento-note/components/sidebar-resize.tsx b/memento-note/components/sidebar-resize.tsx new file mode 100644 index 0000000..1bf6bb7 --- /dev/null +++ b/memento-note/components/sidebar-resize.tsx @@ -0,0 +1,81 @@ +'use client' + +import { useCallback, useEffect, useRef, useState } from 'react' + +const STORAGE_KEY = 'memento-sidebar-width' +const MIN = 280 +const MAX = 560 + +export function useSidebarResize() { + const [width, setWidth] = useState(320) + const [isDragging, setIsDragging] = useState(false) + const startXRef = useRef(0) + const startWidthRef = useRef(320) + + useEffect(() => { + const stored = localStorage.getItem(STORAGE_KEY) + if (stored) { + const parsed = parseInt(stored, 10) + if (!isNaN(parsed) && parsed >= MIN && parsed <= MAX) { + setWidth(parsed) + } + } + }, []) + + const onPointerDown = useCallback((e: React.PointerEvent) => { + e.preventDefault() + setIsDragging(true) + startXRef.current = e.clientX + startWidthRef.current = width + document.body.style.cursor = 'col-resize' + document.body.style.userSelect = 'none' + }, [width]) + + useEffect(() => { + if (!isDragging) return + + const onPointerMove = (e: PointerEvent) => { + const delta = e.clientX - startXRef.current + const newWidth = Math.max(MIN, Math.min(MAX, startWidthRef.current + delta)) + setWidth(newWidth) + } + + const onPointerUp = () => { + setIsDragging(false) + document.body.style.cursor = '' + document.body.style.userSelect = '' + setWidth(prev => { + localStorage.setItem(STORAGE_KEY, String(prev)) + return prev + }) + } + + document.addEventListener('pointermove', onPointerMove) + document.addEventListener('pointerup', onPointerUp) + return () => { + document.removeEventListener('pointermove', onPointerMove) + document.removeEventListener('pointerup', onPointerUp) + } + }, [isDragging]) + + return { width, isDragging, onPointerDown } +} + +export function SidebarResizeHandle({ onPointerDown, isDragging }: { onPointerDown: (e: React.PointerEvent) => void; isDragging: boolean }) { + return ( +