chore: update sidebar closing and opening a0caba60
Steve · 2025-11-01 23:34 2 file(s) · +680 −600
src/components/app-sidebar.tsx +6 −2
16 16
	SidebarMenu,
17 17
	SidebarMenuButton,
18 18
	SidebarMenuItem,
19 +
	useSidebar,
19 20
} from "@/components/ui/sidebar";
20 21
21 22
import { Button } from "@/components/ui/button";
82 83
	const [isAddingFeed, setIsAddingFeed] = React.useState(false);
83 84
	const [statusMessage, setStatusMessage] = React.useState("");
84 85
86 +
	const { hidden } = useSidebar();
85 87
	const { insert, update } = useEvolu();
86 88
	const allFeeds = useQuery(allFeedsQuery);
87 89
	const allReadStatuses = useQuery(allReadStatusesQuery);
365 367
	return (
366 368
		<>
367 369
			<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
368 -
				<Sidebar collapsible="icon" {...props}>
370 +
				<Sidebar collapsible="offcanvas" {...props}>
369 371
					<SidebarHeader>
370 372
						<SidebarMenu>
371 373
							<SidebarMenuItem>
460 462
			</Dialog>
461 463
462 464
			{/* Posts List Panel - Separate from main sidebar */}
463 -
			<div className="bg-sidebar text-sidebar-foreground hidden md:flex overflow-y-scroll h-screen w-[320px] flex-col border-r">
465 +
			<div
466 +
				className={`bg-sidebar text-sidebar-foreground hidden md:flex h-screen flex-col border-r ${hidden ? "w-0 min-w-0 border-0 overflow-hidden" : "w-[320px] overflow-y-auto"}`}
467 +
			>
464 468
				<div className="gap-2 border-b p-3 flex flex-col">
465 469
					<div className="flex w-full items-center justify-between gap-2">
466 470
						<div className="text-foreground text-sm font-semibold truncate">
src/components/ui/sidebar.tsx +674 −598
1 -
import * as React from "react"
2 -
import { Slot } from "@radix-ui/react-slot"
3 -
import { cva, type VariantProps } from "class-variance-authority"
4 -
import { PanelLeftIcon } from "lucide-react"
1 +
import * as React from "react";
2 +
import { Slot } from "@radix-ui/react-slot";
3 +
import { cva, type VariantProps } from "class-variance-authority";
4 +
import { PanelLeftIcon } from "lucide-react";
5 5
6 -
import { useIsMobile } from "@/hooks/use-mobile"
7 -
import { cn } from "@/lib/utils"
8 -
import { Button } from "@/components/ui/button"
9 -
import { Input } from "@/components/ui/input"
10 -
import { Separator } from "@/components/ui/separator"
6 +
import { useIsMobile } from "@/hooks/use-mobile";
7 +
import { cn } from "@/lib/utils";
8 +
import { Button } from "@/components/ui/button";
9 +
import { Input } from "@/components/ui/input";
10 +
import { Separator } from "@/components/ui/separator";
11 11
import {
12 -
  Sheet,
13 -
  SheetContent,
14 -
  SheetDescription,
15 -
  SheetHeader,
16 -
  SheetTitle,
17 -
} from "@/components/ui/sheet"
18 -
import { Skeleton } from "@/components/ui/skeleton"
12 +
	Sheet,
13 +
	SheetContent,
14 +
	SheetDescription,
15 +
	SheetHeader,
16 +
	SheetTitle,
17 +
} from "@/components/ui/sheet";
18 +
import { Skeleton } from "@/components/ui/skeleton";
19 19
import {
20 -
  Tooltip,
21 -
  TooltipContent,
22 -
  TooltipProvider,
23 -
  TooltipTrigger,
24 -
} from "@/components/ui/tooltip"
20 +
	Tooltip,
21 +
	TooltipContent,
22 +
	TooltipProvider,
23 +
	TooltipTrigger,
24 +
} from "@/components/ui/tooltip";
25 25
26 -
const SIDEBAR_COOKIE_NAME = "sidebar_state"
27 -
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
28 -
const SIDEBAR_WIDTH = "16rem"
29 -
const SIDEBAR_WIDTH_MOBILE = "18rem"
30 -
const SIDEBAR_WIDTH_ICON = "3rem"
31 -
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
26 +
const SIDEBAR_COOKIE_NAME = "sidebar_state";
27 +
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
28 +
const SIDEBAR_WIDTH = "16rem";
29 +
const SIDEBAR_WIDTH_MOBILE = "18rem";
30 +
const SIDEBAR_WIDTH_ICON = "3rem";
31 +
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
32 32
33 33
type SidebarContextProps = {
34 -
  state: "expanded" | "collapsed"
35 -
  open: boolean
36 -
  setOpen: (open: boolean) => void
37 -
  openMobile: boolean
38 -
  setOpenMobile: (open: boolean) => void
39 -
  isMobile: boolean
40 -
  toggleSidebar: () => void
41 -
}
34 +
	state: "expanded" | "collapsed";
35 +
	open: boolean;
36 +
	setOpen: (open: boolean) => void;
37 +
	openMobile: boolean;
38 +
	setOpenMobile: (open: boolean) => void;
39 +
	isMobile: boolean;
40 +
	toggleSidebar: () => void;
41 +
	hidden: boolean;
42 +
	setHidden: (hidden: boolean) => void;
43 +
	hideSidebar: () => void;
44 +
	showSidebar: () => void;
