22
33import * as React from "react" ;
44import { useStore } from "@tanstack/react-store" ;
5- import { PanelLeftIcon } from "lucide-react" ;
65import { cn } from "@/lib/utils" ;
76import { Button } from "@/components/ui/button" ;
87import {
@@ -24,36 +23,39 @@ import {
2423 PopoverTrigger ,
2524} from "@/components/ui/popover" ;
2625import { sidebarStore , sidebarActions } from "./sidebar-store" ;
27- import { IconChevronRight } from "@tabler/icons-react" ;
26+ import { IconChevronRight , IconLayoutSidebar } from "@tabler/icons-react" ;
2827
2928const SIDEBAR_WIDTH = "16rem" ;
3029const SIDEBAR_WIDTH_COLLAPSED = "3.5rem" ;
3130
3231// Context for sidebar ID
33- const SidebarContext = React . createContext < string | null > ( null ) ;
34-
35- // Hook to get sidebar ID from context
36- function useSidebarId ( ) {
37- const id = React . useContext ( SidebarContext ) ;
38- if ( ! id ) {
39- throw new Error ( "Sidebar components must be used within a Sidebar component" ) ;
40- }
41- return id ;
32+ interface SidebarContextProps {
33+ id : string ;
34+ isCollapsed ?: boolean ;
4235}
36+ const SidebarContext = React . createContext < SidebarContextProps | null > ( null ) ;
4337
4438// Hook to use sidebar
4539export function useSidebar ( id ?: string ) {
46- const contextId = React . useContext ( SidebarContext ) ;
47- const sidebarId = id ?? contextId ;
40+ const context = React . useContext ( SidebarContext ) ;
41+ const sidebarId = id ?? context ?. id ;
42+ const isCollapsed = context ?. isCollapsed ;
4843
4944 if ( ! sidebarId ) {
5045 throw new Error ( "useSidebar must be called with an id or within a Sidebar component" ) ;
5146 }
5247
5348 const sidebar = useStore ( sidebarStore , ( state ) => state . sidebars [ sidebarId ] ) ;
5449
50+ const effectiveSidebar = React . useMemo ( ( ) => {
51+ if ( isCollapsed && sidebar ) {
52+ return { ...sidebar , open : false , openMobile : false } ;
53+ }
54+ return sidebar ;
55+ } , [ sidebar , isCollapsed ] ) ;
56+
5557 return {
56- sidebar,
58+ sidebar : effectiveSidebar ,
5759 toggle : ( isMobile = false ) => sidebarActions . toggleSidebar ( sidebarId , isMobile ) ,
5860 setOpen : ( open : boolean , isMobile = false ) =>
5961 sidebarActions . setOpen ( sidebarId , open , isMobile ) ,
@@ -72,6 +74,8 @@ interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
7274 width ?: string ;
7375 collapsedWidth ?: string ;
7476 keyboardShortcut ?: string ;
77+ rootClassName ?: string ;
78+ isCollapsed ?: boolean ;
7579}
7680
7781export function Sidebar ( {
@@ -84,6 +88,8 @@ export function Sidebar({
8488 collapsedWidth = SIDEBAR_WIDTH_COLLAPSED ,
8589 keyboardShortcut,
8690 className,
91+ rootClassName,
92+ isCollapsed,
8793 children,
8894 ...props
8995} : SidebarProps ) {
@@ -165,13 +171,13 @@ export function Sidebar({
165171 } , [ ] ) ;
166172
167173 // Use sidebar state if available, otherwise defaultOpen
168- const isOpen = sidebar ? ( isMobile ? sidebar . openMobile : sidebar . open ) : defaultOpen ;
174+ const isOpen = isCollapsed ? false : ( sidebar ? ( isMobile ? sidebar . openMobile : sidebar . open ) : defaultOpen ) ;
169175 const currentWidth = isOpen ? width : collapsedWidth ;
170176
171177 // Mobile: show Sheet
172178 if ( isMobile ) {
173179 return (
174- < SidebarContext . Provider value = { id } >
180+ < SidebarContext . Provider value = { { id , isCollapsed } } >
175181 < Sheet
176182 open = { sidebar ? sidebar . openMobile : false }
177183 onOpenChange = { ( open ) => sidebarActions . setOpen ( id , open , true ) }
@@ -190,7 +196,7 @@ export function Sidebar({
190196
191197 const baseStyles = "sticky top-0 h-full overflow-hidden transition-all" ;
192198 const variantRootStyles = {
193- default : "h-full " ,
199+ default : "" ,
194200 floating : "m-3" ,
195201 } ;
196202 const variantStyles = {
@@ -202,21 +208,21 @@ export function Sidebar({
202208 // This prevents hydration mismatch because server doesn't know localStorage state
203209 if ( ! isClient ) {
204210 return (
205- < div className = { cn ( variantRootStyles [ variant ] ) } >
211+ < div className = { cn ( variantRootStyles [ variant ] , rootClassName ) } >
206212 < aside
207213 data-sidebar-id = { id }
208214 data-variant = { variant }
209215 data-side = { side }
210216 style = { {
211- width : `var(--sidebar-${ id } -width, ${ defaultOpen ? width : collapsedWidth } )` ,
212- minWidth : `var(--sidebar-${ id } -width, ${ defaultOpen ? width : collapsedWidth } )` ,
213- maxWidth : `var(--sidebar-${ id } -width, ${ defaultOpen ? width : collapsedWidth } )`
217+ width : `var(--sidebar-${ id } -width, ${ isCollapsed ? collapsedWidth : ( defaultOpen ? width : collapsedWidth ) } )` ,
218+ minWidth : `var(--sidebar-${ id } -width, ${ isCollapsed ? collapsedWidth : ( defaultOpen ? width : collapsedWidth ) } )` ,
219+ maxWidth : `var(--sidebar-${ id } -width, ${ isCollapsed ? collapsedWidth : ( defaultOpen ? width : collapsedWidth ) } )`
214220 } }
215221 className = { cn ( baseStyles , variantStyles [ variant ] , className , "" ) }
216222 { ...props }
217223 >
218224 { /* Skeleton - no content on server */ }
219- < div className = "flex h-full flex-col overflow-hidden" style = { { width : `var(--sidebar-${ id } -width, ${ defaultOpen ? width : collapsedWidth } )` } } >
225+ < div className = "flex h-full flex-col overflow-hidden" style = { { width : `var(--sidebar-${ id } -width, ${ isCollapsed ? collapsedWidth : ( defaultOpen ? width : collapsedWidth ) } )` } } >
220226
221227 </ div >
222228 </ aside >
@@ -226,9 +232,9 @@ export function Sidebar({
226232
227233 // Client: render full sidebar with content
228234 return (
229- < SidebarContext . Provider value = { id } >
235+ < SidebarContext . Provider value = { { id , isCollapsed } } >
230236 < TooltipProvider delayDuration = { 0 } >
231- < div className = { cn ( variantRootStyles [ variant ] ) } >
237+ < div className = { cn ( variantRootStyles [ variant ] , rootClassName ) } >
232238 < aside
233239 data-sidebar-id = { id }
234240 data-state = { isOpen ? "expanded" : "collapsed" }
@@ -546,10 +552,19 @@ export function SidebarSubmenuItem({
546552
547553// Sidebar Trigger
548554export function SidebarTrigger ( {
555+ sidebarId : propSidebarId ,
549556 className,
550557 ...props
551- } : React . ButtonHTMLAttributes < HTMLButtonElement > ) {
552- const sidebarId = useSidebarId ( ) ;
558+ } : React . ButtonHTMLAttributes < HTMLButtonElement > & { sidebarId ?: string } ) {
559+ const context = React . useContext ( SidebarContext ) ;
560+ const sidebarId = propSidebarId ?? context ?. id ;
561+
562+ if ( ! sidebarId ) {
563+ throw new Error (
564+ "SidebarTrigger must be used within a Sidebar component or passed a sidebarId prop" ,
565+ ) ;
566+ }
567+
553568 const [ isMobile , setIsMobile ] = React . useState ( false ) ;
554569
555570 React . useEffect ( ( ) => {
@@ -569,7 +584,7 @@ export function SidebarTrigger({
569584 onClick = { ( ) => sidebarActions . toggleSidebar ( sidebarId , isMobile ) }
570585 { ...props }
571586 >
572- < PanelLeftIcon className = "h-4 w-4" />
587+ < IconLayoutSidebar className = "h-4 w-4" />
573588 < span className = "sr-only" > Toggle Sidebar</ span >
574589 </ Button >
575590 ) ;
0 commit comments