# All files / src/components/ui sidebar-context.tsx

0% Statements 0/132
0% Branches 0/1
0% Functions 0/1
0% Lines 0/132

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133

     "use client"

    import { CSSProperties, ComponentProps, createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"
    import { useIsMobile } from "@/hooks/use-mobile"
    import { cn } from "@/lib/utils"
    import { TooltipProvider } from "@/components/ui/tooltip"

    const SIDEBAR_COOKIE_NAME = "sidebar_state"
    const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
    export const SIDEBAR_WIDTH = "16rem"
    export const SIDEBAR_WIDTH_MOBILE = "18rem"
    export const SIDEBAR_WIDTH_ICON = "3rem"
    const SIDEBAR_KEYBOARD_SHORTCUT = "b"

    export interface SidebarContextProps {
      state: "expanded" | "collapsed"
      open: boolean
      setOpen: (open: boolean) => void
      openMobile: boolean
      setOpenMobile: (open: boolean) => void
      isMobile: boolean
      toggleSidebar: () => void
    }

    const SidebarContext = createContext<SidebarContextProps | null>(null)

    export function useSidebar() {
      const context = 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
    }: ComponentProps<"div"> & {
      defaultOpen?: boolean
      open?: boolean
      onOpenChange?: (open: boolean) => void
    }) {
      const isMobile = useIsMobile()
      const [openMobile, setOpenMobile] = useState(false)

      // This is the internal state of the sidebar.
      // We use openProp and setOpenProp for control from outside the component.
      const [_open, _setOpen] = useState(defaultOpen)
      const open = openProp ?? _open
      const setOpen = useCallback(
        (value: boolean | ((value: boolean) => boolean)) => {
          const openState = typeof value === "function" ? value(open) : value
          if (setOpenProp) {
            setOpenProp(openState)
          } else {
            _setOpen(openState)
          }

          // This sets the cookie to keep the sidebar state.
          document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
        },
        [setOpenProp, open]
      )

      // Helper to toggle the sidebar.
      const toggleSidebar = useCallback(() => {
        return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
      }, [isMobile, setOpen, setOpenMobile])

      // Adds a keyboard shortcut to toggle the sidebar.
      useEffect(() => {
        const handleKeyDown = (event: KeyboardEvent) => {
          if (
            event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
            (event.metaKey || event.ctrlKey)
          ) {
            event.preventDefault()
            toggleSidebar()
          }
        }

        window.addEventListener("keydown", handleKeyDown)
        return () => window.removeEventListener("keydown", handleKeyDown)
      }, [toggleSidebar])

      // 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 = useMemo<SidebarContextProps>(
        () => ({
          state,
          open,
          setOpen,
          isMobile,
          openMobile,
          setOpenMobile,
          toggleSidebar,
        }),
        [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
      )

      return (
        <SidebarContext.Provider value={contextValue}>
          <TooltipProvider delayDuration={0}>
            <div
              data-slot="sidebar-wrapper"
              style={
                {
                  "--sidebar-width": SIDEBAR_WIDTH,
                  "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
                  ...style,
                } as CSSProperties
              }
              className={cn(
                "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
                className
              )}
              {...props}
            >
              {children}
            </div>
          </TooltipProvider>
        </SidebarContext.Provider>
      )
    }