45 +
};
42 46
43 -
const SidebarContext = React.createContext<SidebarContextProps | null>(null)
47 +
const SidebarContext = React.createContext<SidebarContextProps | null>(null);
44 48
45 49
function useSidebar() {
46 -
  const context = React.useContext(SidebarContext)
47 -
  if (!context) {
48 -
    throw new Error("useSidebar must be used within a SidebarProvider.")
49 -
  }
50 +
	const context = React.useContext(SidebarContext);
51 +
	if (!context) {
52 +
		throw new Error("useSidebar must be used within a SidebarProvider.");
53 +
	}
50 54
51 -
  return context
55 +
	return context;
52 56
}
53 57
54 58
function SidebarProvider({
55 -
  defaultOpen = true,
56 -
  open: openProp,
57 -
  onOpenChange: setOpenProp,
58 -
  className,
59 -
  style,
60 -
  children,
61 -
  ...props
59 +
	defaultOpen = true,
60 +
	open: openProp,
61 +
	onOpenChange: setOpenProp,
62 +
	className,
63 +
	style,
64 +
	children,
65 +
	...props
62 66
}: React.ComponentProps<"div"> & {
63 -
  defaultOpen?: boolean
64 -
  open?: boolean
65 -
  onOpenChange?: (open: boolean) => void
67 +
	defaultOpen?: boolean;
68 +
	open?: boolean;
69 +
	onOpenChange?: (open: boolean) => void;
66 70
}) {
67 -
  const isMobile = useIsMobile()
68 -
  const [openMobile, setOpenMobile] = React.useState(false)
71 +
	const isMobile = useIsMobile();
72 +
	const [openMobile, setOpenMobile] = React.useState(false);
73 +
	const [hidden, setHidden] = React.useState(false);
69 74
70 -
  // This is the internal state of the sidebar.
71 -
  // We use openProp and setOpenProp for control from outside the component.
72 -
  const [_open, _setOpen] = React.useState(defaultOpen)
73 -
  const open = openProp ?? _open
74 -
  const setOpen = React.useCallback(
75 -
    (value: boolean | ((value: boolean) => boolean)) => {
76 -
      const openState = typeof value === "function" ? value(open) : value
77 -
      if (setOpenProp) {
78 -
        setOpenProp(openState)
79 -
      } else {
80 -
        _setOpen(openState)
81 -
      }
75 +
	// This is the internal state of the sidebar.
76 +
	// We use openProp and setOpenProp for control from outside the component.
77 +
	const [_open, _setOpen] = React.useState(defaultOpen);
78 +
	const open = openProp ?? _open;
79 +
	const setOpen = React.useCallback(
80 +
		(value: boolean | ((value: boolean) => boolean)) => {
81 +
			const openState = typeof value === "function" ? value(open) : value;
82 +
			if (setOpenProp) {
83 +
				setOpenProp(openState);
84 +
			} else {
85 +
				_setOpen(openState);
86 +
			}
82 87
83 -
      // This sets the cookie to keep the sidebar state.
84 -
      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
85 -
    },
86 -
    [setOpenProp, open]
87 -
  )
88 +
			// This sets the cookie to keep the sidebar state.
89 +
			document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
90 +
		},
91 +
		[setOpenProp, open],
92 +
	);
88 93
89 -
  // Helper to toggle the sidebar.
90 -
  const toggleSidebar = React.useCallback(() => {
91 -
    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
92 -
  }, [isMobile, setOpen, setOpenMobile])
94 +
	// Helper to toggle the sidebar.
95 +
	const toggleSidebar = React.useCallback(() => {
96 +
		return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
97 +
	}, [isMobile, setOpen, setOpenMobile]);
93 98
94 -
  // Adds a keyboard shortcut to toggle the sidebar.
95 -
  React.useEffect(() => {
96 -
    const handleKeyDown = (event: KeyboardEvent) => {
97 -
      if (
98 -
        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
99 -
        (event.metaKey || event.ctrlKey)
100 -
      ) {
101 -
        event.preventDefault()
102 -
        toggleSidebar()
103 -
      }
104 -
    }
99 +
	// Helper to completely hide the sidebar
100 +
	const hideSidebar = React.useCallback(() => {
101 +
		setHidden(true);
102 +
		if (!isMobile) {
103 +
			setOpen(false);
104 +
		}
105 +
	}, [isMobile, setOpen]);
105 106
106 -
    window.addEventListener("keydown", handleKeyDown)
107 -
    return () => window.removeEventListener("keydown", handleKeyDown)
108 -
  }, [toggleSidebar])
107 +
	// Helper to show the sidebar
108 +
	const showSidebar = React.useCallback(() => {
109 +
		setHidden(false);
110 +
		if (!isMobile) {
111 +
			setOpen(true);
112 +
		}
113 +
	}, [isMobile, setOpen]);
109 114
110 -
  // We add a state so that we can do data-state="expanded" or "collapsed".
111 -
  // This makes it easier to style the sidebar with Tailwind classes.
112 -
  const state = open ? "expanded" : "collapsed"
115 +
	// Adds a keyboard shortcut to toggle the sidebar.
116 +
	React.useEffect(() => {
117 +
		const handleKeyDown = (event: KeyboardEvent) => {
118 +
			if (
119 +
				event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
120 +
				(event.metaKey || event.ctrlKey)
121 +
			) {
122 +
				event.preventDefault();
123 +
				toggleSidebar();
124 +
			}
125 +
		};
113 126
114 -
  const contextValue = React.useMemo<SidebarContextProps>(
115 -
    () => ({
116 -
      state,
117 -
      open,
118 -
      setOpen,
119 -
      isMobile,
120 -
      openMobile,
121 -
      setOpenMobile,
122 -
      toggleSidebar,
123 -
    }),
124 -
    [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
125 -
  )
127 +
		window.addEventListener("keydown", handleKeyDown);
128 +
		return () => window.removeEventListener("keydown", handleKeyDown);
129 +
	}, [toggleSidebar]);
130 +
131 +
	// We add a state so that we can do data-state="expanded" or "collapsed".
132 +
	// This makes it easier to style the sidebar with Tailwind classes.
133 +
	const state = open ? "expanded" : "collapsed";
134 +
135 +
	const contextValue = React.useMemo<SidebarContextProps>(
136 +
		() => ({
137 +
			state,
138 +
			open,
139 +
			setOpen,
140 +
			isMobile,
141 +
			openMobile,
142 +
			setOpenMobile,
143 +
			toggleSidebar,
144 +
			hidden,
145 +
			setHidden,
146 +
			hideSidebar,
147 +
			showSidebar,
148 +
		}),
149 +
		[
150 +
			state,
151 +
			open,
152 +
			setOpen,
153 +
			isMobile,
154 +
			openMobile,
155 +
			setOpenMobile,
156 +
			toggleSidebar,
157 +
			hidden,
158 +
			hideSidebar,
159 +
			showSidebar,
160 +
		],
161 +
	);
126 162
127 -
  return (
128 -
    <SidebarContext.Provider value={contextValue}>
129 -
      <TooltipProvider delayDuration={0}>
130 -
        <div
131 -
          data-slot="sidebar-wrapper"
132 -
          style={
133 -
            {
134 -
              "--sidebar-width": SIDEBAR_WIDTH,
135 -
              "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
136 -
              ...style,
137 -
            } as React.CSSProperties
138 -
          }
139 -
          className={cn(
140 -
            "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
141 -
            className
142 -
          )}
143 -
          {...props}
144 -
        >
145 -
          {children}
146 -
        </div>
147 -
      </TooltipProvider>
148 -
    </SidebarContext.Provider>
149 -
  )
163 +
	return (
164 +
		<SidebarContext.Provider value={contextValue}>
165 +
			<TooltipProvider delayDuration={0}>
166 +
				<div
167 +
					data-slot="sidebar-wrapper"
168 +
					style={
169 +
						{
170 +
							"--sidebar-width": SIDEBAR_WIDTH,
171 +
							"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
172 +
							...style,
173 +
						} as React.CSSProperties
174 +
					}
175 +
					className={cn(
176 +
						"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
177 +
						className,
178 +
					)}
179 +
					{...props}
180 +
				>
181 +
					{children}
182 +
				</div>
183 +
			</TooltipProvider>
184 +
		</SidebarContext.Provider>
185 +
	);
150 186
}
151 187
152 188
function Sidebar({
153 -
  side = "left",
154 -
  variant = "sidebar",
155 -
  collapsible = "offcanvas",
156 -
  className,
157 -
  children,
158 -
  ...props
189 +
	side = "left",
190 +
	variant = "sidebar",
191 +
	collapsible = "offcanvas",
192 +
	className,
193 +
	children,
194 +
	...props
159 195
}: React.ComponentProps<"div"> & {
160 -
  side?: "left" | "right"
161 -
  variant?: "sidebar" | "floating" | "inset"
162 -
  collapsible?: "offcanvas" | "icon" | "none"
196 +
	side?: "left" | "right";
197 +
	variant?: "sidebar" | "floating" | "inset";
198 +
	collapsible?: "offcanvas" | "icon" | "none";
163 199
}) {
164 -
  const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
200 +
	const { isMobile, state, openMobile, setOpenMobile, hidden } = useSidebar();
165 201
166 -
  if (collapsible === "none") {
167 -
    return (
168 -
      <div
169 -
        data-slot="sidebar"
170 -
        className={cn(
171 -
          "bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
172 -
          className
173 -
        )}
174 -
        {...props}
175 -
      >
176 -
        {children}
177 -
      </div>
178 -
    )
179 -
  }
202 +
	if (collapsible === "none") {
203 +
		return (
204 +
			<div
205 +
				data-slot="sidebar"
206 +
				className={cn(
207 +
					"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
208 +
					className,
209 +
				)}
210 +
				{...props}
211 +
			>
212 +
				{children}
213 +
			</div>
214 +
		);
215 +
	}
180 216
181 -
  if (isMobile) {
182 -
    return (
183 -
      <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
184 -
        <SheetContent
185 -
          data-sidebar="sidebar"
186 -
          data-slot="sidebar"
187 -
          data-mobile="true"
188 -
          className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
189 -
          style={
190 -
            {
191 -
              "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
192 -
            } as React.CSSProperties
193 -
          }
194 -
          side={side}
195 -
        >
196 -
          <SheetHeader className="sr-only">
197 -
            <SheetTitle>Sidebar</SheetTitle>
198 -
            <SheetDescription>Displays the mobile sidebar.</SheetDescription>
199 -
          </SheetHeader>
200 -
          <div className="flex h-full w-full flex-col">{children}</div>
201 -
        </SheetContent>
202 -
      </Sheet>
203 -
    )
204 -
  }
217 +
	if (isMobile) {
218 +
		return (
219 +
			<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
220 +
				<SheetContent
221 +
					data-sidebar="sidebar"
222 +
					data-slot="sidebar"
223 +
					data-mobile="true"
224 +
					className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
225 +
					style={
226 +
						{
227 +
							"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
228 +
						} as React.CSSProperties
229 +
					}
230 +
					side={side}
231 +
				>
232 +
					<SheetHeader className="sr-only">
233 +
						<SheetTitle>Sidebar</SheetTitle>
234 +
						<SheetDescription>Displays the mobile sidebar.</SheetDescription>
235 +
					</SheetHeader>
236 +
					<div className="flex h-full w-full flex-col">{children}</div>
237 +
				</SheetContent>
238 +
			</Sheet>
239 +
		);
240 +
	}
205 241
206 -
  return (
207 -
    <div
208 -
      className="group peer text-sidebar-foreground hidden md:block"
209 -
      data-state={state}
210 -
      data-collapsible={state === "collapsed" ? collapsible : ""}
211 -
      data-variant={variant}
212 -
      data-side={side}
213 -
      data-slot="sidebar"
214 -
    >
215 -
      {/* This is what handles the sidebar gap on desktop */}
216 -
      <div
217 -
        data-slot="sidebar-gap"
218 -
        className={cn(
219 -
          "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
220 -
          "group-data-[collapsible=offcanvas]:w-0",
221 -
          "group-data-[side=right]:rotate-180",
222 -
          variant === "floating" || variant === "inset"
223 -
            ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
224 -
            : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
225 -
        )}
226 -
      />
227 -
      <div
228 -
        data-slot="sidebar-container"
229 -
        className={cn(
230 -
          "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
231 -
          side === "left"
232 -
            ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
233 -
            : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
234 -
          // Adjust the padding for floating and inset variants.
235 -
          variant === "floating" || variant === "inset"
236 -
            ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
237 -
            : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
238 -
          className
239 -
        )}
240 -
        {...props}
241 -
      >
242 -
        <div
243 -
          data-sidebar="sidebar"
244 -
          data-slot="sidebar-inner"
245 -
          className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
246 -
        >
247 -
          {children}
248 -
        </div>
249 -
      </div>
250 -
    </div>
251 -
  )
242 +
	return (
243 +
		<div
244 +
			className={cn(
245 +
				"group peer text-sidebar-foreground hidden md:block transition-all duration-300 ease-in-out",
246 +
				hidden && "!hidden",
247 +
			)}
248 +
			data-state={state}
249 +
			data-collapsible={state === "collapsed" ? collapsible : ""}
250 +
			data-variant={variant}
251 +
			data-side={side}
252 +
			data-slot="sidebar"
253 +
			data-hidden={hidden}
254 +
		>
255 +
			{/* This is what handles the sidebar gap on desktop */}
256 +
			<div
257 +
				data-slot="sidebar-gap"
258 +
				className={cn(
259 +
					"relative w-(--sidebar-width) bg-transparent transition-[width] duration-300 ease-in-out",
260 +
					"group-data-[collapsible=offcanvas]:w-0",
261 +
					"group-data-[side=right]:rotate-180",
262 +
					variant === "floating" || variant === "inset"
263 +
						? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
264 +
						: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
265 +
				)}
266 +
			/>
267 +
			<div
268 +
				data-slot="sidebar-container"
269 +
				className={cn(
270 +
					"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width,opacity] duration-300 ease-in-out md:flex",
271 +
					side === "left"
272 +
						? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
273 +
						: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
274 +
					// Adjust the padding for floating and inset variants.
275 +
					variant === "floating" || variant === "inset"
276 +
						? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
277 +
						: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
278 +
					className,
279 +
				)}
280 +
				{...props}
281 +
			>
282 +
				<div
283 +
					data-sidebar="sidebar"
284 +
					data-slot="sidebar-inner"
285 +
					className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
286 +
				>
287 +
					{children}
288 +
				</div>
289 +
			</div>
290 +
		</div>
291 +
	);
252 292
}
253 293
254 294
function SidebarTrigger({
255 -
  className,
256 -
  onClick,
257 -
  ...props
295 +
	className,
296 +
	onClick,
297 +
	...props
258 298
}: React.ComponentProps<typeof Button>) {
259 -
  const { toggleSidebar } = useSidebar()
299 +
	const { hideSidebar, showSidebar, hidden } = useSidebar();
260 300
261 -
  return (
262 -
    <Button
263 -
      data-sidebar="trigger"
264 -
      data-slot="sidebar-trigger"
265 -
      variant="ghost"
266 -
      size="icon"
267 -
      className={cn("size-7", className)}
268 -
      onClick={(event) => {
269 -
        onClick?.(event)
270 -
        toggleSidebar()
271 -
      }}
272 -
      {...props}
273 -
    >
274 -
      <PanelLeftIcon />
275 -
      <span className="sr-only">Toggle Sidebar</span>
276 -
    </Button>
277 -
  )
301 +
	return (
302 +
		<Button
303 +
			data-sidebar="trigger"
304 +
			data-slot="sidebar-trigger"
305 +
			variant="ghost"
306 +
			size="icon"
307 +
			className={cn("size-7", className)}
308 +
			onClick={(event) => {
309 +
				onClick?.(event);
310 +
				if (hidden) {
311 +
					showSidebar();
312 +
				} else {
313 +
					hideSidebar();
314 +
				}
315 +
			}}
316 +
			{...props}
317 +
		>
318 +
			<PanelLeftIcon />
319 +
			<span className="sr-only">Toggle Sidebar</span>
320 +
		</Button>
321 +
	);
322 +
}
323 +
324 +
function SidebarShowButton({
325 +
	className,
326 +
	onClick,
327 +
	...props
328 +
}: React.ComponentProps<typeof Button>) {
329 +
	const { showSidebar, hidden } = useSidebar();
330 +
331 +
	if (!hidden) {
332 +
		return null;
333 +
	}
334 +
335 +
	return (
336 +
		<Button
337 +
			data-sidebar="show-button"
338 +
			data-slot="sidebar-show-button"
339 +
			variant="outline"
340 +
			size="icon"
341 +
			className={cn("fixed left-4 top-4 z-50 size-9 shadow-lg", className)}
342 +
			onClick={(event) => {
343 +
				onClick?.(event);
344 +
				showSidebar();
345 +
			}}
346 +
			{...props}
347 +
		>
348 +
			<PanelLeftIcon />
349 +
			<span className="sr-only">Show Sidebar</span>
350 +
		</Button>
351 +
	);
278 352
}
279 353
280 354
function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
281 -
  const { toggleSidebar } = useSidebar()
355 +
	const { toggleSidebar } = useSidebar();
282 356
283 -
  return (
284 -
    <button
285 -
      data-sidebar="rail"
286 -
      data-slot="sidebar-rail"
287 -
      aria-label="Toggle Sidebar"
288 -
      tabIndex={-1}
289 -
      onClick={toggleSidebar}
290 -
      title="Toggle Sidebar"
291 -
      className={cn(
292 -
        "hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
293 -
        "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
294 -
        "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
295 -
        "hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
296 -
        "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
297 -
        "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
298 -
        className
299 -
      )}
300 -
      {...props}
301 -
    />
302 -
  )
357 +
	return (
358 +
		<button
359 +
			data-sidebar="rail"
360 +
			data-slot="sidebar-rail"
361 +
			aria-label="Toggle Sidebar"
362 +
			tabIndex={-1}
363 +
			onClick={toggleSidebar}
364 +
			title="Toggle Sidebar"
365 +
			className={cn(
366 +
				"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
367 +
				"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
368 +
				"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
369 +
				"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
370 +
				"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
371 +
				"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
372 +
				className,
373 +
			)}
374 +
			{...props}
375 +
		/>
376 +
	);
303 377
}
304 378
305 379
function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
306 -
  return (
307 -
    <main
308 -
      data-slot="sidebar-inset"
309 -
      className={cn(
310 -
        "bg-background relative flex w-full flex-1 flex-col",
311 -
        "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
312 -
        className
313 -
      )}
314 -
      {...props}
315 -
    />
316 -
  )
