170 lines
5.3 KiB
TypeScript
170 lines
5.3 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { Slot } from "@radix-ui/react-slot"
|
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
|
|
import { cn } from "@/lib/utils"
|
|
|
|
const sidebarVariants = cva("flex h-full w-64 flex-col border-r bg-background", {
|
|
variants: {
|
|
variant: {
|
|
default: "border-border",
|
|
ghost: "border-transparent",
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
variant: "default",
|
|
},
|
|
})
|
|
|
|
interface SidebarContextProps {
|
|
open: boolean
|
|
setOpen: (open: boolean) => void
|
|
}
|
|
|
|
const SidebarContext = React.createContext<SidebarContextProps | undefined>(undefined)
|
|
|
|
function useSidebar() {
|
|
const context = React.useContext(SidebarContext)
|
|
if (!context) {
|
|
throw new Error("useSidebar must be used within a SidebarProvider")
|
|
}
|
|
return context
|
|
}
|
|
|
|
interface SidebarProviderProps {
|
|
children: React.ReactNode
|
|
defaultOpen?: boolean
|
|
open?: boolean
|
|
onOpenChange?: (open: boolean) => void
|
|
}
|
|
|
|
const SidebarProvider = React.forwardRef<HTMLDivElement, SidebarProviderProps>(
|
|
({ children, defaultOpen = true, open: controlledOpen, onOpenChange, ...props }, ref) => {
|
|
const [internalOpen, setInternalOpen] = React.useState(defaultOpen)
|
|
|
|
const open = controlledOpen !== undefined ? controlledOpen : internalOpen
|
|
const setOpen = React.useCallback(
|
|
(newOpen: boolean) => {
|
|
if (controlledOpen === undefined) {
|
|
setInternalOpen(newOpen)
|
|
}
|
|
onOpenChange?.(newOpen)
|
|
},
|
|
[controlledOpen, onOpenChange],
|
|
)
|
|
|
|
return (
|
|
<SidebarContext.Provider value={{ open, setOpen }}>
|
|
<div ref={ref} {...props}>
|
|
{children}
|
|
</div>
|
|
</SidebarContext.Provider>
|
|
)
|
|
},
|
|
)
|
|
SidebarProvider.displayName = "SidebarProvider"
|
|
|
|
interface SidebarProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof sidebarVariants> {
|
|
asChild?: boolean
|
|
}
|
|
|
|
const Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>(
|
|
({ className, variant, asChild = false, ...props }, ref) => {
|
|
const Comp = asChild ? Slot : "div"
|
|
return <Comp className={cn(sidebarVariants({ variant, className }))} ref={ref} {...props} />
|
|
},
|
|
)
|
|
Sidebar.displayName = "Sidebar"
|
|
|
|
const SidebarHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
({ className, ...props }, ref) => (
|
|
<div ref={ref} className={cn("flex items-center px-4 py-2", className)} {...props} />
|
|
),
|
|
)
|
|
SidebarHeader.displayName = "SidebarHeader"
|
|
|
|
const SidebarContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
({ className, ...props }, ref) => <div ref={ref} className={cn("flex-1 overflow-auto py-2", className)} {...props} />,
|
|
)
|
|
SidebarContent.displayName = "SidebarContent"
|
|
|
|
const SidebarFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
({ className, ...props }, ref) => <div ref={ref} className={cn("mt-auto px-4 py-2", className)} {...props} />,
|
|
)
|
|
SidebarFooter.displayName = "SidebarFooter"
|
|
|
|
const SidebarMenu = React.forwardRef<HTMLUListElement, React.HTMLAttributes<HTMLUListElement>>(
|
|
({ className, ...props }, ref) => <ul ref={ref} className={cn("space-y-1 px-2", className)} {...props} />,
|
|
)
|
|
SidebarMenu.displayName = "SidebarMenu"
|
|
|
|
const SidebarMenuItem = React.forwardRef<HTMLLIElement, React.HTMLAttributes<HTMLLIElement>>(
|
|
({ className, ...props }, ref) => <li ref={ref} className={cn("", className)} {...props} />,
|
|
)
|
|
SidebarMenuItem.displayName = "SidebarMenuItem"
|
|
|
|
interface SidebarMenuButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
asChild?: boolean
|
|
isActive?: boolean
|
|
}
|
|
|
|
const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(
|
|
({ className, asChild = false, isActive = false, ...props }, ref) => {
|
|
const Comp = asChild ? Slot : "button"
|
|
return (
|
|
<Comp
|
|
className={cn(
|
|
"flex w-full items-center rounded-md px-2 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50",
|
|
isActive && "bg-accent text-accent-foreground",
|
|
className,
|
|
)}
|
|
ref={ref}
|
|
{...props}
|
|
/>
|
|
)
|
|
},
|
|
)
|
|
SidebarMenuButton.displayName = "SidebarMenuButton"
|
|
|
|
const SidebarInset = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
({ className, ...props }, ref) => <div ref={ref} className={cn("flex-1", className)} {...props} />,
|
|
)
|
|
SidebarInset.displayName = "SidebarInset"
|
|
|
|
const SidebarTrigger = React.forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
|
|
({ className, children, ...props }, ref) => {
|
|
const { setOpen, open } = useSidebar()
|
|
|
|
return (
|
|
<button
|
|
ref={ref}
|
|
className={cn(
|
|
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 w-9",
|
|
className,
|
|
)}
|
|
onClick={() => setOpen(!open)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</button>
|
|
)
|
|
},
|
|
)
|
|
SidebarTrigger.displayName = "SidebarTrigger"
|
|
|
|
export {
|
|
Sidebar,
|
|
SidebarProvider,
|
|
SidebarHeader,
|
|
SidebarContent,
|
|
SidebarFooter,
|
|
SidebarMenu,
|
|
SidebarMenuItem,
|
|
SidebarMenuButton,
|
|
SidebarInset,
|
|
SidebarTrigger,
|
|
useSidebar,
|
|
}
|