The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit 32cb43fd4001Commit detailsView latest

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