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