diff --git a/package.json b/package.json index b793ac7..0f677e2 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "dependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", + "react-resizable-panels": "^3.0.6", "react-router-dom": "^7.4.1" }, "devDependencies": { diff --git a/public/manifest.json b/public/manifest.json index afbccd7..0f71482 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -3,7 +3,7 @@ "name": "디룩(Delook)", "author": "@waterbinnn", "homepage_url": "https://www.delook.co.kr/", - "version": "1.0.9", + "version": "1.0.10", "description": "개념 학습부터 기술 면접 준비까지, 성장하는 개발자의 새 탭", "action": { "default_popup": "popup.html", diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index 2502dd1..cb0089a 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -14,7 +14,7 @@ export function Layout() {
diff --git a/src/components/layout/SidebarLayout.tsx b/src/components/layout/SidebarLayout.tsx index 72f7d06..915c8a7 100644 --- a/src/components/layout/SidebarLayout.tsx +++ b/src/components/layout/SidebarLayout.tsx @@ -1,6 +1,13 @@ import { useIsMobile } from '@/hooks'; -import { Sidebar, SidebarInset, SidebarMenu, SidebarProvider, SidebarTrigger } from '../ui'; +import { + Sidebar, + SidebarInset, + SidebarMenu, + SidebarProvider, + SidebarRail, + SidebarTrigger, +} from '../ui'; export const SidebarLayout = ({ sidebarMenu, @@ -13,14 +20,16 @@ export const SidebarLayout = ({ return ( - + {sidebarMenu} + -
+
{isMobile && ( )} + {children}
diff --git a/src/components/ui/Resizable.tsx b/src/components/ui/Resizable.tsx new file mode 100644 index 0000000..bc1e0ff --- /dev/null +++ b/src/components/ui/Resizable.tsx @@ -0,0 +1,40 @@ +import { GripVertical } from 'lucide-react'; +import * as ResizablePrimitive from 'react-resizable-panels'; + +import { cn } from '@/lib/utils'; + +const ResizablePanelGroup = ({ + className, + ...props +}: React.ComponentProps) => ( + +); + +const ResizablePanel = ResizablePrimitive.Panel; + +const ResizableHandle = ({ + withHandle, + className, + ...props +}: React.ComponentProps & { + withHandle?: boolean; +}) => ( + div]:rotate-90', + className, + )} + {...props} + > + {withHandle && ( +
+ +
+ )} +
+); + +export { ResizableHandle, ResizablePanel, ResizablePanelGroup }; diff --git a/src/components/ui/Sidebar.tsx b/src/components/ui/Sidebar.tsx index 0cd4326..811fe70 100644 --- a/src/components/ui/Sidebar.tsx +++ b/src/components/ui/Sidebar.tsx @@ -17,6 +17,8 @@ import { TooltipTrigger, } from '@/components'; import { useIsMobile } from '@/hooks'; +import { useSidebarResize } from '@/hooks/useSidebarResize'; +import { mergeButtonRefs } from '@/lib/merge-button-refs'; import { cn } from '@/lib/utils'; const SIDEBAR_COOKIE_NAME = 'sidebar_state'; @@ -25,6 +27,8 @@ const SIDEBAR_WIDTH = '22rem'; const SIDEBAR_WIDTH_MOBILE = '18rem'; const SIDEBAR_WIDTH_ICON = '3rem'; const SIDEBAR_KEYBOARD_SHORTCUT = 'b'; +const MAX_SIDEBAR_WIDTH = '25rem'; +const MIN_SIDEBAR_WIDTH = '18rem'; export type SidebarContextProps = { state: 'expanded' | 'collapsed'; @@ -34,6 +38,10 @@ export type SidebarContextProps = { setOpenMobile: (open: boolean) => void; isMobile: boolean; toggleSidebar: () => void; + width: string; + setWidth: (width: string) => void; + isDraggingRail: boolean; + setIsDraggingRail: (isDraggingRail: boolean) => void; }; const SidebarContext = React.createContext(null); @@ -53,6 +61,7 @@ const SidebarProvider = React.forwardRef< defaultOpen?: boolean; open?: boolean; onOpenChange?: (open: boolean) => void; + defaultWidth?: string; } >( ( @@ -63,12 +72,15 @@ const SidebarProvider = React.forwardRef< className, style, children, + defaultWidth = SIDEBAR_WIDTH, ...props }, ref, ) => { const isMobile = useIsMobile(); + const [width, setWidth] = React.useState(defaultWidth); const [openMobile, setOpenMobile] = React.useState(false); + const [isDraggingRail, setIsDraggingRail] = React.useState(false); // This is the internal state of the sidebar. // We use openProp and setOpenProp for control from outside the component. @@ -120,8 +132,22 @@ const SidebarProvider = React.forwardRef< openMobile, setOpenMobile, toggleSidebar, + width, + setWidth, + isDraggingRail, + setIsDraggingRail, }), - [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar], + [ + state, + open, + setOpen, + isMobile, + openMobile, + // setOpenMobile, + toggleSidebar, + width, + isDraggingRail, + ], ); return ( @@ -130,7 +156,7 @@ const SidebarProvider = React.forwardRef<
{ - const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); + const { isMobile, state, openMobile, setOpenMobile, isDraggingRail } = useSidebar(); if (collapsible === 'none') { return ( @@ -234,28 +260,33 @@ const Sidebar = React.forwardRef< data-collapsible={state === 'collapsed' ? collapsible : ''} data-variant={variant} data-side={side} + data-dragging={isDraggingRail} > {/* This is what handles the sidebar gap on desktop */}