380 +
	return (
381 +
		<main
382 +
			data-slot="sidebar-inset"
383 +
			className={cn(
384 +
				"bg-background relative flex w-full flex-1 flex-col",
385 +
				"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
386 +
				"peer-data-[hidden=true]:ml-0",
387 +
				className,
388 +
			)}
389 +
			{...props}
390 +
		/>
391 +
	);
317 392
}
318 393
319 394
function SidebarInput({
320 -
  className,
321 -
  ...props
395 +
	className,
396 +
	...props
322 397
}: React.ComponentProps<typeof Input>) {
323 -
  return (
324 -
    <Input
325 -
      data-slot="sidebar-input"
326 -
      data-sidebar="input"
327 -
      className={cn("bg-background h-8 w-full shadow-none", className)}
328 -
      {...props}
329 -
    />
330 -
  )
398 +
	return (
399 +
		<Input
400 +
			data-slot="sidebar-input"
401 +
			data-sidebar="input"
402 +
			className={cn("bg-background h-8 w-full shadow-none", className)}
403 +
			{...props}
404 +
		/>
405 +
	);
331 406
}
332 407
333 408
function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
334 -
  return (
335 -
    <div
336 -
      data-slot="sidebar-header"
337 -
      data-sidebar="header"
338 -
      className={cn("flex flex-col gap-2 p-2", className)}
339 -
      {...props}
340 -
    />
341 -
  )
