1
0
Fork 0

fix(ui): correct sidebar inset layout and Tailwind v4 custom-property widths

The shadcn component update pulled sidebar.tsx using Tailwind v3-style
bare-bracket custom property syntax (w-[--sidebar-width]). Under Tailwind
v4 this compiles to invalid CSS (width: --sidebar-width) which the browser
ignores, so width fell back to auto and the fixed sidebar blew out to its
content's intrinsic width (the 1372px logo header ~ half the page). The
empty spacer div was equally broken, reserving zero space and leaving the
inset m-2 margin as a stray left gap.

Three fixes:
- sidebar.tsx: wrap all 7 bare-bracket custom properties in var() so they
  compile to width: var(--sidebar-width) / var(--sidebar-width-icon) /
  max-width: var(--skeleton-width). The calc(var(...)) rules were already
  valid; only the bare forms were broken.
- layout.tsx: replace the raw <main className="min-h-full bg-background
  w-full"> with <SidebarInset>. AppSidebar uses variant="inset", whose
  peer styling (md:peer-data-[variant=inset]:m-2 / :rounded-xl / :shadow)
  only applies to a SidebarInset sibling, so the inset gap/rounding/shadow
  were silently dropped.
- SiteHeader.tsx: replace the raw <div> + border-gray-800 + p-4 with the
  standard shadcn <header className="flex h-16 shrink-0 items-center gap-2
  border-b px-4"> using semantic border-b, plus -ml-1 on SidebarTrigger
  and an ml-auto wrapper for NotificationsBell (which takes no className).

Verified via the running dev server: SSR HTML carries
data-variant="inset", a single <main> with the peer-data-[variant=inset]
classes, <header ... border-b>, and zero border-gray-800 occurrences.
tsc --noEmit clean for the changed files.
This commit is contained in:
Jason Fraley 2026-08-16 23:27:51 -04:00
parent fa70ed0956
commit 088b6cefb6
3 changed files with 321 additions and 348 deletions

View file

