'use client'; import { useContext, useId, useRef, useState } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { cva } from 'class-variance-authority'; import { ChevronDown } from 'lucide-react'; import { z } from 'zod'; import { cn, isRouteActive } from '../lib/utils'; import { Button } from '../shadcn/button'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '../shadcn/tooltip'; import { SidebarContext } from './context/sidebar.context'; import { If } from './if'; import type { NavigationConfigSchema } from './navigation-config.schema'; import { Trans } from './trans'; export type SidebarConfig = z.infer; export { SidebarContext }; /** * @deprecated * This component is deprecated and will be removed in a future version. * Please use the Shadcn Sidebar component instead. */ export function Sidebar(props: { collapsed?: boolean; expandOnHover?: boolean; className?: string; children: | React.ReactNode | ((props: { collapsed: boolean; setCollapsed: (collapsed: boolean) => void; }) => React.ReactNode); }) { const [collapsed, setCollapsed] = useState(props.collapsed ?? false); const isExpandedRef = useRef(false); const expandOnHover = props.expandOnHover ?? process.env.NEXT_PUBLIC_EXPAND_SIDEBAR_ON_HOVER === 'true'; const sidebarSizeClassName = getSidebarSizeClassName( collapsed, isExpandedRef.current, ); const className = getClassNameBuilder( cn(props.className ?? '', sidebarSizeClassName, {}), )(); const containerClassName = cn(sidebarSizeClassName, 'bg-inherit', { 'max-w-[4rem]': expandOnHover && isExpandedRef.current, }); const ctx = { collapsed, setCollapsed }; const onMouseEnter = props.collapsed && expandOnHover ? () => { setCollapsed(false); isExpandedRef.current = true; } : undefined; const onMouseLeave = props.collapsed && expandOnHover ? () => { if (!isRadixPopupOpen()) { setCollapsed(true); isExpandedRef.current = false; } else { onRadixPopupClose(() => { setCollapsed(true); isExpandedRef.current = false; }); } } : undefined; return (
{typeof props.children === 'function' ? props.children(ctx) : props.children}
); } export function SidebarContent({ children, className: customClassName, }: React.PropsWithChildren<{ className?: string; }>) { const { collapsed } = useContext(SidebarContext); const className = cn( 'flex w-full flex-col space-y-1.5 py-1', customClassName, { 'px-4': !collapsed, 'px-2': collapsed, }, ); return
{children}
; } export function SidebarGroup({ label, collapsed = false, collapsible = true, children, }: React.PropsWithChildren<{ label: string | React.ReactNode; collapsible?: boolean; collapsed?: boolean; }>) { const { collapsed: sidebarCollapsed } = useContext(SidebarContext); const [isGroupCollapsed, setIsGroupCollapsed] = useState(collapsed); const id = useId(); const Title = (props: React.PropsWithChildren) => { if (sidebarCollapsed) { return null; } return ( {props.children} ); }; const Wrapper = () => { const className = cn( 'px-container group flex items-center justify-between space-x-2.5', { 'py-2.5': !sidebarCollapsed, }, ); if (collapsible) { return ( ); } return (
{label}
); }; return (
{children}
); } export function SidebarDivider() { return (
); } export function SidebarItem({ end, path, children, Icon, }: React.PropsWithChildren<{ path: string; Icon: React.ReactNode; end?: boolean | ((path: string) => boolean); }>) { const { collapsed } = useContext(SidebarContext); const currentPath = usePathname() ?? ''; const active = isRouteActive(path, currentPath, end ?? false); const variant = active ? 'secondary' : 'ghost'; return ( {children} ); } function getClassNameBuilder(className: string) { return cva([ cn( 'group/sidebar transition-width fixed box-content flex h-screen w-2/12 flex-col bg-inherit backdrop-blur-xs duration-200', className, ), ]); } function getSidebarSizeClassName(collapsed: boolean, isExpanded: boolean) { return cn(['z-50 flex w-full flex-col'], { 'dark:shadow-primary/20 lg:w-[17rem]': !collapsed, 'lg:w-[4rem]': collapsed, shadow: isExpanded, }); } function getRadixPopup() { return document.querySelector('[data-radix-popper-content-wrapper]'); } function isRadixPopupOpen() { return getRadixPopup() !== null; } function onRadixPopupClose(callback: () => void) { const element = getRadixPopup(); if (element) { const observer = new MutationObserver(() => { if (!getRadixPopup()) { callback(); observer.disconnect(); } }); observer.observe(element.parentElement!, { childList: true, subtree: true, }); } } export function SidebarNavigation({ config, }: React.PropsWithChildren<{ config: SidebarConfig; }>) { return ( <> {config.routes.map((item, index) => { if ('divider' in item) { return ; } if ('children' in item) { return ( } collapsible={item.collapsible} collapsed={item.collapsed} > {item.children.map((child) => { if ('collapsible' in child && child.collapsible) { throw new Error( 'Collapsible groups are not supported in the old Sidebar. Please migrate to the new Sidebar.', ); } if ('path' in child) { return ( ); } })} ); } })} ); }