409 +
	return (
410 +
		<div
411 +
			data-slot="sidebar-header"
412 +
			data-sidebar="header"
413 +
			className={cn("flex flex-col gap-2 p-2", className)}
414 +
			{...props}
415 +
		/>
416 +
	);
342 417
}
343 418
344 419
function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
345 -
  return (
346 -
    <div
347 -
      data-slot="sidebar-footer"
348 -
      data-sidebar="footer"
349 -
      className={cn("flex flex-col gap-2 p-2", className)}
350 -
      {...props}
351 -
    />
352 -
  )
420 +
	return (
421 +
		<div
422 +
			data-slot="sidebar-footer"
423 +
			data-sidebar="footer"
424 +
			className={cn("flex flex-col gap-2 p-2", className)}
425 +
			{...props}
426 +
		/>
427 +
	);
353 428
}
354 429
355 430
function SidebarSeparator({
356 -
  className,
357 -
  ...props
431 +
	className,
432 +
	...props
358 433
}: React.ComponentProps<typeof Separator>) {
359 -
  return (
360 -
    <Separator
361 -
      data-slot="sidebar-separator"
362 -
      data-sidebar="separator"
363 -
      className={cn("bg-sidebar-border mx-2 w-auto", className)}
364 -
      {...props}
365 -
    />
366 -
  )
434 +
	return (
435 +
		<Separator
436 +
			data-slot="sidebar-separator"
437 +
			data-sidebar="separator"
438 +
			className={cn("bg-sidebar-border mx-2 w-auto", className)}
439 +
			{...props}
440 +
		/>
441 +
	);
367 442
}
368 443
369 444
function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
370 -
  return (
371 -
    <div
372 -
      data-slot="sidebar-content"
373 -
      data-sidebar="content"
374 -
      className={cn(
375 -
        "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
376 -
        className
377 -
      )}
378 -
      {...props}
379 -
    />
380 -
  )
