diff --git a/web/src/components/app-sidebar.tsx b/web/src/components/app-sidebar.tsx --- a/web/src/components/app-sidebar.tsx +++ b/web/src/components/app-sidebar.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { IconDashboard, @@ -7,83 +7,137 @@ IconDatabase, IconTable, IconClipboardList, IconUsers, - IconSettings, IconLogout, IconKey, IconVariable, IconTag, - IconChevronRight, IconLink, IconPuzzle, - IconLockAccess, + IconSettings, IconInfoCircle, IconApps, -} from "@tabler/icons-react" -import Image from "next/image" -import Link from "next/link" -import { usePathname } from "next/navigation" +} from "@tabler/icons-react"; +import Image from "next/image"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; -import { useAuth } from "@/lib/auth-context" -import { useConfig } from "@/lib/config-context" -import { useCurrentUser } from "@/hooks/use-current-user" -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "@/components/ui/collapsible" +import { useAuth } from "@/lib/auth-context"; +import { useConfig } from "@/lib/config-context"; +import { useCurrentUser } from "@/hooks/use-current-user"; +import { Scroller } from "@/components/ui/scroller"; import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, + SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, - SidebarMenuSub, - SidebarMenuSubButton, - SidebarMenuSubItem, -} from "@/components/ui/sidebar" + SidebarSeparator, +} from "@/components/ui/sidebar"; -const navItems = [ - { title: "Dashboard", url: "/dashboard", icon: IconDashboard }, +type NavItem = { + title: string; + url: string; + icon: React.ComponentType; + requiredPermissions?: string[]; +}; + +const dataItems: NavItem[] = [ { title: "Lexicons", url: "/dashboard/lexicons", icon: IconFileDescription }, + { title: "Records", url: "/dashboard/records", icon: IconTable }, { title: "Backfill", url: "/dashboard/backfill", icon: IconDatabase }, - { title: "Records", url: "/dashboard/records", icon: IconTable }, - { title: "Event Logs", url: "/dashboard/events", icon: IconClipboardList, requiredPermissions: ["events:read"] }, -] as const +]; + +const accessItems: NavItem[] = [ + { + title: "Users", + url: "/dashboard/settings/users", + icon: IconUsers, + requiredPermissions: ["users:read"], + }, + { + title: "API Keys", + url: "/dashboard/settings/api-keys", + icon: IconKey, + requiredPermissions: ["api-keys:read"], + }, + { + title: "API Clients", + url: "/dashboard/settings/api-clients", + icon: IconApps, + requiredPermissions: ["api-clients:view"], + }, +]; + +const integrationItems: NavItem[] = [ + { + title: "Plugins", + url: "/dashboard/settings/plugins", + icon: IconPuzzle, + requiredPermissions: ["plugins:read"], + }, + { + title: "Linked Accounts", + url: "/dashboard/settings/accounts", + icon: IconLink, + }, + { + title: "Labelers", + url: "/dashboard/settings/labelers", + icon: IconTag, + requiredPermissions: ["labelers:read"], + }, +]; -const settingsSubItems = [ - { title: "Users", url: "/dashboard/settings/users", icon: IconUsers, requiredPermissions: ["users:read"] }, - { title: "Linked Accounts", url: "/dashboard/settings/accounts", icon: IconLink, requiredPermissions: [] as string[] }, - { title: "Plugins", url: "/dashboard/settings/plugins", icon: IconPuzzle, requiredPermissions: ["plugins:read"] }, - { title: "ENV Variables", url: "/dashboard/settings/env-variables", icon: IconVariable, requiredPermissions: ["script-variables:read"] }, - { title: "API Keys", url: "/dashboard/settings/api-keys", icon: IconKey, requiredPermissions: ["api-keys:read"] }, - { title: "API Clients", url: "/dashboard/settings/api-clients", icon: IconApps, requiredPermissions: ["api-clients:view"] }, - { title: "Labelers", url: "/dashboard/settings/labelers", icon: IconTag, requiredPermissions: ["labelers:read"] }, - { title: "General", url: "/dashboard/settings/general", icon: IconLockAccess, requiredPermissions: ["settings:manage"] }, -] as const +const systemItems: NavItem[] = [ + { + title: "General", + url: "/dashboard/settings/general", + icon: IconSettings, + requiredPermissions: ["settings:manage"], + }, + { + title: "ENV Variables", + url: "/dashboard/settings/env-variables", + icon: IconVariable, + requiredPermissions: ["script-variables:read"], + }, + { + title: "Event Logs", + url: "/dashboard/events", + icon: IconClipboardList, + requiredPermissions: ["events:read"], + }, +]; -export function AppSidebar({ - ...props -}: React.ComponentProps) { - const pathname = usePathname() - const { logout } = useAuth() - const { app_name, logo_url } = useConfig() - const { hasPermission } = useCurrentUser() +export function AppSidebar({ ...props }: React.ComponentProps) { + const pathname = usePathname(); + const { logout } = useAuth(); + const { app_name, logo_url } = useConfig(); + const { hasPermission } = useCurrentUser(); - const visibleNavItems = navItems.filter((item) => { - if (!("requiredPermissions" in item)) return true - return item.requiredPermissions.some((perm) => hasPermission(perm)) - }) + function filterByPermission(items: NavItem[]) { + return items.filter( + (item) => + !item.requiredPermissions || + item.requiredPermissions.some((perm) => hasPermission(perm)), + ); + } - const visibleSettingsItems = settingsSubItems.filter((item) => - item.requiredPermissions.length === 0 || - item.requiredPermissions.some((perm) => hasPermission(perm)) - ) + function isActive(url: string) { + return url === "/dashboard" + ? pathname === "/dashboard" + : pathname.startsWith(url); + } - const isSettingsActive = pathname.startsWith("/dashboard/settings") + const visibleData = filterByPermission(dataItems); + const visibleAccess = filterByPermission(accessItems); + const visibleIntegrations = filterByPermission(integrationItems); + const visibleSystem = filterByPermission(systemItems); return ( @@ -116,67 +170,134 @@ /> )} - - - - - {visibleNavItems.map((item) => ( - + + + + + + + - - - {item.title} + + + Dashboard - ))} + + + - {visibleSettingsItems.length > 0 && ( - - - - - - Settings - + {visibleData.length > 0 && ( + + Data + + + {visibleData.map((item) => ( + + + + + {item.title} + - - - - {visibleSettingsItems.map((item) => ( - - - - - {item.title} - - - - ))} - - - - - )} - - - - + + ))} + + + + )} + + {visibleAccess.length > 0 && ( + + Access + + + {visibleAccess.map((item) => ( + + + + + {item.title} + + + + ))} + + + + )} + + {visibleIntegrations.length > 0 && ( + + Integrations + + + {visibleIntegrations.map((item) => ( + + + + + {item.title} + + + + ))} + + + + )} + + {visibleSystem.length > 0 && ( + + System + + + {visibleSystem.map((item) => ( + + + + + {item.title} + + + + ))} + + + + )} + + + - + About @@ -192,5 +313,5 @@ - ) + ); } diff --git a/web/src/components/ui/scroller.tsx b/web/src/components/ui/scroller.tsx new file mode 100644 --- /dev/null +++ b/web/src/components/ui/scroller.tsx @@ -0,0 +1,387 @@ +"use client"; + +import { cva, type VariantProps } from "class-variance-authority"; +import { + ChevronDown, + ChevronLeft, + ChevronRight, + ChevronUp, +} from "lucide-react"; +import { Slot as SlotPrimitive } from "radix-ui"; +import * as React from "react"; +import { useComposedRefs } from "@/lib/compose-refs"; +import { cn } from "@/lib/utils"; + +const DATA_TOP_SCROLL = "data-top-scroll"; +const DATA_BOTTOM_SCROLL = "data-bottom-scroll"; +const DATA_LEFT_SCROLL = "data-left-scroll"; +const DATA_RIGHT_SCROLL = "data-right-scroll"; +const DATA_TOP_BOTTOM_SCROLL = "data-top-bottom-scroll"; +const DATA_LEFT_RIGHT_SCROLL = "data-left-right-scroll"; + +const scrollerVariants = cva("", { + variants: { + orientation: { + vertical: [ + "overflow-y-auto", + "data-[top-scroll=true]:[mask-image:linear-gradient(0deg,#000_calc(100%_-_var(--scroll-shadow-size)),transparent)]", + "data-[bottom-scroll=true]:[mask-image:linear-gradient(180deg,#000_calc(100%_-_var(--scroll-shadow-size)),transparent)]", + "data-[top-bottom-scroll=true]:[mask-image:linear-gradient(#000,#000,transparent_0,#000_var(--scroll-shadow-size),#000_calc(100%_-_var(--scroll-shadow-size)),transparent)]", + ], + horizontal: [ + "overflow-x-auto", + "data-[left-scroll=true]:[mask-image:linear-gradient(270deg,#000_calc(100%_-_var(--scroll-shadow-size)),transparent)]", + "data-[right-scroll=true]:[mask-image:linear-gradient(90deg,#000_calc(100%_-_var(--scroll-shadow-size)),transparent)]", + "data-[left-right-scroll=true]:[mask-image:linear-gradient(to_right,#000,#000,transparent_0,#000_var(--scroll-shadow-size),#000_calc(100%_-_var(--scroll-shadow-size)),transparent)]", + ], + }, + hideScrollbar: { + true: "[-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", + false: "", + }, + }, + defaultVariants: { + orientation: "vertical", + hideScrollbar: false, + }, +}); + +type ScrollDirection = "up" | "down" | "left" | "right"; + +type ScrollVisibility = { + [key in ScrollDirection]: boolean; +}; + +interface ScrollerProps + extends VariantProps, + React.ComponentProps<"div"> { + size?: number; + offset?: number; + asChild?: boolean; + withNavigation?: boolean; + scrollStep?: number; + scrollTriggerMode?: "press" | "hover" | "click"; +} + +function Scroller(props: ScrollerProps) { + const { + orientation = "vertical", + hideScrollbar, + className, + size = 40, + offset = 0, + scrollStep = 40, + style, + asChild, + withNavigation = false, + scrollTriggerMode = "press", + ref, + ...scrollerProps + } = props; + + const containerRef = React.useRef(null); + const composedRef = useComposedRefs(ref, containerRef); + const [scrollVisibility, setScrollVisibility] = + React.useState({ + up: false, + down: false, + left: false, + right: false, + }); + + const onScrollBy = React.useCallback( + (direction: ScrollDirection) => { + const container = containerRef.current; + if (!container) return; + + const scrollMap: Record void> = { + up: () => (container.scrollTop -= scrollStep), + down: () => (container.scrollTop += scrollStep), + left: () => (container.scrollLeft -= scrollStep), + right: () => (container.scrollLeft += scrollStep), + }; + + scrollMap[direction](); + }, + [scrollStep], + ); + + const scrollHandlers = React.useMemo( + () => ({ + up: () => onScrollBy("up"), + down: () => onScrollBy("down"), + left: () => onScrollBy("left"), + right: () => onScrollBy("right"), + }), + [onScrollBy], + ); + + React.useLayoutEffect(() => { + const container = containerRef.current; + if (!container) return; + + function onScroll() { + if (!container) return; + + const isVertical = orientation === "vertical"; + + if (isVertical) { + const scrollTop = container.scrollTop; + const clientHeight = container.clientHeight; + const scrollHeight = container.scrollHeight; + + if (withNavigation) { + setScrollVisibility((prev) => { + const newUp = scrollTop > offset; + const newDown = scrollTop + clientHeight < scrollHeight; + + if (prev.up !== newUp || prev.down !== newDown) { + return { + ...prev, + up: newUp, + down: newDown, + }; + } + return prev; + }); + } + + const hasTopScroll = scrollTop > offset; + const hasBottomScroll = + scrollTop + clientHeight + offset < scrollHeight; + const isVerticallyScrollable = scrollHeight > clientHeight; + + if (hasTopScroll && hasBottomScroll && isVerticallyScrollable) { + container.setAttribute(DATA_TOP_BOTTOM_SCROLL, "true"); + container.removeAttribute(DATA_TOP_SCROLL); + container.removeAttribute(DATA_BOTTOM_SCROLL); + } else { + container.removeAttribute(DATA_TOP_BOTTOM_SCROLL); + if (hasTopScroll) container.setAttribute(DATA_TOP_SCROLL, "true"); + else container.removeAttribute(DATA_TOP_SCROLL); + if (hasBottomScroll && isVerticallyScrollable) + container.setAttribute(DATA_BOTTOM_SCROLL, "true"); + else container.removeAttribute(DATA_BOTTOM_SCROLL); + } + } + + const scrollLeft = container.scrollLeft; + const clientWidth = container.clientWidth; + const scrollWidth = container.scrollWidth; + + if (withNavigation) { + setScrollVisibility((prev) => { + const newLeft = scrollLeft > offset; + const newRight = scrollLeft + clientWidth < scrollWidth; + + if (prev.left !== newLeft || prev.right !== newRight) { + return { + ...prev, + left: newLeft, + right: newRight, + }; + } + return prev; + }); + } + + const hasLeftScroll = scrollLeft > offset; + const hasRightScroll = scrollLeft + clientWidth + offset < scrollWidth; + const isHorizontallyScrollable = scrollWidth > clientWidth; + + if (hasLeftScroll && hasRightScroll && isHorizontallyScrollable) { + container.setAttribute(DATA_LEFT_RIGHT_SCROLL, "true"); + container.removeAttribute(DATA_LEFT_SCROLL); + container.removeAttribute(DATA_RIGHT_SCROLL); + } else { + container.removeAttribute(DATA_LEFT_RIGHT_SCROLL); + if (hasLeftScroll) container.setAttribute(DATA_LEFT_SCROLL, "true"); + else container.removeAttribute(DATA_LEFT_SCROLL); + if (hasRightScroll && isHorizontallyScrollable) + container.setAttribute(DATA_RIGHT_SCROLL, "true"); + else container.removeAttribute(DATA_RIGHT_SCROLL); + } + } + + onScroll(); + container.addEventListener("scroll", onScroll); + window.addEventListener("resize", onScroll); + + return () => { + container.removeEventListener("scroll", onScroll); + window.removeEventListener("resize", onScroll); + }; + }, [orientation, offset, withNavigation]); + + const composedStyle = React.useMemo( + () => ({ + "--scroll-shadow-size": `${size}px`, + ...style, + }), + [size, style], + ); + + const activeDirections = React.useMemo(() => { + if (!withNavigation) return []; + return orientation === "vertical" ? ["up", "down"] : ["left", "right"]; + }, [orientation, withNavigation]); + + const ScrollerPrimitive = asChild ? SlotPrimitive.Slot : "div"; + + const ScrollerImpl = ( + + ); + + const navigationButtons = React.useMemo(() => { + if (!withNavigation) return null; + + return activeDirections + .filter((direction) => scrollVisibility[direction]) + .map((direction) => ( + + )); + }, [ + activeDirections, + scrollVisibility, + scrollHandlers, + scrollTriggerMode, + withNavigation, + ]); + + if (withNavigation) { + return ( +
+ {navigationButtons} + {ScrollerImpl} +
+ ); + } + + return ScrollerImpl; +} + +const scrollButtonVariants = cva( + "absolute z-10 transition-opacity [&>svg]:size-4 [&>svg]:opacity-80 hover:[&>svg]:opacity-100", + { + variants: { + direction: { + up: "top-2 left-1/2 -translate-x-1/2", + down: "bottom-2 left-1/2 -translate-x-1/2", + left: "top-1/2 left-2 -translate-y-1/2", + right: "top-1/2 right-2 -translate-y-1/2", + }, + }, + defaultVariants: { + direction: "up", + }, + }, +); + +const directionToIcon: Record = { + up: ChevronUp, + down: ChevronDown, + left: ChevronLeft, + right: ChevronRight, +} as const; + +interface ScrollButtonProps extends React.ComponentProps<"button"> { + direction: ScrollDirection; + triggerMode?: "press" | "hover" | "click"; +} + +function ScrollButton(props: ScrollButtonProps) { + const { + direction, + className, + triggerMode = "press", + onClick, + ref, + ...buttonProps + } = props; + + const [autoScrollTimer, setAutoScrollTimer] = React.useState( + null, + ); + + const onAutoScrollStart = React.useCallback( + (event?: React.MouseEvent) => { + if (autoScrollTimer !== null) return; + + if (triggerMode === "press") { + const timer = window.setInterval(onClick ?? (() => {}), 50); + setAutoScrollTimer(timer); + } else if (triggerMode === "hover") { + const timer = window.setInterval(() => { + if (event) onClick?.(event); + }, 50); + setAutoScrollTimer(timer); + } + }, + [autoScrollTimer, onClick, triggerMode], + ); + + const onAutoScrollStop = React.useCallback(() => { + if (autoScrollTimer === null) return; + + window.clearInterval(autoScrollTimer); + setAutoScrollTimer(null); + }, [autoScrollTimer]); + + const eventHandlers = React.useMemo(() => { + const triggerModeHandlers: Record< + NonNullable, + React.ComponentProps<"button"> + > = { + press: { + onPointerDown: onAutoScrollStart, + onPointerUp: onAutoScrollStop, + onPointerLeave: onAutoScrollStop, + onClick: () => {}, + }, + hover: { + onPointerEnter: onAutoScrollStart, + onPointerLeave: onAutoScrollStop, + onClick: () => {}, + }, + click: { + onClick, + }, + } as const; + + return triggerModeHandlers[triggerMode] ?? {}; + }, [triggerMode, onAutoScrollStart, onAutoScrollStop, onClick]); + + React.useEffect(() => { + return () => onAutoScrollStop(); + }, [onAutoScrollStop]); + + const Icon = directionToIcon[direction]; + + return ( + + ); +} + +export { Scroller }; diff --git a/web/src/components/ui/sidebar.tsx b/web/src/components/ui/sidebar.tsx --- a/web/src/components/ui/sidebar.tsx +++ b/web/src/components/ui/sidebar.tsx @@ -1,56 +1,56 @@ -"use client" +"use client"; -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" -import { PanelLeftIcon } from "lucide-react" -import { Slot } from "radix-ui" +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { PanelLeftIcon } from "lucide-react"; +import { Slot } from "radix-ui"; -import { useIsMobile } from "@/hooks/use-mobile" -import { cn } from "@/lib/utils" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Separator } from "@/components/ui/separator" +import { useIsMobile } from "@/hooks/use-mobile"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, -} from "@/components/ui/sheet" -import { Skeleton } from "@/components/ui/skeleton" +} from "@/components/ui/sheet"; +import { Skeleton } from "@/components/ui/skeleton"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from "@/components/ui/tooltip" +} from "@/components/ui/tooltip"; -const SIDEBAR_COOKIE_NAME = "sidebar_state" -const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 -const SIDEBAR_WIDTH = "16rem" -const SIDEBAR_WIDTH_MOBILE = "18rem" -const SIDEBAR_WIDTH_ICON = "3rem" -const SIDEBAR_KEYBOARD_SHORTCUT = "b" +const SIDEBAR_COOKIE_NAME = "sidebar_state"; +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +const SIDEBAR_WIDTH = "16rem"; +const SIDEBAR_WIDTH_MOBILE = "18rem"; +const SIDEBAR_WIDTH_ICON = "3rem"; +const SIDEBAR_KEYBOARD_SHORTCUT = "b"; type SidebarContextProps = { - state: "expanded" | "collapsed" - open: boolean - setOpen: (open: boolean) => void - openMobile: boolean - setOpenMobile: (open: boolean) => void - isMobile: boolean - toggleSidebar: () => void -} + state: "expanded" | "collapsed"; + open: boolean; + setOpen: (open: boolean) => void; + openMobile: boolean; + setOpenMobile: (open: boolean) => void; + isMobile: boolean; + toggleSidebar: () => void; +}; -const SidebarContext = React.createContext(null) +const SidebarContext = React.createContext(null); function useSidebar() { - const context = React.useContext(SidebarContext) + const context = React.useContext(SidebarContext); if (!context) { - throw new Error("useSidebar must be used within a SidebarProvider.") + throw new Error("useSidebar must be used within a SidebarProvider."); } - return context + return context; } function SidebarProvider({ @@ -62,36 +62,36 @@ style, children, ...props }: React.ComponentProps<"div"> & { - defaultOpen?: boolean - open?: boolean - onOpenChange?: (open: boolean) => void + defaultOpen?: boolean; + open?: boolean; + onOpenChange?: (open: boolean) => void; }) { - const isMobile = useIsMobile() - const [openMobile, setOpenMobile] = React.useState(false) + 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 [_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 + const openState = typeof value === "function" ? value(open) : value; if (setOpenProp) { - setOpenProp(openState) + setOpenProp(openState); } else { - _setOpen(openState) + _setOpen(openState); } // This sets the cookie to keep the sidebar state. - document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; }, - [setOpenProp, open] - ) + [setOpenProp, open], + ); // Helper to toggle the sidebar. const toggleSidebar = React.useCallback(() => { - return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open) - }, [isMobile, setOpen, setOpenMobile]) + return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); + }, [isMobile, setOpen, setOpenMobile]); // Adds a keyboard shortcut to toggle the sidebar. React.useEffect(() => { @@ -100,18 +100,18 @@ if ( event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey) ) { - event.preventDefault() - toggleSidebar() + event.preventDefault(); + toggleSidebar(); } - } + }; - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) - }, [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 state = open ? "expanded" : "collapsed"; const contextValue = React.useMemo( () => ({ @@ -123,8 +123,8 @@ openMobile, setOpenMobile, toggleSidebar, }), - [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] - ) + [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar], + ); return ( @@ -140,7 +140,7 @@ } as React.CSSProperties } className={cn( "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full", - className + className, )} {...props} > @@ -148,7 +148,7 @@ {children} - ) + ); } function Sidebar({ @@ -159,11 +159,11 @@ className, children, ...props }: React.ComponentProps<"div"> & { - side?: "left" | "right" - variant?: "sidebar" | "floating" | "inset" - collapsible?: "offcanvas" | "icon" | "none" + side?: "left" | "right"; + variant?: "sidebar" | "floating" | "inset"; + collapsible?: "offcanvas" | "icon" | "none"; }) { - const { isMobile, state, openMobile, setOpenMobile } = useSidebar() + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); if (collapsible === "none") { return ( @@ -171,13 +171,13 @@
{children}
- ) + ); } if (isMobile) { @@ -202,7 +202,7 @@
{children}
- ) + ); } return ( @@ -223,7 +223,7 @@ "group-data-[collapsible=offcanvas]:w-0", "group-data-[side=right]:rotate-180", variant === "floating" || variant === "inset" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" - : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)" + : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)", )} />
@@ -250,7 +250,7 @@ {children}
- ) + ); } function SidebarTrigger({ @@ -258,7 +258,7 @@ className, onClick, ...props }: React.ComponentProps) { - const { toggleSidebar } = useSidebar() + const { toggleSidebar } = useSidebar(); return ( - ) + ); } function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { - const { toggleSidebar } = useSidebar() + const { toggleSidebar } = useSidebar(); return (