100 lines
2.5 KiB
TypeScript
100 lines
2.5 KiB
TypeScript
![]() |
"use client"
|
||
|
|
||
|
import * as React from "react"
|
||
|
import { useIsMobile } from "@/hooks/use-mobile"
|
||
|
|
||
|
type SidebarContext = {
|
||
|
state: "expanded" | "collapsed"
|
||
|
open: boolean
|
||
|
setOpen: (open: boolean) => void
|
||
|
openMobile: boolean
|
||
|
setOpenMobile: (open: boolean) => void
|
||
|
isMobile: boolean
|
||
|
toggleSidebar: () => void
|
||
|
}
|
||
|
|
||
|
const SidebarContext = React.createContext<SidebarContext | null>(null)
|
||
|
|
||
|
export function useSidebar() {
|
||
|
const context = React.useContext(SidebarContext)
|
||
|
if (!context) {
|
||
|
throw new Error("useSidebar must be used within a SidebarProvider.")
|
||
|
}
|
||
|
|
||
|
return context
|
||
|
}
|
||
|
|
||
|
export function SidebarProvider({
|
||
|
defaultOpen = true,
|
||
|
open: openProp,
|
||
|
onOpenChange: setOpenProp,
|
||
|
className,
|
||
|
style,
|
||
|
children,
|
||
|
...props
|
||
|
}: React.ComponentProps<"div"> & {
|
||
|
defaultOpen?: boolean
|
||
|
open?: boolean
|
||
|
onOpenChange?: (open: boolean) => void
|
||
|
}) {
|
||
|
const isMobile = useIsMobile()
|
||
|
const [openMobile, setOpenMobile] = React.useState(false)
|
||
|
|
||
|
// This is the internal state of the sidebar.
|
||
|
// We use openProp and setOpenProp for control from outside the component.
|
||
|
const [_open, _setOpen] = React.useState(defaultOpen)
|
||
|
const open = openProp ?? _open
|
||
|
const setOpen = React.useCallback(
|
||
|
(value: boolean | ((value: boolean) => boolean)) => {
|
||
|
const openState = typeof value === "function" ? value(open) : value
|
||
|
if (setOpenProp) {
|
||
|
setOpenProp(openState)
|
||
|
} else {
|
||
|
_setOpen(openState)
|
||
|
}
|
||
|
},
|
||
|
[setOpenProp, open],
|
||
|
)
|
||
|
|
||
|
// Helper to toggle the sidebar.
|
||
|
const toggleSidebar = React.useCallback(() => {
|
||
|
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
|
||
|
}, [isMobile, setOpen, setOpenMobile])
|
||
|
|
||
|
// We add a state so that we can do data-state="expanded" or "collapsed".
|
||
|
// This makes it easier to style the sidebar with Tailwind classes.
|
||
|
const state = open ? "expanded" : "collapsed"
|
||
|
|
||
|
const contextValue = React.useMemo<SidebarContext>(
|
||
|
() => ({
|
||
|
state,
|
||
|
open,
|
||
|
setOpen,
|
||
|
isMobile,
|
||
|
openMobile,
|
||
|
setOpenMobile,
|
||
|
toggleSidebar,
|
||
|
}),
|
||
|
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
|
||
|
)
|
||
|
|
||
|
return (
|
||
|
<SidebarContext.Provider value={contextValue}>
|
||
|
<div
|
||
|
style={
|
||
|
{
|
||
|
"--sidebar-width": "16rem",
|
||
|
"--sidebar-width-icon": "3rem",
|
||
|
...style,
|
||
|
} as React.CSSProperties
|
||
|
}
|
||
|
className="group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar"
|
||
|
{...props}
|
||
|
>
|
||
|
{children}
|
||
|
</div>
|
||
|
</SidebarContext.Provider>
|
||
|
)
|
||
|
}
|
||
|
|