445 +
	return (
446 +
		<div
447 +
			data-slot="sidebar-content"
448 +
			data-sidebar="content"
449 +
			className={cn(
450 +
				"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
451 +
				className,
452 +
			)}
453 +
			{...props}
454 +
		/>
455 +
	);
381 456
}
382 457
383 458
function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
384 -
  return (
385 -
    <div
386 -
      data-slot="sidebar-group"
387 -
      data-sidebar="group"
388 -
      className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
389 -
      {...props}
390 -
    />
391 -
  )
459 +
	return (
460 +
		<div
461 +
			data-slot="sidebar-group"
462 +
			data-sidebar="group"
463 +
			className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
464 +
			{...props}
465 +
		/>
466 +
	);
392 467
}
393 468
394 469
function SidebarGroupLabel({
395 -
  className,
396 -
  asChild = false,
397 -
  ...props
470 +
	className,
471 +
	asChild = false,
472 +
	...props
398 473
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
399 -
  const Comp = asChild ? Slot : "div"
474 +
	const Comp = asChild ? Slot : "div";
400 475
401 -
  return (
402 -
    <Comp
403 -
      data-slot="sidebar-group-label"
404 -
      data-sidebar="group-label"
405 -
      className={cn(
406 -
        "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
407 -
        "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
408 -
        className
409 -
      )}
410 -
      {...props}
411 -
    />
412 -
  )
476 +
	return (
477 +
		<Comp
478 +
			data-slot="sidebar-group-label"
479 +
			data-sidebar="group-label"
480 +
			className={cn(
481 +
				"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
482 +
				"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
483 +
				className,
484 +
			)}
485 +
			{...props}
486 +
		/>
487 +
	);
413 488
}
414 489
415 490
function SidebarGroupAction({
416 -
  className,
417 -
  asChild = false,
418 -
  ...props
491 +
	className,
492 +
	asChild = false,
493 +
	...props
419 494
}: React.ComponentProps<"button"> & { asChild?: boolean }) {
420 -
  const Comp = asChild ? Slot : "button"
495 +
	const Comp = asChild ? Slot : "button";
421 496
422 -
  return (
423 -
    <Comp
424 -
      data-slot="sidebar-group-action"
425 -
      data-sidebar="group-action"
426 -
      className={cn(
427 -
        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
428 -
        // Increases the hit area of the button on mobile.
429 -
        "after:absolute after:-inset-2 md:after:hidden",
430 -
        "group-data-[collapsible=icon]:hidden",
431 -
        className
432 -
      )}
433 -
      {...props}
434 -
    />
435 -
  )
497 +
	return (
498 +
		<Comp
499 +
			data-slot="sidebar-group-action"
500 +
			data-sidebar="group-action"
501 +
			className={cn(
502 +
				"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
503 +
				// Increases the hit area of the button on mobile.
504 +
				"after:absolute after:-inset-2 md:after:hidden",
505 +
				"group-data-[collapsible=icon]:hidden",
506 +
				className,
507 +
			)}
508 +
			{...props}
509 +
		/>
510 +
	);
436 511
}
437 512
438 513
function SidebarGroupContent({
439 -
  className,
440 -
  ...props
514 +
	className,
515 +
	...props
441 516
}: React.ComponentProps<"div">) {
442 -
  return (
443 -
    <div
444 -
      data-slot="sidebar-group-content"
445 -
      data-sidebar="group-content"
446 -
      className={cn("w-full text-sm", className)}
447 -
      {...props}
448 -
    />
449 -
  )
517 +
	return (
518 +
		<div
519 +
			data-slot="sidebar-group-content"
520 +
			data-sidebar="group-content"
521 +
			className={cn("w-full text-sm", className)}
522 +
			{...props}
523 +
		/>
524 +
	);
450 525
}
451 526
452 527
function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
453 -
  return (
454 -
    <ul
455 -
      data-slot="sidebar-menu"
456 -
      data-sidebar="menu"
457 -
      className={cn("flex w-full min-w-0 flex-col gap-1", className)}
458 -
      {...props}
459 -
    />
460 -
  )
528 +
	return (
529 +
		<ul
530 +
			data-slot="sidebar-menu"
531 +
			data-sidebar="menu"
532 +
			className={cn("flex w-full min-w-0 flex-col gap-1", className)}
533 +
			{...props}
534 +
		/>
535 +
	);
461 536
}
462 537
463 538
function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
464 -
  return (
465 -
    <li
466 -
      data-slot="sidebar-menu-item"
467 -
      data-sidebar="menu-item"
468 -
      className={cn("group/menu-item relative", className)}
469 -
      {...props}
470 -
    />
471 -
  )
