From e41c4a381e89ecb11f38f9e4380dd11ecdee8611 Mon Sep 17 00:00:00 2001 From: waterbinnn Date: Sat, 13 Sep 2025 23:47:56 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20sidebar=20resize,=20=EB=A9=94?= =?UTF-8?q?=EB=89=B4=20=EC=97=AC=EB=8B=AB=EC=9D=B4=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/layout/Layout.tsx | 2 +- src/components/layout/SidebarLayout.tsx | 15 +- src/components/ui/Resizable.tsx | 40 ++ src/components/ui/Sidebar.tsx | 126 +++-- src/components/ui/SidebarCollapsibleMenu.tsx | 4 +- src/components/ui/index.ts | 1 + .../post/components/PostSidebarLayout.tsx | 6 +- src/hooks/useSidebarResize.ts | 459 ++++++++++++++++++ src/lib/merge-button-refs.ts | 19 + src/pages/archive/index.tsx | 2 +- src/pages/bookmark/index.tsx | 2 +- 11 files changed, 630 insertions(+), 46 deletions(-) create mode 100644 src/components/ui/Resizable.tsx create mode 100644 src/hooks/useSidebarResize.ts create mode 100644 src/lib/merge-button-refs.ts 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 */}