@ -1,13 +1,13 @@
import React from "react"; import React from "react";
import "./styles.css"; import "./styles.css";
import { SidebarProvider } from "@/components/ui/sidebar"; import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/frontend/blocks/AppSidebar"; import { AppSidebar } from "@/components/frontend/blocks/AppSidebar";
import { getPayload } from "payload"; import { getPayload } from "payload";
import config from "@payload-config"; import config from "@payload-config";
import { headers as nextHeaders } from "next/headers"; import { headers as nextHeaders } from "next/headers";
import { SiteHeader } from "@/components/frontend/SiteHeader"; import { SiteHeader } from "@/components/frontend/SiteHeader";
import { Metadata } from "next"; import { Metadata } from "next";
import { resolveLevel, type ResolvedLevel } from "@/utils/xp/resolveLevel"; import { type ResolvedLevel, resolveLevel } from "@/utils/xp/resolveLevel";
import { hasIntelligenceQualification } from "@/utils/access-control/hasIntelligenceQualification"; import { hasIntelligenceQualification } from "@/utils/access-control/hasIntelligenceQualification";
import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification"; import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
import { ShipmentToasts } from "@/components/frontend/logistics/ShipmentToasts"; import { ShipmentToasts } from "@/components/frontend/logistics/ShipmentToasts";
@ -98,10 +98,10 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
showCallsign={user.preferences?.display?.showCallsign ?? false} showCallsign={user.preferences?.display?.showCallsign ?? false}
className="border-r" className="border-r"
/> />
<main className="min-h-full bg-background w-full"> <SidebarInset>
<SiteHeader /> <SiteHeader />
{children} {children}
</main> </SidebarInset>
</SidebarProvider> </SidebarProvider>
) : ( ) : (
<LandingPage /> <LandingPage />

View file

@ -4,12 +4,12 @@ import { NotificationsBell } from "@/components/frontend/notifications/Notificat
export const SiteHeader = () => { export const SiteHeader = () => {
return ( return (
<div className="flex items-center justify-between p-4 border-b border-gray-800"> <header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
<div className="flex items-center gap-2"> <SidebarTrigger className="-ml-1" />
<SidebarTrigger /> <Separator orientation="vertical" className="mr-2 data-[orientation=vertical]:h-4" />
<Separator orientation="vertical" className="mr-2 data-[orientation=vertical]:h-4" /> <div className="ml-auto flex items-center">
<NotificationsBell />
</div> </div>
<NotificationsBell /> </header>
</div>
); );
}; };

View file

@ -1,64 +1,59 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Slot } from "@radix-ui/react-slot" import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { PanelLeft } from "lucide-react" import { PanelLeft } from "lucide-react";
import { useIsMobile } from "@/components/hooks/use-mobile" import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/components/lib/utils" import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator" import { Separator } from "@/components/ui/separator";
import { import {
Sheet, Sheet,
SheetContent, SheetContent,
SheetDescription, SheetDescription,
SheetHeader, SheetHeader,
SheetTitle, SheetTitle,
} from "@/components/ui/sheet" } from "@/components/ui/sheet";
import { Skeleton } from "@/components/ui/skeleton" import { Skeleton } from "@/components/ui/skeleton";
import { import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
const SIDEBAR_COOKIE_NAME = "sidebar_state" const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "16rem" const SIDEBAR_WIDTH = "16rem";
const SIDEBAR_WIDTH_MOBILE = "18rem" const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem" const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b" const SIDEBAR_KEYBOARD_SHORTCUT = "b";
type SidebarContextProps = { type SidebarContextProps = {
state: "expanded" | "collapsed" state: "expanded" | "collapsed";
open: boolean open: boolean;
setOpen: (open: boolean) => void setOpen: (open: boolean) => void;
openMobile: boolean openMobile: boolean;
setOpenMobile: (open: boolean) => void setOpenMobile: (open: boolean) => void;
isMobile: boolean isMobile: boolean;
toggleSidebar: () => void toggleSidebar: () => void;
} };
const SidebarContext = React.createContext<SidebarContextProps | null>(null) const SidebarContext = React.createContext<SidebarContextProps | null>(null);
function useSidebar() { function useSidebar() {
const context = React.useContext(SidebarContext) const context = React.useContext(SidebarContext);
if (!context) { 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;
} }
const SidebarProvider = React.forwardRef< const SidebarProvider = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.ComponentProps<"div"> & { React.ComponentProps<"div"> & {
defaultOpen?: boolean defaultOpen?: boolean;
open?: boolean open?: boolean;
onOpenChange?: (open: boolean) => void onOpenChange?: (open: boolean) => void;
} }
>( >(
( (
@ -71,56 +66,51 @@ const SidebarProvider = React.forwardRef<
children, children,
...props ...props
}, },
ref ref,
) => { ) => {
const isMobile = useIsMobile() const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false) const [openMobile, setOpenMobile] = React.useState(false);
// This is the internal state of the sidebar. // This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component. // We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen) const [_open, _setOpen] = React.useState(defaultOpen);
const open = openProp ?? _open const open = openProp ?? _open;
const setOpen = React.useCallback( const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => { (value: boolean | ((value: boolean) => boolean)) => {
const openState = typeof value === "function" ? value(open) : value const openState = typeof value === "function" ? value(open) : value;
if (setOpenProp) { if (setOpenProp) {
setOpenProp(openState) setOpenProp(openState);
} else { } else {
_setOpen(openState) _setOpen(openState);
} }
// This sets the cookie to keep the sidebar state. // 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. // Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => { const toggleSidebar = React.useCallback(() => {
return isMobile return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
? setOpenMobile((open) => !open) }, [isMobile, setOpen, setOpenMobile]);
: setOpen((open) => !open)
}, [isMobile, setOpen, setOpenMobile])
// Adds a keyboard shortcut to toggle the sidebar. // Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => { React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => { const handleKeyDown = (event: KeyboardEvent) => {
if ( if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
event.key === SIDEBAR_KEYBOARD_SHORTCUT && event.preventDefault();
(event.metaKey || event.ctrlKey) toggleSidebar();
) {
event.preventDefault()
toggleSidebar()
} }
} };
window.addEventListener("keydown", handleKeyDown) window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown) return () => window.removeEventListener("keydown", handleKeyDown);
}, [toggleSidebar]) }, [toggleSidebar]);
// We add a state so that we can do data-state="expanded" or "collapsed". // 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. // 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<SidebarContextProps>( const contextValue = React.useMemo<SidebarContextProps>(
() => ({ () => ({
@ -132,8 +122,8 @@ const SidebarProvider = React.forwardRef<
setOpenMobile, setOpenMobile,
toggleSidebar, toggleSidebar,
}), }),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
) );
return ( return (
<SidebarContext.Provider value={contextValue}> <SidebarContext.Provider value={contextValue}>
@ -148,7 +138,7 @@ const SidebarProvider = React.forwardRef<
} }
className={cn( className={cn(
"group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar", "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
className className,
)} )}
ref={ref} ref={ref}
{...props} {...props}
@ -157,17 +147,17 @@ const SidebarProvider = React.forwardRef<
</div> </div>
</TooltipProvider> </TooltipProvider>
</SidebarContext.Provider> </SidebarContext.Provider>
) );
} },
) );
SidebarProvider.displayName = "SidebarProvider" SidebarProvider.displayName = "SidebarProvider";
const Sidebar = React.forwardRef< const Sidebar = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.ComponentProps<"div"> & { React.ComponentProps<"div"> & {
side?: "left" | "right" side?: "left" | "right";
variant?: "sidebar" | "floating" | "inset" variant?: "sidebar" | "floating" | "inset";
collapsible?: "offcanvas" | "icon" | "none" collapsible?: "offcanvas" | "icon" | "none";
} }
>( >(
( (
@ -179,23 +169,23 @@ const Sidebar = React.forwardRef<
children, children,
...props ...props
}, },
ref ref,
) => { ) => {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar() const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
if (collapsible === "none") { if (collapsible === "none") {
return ( return (
<div <div
className={cn( className={cn(
"flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground", "flex h-full w-[var(--sidebar-width)] flex-col bg-sidebar text-sidebar-foreground",
className className,
)} )}
ref={ref} ref={ref}
{...props} {...props}
> >
{children} {children}
</div> </div>
) );
} }
if (isMobile) { if (isMobile) {
@ -204,7 +194,7 @@ const Sidebar = React.forwardRef<
<SheetContent <SheetContent
data-sidebar="sidebar" data-sidebar="sidebar"
data-mobile="true" data-mobile="true"
className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden" className="w-[var(--sidebar-width)] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
style={ style={
{ {
"--sidebar-width": SIDEBAR_WIDTH_MOBILE, "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
@ -219,7 +209,7 @@ const Sidebar = React.forwardRef<
<div className="flex h-full w-full flex-col">{children}</div> <div className="flex h-full w-full flex-col">{children}</div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
) );
} }
return ( return (
@ -234,25 +224,25 @@ const Sidebar = React.forwardRef<
{/* This is what handles the sidebar gap on desktop */} {/* This is what handles the sidebar gap on desktop */}
<div <div
className={cn( className={cn(
"relative w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear", "relative w-[var(--sidebar-width)] bg-transparent transition-[width] duration-200 ease-linear",
"group-data-[collapsible=offcanvas]:w-0", "group-data-[collapsible=offcanvas]:w-0",
"group-data-[side=right]:rotate-180", "group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset" variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon]" : "group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)]",
)} )}
/> />
<div <div
className={cn( className={cn(
"fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex", "fixed inset-y-0 z-10 hidden h-svh w-[var(--sidebar-width)] transition-[left,right,width] duration-200 ease-linear md:flex",
side === "left" side === "left"
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
// Adjust the padding for floating and inset variants. // Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset" variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l", : "group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)] group-data-[side=left]:border-r group-data-[side=right]:border-l",
className className,
)} )}
{...props} {...props}
> >
@ -264,16 +254,16 @@ const Sidebar = React.forwardRef<
</div> </div>
</div> </div>
</div> </div>
) );
} },
) );
Sidebar.displayName = "Sidebar" Sidebar.displayName = "Sidebar";
const SidebarTrigger = React.forwardRef< const SidebarTrigger = React.forwardRef<
React.ElementRef<typeof Button>, React.ElementRef<typeof Button>,
React.ComponentProps<typeof Button> React.ComponentProps<typeof Button>
>(({ className, onClick, ...props }, ref) => { >(({ className, onClick, ...props }, ref) => {
const { toggleSidebar } = useSidebar() const { toggleSidebar } = useSidebar();
return ( return (
<Button <Button
@ -283,64 +273,62 @@ const SidebarTrigger = React.forwardRef<
size="icon" size="icon"
className={cn("h-7 w-7", className)} className={cn("h-7 w-7", className)}
onClick={(event) => { onClick={(event) => {
onClick?.(event) onClick?.(event);
toggleSidebar() toggleSidebar();
}} }}
{...props} {...props}
> >
<PanelLeft /> <PanelLeft />
<span className="sr-only">Toggle Sidebar</span> <span className="sr-only">Toggle Sidebar</span>
</Button> </Button>
) );
}) });
SidebarTrigger.displayName = "SidebarTrigger" SidebarTrigger.displayName = "SidebarTrigger";
const SidebarRail = React.forwardRef< const SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<"button">>(
HTMLButtonElement, ({ className, ...props }, ref) => {
React.ComponentProps<"button"> const { toggleSidebar } = useSidebar();
>(({ className, ...props }, ref) => {
const { toggleSidebar } = useSidebar()
return ( return (
<button <button
ref={ref} ref={ref}
data-sidebar="rail" data-sidebar="rail"
aria-label="Toggle Sidebar" aria-label="Toggle Sidebar"
tabIndex={-1} tabIndex={-1}
onClick={toggleSidebar} onClick={toggleSidebar}
title="Toggle Sidebar" title="Toggle Sidebar"
className={cn( className={cn(
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex", "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize", "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar", "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className className,
)} )}
{...props} {...props}
/> />
) );
}) },
SidebarRail.displayName = "SidebarRail" );
SidebarRail.displayName = "SidebarRail";
const SidebarInset = React.forwardRef< const SidebarInset = React.forwardRef<HTMLDivElement, React.ComponentProps<"main">>(
HTMLDivElement, ({ className, ...props }, ref) => {
React.ComponentProps<"main"> return (
>(({ className, ...props }, ref) => { <main
return ( ref={ref}
<main className={cn(
ref={ref} "relative flex w-full flex-1 flex-col bg-background",
className={cn( "md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
"relative flex w-full flex-1 flex-col bg-background", className,
"md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow", )}
className {...props}
)} />
{...props} );
/> },
) );
}) SidebarInset.displayName = "SidebarInset";
SidebarInset.displayName = "SidebarInset"
const SidebarInput = React.forwardRef< const SidebarInput = React.forwardRef<
React.ElementRef<typeof Input>, React.ElementRef<typeof Input>,
@ -352,43 +340,41 @@ const SidebarInput = React.forwardRef<
data-sidebar="input" data-sidebar="input"
className={cn( className={cn(
"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring", "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarInput.displayName = "SidebarInput" SidebarInput.displayName = "SidebarInput";
const SidebarHeader = React.forwardRef< const SidebarHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
HTMLDivElement, ({ className, ...props }, ref) => {
React.ComponentProps<"div"> return (
>(({ className, ...props }, ref) => { <div
return ( ref={ref}
<div data-sidebar="header"
ref={ref} className={cn("flex flex-col gap-2 p-2", className)}
data-sidebar="header" {...props}
className={cn("flex flex-col gap-2 p-2", className)} />
{...props} );
/> },
) );
}) SidebarHeader.displayName = "SidebarHeader";
SidebarHeader.displayName = "SidebarHeader"
const SidebarFooter = React.forwardRef< const SidebarFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
HTMLDivElement, ({ className, ...props }, ref) => {
React.ComponentProps<"div"> return (
>(({ className, ...props }, ref) => { <div
return ( ref={ref}
<div data-sidebar="footer"
ref={ref} className={cn("flex flex-col gap-2 p-2", className)}
data-sidebar="footer" {...props}
className={cn("flex flex-col gap-2 p-2", className)} />
{...props} );
/> },
) );
}) SidebarFooter.displayName = "SidebarFooter";
SidebarFooter.displayName = "SidebarFooter"
const SidebarSeparator = React.forwardRef< const SidebarSeparator = React.forwardRef<
React.ElementRef<typeof Separator>, React.ElementRef<typeof Separator>,
@ -401,48 +387,46 @@ const SidebarSeparator = React.forwardRef<
className={cn("mx-2 w-auto bg-sidebar-border", className)} className={cn("mx-2 w-auto bg-sidebar-border", className)}
{...props} {...props}
/> />
) );
}) });
SidebarSeparator.displayName = "SidebarSeparator" SidebarSeparator.displayName = "SidebarSeparator";
const SidebarContent = React.forwardRef< const SidebarContent = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
HTMLDivElement, ({ className, ...props }, ref) => {
React.ComponentProps<"div"> return (
>(({ className, ...props }, ref) => { <div
return ( ref={ref}
<div data-sidebar="content"
ref={ref} className={cn(
data-sidebar="content" "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
className={cn( className,
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", )}
className {...props}
)} />
{...props} );
/> },
) );
}) SidebarContent.displayName = "SidebarContent";
SidebarContent.displayName = "SidebarContent"
const SidebarGroup = React.forwardRef< const SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
HTMLDivElement, ({ className, ...props }, ref) => {
React.ComponentProps<"div"> return (
>(({ className, ...props }, ref) => { <div
return ( ref={ref}
<div data-sidebar="group"
ref={ref} className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
data-sidebar="group" {...props}
className={cn("relative flex w-full min-w-0 flex-col p-2", className)} />
{...props} );
/> },
) );
}) SidebarGroup.displayName = "SidebarGroup";
SidebarGroup.displayName = "SidebarGroup"
const SidebarGroupLabel = React.forwardRef< const SidebarGroupLabel = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.ComponentProps<"div"> & { asChild?: boolean } React.ComponentProps<"div"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => { >(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "div" const Comp = asChild ? Slot : "div";
return ( return (
<Comp <Comp
@ -451,19 +435,19 @@ const SidebarGroupLabel = React.forwardRef<
className={cn( className={cn(
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarGroupLabel.displayName = "SidebarGroupLabel" SidebarGroupLabel.displayName = "SidebarGroupLabel";
const SidebarGroupAction = React.forwardRef< const SidebarGroupAction = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<"button"> & { asChild?: boolean } React.ComponentProps<"button"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => { >(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : "button";
return ( return (
<Comp <Comp
@ -474,52 +458,49 @@ const SidebarGroupAction = React.forwardRef<
// Increases the hit area of the button on mobile. // Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 after:md:hidden", "after:absolute after:-inset-2 after:md:hidden",
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarGroupAction.displayName = "SidebarGroupAction" SidebarGroupAction.displayName = "SidebarGroupAction";
const SidebarGroupContent = React.forwardRef< const SidebarGroupContent = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
HTMLDivElement, ({ className, ...props }, ref) => (
React.ComponentProps<"div"> <div
>(({ className, ...props }, ref) => ( ref={ref}
<div data-sidebar="group-content"
ref={ref} className={cn("w-full text-sm", className)}
data-sidebar="group-content" {...props}
className={cn("w-full text-sm", className)} />
{...props} ),
/> );
)) SidebarGroupContent.displayName = "SidebarGroupContent";
SidebarGroupContent.displayName = "SidebarGroupContent"
const SidebarMenu = React.forwardRef< const SidebarMenu = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>(
HTMLUListElement, ({ className, ...props }, ref) => (
React.ComponentProps<"ul"> <ul
>(({ className, ...props }, ref) => ( ref={ref}
<ul data-sidebar="menu"
ref={ref} className={cn("flex w-full min-w-0 flex-col gap-1", className)}
data-sidebar="menu" {...props}
className={cn("flex w-full min-w-0 flex-col gap-1", className)} />
{...props} ),
/> );
)) SidebarMenu.displayName = "SidebarMenu";
SidebarMenu.displayName = "SidebarMenu"
const SidebarMenuItem = React.forwardRef< const SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(
HTMLLIElement, ({ className, ...props }, ref) => (
React.ComponentProps<"li"> <li
>(({ className, ...props }, ref) => ( ref={ref}
<li data-sidebar="menu-item"
ref={ref} className={cn("group/menu-item relative", className)}
data-sidebar="menu-item" {...props}
className={cn("group/menu-item relative", className)} />
{...props} ),
/> );
)) SidebarMenuItem.displayName = "SidebarMenuItem";
SidebarMenuItem.displayName = "SidebarMenuItem"
const sidebarMenuButtonVariants = cva( const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
@ -540,15 +521,15 @@ const sidebarMenuButtonVariants = cva(
variant: "default", variant: "default",
size: "default", size: "default",
}, },
} },
) );
const SidebarMenuButton = React.forwardRef< const SidebarMenuButton = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<"button"> & { React.ComponentProps<"button"> & {
asChild?: boolean asChild?: boolean;
isActive?: boolean isActive?: boolean;
tooltip?: string | React.ComponentProps<typeof TooltipContent> tooltip?: string | React.ComponentProps<typeof TooltipContent>;
} & VariantProps<typeof sidebarMenuButtonVariants> } & VariantProps<typeof sidebarMenuButtonVariants>
>( >(
( (
@ -561,10 +542,10 @@ const SidebarMenuButton = React.forwardRef<
className, className,
...props ...props
}, },
ref ref,
) => { ) => {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : "button";
const { isMobile, state } = useSidebar() const { isMobile, state } = useSidebar();
const button = ( const button = (
<Comp <Comp
@ -575,16 +556,16 @@ const SidebarMenuButton = React.forwardRef<
className={cn(sidebarMenuButtonVariants({ variant, size }), className)} className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props} {...props}
/> />
) );
if (!tooltip) { if (!tooltip) {
return button return button;
} }
if (typeof tooltip === "string") { if (typeof tooltip === "string") {
tooltip = { tooltip = {
children: tooltip, children: tooltip,
} };
} }
return ( return (
@ -597,19 +578,19 @@ const SidebarMenuButton = React.forwardRef<
{...tooltip} {...tooltip}
/> />
</Tooltip> </Tooltip>
) );
} },
) );
SidebarMenuButton.displayName = "SidebarMenuButton" SidebarMenuButton.displayName = "SidebarMenuButton";
const SidebarMenuAction = React.forwardRef< const SidebarMenuAction = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<"button"> & { React.ComponentProps<"button"> & {
asChild?: boolean asChild?: boolean;
showOnHover?: boolean showOnHover?: boolean;
} }
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => { >(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : "button";
return ( return (
<Comp <Comp
@ -625,45 +606,44 @@ const SidebarMenuAction = React.forwardRef<
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
showOnHover && showOnHover &&
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0", "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarMenuAction.displayName = "SidebarMenuAction" SidebarMenuAction.displayName = "SidebarMenuAction";
const SidebarMenuBadge = React.forwardRef< const SidebarMenuBadge = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
HTMLDivElement, ({ className, ...props }, ref) => (
React.ComponentProps<"div"> <div
>(({ className, ...props }, ref) => ( ref={ref}
<div data-sidebar="menu-badge"
ref={ref} className={cn(
data-sidebar="menu-badge" "pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground",
className={cn( "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
"pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground", "peer-data-[size=sm]/menu-button:top-1",
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground", "peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=sm]/menu-button:top-1", "peer-data-[size=lg]/menu-button:top-2.5",
"peer-data-[size=default]/menu-button:top-1.5", "group-data-[collapsible=icon]:hidden",
"peer-data-[size=lg]/menu-button:top-2.5", className,
"group-data-[collapsible=icon]:hidden", )}
className {...props}
)} />
{...props} ),
/> );
)) SidebarMenuBadge.displayName = "SidebarMenuBadge";
SidebarMenuBadge.displayName = "SidebarMenuBadge"
const SidebarMenuSkeleton = React.forwardRef< const SidebarMenuSkeleton = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.ComponentProps<"div"> & { React.ComponentProps<"div"> & {
showIcon?: boolean showIcon?: boolean;
} }
>(({ className, showIcon = false, ...props }, ref) => { >(({ className, showIcon = false, ...props }, ref) => {
// Random width between 50 to 90%. // Random width between 50 to 90%.
const width = React.useMemo(() => { const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%` return `${Math.floor(Math.random() * 40) + 50}%`;
}, []) }, []);
return ( return (
<div <div
@ -672,14 +652,9 @@ const SidebarMenuSkeleton = React.forwardRef<
className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)} className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
{...props} {...props}
> >
{showIcon && ( {showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
<Skeleton
className="size-4 rounded-md"
data-sidebar="menu-skeleton-icon"
/>
)}
<Skeleton <Skeleton
className="h-4 max-w-[--skeleton-width] flex-1" className="h-4 max-w-[var(--skeleton-width)] flex-1"
data-sidebar="menu-skeleton-text" data-sidebar="menu-skeleton-text"
style={ style={
{ {
@ -688,42 +663,40 @@ const SidebarMenuSkeleton = React.forwardRef<
} }
/> />
</div> </div>
) );
}) });
SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton" SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton";
const SidebarMenuSub = React.forwardRef< const SidebarMenuSub = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>(
HTMLUListElement, ({ className, ...props }, ref) => (
React.ComponentProps<"ul"> <ul
>(({ className, ...props }, ref) => ( ref={ref}
<ul data-sidebar="menu-sub"
ref={ref} className={cn(
data-sidebar="menu-sub" "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
className={cn( "group-data-[collapsible=icon]:hidden",
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5", className,
"group-data-[collapsible=icon]:hidden", )}
className {...props}
)} />
{...props} ),
/> );
)) SidebarMenuSub.displayName = "SidebarMenuSub";
SidebarMenuSub.displayName = "SidebarMenuSub"
const SidebarMenuSubItem = React.forwardRef< const SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(
HTMLLIElement, ({ ...props }, ref) => <li ref={ref} {...props} />,
React.ComponentProps<"li"> );
>(({ ...props }, ref) => <li ref={ref} {...props} />) SidebarMenuSubItem.displayName = "SidebarMenuSubItem";
SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
const SidebarMenuSubButton = React.forwardRef< const SidebarMenuSubButton = React.forwardRef<
HTMLAnchorElement, HTMLAnchorElement,
React.ComponentProps<"a"> & { React.ComponentProps<"a"> & {
asChild?: boolean asChild?: boolean;
size?: "sm" | "md" size?: "sm" | "md";
isActive?: boolean isActive?: boolean;
} }
>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => { >(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a" const Comp = asChild ? Slot : "a";
return ( return (
<Comp <Comp
@ -737,13 +710,13 @@ const SidebarMenuSubButton = React.forwardRef<
size === "sm" && "text-xs", size === "sm" && "text-xs",
size === "md" && "text-sm", size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarMenuSubButton.displayName = "SidebarMenuSubButton" SidebarMenuSubButton.displayName = "SidebarMenuSubButton";
export { export {
Sidebar, Sidebar,
@ -770,4 +743,4 @@ export {
SidebarSeparator, SidebarSeparator,
SidebarTrigger, SidebarTrigger,
useSidebar, useSidebar,
} };