539 +
	return (
540 +
		<li
541 +
			data-slot="sidebar-menu-item"
542 +
			data-sidebar="menu-item"
543 +
			className={cn("group/menu-item relative", className)}
544 +
			{...props}
545 +
		/>
546 +
	);
472 547
}
473 548
474 549
const sidebarMenuButtonVariants = cva(
475 -
  "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden 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",
476 -
  {
477 -
    variants: {
478 -
      variant: {
479 -
        default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
480 -
        outline:
481 -
          "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
482 -
      },
483 -
      size: {
484 -
        default: "h-8 text-sm",
485 -
        sm: "h-7 text-xs",
486 -
        lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
487 -
      },
488 -
    },
489 -
    defaultVariants: {
490 -
      variant: "default",
491 -
      size: "default",
492 -
    },
493 -
  }
494 -
)
550 +
	"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden 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",
551 +
	{
552 +
		variants: {
553 +
			variant: {
554 +
				default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
555 +
				outline:
556 +
					"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
557 +
			},
558 +
			size: {
559 +
				default: "h-8 text-sm",
560 +
				sm: "h-7 text-xs",
561 +
				lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
562 +
			},
563 +
		},
564 +
		defaultVariants: {
565 +
			variant: "default",
566 +
			size: "default",
567 +
		},
568 +
	},
569 +
);
495 570
496 571
function SidebarMenuButton({
497 -
  asChild = false,
498 -
  isActive = false,
499 -
  variant = "default",
500 -
  size = "default",
501 -
  tooltip,
502 -
  className,
503 -
  ...props
572 +
	asChild = false,
573 +
	isActive = false,
574 +
	variant = "default",
575 +
	size = "default",
576 +
	tooltip,
577 +
	className,
578 +
	...props
504 579
}: React.ComponentProps<"button"> & {
505 -
  asChild?: boolean
506 -
  isActive?: boolean
507 -
  tooltip?: string | React.ComponentProps<typeof TooltipContent>
580 +
	asChild?: boolean;
581 +
	isActive?: boolean;
582 +
	tooltip?: string | React.ComponentProps<typeof TooltipContent>;
508 583
} & VariantProps<typeof sidebarMenuButtonVariants>) {
509 -
  const Comp = asChild ? Slot : "button"
510 -
  const { isMobile, state } = useSidebar()
584 +
	const Comp = asChild ? Slot : "button";
585 +
	const { isMobile, state } = useSidebar();
511 586
512 -
  const button = (
513 -
    <Comp
514 -
      data-slot="sidebar-menu-button"
515 -
      data-sidebar="menu-button"
516 -
      data-size={size}
517 -
      data-active={isActive}
518 -
      className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
519 -
      {...props}
520 -
    />
521 -
  )
587 +
	const button = (
588 +
		<Comp
589 +
			data-slot="sidebar-menu-button"
590 +
			data-sidebar="menu-button"
591 +
			data-size={size}
592 +
			data-active={isActive}
593 +
			className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
594 +
			{...props}
595 +
		/>
596 +
	);
522 597
523 -
  if (!tooltip) {
524 -
    return button
525 -
  }
598 +
	if (!tooltip) {
599 +
		return button;
600 +
	}
526 601
527 -
  if (typeof tooltip === "string") {
528 -
    tooltip = {
529 -
      children: tooltip,
530 -
    }
531 -
  }
602 +
	if (typeof tooltip === "string") {
603 +
		tooltip = {
604 +
			children: tooltip,
605 +
		};
606 +
	}
532 607
533 -
  return (
534 -
    <Tooltip>
535 -
      <TooltipTrigger asChild>{button}</TooltipTrigger>
536 -
      <TooltipContent
537 -
        side="right"
538 -
        align="center"
539 -
        hidden={state !== "collapsed" || isMobile}
540 -
        {...tooltip}
541 -
      />
542 -
    </Tooltip>
543 -
  )
608 +
	return (
609 +
		<Tooltip>
610 +
			<TooltipTrigger asChild>{button}</TooltipTrigger>
611 +
			<TooltipContent
612 +
				side="right"
613 +
				align="center"
614 +
				hidden={state !== "collapsed" || isMobile}
615 +
				{...tooltip}
616 +
			/>
617 +
		</Tooltip>
618 +
	);
544 619
}
545 620
546 621
function SidebarMenuAction({
547 -
  className,
548 -
  asChild = false,
549 -
  showOnHover = false,
550 -
  ...props
622 +
	className,
623 +
	asChild = false,
624 +
	showOnHover = false,
625 +
	...props
551 626
}: React.ComponentProps<"button"> & {
552 -
  asChild?: boolean
553 -
  showOnHover?: boolean
627 +
	asChild?: boolean;
628 +
	showOnHover?: boolean;
554 629
}) {
555 -
  const Comp = asChild ? Slot : "button"
630 +
	const Comp = asChild ? Slot : "button";
556 631
557 -
  return (
558 -
    <Comp
559 -
      data-slot="sidebar-menu-action"
560 -
      data-sidebar="menu-action"
561 -
      className={cn(
562 -
        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
563 -
        // Increases the hit area of the button on mobile.
564 -
        "after:absolute after:-inset-2 md:after:hidden",
565 -
        "peer-data-[size=sm]/menu-button:top-1",
566 -
        "peer-data-[size=default]/menu-button:top-1.5",
567 -
        "peer-data-[size=lg]/menu-button:top-2.5",
568 -
        "group-data-[collapsible=icon]:hidden",
569 -
        showOnHover &&
570 -
          "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
571 -
        className
572 -
      )}
573 -
      {...props}
574 -
    />
575 -
  )
632 +
	return (
633 +
		<Comp
634 +
			data-slot="sidebar-menu-action"
635 +
			data-sidebar="menu-action"
636 +
			className={cn(
637 +
				"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
638 +
				// Increases the hit area of the button on mobile.
639 +
				"after:absolute after:-inset-2 md:after:hidden",
640 +
				"peer-data-[size=sm]/menu-button:top-1",
641 +
				"peer-data-[size=default]/menu-button:top-1.5",
642 +
				"peer-data-[size=lg]/menu-button:top-2.5",
643 +
				"group-data-[collapsible=icon]:hidden",
644 +
				showOnHover &&
645 +
					"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
646 +
				className,
647 +
			)}
648 +
			{...props}
649 +
		/>
650 +
	);
576 651
}
577 652
578 653
function SidebarMenuBadge({
579 -
  className,
580 -
  ...props
654 +
	className,
655 +
	...props
581 656
}: React.ComponentProps<"div">) {
582 -
  return (
583 -
    <div
584 -
      data-slot="sidebar-menu-badge"
585 -
      data-sidebar="menu-badge"
586 -
      className={cn(
587 -
        "text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none",
588 -
        "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
589 -
        "peer-data-[size=sm]/menu-button:top-1",
590 -
        "peer-data-[size=default]/menu-button:top-1.5",
591 -
        "peer-data-[size=lg]/menu-button:top-2.5",
592 -
        "group-data-[collapsible=icon]:hidden",
593 -
        className
594 -
      )}
595 -
      {...props}
596 -
    />
597 -
  )
657 +
	return (
658 +
		<div
659 +
			data-slot="sidebar-menu-badge"
660 +
			data-sidebar="menu-badge"
661 +
			className={cn(
662 +
				"text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none",
663 +
				"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
664 +
				"peer-data-[size=sm]/menu-button:top-1",
665 +
				"peer-data-[size=default]/menu-button:top-1.5",
666 +
				"peer-data-[size=lg]/menu-button:top-2.5",
667 +
				"group-data-[collapsible=icon]:hidden",
668 +
				className,
669 +
			)}
670 +
			{...props}
671 +
		/>
672 +
	);
598 673
}
599 674
600 675
function SidebarMenuSkeleton({
601 -
  className,
602 -
  showIcon = false,
603 -
  ...props
676 +
	className,
677 +
	showIcon = false,
678 +
	...props
604 679
}: React.ComponentProps<"div"> & {
605 -
  showIcon?: boolean
680 +
	showIcon?: boolean;
606 681
}) {
607 -
  // Random width between 50 to 90%.
608 -
  const width = React.useMemo(() => {
609 -
    return `${Math.floor(Math.random() * 40) + 50}%`
610 -
  }, [])
682 +
	// Random width between 50 to 90%.
683 +
	const width = React.useMemo(() => {
684 +
		return `${Math.floor(Math.random() * 40) + 50}%`;
685 +
	}, []);
611 686
612 -
  return (
613 -
    <div
614 -
      data-slot="sidebar-menu-skeleton"
615 -
      data-sidebar="menu-skeleton"
616 -
      className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
617 -
      {...props}
618 -
    >
619 -
      {showIcon && (
620 -
        <Skeleton
621 -
          className="size-4 rounded-md"
622 -
          data-sidebar="menu-skeleton-icon"
623 -
        />
624 -
      )}
625 -
      <Skeleton
626 -
        className="h-4 max-w-(--skeleton-width) flex-1"
627 -
        data-sidebar="menu-skeleton-text"
628 -
        style={
629 -
          {
630 -
            "--skeleton-width": width,
631 -
          } as React.CSSProperties
632 -
        }
633 -
      />
634 -
    </div>
635 -
  )
687 +
	return (
688 +
		<div
689 +
			data-slot="sidebar-menu-skeleton"
690 +
			data-sidebar="menu-skeleton"
691 +
			className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
692 +
			{...props}
693 +
		>
694 +
			{showIcon && (
695 +
				<Skeleton
696 +
					className="size-4 rounded-md"
697 +
					data-sidebar="menu-skeleton-icon"
698 +
				/>
699 +
			)}
700 +
			<Skeleton
701 +
				className="h-4 max-w-(--skeleton-width) flex-1"
702 +
				data-sidebar="menu-skeleton-text"
703 +
				style={
704 +
					{
705 +
						"--skeleton-width": width,
706 +
					} as React.CSSProperties
707 +
				}
708 +
			/>
709 +
		</div>
710 +
	);
636 711
}
637 712
638 713
function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
639 -
  return (
640 -
    <ul
641 -
      data-slot="sidebar-menu-sub"
642 -
      data-sidebar="menu-sub"
643 -
      className={cn(
644 -
        "border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
645 -
        "group-data-[collapsible=icon]:hidden",
646 -
        className
647 -
      )}
648 -
      {...props}
649 -
    />
650 -
  )
714 +
	return (
715 +
		<ul
716 +
			data-slot="sidebar-menu-sub"
717 +
			data-sidebar="menu-sub"
718 +
			className={cn(
719 +
				"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
720 +
				"group-data-[collapsible=icon]:hidden",
721 +
				className,
722 +
			)}
723 +
			{...props}
724 +
		/>
725 +
	);
651 726
}
652 727
653 728
function SidebarMenuSubItem({
654 -
  className,
655 -
  ...props
729 +
	className,
730 +
	...props
656 731
}: React.ComponentProps<"li">) {
657 -
  return (
658 -
    <li
659 -
      data-slot="sidebar-menu-sub-item"
660 -
      data-sidebar="menu-sub-item"
661 -
      className={cn("group/menu-sub-item relative", className)}
662 -
      {...props}
663 -
    />
664 -
  )
732 +
	return (
733 +
		<li
734 +
			data-slot="sidebar-menu-sub-item"
735 +
			data-sidebar="menu-sub-item"
736 +
			className={cn("group/menu-sub-item relative", className)}
737 +
			{...props}
738 +
		/>
739 +
	);
665 740
}
666 741
667 742
function SidebarMenuSubButton({
668 -
  asChild = false,
669 -
  size = "md",
670 -
  isActive = false,
671 -
  className,
672 -
  ...props
743 +
	asChild = false,
744 +
	size = "md",
745 +
	isActive = false,
746 +
	className,
747 +
	...props
673 748
}: React.ComponentProps<"a"> & {
674 -
  asChild?: boolean
675 -
  size?: "sm" | "md"
676 -
  isActive?: boolean
749 +
	asChild?: boolean;
750 +
	size?: "sm" | "md";
751 +
	isActive?: boolean;
677 752
}) {
678 -
  const Comp = asChild ? Slot : "a"
753 +
	const Comp = asChild ? Slot : "a";
679 754
680 -
  return (
681 -
    <Comp
682 -
      data-slot="sidebar-menu-sub-button"
683 -
      data-sidebar="menu-sub-button"
684 -
      data-size={size}
685 -
      data-active={isActive}
686 -
      className={cn(
687 -
        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
688 -
        "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
689 -
        size === "sm" && "text-xs",
690 -
        size === "md" && "text-sm",
691 -
        "group-data-[collapsible=icon]:hidden",
692 -
        className
693 -
      )}
694 -
      {...props}
695 -
    />
696 -
  )
755 +
	return (
756 +
		<Comp
757 +
			data-slot="sidebar-menu-sub-button"
758 +
			data-sidebar="menu-sub-button"
759 +
			data-size={size}
760 +
			data-active={isActive}
761 +
			className={cn(
762 +
				"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
763 +
				"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
764 +
				size === "sm" && "text-xs",
765 +
				size === "md" && "text-sm",
766 +
				"group-data-[collapsible=icon]:hidden",
767 +
				className,
768 +
			)}
769 +
			{...props}
770 +
		/>
771 +
	);
697 772
}
698 773
699 774
export {
700 -
  Sidebar,
701 -
  SidebarContent,
702 -
  SidebarFooter,
703 -
  SidebarGroup,
704 -
  SidebarGroupAction,
705 -
  SidebarGroupContent,
706 -
  SidebarGroupLabel,
707 -
  SidebarHeader,
708 -
  SidebarInput,
709 -
  SidebarInset,
710 -
  SidebarMenu,
711 -
  SidebarMenuAction,
712 -
  SidebarMenuBadge,
713 -
  SidebarMenuButton,
714 -
  SidebarMenuItem,
715 -
  SidebarMenuSkeleton,
716 -
  SidebarMenuSub,
717 -
  SidebarMenuSubButton,
718 -
  SidebarMenuSubItem,
719 -
  SidebarProvider,
720 -
  SidebarRail,
721 -
  SidebarSeparator,
722 -
  SidebarTrigger,
723 -
  useSidebar,
724 -
}
775 +
	Sidebar,
776 +
	SidebarContent,
777 +
	SidebarFooter,
778 +
	SidebarGroup,
779 +
	SidebarGroupAction,
780 +
	SidebarGroupContent,
781 +
	SidebarGroupLabel,
782 +
	SidebarHeader,
783 +
	SidebarInput,
784 +
	SidebarInset,
785 +
	SidebarMenu,
786 +
	SidebarMenuAction,
787 +
	SidebarMenuBadge,
788 +
	SidebarMenuButton,
789 +
	SidebarMenuItem,
790 +
	SidebarMenuSkeleton,
791 +
	SidebarMenuSub,
792 +
	SidebarMenuSubButton,
793 +
	SidebarMenuSubItem,
794 +
	SidebarProvider,
795 +
	SidebarRail,
796 +
	SidebarSeparator,
797 +
	SidebarTrigger,
798 +
	SidebarShowButton,
799 +
	useSidebar,
800 +
};