Rami-Troudi commited on
Commit
eaf815d
·
1 Parent(s): 56d7f80

Improve sidebar

Browse files
ui/app/api/discovery/route.ts CHANGED
@@ -4,7 +4,7 @@ export async function POST(request: Request) {
4
  const body = await request.json();
5
  const { query } = body;
6
 
7
- console.log("Starting discovery for:", query);
8
 
9
  // Here you would typically call your Python backend
10
  // e.g., await fetch('http://localhost:8000/api/discovery', { ... })
 
4
  const body = await request.json();
5
  const { query } = body;
6
 
7
+ console.info("Starting discovery for:", query);
8
 
9
  // Here you would typically call your Python backend
10
  // e.g., await fetch('http://localhost:8000/api/discovery', { ... })
ui/app/api/explorer/route.ts CHANGED
@@ -11,7 +11,7 @@ export async function GET(request: Request) {
11
  searchParams.get("colorBy") || undefined
12
  );
13
  return NextResponse.json(data);
14
- } catch (e) {
15
  return NextResponse.json({ error: "Invalid parameters" }, { status: 400 });
16
  }
17
  }
 
11
  searchParams.get("colorBy") || undefined
12
  );
13
  return NextResponse.json(data);
14
+ } catch {
15
  return NextResponse.json({ error: "Invalid parameters" }, { status: 400 });
16
  }
17
  }
ui/app/layout.tsx CHANGED
@@ -1,7 +1,11 @@
1
  import type { Metadata, Viewport } from "next";
2
  import { Geist, Geist_Mono } from "next/font/google";
3
  import "./globals.css";
4
- import { Sidebar } from "@/components/sidebar";
 
 
 
 
5
 
6
  const geistSans = Geist({
7
  variable: "--font-geist-sans",
@@ -41,14 +45,18 @@ export default function RootLayout({
41
  return (
42
  <html lang="en">
43
  <body
44
- className={`${geistSans.variable} ${geistMono.variable} antialiased flex h-screen overflow-hidden`}
45
  >
46
- <Sidebar />
47
- <main className="flex-1 overflow-y-auto bg-background p-8">
48
- <div className="mx-auto max-w-7xl">
49
- {children}
50
- </div>
51
- </main>
 
 
 
 
52
  </body>
53
  </html>
54
  );
 
1
  import type { Metadata, Viewport } from "next";
2
  import { Geist, Geist_Mono } from "next/font/google";
3
  import "./globals.css";
4
+ import { AppSidebar } from "@/components/sidebar";
5
+ import {
6
+ SidebarProvider,
7
+ SidebarInset,
8
+ } from "@/components/animate-ui/components/radix/sidebar";
9
 
10
  const geistSans = Geist({
11
  variable: "--font-geist-sans",
 
45
  return (
46
  <html lang="en">
47
  <body
48
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
49
  >
50
+ <SidebarProvider>
51
+ <AppSidebar />
52
+ <SidebarInset>
53
+ <main className="flex-1 overflow-y-auto bg-background p-8">
54
+ <div className="mx-auto max-w-7xl">
55
+ {children}
56
+ </div>
57
+ </main>
58
+ </SidebarInset>
59
+ </SidebarProvider>
60
  </body>
61
  </html>
62
  );
ui/app/not-found.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button } from "@/components/ui/button";
2
+ import {
3
+ Empty,
4
+ EmptyContent,
5
+ EmptyDescription,
6
+ EmptyHeader,
7
+ EmptyTitle,
8
+ } from "@/components/ui/empty";
9
+ import { Compass, Home } from "lucide-react";
10
+
11
+ export function NotFoundPage() {
12
+ return (
13
+ <div className="flex w-full items-center justify-center">
14
+ <div className="flex h-screen items-center border-x">
15
+ <div>
16
+ <div className="absolute inset-x-0 h-px bg-border" />
17
+ <Empty>
18
+ <EmptyHeader>
19
+ <EmptyTitle className="font-black font-mono text-8xl">
20
+ 404
21
+ </EmptyTitle>
22
+ <EmptyDescription className="text-nowrap">
23
+ The page you're looking for might have been <br />
24
+ moved or doesn't exist.
25
+ </EmptyDescription>
26
+ </EmptyHeader>
27
+ <EmptyContent>
28
+ <div className="flex gap-2">
29
+ <Button asChild>
30
+ <a href="#">
31
+ <Home /> Go Home
32
+ </a>
33
+ </Button>
34
+
35
+ <Button asChild variant="outline">
36
+ <a href="#">
37
+ <Compass /> Explore
38
+ </a>
39
+ </Button>
40
+ </div>
41
+ </EmptyContent>
42
+ </Empty>
43
+ <div className="absolute inset-x-0 h-px bg-border" />
44
+ </div>
45
+ </div>
46
+ </div>
47
+ );
48
+ }
ui/components/animate-ui/components/animate/tooltip.tsx ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react';
2
+ import * as motion from 'motion/react-client';
3
+
4
+ import {
5
+ TooltipProvider as TooltipProviderPrimitive,
6
+ Tooltip as TooltipPrimitive,
7
+ TooltipTrigger as TooltipTriggerPrimitive,
8
+ TooltipContent as TooltipContentPrimitive,
9
+ TooltipArrow as TooltipArrowPrimitive,
10
+ type TooltipProviderProps as TooltipProviderPrimitiveProps,
11
+ type TooltipProps as TooltipPrimitiveProps,
12
+ type TooltipTriggerProps as TooltipTriggerPrimitiveProps,
13
+ type TooltipContentProps as TooltipContentPrimitiveProps,
14
+ } from '@/components/animate-ui/primitives/animate/tooltip';
15
+ import { cn } from '@/lib/utils';
16
+
17
+ type TooltipProviderProps = TooltipProviderPrimitiveProps;
18
+
19
+ function TooltipProvider({ openDelay = 0, ...props }: TooltipProviderProps) {
20
+ return <TooltipProviderPrimitive openDelay={openDelay} {...props} />;
21
+ }
22
+
23
+ type TooltipProps = TooltipPrimitiveProps;
24
+
25
+ function Tooltip({ sideOffset = 10, ...props }: TooltipProps) {
26
+ return <TooltipPrimitive sideOffset={sideOffset} {...props} />;
27
+ }
28
+
29
+ type TooltipTriggerProps = TooltipTriggerPrimitiveProps;
30
+
31
+ function TooltipTrigger({ ...props }: TooltipTriggerProps) {
32
+ return <TooltipTriggerPrimitive {...props} />;
33
+ }
34
+
35
+ type TooltipContentProps = Omit<TooltipContentPrimitiveProps, 'asChild'> & {
36
+ children: React.ReactNode;
37
+ layout?: boolean | 'position' | 'size' | 'preserve-aspect';
38
+ };
39
+
40
+ function TooltipContent({
41
+ className,
42
+ children,
43
+ layout = 'preserve-aspect',
44
+ ...props
45
+ }: TooltipContentProps) {
46
+ return (
47
+ <TooltipContentPrimitive
48
+ className={cn(
49
+ 'z-50 w-fit bg-primary text-primary-foreground rounded-md',
50
+ className,
51
+ )}
52
+ {...props}
53
+ >
54
+ <motion.div className="overflow-hidden px-3 py-1.5 text-xs text-balance">
55
+ <motion.div layout={layout}>{children}</motion.div>
56
+ </motion.div>
57
+ <TooltipArrowPrimitive
58
+ className="fill-primary size-3 data-[side='bottom']:translate-y-[1px] data-[side='right']:translate-x-[1px] data-[side='left']:translate-x-[-1px] data-[side='top']:translate-y-[-1px]"
59
+ tipRadius={2}
60
+ />
61
+ </TooltipContentPrimitive>
62
+ );
63
+ }
64
+
65
+ export {
66
+ TooltipProvider,
67
+ Tooltip,
68
+ TooltipTrigger,
69
+ TooltipContent,
70
+ type TooltipProviderProps,
71
+ type TooltipProps,
72
+ type TooltipTriggerProps,
73
+ type TooltipContentProps,
74
+ };
ui/components/animate-ui/components/radix/dropdown-menu.tsx ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5
+ import { Check, ChevronRight, Circle } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const DropdownMenu = DropdownMenuPrimitive.Root
10
+
11
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12
+
13
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group
14
+
15
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16
+
17
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub
18
+
19
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20
+
21
+ const DropdownMenuSubTrigger = React.forwardRef<
22
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
23
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
24
+ inset?: boolean
25
+ }
26
+ >(({ className, inset, children, ...props }, ref) => (
27
+ <DropdownMenuPrimitive.SubTrigger
28
+ ref={ref}
29
+ className={cn(
30
+ "flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
31
+ inset && "pl-8",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <ChevronRight className="ml-auto" />
38
+ </DropdownMenuPrimitive.SubTrigger>
39
+ ))
40
+ DropdownMenuSubTrigger.displayName =
41
+ DropdownMenuPrimitive.SubTrigger.displayName
42
+
43
+ const DropdownMenuSubContent = React.forwardRef<
44
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
45
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
46
+ >(({ className, ...props }, ref) => (
47
+ <DropdownMenuPrimitive.SubContent
48
+ ref={ref}
49
+ className={cn(
50
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
51
+ className
52
+ )}
53
+ {...props}
54
+ />
55
+ ))
56
+ DropdownMenuSubContent.displayName =
57
+ DropdownMenuPrimitive.SubContent.displayName
58
+
59
+ const DropdownMenuContent = React.forwardRef<
60
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
61
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
62
+ >(({ className, sideOffset = 4, ...props }, ref) => (
63
+ <DropdownMenuPrimitive.Portal>
64
+ <DropdownMenuPrimitive.Content
65
+ ref={ref}
66
+ sideOffset={sideOffset}
67
+ className={cn(
68
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
69
+ className
70
+ )}
71
+ {...props}
72
+ />
73
+ </DropdownMenuPrimitive.Portal>
74
+ ))
75
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76
+
77
+ const DropdownMenuItem = React.forwardRef<
78
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
79
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
80
+ inset?: boolean
81
+ }
82
+ >(({ className, inset, ...props }, ref) => (
83
+ <DropdownMenuPrimitive.Item
84
+ ref={ref}
85
+ className={cn(
86
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
87
+ inset && "pl-8",
88
+ className
89
+ )}
90
+ {...props}
91
+ />
92
+ ))
93
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94
+
95
+ const DropdownMenuCheckboxItem = React.forwardRef<
96
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
97
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
98
+ >(({ className, children, checked, ...props }, ref) => (
99
+ <DropdownMenuPrimitive.CheckboxItem
100
+ ref={ref}
101
+ className={cn(
102
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
103
+ className
104
+ )}
105
+ checked={checked}
106
+ {...props}
107
+ >
108
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
109
+ <DropdownMenuPrimitive.ItemIndicator>
110
+ <Check className="h-4 w-4" />
111
+ </DropdownMenuPrimitive.ItemIndicator>
112
+ </span>
113
+ {children}
114
+ </DropdownMenuPrimitive.CheckboxItem>
115
+ ))
116
+ DropdownMenuCheckboxItem.displayName =
117
+ DropdownMenuPrimitive.CheckboxItem.displayName
118
+
119
+ const DropdownMenuRadioItem = React.forwardRef<
120
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
121
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
122
+ >(({ className, children, ...props }, ref) => (
123
+ <DropdownMenuPrimitive.RadioItem
124
+ ref={ref}
125
+ className={cn(
126
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
127
+ className
128
+ )}
129
+ {...props}
130
+ >
131
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
132
+ <DropdownMenuPrimitive.ItemIndicator>
133
+ <Circle className="h-2 w-2 fill-current" />
134
+ </DropdownMenuPrimitive.ItemIndicator>
135
+ </span>
136
+ {children}
137
+ </DropdownMenuPrimitive.RadioItem>
138
+ ))
139
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140
+
141
+ const DropdownMenuLabel = React.forwardRef<
142
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
143
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
144
+ inset?: boolean
145
+ }
146
+ >(({ className, inset, ...props }, ref) => (
147
+ <DropdownMenuPrimitive.Label
148
+ ref={ref}
149
+ className={cn(
150
+ "px-2 py-1.5 text-sm font-semibold",
151
+ inset && "pl-8",
152
+ className
153
+ )}
154
+ {...props}
155
+ />
156
+ ))
157
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158
+
159
+ const DropdownMenuSeparator = React.forwardRef<
160
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
161
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
162
+ >(({ className, ...props }, ref) => (
163
+ <DropdownMenuPrimitive.Separator
164
+ ref={ref}
165
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
166
+ {...props}
167
+ />
168
+ ))
169
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170
+
171
+ const DropdownMenuShortcut = ({
172
+ className,
173
+ ...props
174
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
175
+ return (
176
+ <span
177
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
178
+ {...props}
179
+ />
180
+ )
181
+ }
182
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183
+
184
+ export {
185
+ DropdownMenu,
186
+ DropdownMenuTrigger,
187
+ DropdownMenuContent,
188
+ DropdownMenuItem,
189
+ DropdownMenuCheckboxItem,
190
+ DropdownMenuRadioItem,
191
+ DropdownMenuLabel,
192
+ DropdownMenuSeparator,
193
+ DropdownMenuShortcut,
194
+ DropdownMenuGroup,
195
+ DropdownMenuPortal,
196
+ DropdownMenuSub,
197
+ DropdownMenuSubContent,
198
+ DropdownMenuSubTrigger,
199
+ DropdownMenuRadioGroup,
200
+ }
ui/components/animate-ui/components/radix/sheet.tsx ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react';
2
+
3
+ import {
4
+ Sheet as SheetPrimitive,
5
+ SheetTrigger as SheetTriggerPrimitive,
6
+ SheetOverlay as SheetOverlayPrimitive,
7
+ SheetClose as SheetClosePrimitive,
8
+ SheetPortal as SheetPortalPrimitive,
9
+ SheetContent as SheetContentPrimitive,
10
+ SheetHeader as SheetHeaderPrimitive,
11
+ SheetFooter as SheetFooterPrimitive,
12
+ SheetTitle as SheetTitlePrimitive,
13
+ SheetDescription as SheetDescriptionPrimitive,
14
+ type SheetProps as SheetPrimitiveProps,
15
+ type SheetTriggerProps as SheetTriggerPrimitiveProps,
16
+ type SheetOverlayProps as SheetOverlayPrimitiveProps,
17
+ type SheetCloseProps as SheetClosePrimitiveProps,
18
+ type SheetContentProps as SheetContentPrimitiveProps,
19
+ type SheetHeaderProps as SheetHeaderPrimitiveProps,
20
+ type SheetFooterProps as SheetFooterPrimitiveProps,
21
+ type SheetTitleProps as SheetTitlePrimitiveProps,
22
+ type SheetDescriptionProps as SheetDescriptionPrimitiveProps,
23
+ } from '@/components/animate-ui/primitives/radix/sheet';
24
+ import { cn } from '@/lib/utils';
25
+ import { XIcon } from 'lucide-react';
26
+
27
+ type SheetProps = SheetPrimitiveProps;
28
+
29
+ function Sheet(props: SheetProps) {
30
+ return <SheetPrimitive {...props} />;
31
+ }
32
+
33
+ type SheetTriggerProps = SheetTriggerPrimitiveProps;
34
+
35
+ function SheetTrigger(props: SheetTriggerProps) {
36
+ return <SheetTriggerPrimitive {...props} />;
37
+ }
38
+
39
+ type SheetOverlayProps = SheetOverlayPrimitiveProps;
40
+
41
+ function SheetOverlay({ className, ...props }: SheetOverlayProps) {
42
+ return (
43
+ <SheetOverlayPrimitive
44
+ className={cn('fixed inset-0 z-50 bg-black/50', className)}
45
+ {...props}
46
+ />
47
+ );
48
+ }
49
+
50
+ type SheetCloseProps = SheetClosePrimitiveProps;
51
+
52
+ function SheetClose(props: SheetCloseProps) {
53
+ return <SheetClosePrimitive {...props} />;
54
+ }
55
+
56
+ type SheetContentProps = SheetContentPrimitiveProps & {
57
+ showCloseButton?: boolean;
58
+ };
59
+
60
+ function SheetContent({
61
+ className,
62
+ children,
63
+ side = 'right',
64
+ showCloseButton = true,
65
+ ...props
66
+ }: SheetContentProps) {
67
+ return (
68
+ <SheetPortalPrimitive>
69
+ <SheetOverlay />
70
+ <SheetContentPrimitive
71
+ className={cn(
72
+ 'bg-background fixed z-50 flex flex-col gap-4 shadow-lg',
73
+ side === 'right' && 'h-full w-[350px] border-l',
74
+ side === 'left' && 'h-full w-[350px] border-r',
75
+ side === 'top' && 'w-full h-[350px] border-b',
76
+ side === 'bottom' && 'w-full h-[350px] border-t',
77
+ className,
78
+ )}
79
+ side={side}
80
+ {...props}
81
+ >
82
+ {children}
83
+ {showCloseButton && (
84
+ <SheetClose className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
85
+ <XIcon className="size-4" />
86
+ <span className="sr-only">Close</span>
87
+ </SheetClose>
88
+ )}
89
+ </SheetContentPrimitive>
90
+ </SheetPortalPrimitive>
91
+ );
92
+ }
93
+
94
+ type SheetHeaderProps = SheetHeaderPrimitiveProps;
95
+
96
+ function SheetHeader({ className, ...props }: SheetHeaderProps) {
97
+ return (
98
+ <SheetHeaderPrimitive
99
+ className={cn('flex flex-col gap-1.5 p-4', className)}
100
+ {...props}
101
+ />
102
+ );
103
+ }
104
+
105
+ type SheetFooterProps = SheetFooterPrimitiveProps;
106
+
107
+ function SheetFooter({ className, ...props }: SheetFooterProps) {
108
+ return (
109
+ <SheetFooterPrimitive
110
+ className={cn('mt-auto flex flex-col gap-2 p-4', className)}
111
+ {...props}
112
+ />
113
+ );
114
+ }
115
+
116
+ type SheetTitleProps = SheetTitlePrimitiveProps;
117
+
118
+ function SheetTitle({ className, ...props }: SheetTitleProps) {
119
+ return (
120
+ <SheetTitlePrimitive
121
+ className={cn('text-foreground font-semibold', className)}
122
+ {...props}
123
+ />
124
+ );
125
+ }
126
+
127
+ type SheetDescriptionProps = SheetDescriptionPrimitiveProps;
128
+
129
+ function SheetDescription({ className, ...props }: SheetDescriptionProps) {
130
+ return (
131
+ <SheetDescriptionPrimitive
132
+ className={cn('text-muted-foreground text-sm', className)}
133
+ {...props}
134
+ />
135
+ );
136
+ }
137
+
138
+ export {
139
+ Sheet,
140
+ SheetTrigger,
141
+ SheetClose,
142
+ SheetContent,
143
+ SheetHeader,
144
+ SheetFooter,
145
+ SheetTitle,
146
+ SheetDescription,
147
+ type SheetProps,
148
+ type SheetTriggerProps,
149
+ type SheetCloseProps,
150
+ type SheetContentProps,
151
+ type SheetHeaderProps,
152
+ type SheetFooterProps,
153
+ type SheetTitleProps,
154
+ type SheetDescriptionProps,
155
+ };
ui/components/animate-ui/components/radix/sidebar.tsx ADDED
@@ -0,0 +1,810 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Slot } from 'radix-ui';
5
+ import { cva, VariantProps } from 'class-variance-authority';
6
+ import { PanelLeftIcon } from 'lucide-react';
7
+ import { type Transition } from 'motion/react';
8
+
9
+ import { useIsMobile } from '@/hooks/use-mobile';
10
+ import { cn } from '@/lib/utils';
11
+ import { Button } from '@/components/ui/button';
12
+ import { Input } from '@/components/ui/input';
13
+ import { Separator } from '@/components/ui/separator';
14
+ import { Skeleton } from '@/components/ui/skeleton';
15
+ import {
16
+ Sheet,
17
+ SheetContent,
18
+ SheetDescription,
19
+ SheetHeader,
20
+ SheetTitle,
21
+ } from '@/components/animate-ui/components/radix/sheet';
22
+ import {
23
+ TooltipProvider,
24
+ Tooltip,
25
+ TooltipContent,
26
+ TooltipTrigger,
27
+ } from '@/components/animate-ui/components/animate/tooltip';
28
+ import {
29
+ Highlight,
30
+ HighlightItem,
31
+ } from '@/components/animate-ui/primitives/effects/highlight';
32
+ import { getStrictContext } from '@/lib/get-strict-context';
33
+
34
+ const SIDEBAR_COOKIE_NAME = 'sidebar_state';
35
+ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
36
+ const SIDEBAR_WIDTH = '16rem';
37
+ const SIDEBAR_WIDTH_MOBILE = '18rem';
38
+ const SIDEBAR_WIDTH_ICON = '3rem';
39
+ const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
40
+
41
+ type SidebarContextProps = {
42
+ state: 'expanded' | 'collapsed';
43
+ open: boolean;
44
+ setOpen: (open: boolean) => void;
45
+ openMobile: boolean;
46
+ setOpenMobile: (open: boolean) => void;
47
+ isMobile: boolean;
48
+ toggleSidebar: () => void;
49
+ };
50
+
51
+ const [LocalSidebarProvider, useSidebar] =
52
+ getStrictContext<SidebarContextProps>('SidebarContext');
53
+
54
+ type SidebarProviderProps = React.ComponentProps<'div'> & {
55
+ defaultOpen?: boolean;
56
+ open?: boolean;
57
+ onOpenChange?: (open: boolean) => void;
58
+ };
59
+
60
+ function SidebarProvider({
61
+ defaultOpen = true,
62
+ open: openProp,
63
+ onOpenChange: setOpenProp,
64
+ className,
65
+ style,
66
+ children,
67
+ ...props
68
+ }: SidebarProviderProps) {
69
+ const isMobile = useIsMobile();
70
+ const [openMobile, setOpenMobile] = React.useState(false);
71
+
72
+ // This is the internal state of the sidebar.
73
+ // We use openProp and setOpenProp for control from outside the component.
74
+ const [_open, _setOpen] = React.useState(defaultOpen);
75
+ const open = openProp ?? _open;
76
+ const setOpen = React.useCallback(
77
+ (value: boolean | ((value: boolean) => boolean)) => {
78
+ const openState = typeof value === 'function' ? value(open) : value;
79
+ if (setOpenProp) {
80
+ setOpenProp(openState);
81
+ } else {
82
+ _setOpen(openState);
83
+ }
84
+
85
+ // This sets the cookie to keep the sidebar state.
86
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
87
+ },
88
+ [setOpenProp, open],
89
+ );
90
+
91
+ // Helper to toggle the sidebar.
92
+ const toggleSidebar = React.useCallback(() => {
93
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
94
+ }, [isMobile, setOpen, setOpenMobile]);
95
+
96
+ // Adds a keyboard shortcut to toggle the sidebar.
97
+ React.useEffect(() => {
98
+ const handleKeyDown = (event: KeyboardEvent) => {
99
+ if (
100
+ event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
101
+ (event.metaKey || event.ctrlKey)
102
+ ) {
103
+ event.preventDefault();
104
+ toggleSidebar();
105
+ }
106
+ };
107
+
108
+ window.addEventListener('keydown', handleKeyDown);
109
+ return () => window.removeEventListener('keydown', handleKeyDown);
110
+ }, [toggleSidebar]);
111
+
112
+ // We add a state so that we can do data-state="expanded" or "collapsed".
113
+ // This makes it easier to style the sidebar with Tailwind classes.
114
+ const state = open ? 'expanded' : 'collapsed';
115
+
116
+ const contextValue = React.useMemo<SidebarContextProps>(
117
+ () => ({
118
+ state,
119
+ open,
120
+ setOpen,
121
+ isMobile,
122
+ openMobile,
123
+ setOpenMobile,
124
+ toggleSidebar,
125
+ }),
126
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
127
+ );
128
+
129
+ return (
130
+ <LocalSidebarProvider value={contextValue}>
131
+ <TooltipProvider openDelay={0}>
132
+ <div
133
+ data-slot="sidebar-wrapper"
134
+ style={
135
+ {
136
+ '--sidebar-width': SIDEBAR_WIDTH,
137
+ '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
138
+ ...style,
139
+ } as React.CSSProperties
140
+ }
141
+ className={cn(
142
+ 'group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full',
143
+ className,
144
+ )}
145
+ {...props}
146
+ >
147
+ {children}
148
+ </div>
149
+ </TooltipProvider>
150
+ </LocalSidebarProvider>
151
+ );
152
+ }
153
+
154
+ type SidebarProps = React.ComponentProps<'div'> & {
155
+ side?: 'left' | 'right';
156
+ variant?: 'sidebar' | 'floating' | 'inset';
157
+ collapsible?: 'offcanvas' | 'icon' | 'none';
158
+ containerClassName?: string;
159
+ animateOnHover?: boolean;
160
+ transition?: Transition;
161
+ };
162
+
163
+ function Sidebar({
164
+ side = 'left',
165
+ variant = 'sidebar',
166
+ collapsible = 'offcanvas',
167
+ className,
168
+ children,
169
+ animateOnHover = true,
170
+ containerClassName,
171
+ transition = { type: 'spring', stiffness: 350, damping: 35 },
172
+ ...props
173
+ }: SidebarProps) {
174
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
175
+
176
+ if (collapsible === 'none') {
177
+ return (
178
+ <Highlight
179
+ enabled={animateOnHover}
180
+ hover
181
+ controlledItems
182
+ mode="parent"
183
+ containerClassName={containerClassName}
184
+ transition={transition}
185
+ >
186
+ <div
187
+ data-slot="sidebar"
188
+ className={cn(
189
+ 'bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col',
190
+ className,
191
+ )}
192
+ {...props}
193
+ >
194
+ {children}
195
+ </div>
196
+ </Highlight>
197
+ );
198
+ }
199
+
200
+ if (isMobile) {
201
+ return (
202
+ <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
203
+ <SheetContent
204
+ data-sidebar="sidebar"
205
+ data-slot="sidebar"
206
+ data-mobile="true"
207
+ className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
208
+ style={
209
+ {
210
+ '--sidebar-width': SIDEBAR_WIDTH_MOBILE,
211
+ } as React.CSSProperties
212
+ }
213
+ side={side}
214
+ >
215
+ <SheetHeader className="sr-only">
216
+ <SheetTitle>Sidebar</SheetTitle>
217
+ <SheetDescription>Displays the mobile sidebar.</SheetDescription>
218
+ </SheetHeader>
219
+ <Highlight
220
+ enabled={animateOnHover}
221
+ hover
222
+ controlledItems
223
+ mode="parent"
224
+ containerClassName={cn('h-full', containerClassName)}
225
+ transition={transition}
226
+ >
227
+ <div className="flex h-full w-full flex-col">{children}</div>
228
+ </Highlight>
229
+ </SheetContent>
230
+ </Sheet>
231
+ );
232
+ }
233
+
234
+ return (
235
+ <div
236
+ className="group peer text-sidebar-foreground hidden md:block"
237
+ data-state={state}
238
+ data-collapsible={state === 'collapsed' ? collapsible : ''}
239
+ data-variant={variant}
240
+ data-side={side}
241
+ data-slot="sidebar"
242
+ >
243
+ {/* This is what handles the sidebar gap on desktop */}
244
+ <div
245
+ data-slot="sidebar-gap"
246
+ className={cn(
247
+ 'relative w-(--sidebar-width) bg-transparent transition-[width] duration-400 ease-[cubic-bezier(0.7,-0.15,0.25,1.15)]',
248
+ 'group-data-[collapsible=offcanvas]:w-0',
249
+ 'group-data-[side=right]:rotate-180',
250
+ variant === 'floating' || variant === 'inset'
251
+ ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'
252
+ : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
253
+ )}
254
+ />
255
+ <div
256
+ data-slot="sidebar-container"
257
+ className={cn(
258
+ 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-400 ease-[cubic-bezier(0.75,0,0.25,1)] md:flex',
259
+ side === 'left'
260
+ ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
261
+ : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
262
+ // Adjust the padding for floating and inset variants.
263
+ variant === 'floating' || variant === 'inset'
264
+ ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
265
+ : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',
266
+ className,
267
+ )}
268
+ {...props}
269
+ >
270
+ <Highlight
271
+ containerClassName={cn('size-full', containerClassName)}
272
+ enabled={animateOnHover}
273
+ hover
274
+ controlledItems
275
+ mode="parent"
276
+ forceUpdateBounds
277
+ transition={transition}
278
+ >
279
+ <div
280
+ data-sidebar="sidebar"
281
+ data-slot="sidebar-inner"
282
+ 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"
283
+ >
284
+ {children}
285
+ </div>
286
+ </Highlight>
287
+ </div>
288
+ </div>
289
+ );
290
+ }
291
+
292
+ type SidebarTriggerProps = React.ComponentProps<typeof Button>;
293
+
294
+ function SidebarTrigger({ className, onClick, ...props }: SidebarTriggerProps) {
295
+ const { toggleSidebar } = useSidebar();
296
+
297
+ return (
298
+ <Button
299
+ data-sidebar="trigger"
300
+ data-slot="sidebar-trigger"
301
+ variant="ghost"
302
+ size="icon"
303
+ className={cn('size-7', className)}
304
+ onClick={(event) => {
305
+ onClick?.(event);
306
+ toggleSidebar();
307
+ }}
308
+ {...props}
309
+ >
310
+ <PanelLeftIcon />
311
+ <span className="sr-only">Toggle Sidebar</span>
312
+ </Button>
313
+ );
314
+ }
315
+
316
+ type SidebarRailProps = React.ComponentProps<'button'>;
317
+
318
+ function SidebarRail({ className, ...props }: SidebarRailProps) {
319
+ const { toggleSidebar } = useSidebar();
320
+
321
+ return (
322
+ <button
323
+ data-sidebar="rail"
324
+ data-slot="sidebar-rail"
325
+ aria-label="Toggle Sidebar"
326
+ tabIndex={-1}
327
+ onClick={toggleSidebar}
328
+ title="Toggle Sidebar"
329
+ className={cn(
330
+ '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',
331
+ 'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
332
+ '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
333
+ 'hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',
334
+ '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
335
+ '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
336
+ className,
337
+ )}
338
+ {...props}
339
+ />
340
+ );
341
+ }
342
+
343
+ type SidebarInsetProps = React.ComponentProps<'main'>;
344
+
345
+ function SidebarInset({ className, ...props }: SidebarInsetProps) {
346
+ return (
347
+ <main
348
+ data-slot="sidebar-inset"
349
+ className={cn(
350
+ 'bg-background relative flex w-full flex-1 flex-col',
351
+ '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',
352
+ className,
353
+ )}
354
+ {...props}
355
+ />
356
+ );
357
+ }
358
+
359
+ type SidebarInputProps = React.ComponentProps<typeof Input>;
360
+
361
+ function SidebarInput({ className, ...props }: SidebarInputProps) {
362
+ return (
363
+ <Input
364
+ data-slot="sidebar-input"
365
+ data-sidebar="input"
366
+ className={cn('bg-background h-8 w-full shadow-none', className)}
367
+ {...props}
368
+ />
369
+ );
370
+ }
371
+
372
+ type SidebarHeaderProps = React.ComponentProps<'div'>;
373
+
374
+ function SidebarHeader({ className, ...props }: SidebarHeaderProps) {
375
+ return (
376
+ <div
377
+ data-slot="sidebar-header"
378
+ data-sidebar="header"
379
+ className={cn('flex flex-col gap-2 p-2', className)}
380
+ {...props}
381
+ />
382
+ );
383
+ }
384
+
385
+ type SidebarFooterProps = React.ComponentProps<'div'>;
386
+
387
+ function SidebarFooter({ className, ...props }: SidebarFooterProps) {
388
+ return (
389
+ <div
390
+ data-slot="sidebar-footer"
391
+ data-sidebar="footer"
392
+ className={cn('flex flex-col gap-2 p-2', className)}
393
+ {...props}
394
+ />
395
+ );
396
+ }
397
+
398
+ type SidebarSeparatorProps = React.ComponentProps<typeof Separator>;
399
+
400
+ function SidebarSeparator({ className, ...props }: SidebarSeparatorProps) {
401
+ return (
402
+ <Separator
403
+ data-slot="sidebar-separator"
404
+ data-sidebar="separator"
405
+ className={cn('bg-sidebar-border mx-2 w-auto', className)}
406
+ {...props}
407
+ />
408
+ );
409
+ }
410
+
411
+ type SidebarContentProps = React.ComponentProps<'div'>;
412
+
413
+ function SidebarContent({ className, ...props }: SidebarContentProps) {
414
+ return (
415
+ <div
416
+ data-slot="sidebar-content"
417
+ data-sidebar="content"
418
+ className={cn(
419
+ 'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
420
+ className,
421
+ )}
422
+ {...props}
423
+ />
424
+ );
425
+ }
426
+
427
+ type SidebarGroupProps = React.ComponentProps<'div'>;
428
+
429
+ function SidebarGroup({ className, ...props }: SidebarGroupProps) {
430
+ return (
431
+ <div
432
+ data-slot="sidebar-group"
433
+ data-sidebar="group"
434
+ className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
435
+ {...props}
436
+ />
437
+ );
438
+ }
439
+
440
+ type SidebarGroupLabelProps = React.ComponentProps<'div'> & {
441
+ asChild?: boolean;
442
+ };
443
+
444
+ function SidebarGroupLabel({
445
+ className,
446
+ asChild = false,
447
+ ...props
448
+ }: SidebarGroupLabelProps) {
449
+ const Comp = asChild ? Slot.Root : 'div';
450
+
451
+ return (
452
+ <Comp
453
+ data-slot="sidebar-group-label"
454
+ data-sidebar="group-label"
455
+ className={cn(
456
+ '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-300 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
457
+ 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
458
+ className,
459
+ )}
460
+ {...props}
461
+ />
462
+ );
463
+ }
464
+
465
+ type SidebarGroupActionProps = React.ComponentProps<'button'> & {
466
+ asChild?: boolean;
467
+ };
468
+
469
+ function SidebarGroupAction({
470
+ className,
471
+ asChild = false,
472
+ ...props
473
+ }: SidebarGroupActionProps) {
474
+ const Comp = asChild ? Slot.Root : 'button';
475
+
476
+ return (
477
+ <Comp
478
+ data-slot="sidebar-group-action"
479
+ data-sidebar="group-action"
480
+ className={cn(
481
+ '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',
482
+ // Increases the hit area of the button on mobile.
483
+ 'after:absolute after:-inset-2 md:after:hidden',
484
+ 'group-data-[collapsible=icon]:hidden',
485
+ className,
486
+ )}
487
+ {...props}
488
+ />
489
+ );
490
+ }
491
+
492
+ type SidebarGroupContentProps = React.ComponentProps<'div'>;
493
+
494
+ function SidebarGroupContent({
495
+ className,
496
+ ...props
497
+ }: SidebarGroupContentProps) {
498
+ return (
499
+ <div
500
+ data-slot="sidebar-group-content"
501
+ data-sidebar="group-content"
502
+ className={cn('w-full text-sm', className)}
503
+ {...props}
504
+ />
505
+ );
506
+ }
507
+
508
+ type SidebarMenuProps = React.ComponentProps<'ul'>;
509
+
510
+ function SidebarMenu({ className, ...props }: SidebarMenuProps) {
511
+ return (
512
+ <ul
513
+ data-slot="sidebar-menu"
514
+ data-sidebar="menu"
515
+ className={cn('flex w-full min-w-0 flex-col gap-1', className)}
516
+ {...props}
517
+ />
518
+ );
519
+ }
520
+
521
+ type SidebarMenuItemProps = React.ComponentProps<'li'>;
522
+
523
+ function SidebarMenuItem({ className, ...props }: SidebarMenuItemProps) {
524
+ return (
525
+ <li
526
+ data-slot="sidebar-menu-item"
527
+ data-sidebar="menu-item"
528
+ className={cn('group/menu-item relative', className)}
529
+ {...props}
530
+ />
531
+ );
532
+ }
533
+
534
+ const sidebarMenuButtonActiveVariants = cva(
535
+ 'bg-sidebar-accent text-sidebar-accent-foreground rounded-md',
536
+ {
537
+ variants: {
538
+ variant: {
539
+ default: 'bg-sidebar-accent text-sidebar-accent-foreground',
540
+ outline:
541
+ 'bg-sidebar-accent text-sidebar-accent-foreground shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
542
+ },
543
+ },
544
+ defaultVariants: {
545
+ variant: 'default',
546
+ },
547
+ },
548
+ );
549
+
550
+ const sidebarMenuButtonVariants = cva(
551
+ '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] [&:not([data-highlight])]:hover:bg-sidebar-accent [&:not([data-highlight])]: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 [&:not([data-highlight])]:data-[state=open]:hover:bg-sidebar-accent [&:not([data-highlight])]: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',
552
+ {
553
+ variants: {
554
+ variant: {
555
+ default:
556
+ '[&:not([data-highlight])]:hover:bg-sidebar-accent [&:not([data-highlight])]:hover:text-sidebar-accent-foreground',
557
+ outline:
558
+ 'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] [&:not([data-highlight])]:hover:bg-sidebar-accent [&:not([data-highlight])]:hover:text-sidebar-accent-foreground [&:not([data-highlight])]:hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
559
+ },
560
+ size: {
561
+ default: 'h-8 text-sm',
562
+ sm: 'h-7 text-xs',
563
+ lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
564
+ },
565
+ },
566
+ defaultVariants: {
567
+ variant: 'default',
568
+ size: 'default',
569
+ },
570
+ },
571
+ );
572
+
573
+ type SidebarMenuButtonProps = React.ComponentProps<'button'> & {
574
+ asChild?: boolean;
575
+ isActive?: boolean;
576
+ tooltip?: string | React.ComponentProps<typeof TooltipContent>;
577
+ } & VariantProps<typeof sidebarMenuButtonVariants>;
578
+
579
+ function SidebarMenuButton({
580
+ asChild = false,
581
+ isActive = false,
582
+ variant = 'default',
583
+ size = 'default',
584
+ tooltip,
585
+ className,
586
+ ...props
587
+ }: SidebarMenuButtonProps) {
588
+ const Comp = asChild ? Slot.Root : 'button';
589
+ const { isMobile, state } = useSidebar();
590
+
591
+ const button = (
592
+ <HighlightItem
593
+ activeClassName={sidebarMenuButtonActiveVariants({ variant })}
594
+ >
595
+ <Comp
596
+ data-slot="sidebar-menu-button"
597
+ data-sidebar="menu-button"
598
+ data-size={size}
599
+ data-active={isActive}
600
+ className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
601
+ {...props}
602
+ />
603
+ </HighlightItem>
604
+ );
605
+
606
+ if (!tooltip) {
607
+ return button;
608
+ }
609
+
610
+ if (typeof tooltip === 'string') {
611
+ tooltip = {
612
+ children: tooltip,
613
+ };
614
+ }
615
+
616
+ return (
617
+ <Tooltip side="right" align="center">
618
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
619
+ <TooltipContent hidden={state !== 'collapsed' || isMobile} {...tooltip} />
620
+ </Tooltip>
621
+ );
622
+ }
623
+
624
+ type SidebarMenuActionProps = React.ComponentProps<'button'> & {
625
+ asChild?: boolean;
626
+ showOnHover?: boolean;
627
+ };
628
+
629
+ function SidebarMenuAction({
630
+ className,
631
+ asChild = false,
632
+ showOnHover = false,
633
+ ...props
634
+ }: SidebarMenuActionProps) {
635
+ const Comp = asChild ? Slot.Root : 'button';
636
+
637
+ return (
638
+ <Comp
639
+ data-slot="sidebar-menu-action"
640
+ data-sidebar="menu-action"
641
+ className={cn(
642
+ // Increases the hit area of the button on mobile.
643
+ 'z-[1] 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',
644
+ 'after:absolute after:-inset-2 md:after:hidden',
645
+ 'peer-data-[size=sm]/menu-button:top-1',
646
+ 'peer-data-[size=default]/menu-button:top-1.5',
647
+ 'peer-data-[size=lg]/menu-button:top-2.5',
648
+ 'group-data-[collapsible=icon]:hidden',
649
+ showOnHover &&
650
+ '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',
651
+ className,
652
+ )}
653
+ {...props}
654
+ />
655
+ );
656
+ }
657
+
658
+ type SidebarMenuBadgeProps = React.ComponentProps<'div'>;
659
+
660
+ function SidebarMenuBadge({ className, ...props }: SidebarMenuBadgeProps) {
661
+ return (
662
+ <div
663
+ data-slot="sidebar-menu-badge"
664
+ data-sidebar="menu-badge"
665
+ className={cn(
666
+ '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',
667
+ 'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
668
+ 'peer-data-[size=sm]/menu-button:top-1',
669
+ 'peer-data-[size=default]/menu-button:top-1.5',
670
+ 'peer-data-[size=lg]/menu-button:top-2.5',
671
+ 'group-data-[collapsible=icon]:hidden',
672
+ className,
673
+ )}
674
+ {...props}
675
+ />
676
+ );
677
+ }
678
+
679
+ type SidebarMenuSkeletonProps = React.ComponentProps<'div'> & {
680
+ showIcon?: boolean;
681
+ };
682
+
683
+ function SidebarMenuSkeleton({
684
+ className,
685
+ showIcon = false,
686
+ ...props
687
+ }: SidebarMenuSkeletonProps) {
688
+ // Random width between 50 to 90%.
689
+ const width = React.useMemo(() => {
690
+ return `${Math.floor(Math.random() * 40) + 50}%`;
691
+ }, []);
692
+
693
+ return (
694
+ <div
695
+ data-slot="sidebar-menu-skeleton"
696
+ data-sidebar="menu-skeleton"
697
+ className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
698
+ {...props}
699
+ >
700
+ {showIcon && (
701
+ <Skeleton
702
+ className="size-4 rounded-md"
703
+ data-sidebar="menu-skeleton-icon"
704
+ />
705
+ )}
706
+ <Skeleton
707
+ className="h-4 max-w-(--skeleton-width) flex-1"
708
+ data-sidebar="menu-skeleton-text"
709
+ style={
710
+ {
711
+ '--skeleton-width': width,
712
+ } as React.CSSProperties
713
+ }
714
+ />
715
+ </div>
716
+ );
717
+ }
718
+
719
+ type SidebarMenuSubProps = React.ComponentProps<'ul'>;
720
+
721
+ function SidebarMenuSub({ className, ...props }: SidebarMenuSubProps) {
722
+ return (
723
+ <ul
724
+ data-slot="sidebar-menu-sub"
725
+ data-sidebar="menu-sub"
726
+ className={cn(
727
+ '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',
728
+ 'group-data-[collapsible=icon]:hidden',
729
+ className,
730
+ )}
731
+ {...props}
732
+ />
733
+ );
734
+ }
735
+
736
+ type SidebarMenuSubItemProps = React.ComponentProps<'li'>;
737
+
738
+ function SidebarMenuSubItem({ className, ...props }: SidebarMenuSubItemProps) {
739
+ return (
740
+ <li
741
+ data-slot="sidebar-menu-sub-item"
742
+ data-sidebar="menu-sub-item"
743
+ className={cn('group/menu-sub-item relative', className)}
744
+ {...props}
745
+ />
746
+ );
747
+ }
748
+
749
+ type SidebarMenuSubButtonProps = React.ComponentProps<'a'> & {
750
+ asChild?: boolean;
751
+ size?: 'sm' | 'md';
752
+ isActive?: boolean;
753
+ };
754
+
755
+ function SidebarMenuSubButton({
756
+ asChild = false,
757
+ size = 'md',
758
+ isActive = false,
759
+ className,
760
+ ...props
761
+ }: SidebarMenuSubButtonProps) {
762
+ const Comp = asChild ? Slot.Root : 'a';
763
+
764
+ return (
765
+ <HighlightItem activeClassName="bg-sidebar-accent text-sidebar-accent-foreground rounded-md">
766
+ <Comp
767
+ data-slot="sidebar-menu-sub-button"
768
+ data-sidebar="menu-sub-button"
769
+ data-size={size}
770
+ data-active={isActive}
771
+ className={cn(
772
+ 'text-sidebar-foreground ring-sidebar-ring [&:not([data-highlight])]:hover:bg-sidebar-accent [&:not([data-highlight])]: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',
773
+ 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
774
+ size === 'sm' && 'text-xs',
775
+ size === 'md' && 'text-sm',
776
+ 'group-data-[collapsible=icon]:hidden',
777
+ className,
778
+ )}
779
+ {...props}
780
+ />
781
+ </HighlightItem>
782
+ );
783
+ }
784
+
785
+ export {
786
+ Sidebar,
787
+ SidebarContent,
788
+ SidebarFooter,
789
+ SidebarGroup,
790
+ SidebarGroupAction,
791
+ SidebarGroupContent,
792
+ SidebarGroupLabel,
793
+ SidebarHeader,
794
+ SidebarInput,
795
+ SidebarInset,
796
+ SidebarMenu,
797
+ SidebarMenuAction,
798
+ SidebarMenuBadge,
799
+ SidebarMenuButton,
800
+ SidebarMenuItem,
801
+ SidebarMenuSkeleton,
802
+ SidebarMenuSub,
803
+ SidebarMenuSubButton,
804
+ SidebarMenuSubItem,
805
+ SidebarProvider,
806
+ SidebarRail,
807
+ SidebarSeparator,
808
+ SidebarTrigger,
809
+ useSidebar,
810
+ };
ui/components/animate-ui/primitives/animate/slot.tsx ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { motion, isMotionComponent, type HTMLMotionProps } from 'motion/react';
5
+ import { cn } from '@/lib/utils';
6
+
7
+ type AnyProps = Record<string, unknown>;
8
+
9
+ type DOMMotionProps<T extends HTMLElement = HTMLElement> = Omit<
10
+ HTMLMotionProps<keyof HTMLElementTagNameMap>,
11
+ 'ref'
12
+ > & { ref?: React.Ref<T> };
13
+
14
+ type WithAsChild<Base extends object> =
15
+ | (Base & { asChild: true; children: React.ReactElement })
16
+ | (Base & { asChild?: false | undefined });
17
+
18
+ type SlotProps<T extends HTMLElement = HTMLElement> = {
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ children?: any;
21
+ } & DOMMotionProps<T>;
22
+
23
+ function mergeRefs<T>(
24
+ ...refs: (React.Ref<T> | undefined)[]
25
+ ): React.RefCallback<T> {
26
+ return (node) => {
27
+ refs.forEach((ref) => {
28
+ if (!ref) return;
29
+ if (typeof ref === 'function') {
30
+ ref(node);
31
+ } else {
32
+ (ref as React.RefObject<T | null>).current = node;
33
+ }
34
+ });
35
+ };
36
+ }
37
+
38
+ function mergeProps<T extends HTMLElement>(
39
+ childProps: AnyProps,
40
+ slotProps: DOMMotionProps<T>,
41
+ ): AnyProps {
42
+ const merged: AnyProps = { ...childProps, ...slotProps };
43
+
44
+ if (childProps.className || slotProps.className) {
45
+ merged.className = cn(
46
+ childProps.className as string,
47
+ slotProps.className as string,
48
+ );
49
+ }
50
+
51
+ if (childProps.style || slotProps.style) {
52
+ merged.style = {
53
+ ...(childProps.style as React.CSSProperties),
54
+ ...(slotProps.style as React.CSSProperties),
55
+ };
56
+ }
57
+
58
+ return merged;
59
+ }
60
+
61
+ function Slot<T extends HTMLElement = HTMLElement>({
62
+ children,
63
+ ref,
64
+ ...props
65
+ }: SlotProps<T>) {
66
+ const isAlreadyMotion =
67
+ typeof children.type === 'object' &&
68
+ children.type !== null &&
69
+ isMotionComponent(children.type);
70
+
71
+ const Base = React.useMemo(
72
+ () =>
73
+ isAlreadyMotion
74
+ ? (children.type as React.ElementType)
75
+ : motion.create(children.type as React.ElementType),
76
+ [isAlreadyMotion, children.type],
77
+ );
78
+
79
+ if (!React.isValidElement(children)) return null;
80
+
81
+ const { ref: childRef, ...childProps } = children.props as AnyProps;
82
+
83
+ const mergedProps = mergeProps(childProps, props);
84
+
85
+ return (
86
+ <Base {...mergedProps} ref={mergeRefs(childRef as React.Ref<T>, ref)} />
87
+ );
88
+ }
89
+
90
+ export {
91
+ Slot,
92
+ type SlotProps,
93
+ type WithAsChild,
94
+ type DOMMotionProps,
95
+ type AnyProps,
96
+ };
ui/components/animate-ui/primitives/animate/tooltip.tsx ADDED
@@ -0,0 +1,572 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import {
5
+ motion,
6
+ AnimatePresence,
7
+ LayoutGroup,
8
+ type Transition,
9
+ type HTMLMotionProps,
10
+ } from 'motion/react';
11
+ import {
12
+ useFloating,
13
+ autoUpdate,
14
+ offset as floatingOffset,
15
+ flip,
16
+ shift,
17
+ arrow as floatingArrow,
18
+ FloatingPortal,
19
+ FloatingArrow,
20
+ type UseFloatingReturn,
21
+ } from '@floating-ui/react';
22
+
23
+ import { getStrictContext } from '@/lib/get-strict-context';
24
+ import { Slot, type WithAsChild } from '@/components/animate-ui/primitives/animate/slot';
25
+
26
+ type Side = 'top' | 'bottom' | 'left' | 'right';
27
+ type Align = 'start' | 'center' | 'end';
28
+
29
+ type TooltipData = {
30
+ contentProps: HTMLMotionProps<'div'>;
31
+ contentAsChild: boolean;
32
+ rect: DOMRect;
33
+ side: Side;
34
+ sideOffset: number;
35
+ align: Align;
36
+ alignOffset: number;
37
+ id: string;
38
+ };
39
+
40
+ type GlobalTooltipContextType = {
41
+ showTooltip: (data: TooltipData) => void;
42
+ hideTooltip: () => void;
43
+ hideImmediate: () => void;
44
+ currentTooltip: TooltipData | null;
45
+ transition: Transition;
46
+ globalId: string;
47
+ setReferenceEl: (el: HTMLElement | null) => void;
48
+ referenceElRef: React.RefObject<HTMLElement | null>;
49
+ };
50
+
51
+ const [GlobalTooltipProvider, useGlobalTooltip] =
52
+ getStrictContext<GlobalTooltipContextType>('GlobalTooltipProvider');
53
+
54
+ type TooltipContextType = {
55
+ props: HTMLMotionProps<'div'>;
56
+ setProps: React.Dispatch<React.SetStateAction<HTMLMotionProps<'div'>>>;
57
+ asChild: boolean;
58
+ setAsChild: React.Dispatch<React.SetStateAction<boolean>>;
59
+ side: Side;
60
+ sideOffset: number;
61
+ align: Align;
62
+ alignOffset: number;
63
+ id: string;
64
+ };
65
+
66
+ const [LocalTooltipProvider, useTooltip] = getStrictContext<TooltipContextType>(
67
+ 'LocalTooltipProvider',
68
+ );
69
+
70
+ type TooltipPosition = { x: number; y: number };
71
+
72
+ function getResolvedSide(placement: Side | `${Side}-${Align}`) {
73
+ if (placement.includes('-')) {
74
+ return placement.split('-')[0] as Side;
75
+ }
76
+ return placement as Side;
77
+ }
78
+
79
+ function initialFromSide(side: Side): Partial<Record<'x' | 'y', number>> {
80
+ if (side === 'top') return { y: 15 };
81
+ if (side === 'bottom') return { y: -15 };
82
+ if (side === 'left') return { x: 15 };
83
+ return { x: -15 };
84
+ }
85
+
86
+ type TooltipProviderProps = {
87
+ children: React.ReactNode;
88
+ id?: string;
89
+ openDelay?: number;
90
+ closeDelay?: number;
91
+ transition?: Transition;
92
+ };
93
+
94
+ function TooltipProvider({
95
+ children,
96
+ id,
97
+ openDelay = 700,
98
+ closeDelay = 300,
99
+ transition = { type: 'spring', stiffness: 300, damping: 35 },
100
+ }: TooltipProviderProps) {
101
+ const globalId = React.useId();
102
+ const [currentTooltip, setCurrentTooltip] =
103
+ React.useState<TooltipData | null>(null);
104
+ const timeoutRef = React.useRef<number | null>(null);
105
+ const lastCloseTimeRef = React.useRef<number>(0);
106
+ const referenceElRef = React.useRef<HTMLElement | null>(null);
107
+
108
+ const showTooltip = React.useCallback(
109
+ (data: TooltipData) => {
110
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
111
+ if (currentTooltip !== null) {
112
+ setCurrentTooltip(data);
113
+ return;
114
+ }
115
+ const now = Date.now();
116
+ const delay = now - lastCloseTimeRef.current < closeDelay ? 0 : openDelay;
117
+ timeoutRef.current = window.setTimeout(
118
+ () => setCurrentTooltip(data),
119
+ delay,
120
+ );
121
+ },
122
+ [openDelay, closeDelay, currentTooltip],
123
+ );
124
+
125
+ const hideTooltip = React.useCallback(() => {
126
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
127
+ timeoutRef.current = window.setTimeout(() => {
128
+ setCurrentTooltip(null);
129
+ lastCloseTimeRef.current = Date.now();
130
+ }, closeDelay);
131
+ }, [closeDelay]);
132
+
133
+ const hideImmediate = React.useCallback(() => {
134
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
135
+ setCurrentTooltip(null);
136
+ lastCloseTimeRef.current = Date.now();
137
+ }, []);
138
+
139
+ const setReferenceEl = React.useCallback((el: HTMLElement | null) => {
140
+ referenceElRef.current = el;
141
+ }, []);
142
+
143
+ React.useEffect(() => {
144
+ const onKeyDown = (e: KeyboardEvent) => {
145
+ if (e.key === 'Escape') hideImmediate();
146
+ };
147
+ window.addEventListener('keydown', onKeyDown, true);
148
+ window.addEventListener('scroll', hideImmediate, true);
149
+ window.addEventListener('resize', hideImmediate, true);
150
+ return () => {
151
+ window.removeEventListener('keydown', onKeyDown, true);
152
+ window.removeEventListener('scroll', hideImmediate, true);
153
+ window.removeEventListener('resize', hideImmediate, true);
154
+ };
155
+ }, [hideImmediate]);
156
+
157
+ return (
158
+ <GlobalTooltipProvider
159
+ value={{
160
+ showTooltip,
161
+ hideTooltip,
162
+ hideImmediate,
163
+ currentTooltip,
164
+ transition,
165
+ globalId: id ?? globalId,
166
+ setReferenceEl,
167
+ referenceElRef,
168
+ }}
169
+ >
170
+ <LayoutGroup>{children}</LayoutGroup>
171
+ <TooltipOverlay />
172
+ </GlobalTooltipProvider>
173
+ );
174
+ }
175
+
176
+ type RenderedTooltipContextType = {
177
+ side: Side;
178
+ align: Align;
179
+ open: boolean;
180
+ };
181
+
182
+ const [RenderedTooltipProvider, useRenderedTooltip] =
183
+ getStrictContext<RenderedTooltipContextType>('RenderedTooltipContext');
184
+
185
+ type FloatingContextType = {
186
+ context: UseFloatingReturn['context'];
187
+ arrowRef: React.RefObject<SVGSVGElement | null>;
188
+ };
189
+
190
+ const [FloatingProvider, useFloatingContext] =
191
+ getStrictContext<FloatingContextType>('FloatingContext');
192
+
193
+ const MotionTooltipArrow = motion.create(FloatingArrow);
194
+
195
+ type TooltipArrowProps = Omit<
196
+ React.ComponentProps<typeof MotionTooltipArrow>,
197
+ 'context'
198
+ > & {
199
+ withTransition?: boolean;
200
+ };
201
+
202
+ function TooltipArrow({
203
+ ref,
204
+ withTransition = true,
205
+ ...props
206
+ }: TooltipArrowProps) {
207
+ const { side, align, open } = useRenderedTooltip();
208
+ const { context, arrowRef } = useFloatingContext();
209
+ const { transition, globalId } = useGlobalTooltip();
210
+ React.useImperativeHandle(ref, () => arrowRef.current as SVGSVGElement);
211
+
212
+ const deg = { top: 0, right: 90, bottom: 180, left: -90 }[side];
213
+
214
+ return (
215
+ <MotionTooltipArrow
216
+ ref={arrowRef}
217
+ context={context}
218
+ data-state={open ? 'open' : 'closed'}
219
+ data-side={side}
220
+ data-align={align}
221
+ data-slot="tooltip-arrow"
222
+ style={{ rotate: deg }}
223
+ layoutId={withTransition ? `tooltip-arrow-${globalId}` : undefined}
224
+ transition={withTransition ? transition : undefined}
225
+ {...props}
226
+ />
227
+ );
228
+ }
229
+
230
+ type TooltipPortalProps = React.ComponentProps<typeof FloatingPortal>;
231
+
232
+ function TooltipPortal(props: TooltipPortalProps) {
233
+ return <FloatingPortal {...props} />;
234
+ }
235
+
236
+ function TooltipOverlay() {
237
+ const { currentTooltip, transition, globalId, referenceElRef } =
238
+ useGlobalTooltip();
239
+
240
+ const [rendered, setRendered] = React.useState<{
241
+ data: TooltipData | null;
242
+ open: boolean;
243
+ }>({ data: null, open: false });
244
+
245
+ const arrowRef = React.useRef<SVGSVGElement | null>(null);
246
+
247
+ const side = rendered.data?.side ?? 'top';
248
+ const align = rendered.data?.align ?? 'center';
249
+
250
+ const { refs, x, y, strategy, context, update } = useFloating({
251
+ placement: align === 'center' ? side : `${side}-${align}`,
252
+ whileElementsMounted: autoUpdate,
253
+ middleware: [
254
+ floatingOffset({
255
+ mainAxis: rendered.data?.sideOffset ?? 0,
256
+ crossAxis: rendered.data?.alignOffset ?? 0,
257
+ }),
258
+ flip(),
259
+ shift({ padding: 8 }),
260
+ floatingArrow({ element: arrowRef }),
261
+ ],
262
+ });
263
+
264
+ React.useEffect(() => {
265
+ if (currentTooltip) {
266
+ setRendered({ data: currentTooltip, open: true });
267
+ } else {
268
+ setRendered((p) => (p.data ? { ...p, open: false } : p));
269
+ }
270
+ }, [currentTooltip]);
271
+
272
+ React.useLayoutEffect(() => {
273
+ if (referenceElRef.current) {
274
+ refs.setReference(referenceElRef.current);
275
+ update();
276
+ }
277
+ }, [referenceElRef, refs, update, rendered.data]);
278
+
279
+ const ready = x != null && y != null;
280
+ const Component = rendered.data?.contentAsChild ? Slot : motion.div;
281
+ const resolvedSide = getResolvedSide(context.placement);
282
+
283
+ return (
284
+ <AnimatePresence mode="wait">
285
+ {rendered.data && ready && (
286
+ <TooltipPortal>
287
+ <div
288
+ ref={refs.setFloating}
289
+ data-slot="tooltip-overlay"
290
+ data-side={resolvedSide}
291
+ data-align={rendered.data.align}
292
+ data-state={rendered.open ? 'open' : 'closed'}
293
+ style={{
294
+ position: strategy,
295
+ top: 0,
296
+ left: 0,
297
+ zIndex: 50,
298
+ transform: `translate3d(${x!}px, ${y!}px, 0)`,
299
+ }}
300
+ >
301
+ <FloatingProvider value={{ context, arrowRef }}>
302
+ <RenderedTooltipProvider
303
+ value={{
304
+ side: resolvedSide,
305
+ align: rendered.data.align,
306
+ open: rendered.open,
307
+ }}
308
+ >
309
+ <Component
310
+ data-slot="tooltip-content"
311
+ data-side={resolvedSide}
312
+ data-align={rendered.data.align}
313
+ data-state={rendered.open ? 'open' : 'closed'}
314
+ layoutId={`tooltip-content-${globalId}`}
315
+ initial={{
316
+ opacity: 0,
317
+ scale: 0,
318
+ ...initialFromSide(rendered.data.side),
319
+ }}
320
+ animate={
321
+ rendered.open
322
+ ? { opacity: 1, scale: 1, x: 0, y: 0 }
323
+ : {
324
+ opacity: 0,
325
+ scale: 0,
326
+ ...initialFromSide(rendered.data.side),
327
+ }
328
+ }
329
+ exit={{
330
+ opacity: 0,
331
+ scale: 0,
332
+ ...initialFromSide(rendered.data.side),
333
+ }}
334
+ onAnimationComplete={() => {
335
+ if (!rendered.open)
336
+ setRendered({ data: null, open: false });
337
+ }}
338
+ transition={transition}
339
+ {...rendered.data.contentProps}
340
+ style={{
341
+ position: 'relative',
342
+ ...(rendered.data.contentProps?.style || {}),
343
+ }}
344
+ />
345
+ </RenderedTooltipProvider>
346
+ </FloatingProvider>
347
+ </div>
348
+ </TooltipPortal>
349
+ )}
350
+ </AnimatePresence>
351
+ );
352
+ }
353
+
354
+ type TooltipProps = {
355
+ children: React.ReactNode;
356
+ side?: Side;
357
+ sideOffset?: number;
358
+ align?: Align;
359
+ alignOffset?: number;
360
+ };
361
+
362
+ function Tooltip({
363
+ children,
364
+ side = 'top',
365
+ sideOffset = 0,
366
+ align = 'center',
367
+ alignOffset = 0,
368
+ }: TooltipProps) {
369
+ const id = React.useId();
370
+ const [props, setProps] = React.useState<HTMLMotionProps<'div'>>({});
371
+ const [asChild, setAsChild] = React.useState(false);
372
+
373
+ return (
374
+ <LocalTooltipProvider
375
+ value={{
376
+ props,
377
+ setProps,
378
+ asChild,
379
+ setAsChild,
380
+ side,
381
+ sideOffset,
382
+ align,
383
+ alignOffset,
384
+ id,
385
+ }}
386
+ >
387
+ {children}
388
+ </LocalTooltipProvider>
389
+ );
390
+ }
391
+
392
+ type TooltipContentProps = WithAsChild<HTMLMotionProps<'div'>>;
393
+
394
+ function shallowEqualWithoutChildren(
395
+ a?: HTMLMotionProps<'div'>,
396
+ b?: HTMLMotionProps<'div'>,
397
+ ) {
398
+ if (a === b) return true;
399
+ if (!a || !b) return false;
400
+ const keysA = Object.keys(a).filter((k) => k !== 'children');
401
+ const keysB = Object.keys(b).filter((k) => k !== 'children');
402
+ if (keysA.length !== keysB.length) return false;
403
+ for (const k of keysA) {
404
+ // @ts-expect-error index
405
+ if (a[k] !== b[k]) return false;
406
+ }
407
+ return true;
408
+ }
409
+
410
+ function TooltipContent({ asChild = false, ...props }: TooltipContentProps) {
411
+ const { setProps, setAsChild } = useTooltip();
412
+ const lastPropsRef = React.useRef<HTMLMotionProps<'div'> | undefined>(
413
+ undefined,
414
+ );
415
+
416
+ React.useEffect(() => {
417
+ if (!shallowEqualWithoutChildren(lastPropsRef.current, props)) {
418
+ lastPropsRef.current = props;
419
+ setProps(props);
420
+ }
421
+ }, [props, setProps]);
422
+
423
+ React.useEffect(() => {
424
+ setAsChild(asChild);
425
+ }, [asChild, setAsChild]);
426
+
427
+ return null;
428
+ }
429
+
430
+ type TooltipTriggerProps = WithAsChild<HTMLMotionProps<'div'>>;
431
+
432
+ function TooltipTrigger({
433
+ ref,
434
+ onMouseEnter,
435
+ onMouseLeave,
436
+ onFocus,
437
+ onBlur,
438
+ onPointerDown,
439
+ asChild = false,
440
+ ...props
441
+ }: TooltipTriggerProps) {
442
+ const {
443
+ props: contentProps,
444
+ asChild: contentAsChild,
445
+ side,
446
+ sideOffset,
447
+ align,
448
+ alignOffset,
449
+ id,
450
+ } = useTooltip();
451
+ const {
452
+ showTooltip,
453
+ hideTooltip,
454
+ hideImmediate,
455
+ currentTooltip,
456
+ setReferenceEl,
457
+ } = useGlobalTooltip();
458
+
459
+ const triggerRef = React.useRef<HTMLDivElement>(null);
460
+ React.useImperativeHandle(ref, () => triggerRef.current as HTMLDivElement);
461
+
462
+ const suppressNextFocusRef = React.useRef(false);
463
+
464
+ const handleOpen = React.useCallback(() => {
465
+ if (!triggerRef.current) return;
466
+ setReferenceEl(triggerRef.current);
467
+ const rect = triggerRef.current.getBoundingClientRect();
468
+ showTooltip({
469
+ contentProps,
470
+ contentAsChild,
471
+ rect,
472
+ side,
473
+ sideOffset,
474
+ align,
475
+ alignOffset,
476
+ id,
477
+ });
478
+ }, [
479
+ showTooltip,
480
+ setReferenceEl,
481
+ contentProps,
482
+ contentAsChild,
483
+ side,
484
+ sideOffset,
485
+ align,
486
+ alignOffset,
487
+ id,
488
+ ]);
489
+
490
+ const handlePointerDown = React.useCallback(
491
+ (e: React.PointerEvent<HTMLDivElement>) => {
492
+ onPointerDown?.(e);
493
+ if (currentTooltip?.id === id) {
494
+ suppressNextFocusRef.current = true;
495
+ hideImmediate();
496
+ Promise.resolve().then(() => {
497
+ suppressNextFocusRef.current = false;
498
+ });
499
+ }
500
+ },
501
+ [onPointerDown, currentTooltip?.id, id, hideImmediate],
502
+ );
503
+
504
+ const handleMouseEnter = React.useCallback(
505
+ (e: React.MouseEvent<HTMLDivElement>) => {
506
+ onMouseEnter?.(e);
507
+ handleOpen();
508
+ },
509
+ [handleOpen, onMouseEnter],
510
+ );
511
+
512
+ const handleMouseLeave = React.useCallback(
513
+ (e: React.MouseEvent<HTMLDivElement>) => {
514
+ onMouseLeave?.(e);
515
+ hideTooltip();
516
+ },
517
+ [hideTooltip, onMouseLeave],
518
+ );
519
+
520
+ const handleFocus = React.useCallback(
521
+ (e: React.FocusEvent<HTMLDivElement>) => {
522
+ onFocus?.(e);
523
+ if (suppressNextFocusRef.current) return;
524
+ handleOpen();
525
+ },
526
+ [handleOpen, onFocus],
527
+ );
528
+
529
+ const handleBlur = React.useCallback(
530
+ (e: React.FocusEvent<HTMLDivElement>) => {
531
+ onBlur?.(e);
532
+ hideTooltip();
533
+ },
534
+ [hideTooltip, onBlur],
535
+ );
536
+
537
+ const Component = asChild ? Slot : motion.div;
538
+
539
+ return (
540
+ <Component
541
+ ref={triggerRef}
542
+ onPointerDown={handlePointerDown}
543
+ onMouseEnter={handleMouseEnter}
544
+ onMouseLeave={handleMouseLeave}
545
+ onFocus={handleFocus}
546
+ onBlur={handleBlur}
547
+ data-slot="tooltip-trigger"
548
+ data-side={side}
549
+ data-align={align}
550
+ data-state={currentTooltip?.id === id ? 'open' : 'closed'}
551
+ {...props}
552
+ />
553
+ );
554
+ }
555
+
556
+ export {
557
+ TooltipProvider,
558
+ Tooltip,
559
+ TooltipContent,
560
+ TooltipTrigger,
561
+ TooltipArrow,
562
+ useGlobalTooltip,
563
+ useTooltip,
564
+ type TooltipProviderProps,
565
+ type TooltipProps,
566
+ type TooltipContentProps,
567
+ type TooltipTriggerProps,
568
+ type TooltipArrowProps,
569
+ type TooltipPosition,
570
+ type GlobalTooltipContextType,
571
+ type TooltipContextType,
572
+ };
ui/components/animate-ui/primitives/effects/highlight.tsx ADDED
@@ -0,0 +1,640 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { AnimatePresence, motion, type Transition } from 'motion/react';
5
+
6
+ import { cn } from '@/lib/utils';
7
+
8
+ type HighlightMode = 'children' | 'parent';
9
+
10
+ type Bounds = {
11
+ top: number;
12
+ left: number;
13
+ width: number;
14
+ height: number;
15
+ };
16
+
17
+ const DEFAULT_BOUNDS_OFFSET: Bounds = {
18
+ top: 0,
19
+ left: 0,
20
+ width: 0,
21
+ height: 0,
22
+ };
23
+
24
+ type HighlightContextType<T extends string> = {
25
+ as?: keyof HTMLElementTagNameMap;
26
+ mode: HighlightMode;
27
+ activeValue: T | null;
28
+ setActiveValue: (value: T | null) => void;
29
+ setBounds: (bounds: DOMRect) => void;
30
+ clearBounds: () => void;
31
+ id: string;
32
+ hover: boolean;
33
+ click: boolean;
34
+ className?: string;
35
+ style?: React.CSSProperties;
36
+ activeClassName?: string;
37
+ setActiveClassName: (className: string) => void;
38
+ transition?: Transition;
39
+ disabled?: boolean;
40
+ enabled?: boolean;
41
+ exitDelay?: number;
42
+ forceUpdateBounds?: boolean;
43
+ };
44
+
45
+ const HighlightContext = React.createContext<
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ HighlightContextType<any> | undefined
48
+ >(undefined);
49
+
50
+ function useHighlight<T extends string>(): HighlightContextType<T> {
51
+ const context = React.useContext(HighlightContext);
52
+ if (!context) {
53
+ throw new Error('useHighlight must be used within a HighlightProvider');
54
+ }
55
+ return context as unknown as HighlightContextType<T>;
56
+ }
57
+
58
+ type BaseHighlightProps<T extends React.ElementType = 'div'> = {
59
+ as?: T;
60
+ ref?: React.Ref<HTMLDivElement>;
61
+ mode?: HighlightMode;
62
+ value?: string | null;
63
+ defaultValue?: string | null;
64
+ onValueChange?: (value: string | null) => void;
65
+ className?: string;
66
+ style?: React.CSSProperties;
67
+ transition?: Transition;
68
+ hover?: boolean;
69
+ click?: boolean;
70
+ disabled?: boolean;
71
+ enabled?: boolean;
72
+ exitDelay?: number;
73
+ };
74
+
75
+ type ParentModeHighlightProps = {
76
+ boundsOffset?: Partial<Bounds>;
77
+ containerClassName?: string;
78
+ forceUpdateBounds?: boolean;
79
+ };
80
+
81
+ type ControlledParentModeHighlightProps<T extends React.ElementType = 'div'> =
82
+ BaseHighlightProps<T> &
83
+ ParentModeHighlightProps & {
84
+ mode: 'parent';
85
+ controlledItems: true;
86
+ children: React.ReactNode;
87
+ };
88
+
89
+ type ControlledChildrenModeHighlightProps<T extends React.ElementType = 'div'> =
90
+ BaseHighlightProps<T> & {
91
+ mode?: 'children' | undefined;
92
+ controlledItems: true;
93
+ children: React.ReactNode;
94
+ };
95
+
96
+ type UncontrolledParentModeHighlightProps<T extends React.ElementType = 'div'> =
97
+ BaseHighlightProps<T> &
98
+ ParentModeHighlightProps & {
99
+ mode: 'parent';
100
+ controlledItems?: false;
101
+ itemsClassName?: string;
102
+ children: React.ReactElement | React.ReactElement[];
103
+ };
104
+
105
+ type UncontrolledChildrenModeHighlightProps<
106
+ T extends React.ElementType = 'div',
107
+ > = BaseHighlightProps<T> & {
108
+ mode?: 'children';
109
+ controlledItems?: false;
110
+ itemsClassName?: string;
111
+ children: React.ReactElement | React.ReactElement[];
112
+ };
113
+
114
+ type HighlightProps<T extends React.ElementType = 'div'> =
115
+ | ControlledParentModeHighlightProps<T>
116
+ | ControlledChildrenModeHighlightProps<T>
117
+ | UncontrolledParentModeHighlightProps<T>
118
+ | UncontrolledChildrenModeHighlightProps<T>;
119
+
120
+ function Highlight<T extends React.ElementType = 'div'>({
121
+ ref,
122
+ ...props
123
+ }: HighlightProps<T>) {
124
+ const {
125
+ as: Component = 'div',
126
+ children,
127
+ value,
128
+ defaultValue,
129
+ onValueChange,
130
+ className,
131
+ style,
132
+ transition = { type: 'spring', stiffness: 350, damping: 35 },
133
+ hover = false,
134
+ click = true,
135
+ enabled = true,
136
+ controlledItems,
137
+ disabled = false,
138
+ exitDelay = 200,
139
+ mode = 'children',
140
+ } = props;
141
+
142
+ const localRef = React.useRef<HTMLDivElement>(null);
143
+ React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement);
144
+
145
+ const propsBoundsOffset = (props as ParentModeHighlightProps)?.boundsOffset;
146
+ const boundsOffset = propsBoundsOffset ?? DEFAULT_BOUNDS_OFFSET;
147
+ const boundsOffsetTop = boundsOffset.top ?? 0;
148
+ const boundsOffsetLeft = boundsOffset.left ?? 0;
149
+ const boundsOffsetWidth = boundsOffset.width ?? 0;
150
+ const boundsOffsetHeight = boundsOffset.height ?? 0;
151
+
152
+ const boundsOffsetRef = React.useRef({
153
+ top: boundsOffsetTop,
154
+ left: boundsOffsetLeft,
155
+ width: boundsOffsetWidth,
156
+ height: boundsOffsetHeight,
157
+ });
158
+
159
+ React.useEffect(() => {
160
+ boundsOffsetRef.current = {
161
+ top: boundsOffsetTop,
162
+ left: boundsOffsetLeft,
163
+ width: boundsOffsetWidth,
164
+ height: boundsOffsetHeight,
165
+ };
166
+ }, [
167
+ boundsOffsetTop,
168
+ boundsOffsetLeft,
169
+ boundsOffsetWidth,
170
+ boundsOffsetHeight,
171
+ ]);
172
+
173
+ const [activeValue, setActiveValue] = React.useState<string | null>(
174
+ value ?? defaultValue ?? null,
175
+ );
176
+ const [boundsState, setBoundsState] = React.useState<Bounds | null>(null);
177
+ const [activeClassNameState, setActiveClassNameState] =
178
+ React.useState<string>('');
179
+
180
+ const safeSetActiveValue = (id: string | null) => {
181
+ setActiveValue((prev) => {
182
+ if (prev !== id) {
183
+ onValueChange?.(id);
184
+ return id;
185
+ }
186
+ return prev;
187
+ });
188
+ };
189
+
190
+ const safeSetBoundsRef = React.useRef<
191
+ ((bounds: DOMRect) => void) | undefined
192
+ >(undefined);
193
+
194
+ React.useEffect(() => {
195
+ safeSetBoundsRef.current = (bounds: DOMRect) => {
196
+ if (!localRef.current) return;
197
+
198
+ const containerRect = localRef.current.getBoundingClientRect();
199
+ const offset = boundsOffsetRef.current;
200
+ const newBounds: Bounds = {
201
+ top: bounds.top - containerRect.top + offset.top,
202
+ left: bounds.left - containerRect.left + offset.left,
203
+ width: bounds.width + offset.width,
204
+ height: bounds.height + offset.height,
205
+ };
206
+
207
+ setBoundsState((prev) => {
208
+ if (
209
+ prev &&
210
+ prev.top === newBounds.top &&
211
+ prev.left === newBounds.left &&
212
+ prev.width === newBounds.width &&
213
+ prev.height === newBounds.height
214
+ ) {
215
+ return prev;
216
+ }
217
+ return newBounds;
218
+ });
219
+ };
220
+ });
221
+
222
+ const safeSetBounds = (bounds: DOMRect) => {
223
+ safeSetBoundsRef.current?.(bounds);
224
+ };
225
+
226
+ const clearBounds = React.useCallback(() => {
227
+ setBoundsState((prev) => (prev === null ? prev : null));
228
+ }, []);
229
+
230
+ React.useEffect(() => {
231
+ if (value !== undefined) setActiveValue(value);
232
+ else if (defaultValue !== undefined) setActiveValue(defaultValue);
233
+ }, [value, defaultValue]);
234
+
235
+ const id = React.useId();
236
+
237
+ React.useEffect(() => {
238
+ if (mode !== 'parent') return;
239
+ const container = localRef.current;
240
+ if (!container) return;
241
+
242
+ const onScroll = () => {
243
+ if (!activeValue) return;
244
+ const activeEl = container.querySelector<HTMLElement>(
245
+ `[data-value="${activeValue}"][data-highlight="true"]`,
246
+ );
247
+ if (activeEl)
248
+ safeSetBoundsRef.current?.(activeEl.getBoundingClientRect());
249
+ };
250
+
251
+ container.addEventListener('scroll', onScroll, { passive: true });
252
+ return () => container.removeEventListener('scroll', onScroll);
253
+ }, [mode, activeValue]);
254
+
255
+ const render = (children: React.ReactNode) => {
256
+ if (mode === 'parent') {
257
+ return (
258
+ <Component
259
+ ref={localRef}
260
+ data-slot="motion-highlight-container"
261
+ style={{ position: 'relative', zIndex: 1 }}
262
+ className={(props as ParentModeHighlightProps)?.containerClassName}
263
+ >
264
+ <AnimatePresence initial={false} mode="wait">
265
+ {boundsState && (
266
+ <motion.div
267
+ data-slot="motion-highlight"
268
+ animate={{
269
+ top: boundsState.top,
270
+ left: boundsState.left,
271
+ width: boundsState.width,
272
+ height: boundsState.height,
273
+ opacity: 1,
274
+ }}
275
+ initial={{
276
+ top: boundsState.top,
277
+ left: boundsState.left,
278
+ width: boundsState.width,
279
+ height: boundsState.height,
280
+ opacity: 0,
281
+ }}
282
+ exit={{
283
+ opacity: 0,
284
+ transition: {
285
+ ...transition,
286
+ delay: (transition?.delay ?? 0) + (exitDelay ?? 0) / 1000,
287
+ },
288
+ }}
289
+ transition={transition}
290
+ style={{ position: 'absolute', zIndex: 0, ...style }}
291
+ className={cn(className, activeClassNameState)}
292
+ />
293
+ )}
294
+ </AnimatePresence>
295
+ {children}
296
+ </Component>
297
+ );
298
+ }
299
+
300
+ return children;
301
+ };
302
+
303
+ return (
304
+ <HighlightContext.Provider
305
+ value={{
306
+ mode,
307
+ activeValue,
308
+ setActiveValue: safeSetActiveValue,
309
+ id,
310
+ hover,
311
+ click,
312
+ className,
313
+ style,
314
+ transition,
315
+ disabled,
316
+ enabled,
317
+ exitDelay,
318
+ setBounds: safeSetBounds,
319
+ clearBounds,
320
+ activeClassName: activeClassNameState,
321
+ setActiveClassName: setActiveClassNameState,
322
+ forceUpdateBounds: (props as ParentModeHighlightProps)
323
+ ?.forceUpdateBounds,
324
+ }}
325
+ >
326
+ {enabled
327
+ ? controlledItems
328
+ ? render(children)
329
+ : render(
330
+ React.Children.map(children, (child, index) => (
331
+ <HighlightItem key={index} className={props?.itemsClassName}>
332
+ {child}
333
+ </HighlightItem>
334
+ )),
335
+ )
336
+ : children}
337
+ </HighlightContext.Provider>
338
+ );
339
+ }
340
+
341
+ function getNonOverridingDataAttributes(
342
+ element: React.ReactElement,
343
+ dataAttributes: Record<string, unknown>,
344
+ ): Record<string, unknown> {
345
+ return Object.keys(dataAttributes).reduce<Record<string, unknown>>(
346
+ (acc, key) => {
347
+ if ((element.props as Record<string, unknown>)[key] === undefined) {
348
+ acc[key] = dataAttributes[key];
349
+ }
350
+ return acc;
351
+ },
352
+ {},
353
+ );
354
+ }
355
+
356
+ type ExtendedChildProps = React.ComponentProps<'div'> & {
357
+ id?: string;
358
+ ref?: React.Ref<HTMLElement>;
359
+ 'data-active'?: string;
360
+ 'data-value'?: string;
361
+ 'data-disabled'?: boolean;
362
+ 'data-highlight'?: boolean;
363
+ 'data-slot'?: string;
364
+ };
365
+
366
+ type HighlightItemProps<T extends React.ElementType = 'div'> =
367
+ React.ComponentProps<T> & {
368
+ as?: T;
369
+ children: React.ReactElement;
370
+ id?: string;
371
+ value?: string;
372
+ className?: string;
373
+ style?: React.CSSProperties;
374
+ transition?: Transition;
375
+ activeClassName?: string;
376
+ disabled?: boolean;
377
+ exitDelay?: number;
378
+ asChild?: boolean;
379
+ forceUpdateBounds?: boolean;
380
+ };
381
+
382
+ function HighlightItem<T extends React.ElementType>({
383
+ ref,
384
+ as,
385
+ children,
386
+ id,
387
+ value,
388
+ className,
389
+ style,
390
+ transition,
391
+ disabled = false,
392
+ activeClassName,
393
+ exitDelay,
394
+ asChild = false,
395
+ forceUpdateBounds,
396
+ ...props
397
+ }: HighlightItemProps<T>) {
398
+ const itemId = React.useId();
399
+ const {
400
+ activeValue,
401
+ setActiveValue,
402
+ mode,
403
+ setBounds,
404
+ clearBounds,
405
+ hover,
406
+ click,
407
+ enabled,
408
+ className: contextClassName,
409
+ style: contextStyle,
410
+ transition: contextTransition,
411
+ id: contextId,
412
+ disabled: contextDisabled,
413
+ exitDelay: contextExitDelay,
414
+ forceUpdateBounds: contextForceUpdateBounds,
415
+ setActiveClassName,
416
+ } = useHighlight();
417
+
418
+ const Component = as ?? 'div';
419
+ const element = children as React.ReactElement<ExtendedChildProps>;
420
+ const childValue =
421
+ id ?? value ?? element.props?.['data-value'] ?? element.props?.id ?? itemId;
422
+ const isActive = activeValue === childValue;
423
+ const isDisabled = disabled === undefined ? contextDisabled : disabled;
424
+ const itemTransition = transition ?? contextTransition;
425
+
426
+ const localRef = React.useRef<HTMLDivElement>(null);
427
+ React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement);
428
+
429
+ const refCallback = React.useCallback((node: HTMLElement | null) => {
430
+ localRef.current = node as HTMLDivElement;
431
+ }, []);
432
+
433
+ React.useEffect(() => {
434
+ if (mode !== 'parent') return;
435
+ let rafId: number;
436
+ let previousBounds: Bounds | null = null;
437
+ const shouldUpdateBounds =
438
+ forceUpdateBounds === true ||
439
+ (contextForceUpdateBounds && forceUpdateBounds !== false);
440
+
441
+ const updateBounds = () => {
442
+ if (!localRef.current) return;
443
+
444
+ const bounds = localRef.current.getBoundingClientRect();
445
+
446
+ if (shouldUpdateBounds) {
447
+ if (
448
+ previousBounds &&
449
+ previousBounds.top === bounds.top &&
450
+ previousBounds.left === bounds.left &&
451
+ previousBounds.width === bounds.width &&
452
+ previousBounds.height === bounds.height
453
+ ) {
454
+ rafId = requestAnimationFrame(updateBounds);
455
+ return;
456
+ }
457
+ previousBounds = bounds;
458
+ rafId = requestAnimationFrame(updateBounds);
459
+ }
460
+
461
+ setBounds(bounds);
462
+ };
463
+
464
+ if (isActive) {
465
+ updateBounds();
466
+ setActiveClassName(activeClassName ?? '');
467
+ } else if (!activeValue) clearBounds();
468
+
469
+ if (shouldUpdateBounds) return () => cancelAnimationFrame(rafId);
470
+ }, [
471
+ mode,
472
+ isActive,
473
+ activeValue,
474
+ setBounds,
475
+ clearBounds,
476
+ activeClassName,
477
+ setActiveClassName,
478
+ forceUpdateBounds,
479
+ contextForceUpdateBounds,
480
+ ]);
481
+
482
+ if (!React.isValidElement(children)) return children;
483
+
484
+ const dataAttributes = {
485
+ 'data-active': isActive ? 'true' : 'false',
486
+ 'aria-selected': isActive,
487
+ 'data-disabled': isDisabled,
488
+ 'data-value': childValue,
489
+ 'data-highlight': true,
490
+ };
491
+
492
+ const commonHandlers = hover
493
+ ? {
494
+ onMouseEnter: (e: React.MouseEvent<HTMLDivElement>) => {
495
+ setActiveValue(childValue);
496
+ element.props.onMouseEnter?.(e);
497
+ },
498
+ onMouseLeave: (e: React.MouseEvent<HTMLDivElement>) => {
499
+ setActiveValue(null);
500
+ element.props.onMouseLeave?.(e);
501
+ },
502
+ }
503
+ : click
504
+ ? {
505
+ onClick: (e: React.MouseEvent<HTMLDivElement>) => {
506
+ setActiveValue(childValue);
507
+ element.props.onClick?.(e);
508
+ },
509
+ }
510
+ : {};
511
+
512
+ if (asChild) {
513
+ if (mode === 'children') {
514
+ return React.cloneElement(
515
+ element,
516
+ {
517
+ key: childValue,
518
+ ref: refCallback,
519
+ className: cn('relative', element.props.className),
520
+ ...getNonOverridingDataAttributes(element, {
521
+ ...dataAttributes,
522
+ 'data-slot': 'motion-highlight-item-container',
523
+ }),
524
+ ...commonHandlers,
525
+ ...props,
526
+ },
527
+ <>
528
+ <AnimatePresence initial={false} mode="wait">
529
+ {isActive && !isDisabled && (
530
+ <motion.div
531
+ layoutId={`transition-background-${contextId}`}
532
+ data-slot="motion-highlight"
533
+ style={{
534
+ position: 'absolute',
535
+ zIndex: 0,
536
+ ...contextStyle,
537
+ ...style,
538
+ }}
539
+ className={cn(contextClassName, activeClassName)}
540
+ transition={itemTransition}
541
+ initial={{ opacity: 0 }}
542
+ animate={{ opacity: 1 }}
543
+ exit={{
544
+ opacity: 0,
545
+ transition: {
546
+ ...itemTransition,
547
+ delay:
548
+ (itemTransition?.delay ?? 0) +
549
+ (exitDelay ?? contextExitDelay ?? 0) / 1000,
550
+ },
551
+ }}
552
+ {...dataAttributes}
553
+ />
554
+ )}
555
+ </AnimatePresence>
556
+
557
+ <Component
558
+ data-slot="motion-highlight-item"
559
+ style={{ position: 'relative', zIndex: 1 }}
560
+ className={className}
561
+ {...dataAttributes}
562
+ >
563
+ {children}
564
+ </Component>
565
+ </>,
566
+ );
567
+ }
568
+
569
+ return React.cloneElement(element, {
570
+ ref: refCallback,
571
+ ...getNonOverridingDataAttributes(element, {
572
+ ...dataAttributes,
573
+ 'data-slot': 'motion-highlight-item',
574
+ }),
575
+ ...commonHandlers,
576
+ });
577
+ }
578
+
579
+ return enabled ? (
580
+ <Component
581
+ key={childValue}
582
+ ref={localRef}
583
+ data-slot="motion-highlight-item-container"
584
+ className={cn(mode === 'children' && 'relative', className)}
585
+ {...dataAttributes}
586
+ {...props}
587
+ {...commonHandlers}
588
+ >
589
+ {mode === 'children' && (
590
+ <AnimatePresence initial={false} mode="wait">
591
+ {isActive && !isDisabled && (
592
+ <motion.div
593
+ layoutId={`transition-background-${contextId}`}
594
+ data-slot="motion-highlight"
595
+ style={{
596
+ position: 'absolute',
597
+ zIndex: 0,
598
+ ...contextStyle,
599
+ ...style,
600
+ }}
601
+ className={cn(contextClassName, activeClassName)}
602
+ transition={itemTransition}
603
+ initial={{ opacity: 0 }}
604
+ animate={{ opacity: 1 }}
605
+ exit={{
606
+ opacity: 0,
607
+ transition: {
608
+ ...itemTransition,
609
+ delay:
610
+ (itemTransition?.delay ?? 0) +
611
+ (exitDelay ?? contextExitDelay ?? 0) / 1000,
612
+ },
613
+ }}
614
+ {...dataAttributes}
615
+ />
616
+ )}
617
+ </AnimatePresence>
618
+ )}
619
+
620
+ {React.cloneElement(element, {
621
+ style: { position: 'relative', zIndex: 1 },
622
+ className: element.props.className,
623
+ ...getNonOverridingDataAttributes(element, {
624
+ ...dataAttributes,
625
+ 'data-slot': 'motion-highlight-item',
626
+ }),
627
+ })}
628
+ </Component>
629
+ ) : (
630
+ children
631
+ );
632
+ }
633
+
634
+ export {
635
+ Highlight,
636
+ HighlightItem,
637
+ useHighlight,
638
+ type HighlightProps,
639
+ type HighlightItemProps,
640
+ };
ui/components/animate-ui/primitives/radix/checkbox.tsx ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Checkbox as CheckboxPrimitive } from 'radix-ui';
5
+ import { motion, SVGMotionProps, type HTMLMotionProps } from 'motion/react';
6
+
7
+ import { getStrictContext } from '@/lib/get-strict-context';
8
+ import { useControlledState } from '@/hooks/use-controlled-state';
9
+
10
+ type CheckboxContextType = {
11
+ isChecked: boolean | 'indeterminate';
12
+ setIsChecked: (checked: boolean | 'indeterminate') => void;
13
+ };
14
+
15
+ const [CheckboxProvider, useCheckbox] =
16
+ getStrictContext<CheckboxContextType>('CheckboxContext');
17
+
18
+ type CheckboxProps = HTMLMotionProps<'button'> &
19
+ Omit<React.ComponentProps<typeof CheckboxPrimitive.Root>, 'asChild'>;
20
+
21
+ function Checkbox({
22
+ defaultChecked,
23
+ checked,
24
+ onCheckedChange,
25
+ disabled,
26
+ required,
27
+ name,
28
+ value,
29
+ ...props
30
+ }: CheckboxProps) {
31
+ const [isChecked, setIsChecked] = useControlledState({
32
+ value: checked,
33
+ defaultValue: defaultChecked,
34
+ onChange: onCheckedChange,
35
+ });
36
+
37
+ return (
38
+ <CheckboxProvider value={{ isChecked, setIsChecked }}>
39
+ <CheckboxPrimitive.Root
40
+ defaultChecked={defaultChecked}
41
+ checked={checked}
42
+ onCheckedChange={setIsChecked}
43
+ disabled={disabled}
44
+ required={required}
45
+ name={name}
46
+ value={value}
47
+ asChild
48
+ >
49
+ <motion.button
50
+ data-slot="checkbox"
51
+ whileTap={{ scale: 0.95 }}
52
+ whileHover={{ scale: 1.05 }}
53
+ {...props}
54
+ />
55
+ </CheckboxPrimitive.Root>
56
+ </CheckboxProvider>
57
+ );
58
+ }
59
+
60
+ type CheckboxIndicatorProps = SVGMotionProps<SVGSVGElement>;
61
+
62
+ function CheckboxIndicator(props: CheckboxIndicatorProps) {
63
+ const { isChecked } = useCheckbox();
64
+
65
+ return (
66
+ <CheckboxPrimitive.Indicator forceMount asChild>
67
+ <motion.svg
68
+ data-slot="checkbox-indicator"
69
+ xmlns="http://www.w3.org/2000/svg"
70
+ fill="none"
71
+ viewBox="0 0 24 24"
72
+ strokeWidth="3.5"
73
+ stroke="currentColor"
74
+ initial="unchecked"
75
+ animate={isChecked ? 'checked' : 'unchecked'}
76
+ {...props}
77
+ >
78
+ {isChecked === 'indeterminate' ? (
79
+ <motion.line
80
+ x1="5"
81
+ y1="12"
82
+ x2="19"
83
+ y2="12"
84
+ strokeLinecap="round"
85
+ initial={{ pathLength: 0, opacity: 0 }}
86
+ animate={{
87
+ pathLength: 1,
88
+ opacity: 1,
89
+ transition: { duration: 0.2 },
90
+ }}
91
+ />
92
+ ) : (
93
+ <motion.path
94
+ strokeLinecap="round"
95
+ strokeLinejoin="round"
96
+ d="M4.5 12.75l6 6 9-13.5"
97
+ variants={{
98
+ checked: {
99
+ pathLength: 1,
100
+ opacity: 1,
101
+ transition: {
102
+ duration: 0.2,
103
+ delay: 0.2,
104
+ },
105
+ },
106
+ unchecked: {
107
+ pathLength: 0,
108
+ opacity: 0,
109
+ transition: {
110
+ duration: 0.2,
111
+ },
112
+ },
113
+ }}
114
+ />
115
+ )}
116
+ </motion.svg>
117
+ </CheckboxPrimitive.Indicator>
118
+ );
119
+ }
120
+
121
+ export {
122
+ Checkbox,
123
+ CheckboxIndicator,
124
+ useCheckbox,
125
+ type CheckboxProps,
126
+ type CheckboxIndicatorProps,
127
+ type CheckboxContextType,
128
+ };
ui/components/animate-ui/primitives/radix/collapsible.tsx ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4
+
5
+ const Collapsible = CollapsiblePrimitive.Root
6
+
7
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8
+
9
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10
+
11
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
ui/components/animate-ui/primitives/radix/sheet.tsx ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Dialog as SheetPrimitive } from 'radix-ui';
5
+ import { AnimatePresence, motion, type HTMLMotionProps } from 'motion/react';
6
+
7
+ import { getStrictContext } from '@/lib/get-strict-context';
8
+ import { useControlledState } from '@/hooks/use-controlled-state';
9
+
10
+ type SheetContextType = {
11
+ isOpen: boolean;
12
+ setIsOpen: (isOpen: boolean) => void;
13
+ };
14
+
15
+ const [SheetProvider, useSheet] =
16
+ getStrictContext<SheetContextType>('SheetContext');
17
+
18
+ type SheetProps = React.ComponentProps<typeof SheetPrimitive.Root>;
19
+
20
+ function Sheet(props: SheetProps) {
21
+ const [isOpen, setIsOpen] = useControlledState({
22
+ value: props.open,
23
+ defaultValue: props.defaultOpen,
24
+ onChange: props.onOpenChange,
25
+ });
26
+
27
+ return (
28
+ <SheetProvider value={{ isOpen, setIsOpen }}>
29
+ <SheetPrimitive.Root
30
+ data-slot="sheet"
31
+ {...props}
32
+ onOpenChange={setIsOpen}
33
+ />
34
+ </SheetProvider>
35
+ );
36
+ }
37
+
38
+ type SheetTriggerProps = React.ComponentProps<typeof SheetPrimitive.Trigger>;
39
+
40
+ function SheetTrigger(props: SheetTriggerProps) {
41
+ return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
42
+ }
43
+
44
+ type SheetCloseProps = React.ComponentProps<typeof SheetPrimitive.Close>;
45
+
46
+ function SheetClose(props: SheetCloseProps) {
47
+ return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
48
+ }
49
+
50
+ type SheetPortalProps = React.ComponentProps<typeof SheetPrimitive.Portal>;
51
+
52
+ function SheetPortal(props: SheetPortalProps) {
53
+ const { isOpen } = useSheet();
54
+
55
+ return (
56
+ <AnimatePresence>
57
+ {isOpen && (
58
+ <SheetPrimitive.Portal forceMount data-slot="sheet-portal" {...props} />
59
+ )}
60
+ </AnimatePresence>
61
+ );
62
+ }
63
+
64
+ type SheetOverlayProps = Omit<
65
+ React.ComponentProps<typeof SheetPrimitive.Overlay>,
66
+ 'asChild' | 'forceMount'
67
+ > &
68
+ HTMLMotionProps<'div'>;
69
+
70
+ function SheetOverlay({
71
+ transition = { duration: 0.2, ease: 'easeInOut' },
72
+ ...props
73
+ }: SheetOverlayProps) {
74
+ return (
75
+ <SheetPrimitive.Overlay asChild forceMount>
76
+ <motion.div
77
+ key="sheet-overlay"
78
+ data-slot="sheet-overlay"
79
+ initial={{ opacity: 0, filter: 'blur(4px)' }}
80
+ animate={{ opacity: 1, filter: 'blur(0px)' }}
81
+ exit={{ opacity: 0, filter: 'blur(4px)' }}
82
+ transition={transition}
83
+ {...props}
84
+ />
85
+ </SheetPrimitive.Overlay>
86
+ );
87
+ }
88
+
89
+ type Side = 'top' | 'bottom' | 'left' | 'right';
90
+
91
+ type SheetContentProps = React.ComponentProps<typeof SheetPrimitive.Content> &
92
+ HTMLMotionProps<'div'> & {
93
+ side?: Side;
94
+ };
95
+
96
+ function SheetContent({
97
+ side = 'right',
98
+ transition = { type: 'spring', stiffness: 150, damping: 22 },
99
+ style,
100
+ children,
101
+ ...props
102
+ }: SheetContentProps) {
103
+ const axis = side === 'left' || side === 'right' ? 'x' : 'y';
104
+
105
+ const offscreen: Record<Side, { x?: string; y?: string; opacity: number }> = {
106
+ right: { x: '100%', opacity: 0 },
107
+ left: { x: '-100%', opacity: 0 },
108
+ top: { y: '-100%', opacity: 0 },
109
+ bottom: { y: '100%', opacity: 0 },
110
+ };
111
+
112
+ const positionStyle: Record<Side, React.CSSProperties> = {
113
+ right: { insetBlock: 0, right: 0 },
114
+ left: { insetBlock: 0, left: 0 },
115
+ top: { insetInline: 0, top: 0 },
116
+ bottom: { insetInline: 0, bottom: 0 },
117
+ };
118
+
119
+ return (
120
+ <SheetPrimitive.Content asChild forceMount {...props}>
121
+ <motion.div
122
+ key="sheet-content"
123
+ data-slot="sheet-content"
124
+ data-side={side}
125
+ initial={offscreen[side]}
126
+ animate={{ [axis]: 0, opacity: 1 }}
127
+ exit={offscreen[side]}
128
+ style={{
129
+ position: 'fixed',
130
+ ...positionStyle[side],
131
+ ...style,
132
+ }}
133
+ transition={transition}
134
+ >
135
+ {children}
136
+ </motion.div>
137
+ </SheetPrimitive.Content>
138
+ );
139
+ }
140
+
141
+ type SheetHeaderProps = React.ComponentProps<'div'>;
142
+
143
+ function SheetHeader(props: SheetHeaderProps) {
144
+ return <div data-slot="sheet-header" {...props} />;
145
+ }
146
+
147
+ type SheetFooterProps = React.ComponentProps<'div'>;
148
+
149
+ function SheetFooter(props: SheetFooterProps) {
150
+ return <div data-slot="sheet-footer" {...props} />;
151
+ }
152
+
153
+ type SheetTitleProps = React.ComponentProps<typeof SheetPrimitive.Title>;
154
+
155
+ function SheetTitle(props: SheetTitleProps) {
156
+ return <SheetPrimitive.Title data-slot="sheet-title" {...props} />;
157
+ }
158
+
159
+ type SheetDescriptionProps = React.ComponentProps<
160
+ typeof SheetPrimitive.Description
161
+ >;
162
+
163
+ function SheetDescription(props: SheetDescriptionProps) {
164
+ return (
165
+ <SheetPrimitive.Description data-slot="sheet-description" {...props} />
166
+ );
167
+ }
168
+
169
+ export {
170
+ useSheet,
171
+ Sheet,
172
+ SheetPortal,
173
+ SheetOverlay,
174
+ SheetTrigger,
175
+ SheetClose,
176
+ SheetContent,
177
+ SheetHeader,
178
+ SheetFooter,
179
+ SheetTitle,
180
+ SheetDescription,
181
+ type SheetProps,
182
+ type SheetPortalProps,
183
+ type SheetOverlayProps,
184
+ type SheetTriggerProps,
185
+ type SheetCloseProps,
186
+ type SheetContentProps,
187
+ type SheetHeaderProps,
188
+ type SheetFooterProps,
189
+ type SheetTitleProps,
190
+ type SheetDescriptionProps,
191
+ };
ui/components/page-header.tsx CHANGED
@@ -1,25 +1,75 @@
1
- // ui/components/page-header.tsx
2
  import { cn } from "@/lib/utils"
 
 
 
 
 
 
 
 
 
 
3
 
4
  interface PageHeaderProps {
5
  title: string
6
  subtitle?: string
7
  icon?: React.ReactNode
8
  className?: string
 
 
9
  }
10
 
11
- export function PageHeader({ title, subtitle, icon, className }: PageHeaderProps) {
 
 
 
 
 
 
 
12
  return (
13
- <div className={cn("mb-8 space-y-2", className)}>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  <h1 className="flex items-center gap-3 text-3xl font-bold tracking-tight">
15
- {icon && <span className="text-primary">{icon}</span>}
16
- {title}
17
  </h1>
18
  {subtitle && (
19
- <p className="text-lg text-muted-foreground">
20
- {subtitle}
21
- </p>
22
  )}
 
23
  </div>
24
  )
25
  }
@@ -35,3 +85,5 @@ export function SectionHeader({ title, icon, action }: { title: string, icon?: R
35
  </div>
36
  )
37
  }
 
 
 
 
1
  import { cn } from "@/lib/utils"
2
+ import { SidebarTrigger } from "@/components/animate-ui/components/radix/sidebar"
3
+ import { Separator } from "@/components/ui/separator"
4
+ import {
5
+ Breadcrumb,
6
+ BreadcrumbItem,
7
+ BreadcrumbLink,
8
+ BreadcrumbList,
9
+ BreadcrumbPage,
10
+ BreadcrumbSeparator,
11
+ } from "@/components/ui/breadcrumb"
12
 
13
  interface PageHeaderProps {
14
  title: string
15
  subtitle?: string
16
  icon?: React.ReactNode
17
  className?: string
18
+ breadcrumbs?: Array<{ label: string; href?: string }>
19
+ showSidebarTrigger?: boolean
20
  }
21
 
22
+ export function PageHeader({
23
+ title,
24
+ subtitle,
25
+ icon,
26
+ className,
27
+ breadcrumbs,
28
+ showSidebarTrigger = true
29
+ }: PageHeaderProps) {
30
  return (
31
+ <div className={cn("space-y-4", className)}>
32
+ {(showSidebarTrigger || breadcrumbs) && (
33
+ <header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
34
+ <div className="flex items-center gap-2">
35
+ {showSidebarTrigger && (
36
+ <>
37
+ <SidebarTrigger className="-ml-1" />
38
+ <Separator orientation="vertical" className="mr-2 h-4" />
39
+ </>
40
+ )}
41
+ {breadcrumbs && breadcrumbs.length > 0 && (
42
+ <Breadcrumb>
43
+ <BreadcrumbList>
44
+ {breadcrumbs.map((crumb, index) => (
45
+ <React.Fragment key={index}>
46
+ {index > 0 && <BreadcrumbSeparator className="hidden md:block" />}
47
+ <BreadcrumbItem className={index === 0 ? "hidden md:block" : ""}>
48
+ {crumb.href ? (
49
+ <BreadcrumbLink href={crumb.href}>{crumb.label}</BreadcrumbLink>
50
+ ) : (
51
+ <BreadcrumbPage>{crumb.label}</BreadcrumbPage>
52
+ )}
53
+ </BreadcrumbItem>
54
+ </React.Fragment>
55
+ ))}
56
+ </BreadcrumbList>
57
+ </Breadcrumb>
58
+ )}
59
+ </div>
60
+ </header>
61
+ )}
62
+ <div className="space-y-2">
63
  <h1 className="flex items-center gap-3 text-3xl font-bold tracking-tight">
64
+ {icon && <span className="text-primary">{icon}</span>}
65
+ {title}
66
  </h1>
67
  {subtitle && (
68
+ <p className="text-lg text-muted-foreground">
69
+ {subtitle}
70
+ </p>
71
  )}
72
+ </div>
73
  </div>
74
  )
75
  }
 
85
  </div>
86
  )
87
  }
88
+
89
+ import * as React from "react"
ui/components/sidebar.tsx CHANGED
@@ -1,95 +1,315 @@
1
  // ui/components/sidebar.tsx
2
- "use client"
3
 
4
- import Link from "next/link"
5
- import { usePathname } from "next/navigation"
6
- import { Home, Microscope, Dna, BarChart2, Settings, Terminal } from "lucide-react"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
- import { cn } from "@/lib/utils"
9
- import { Button } from "@/components/ui/button"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- const sidebarItems = [
12
  {
13
- title: "Home",
14
- href: "/",
15
  icon: Home,
 
16
  },
17
  {
18
- title: "Discovery",
19
- href: "/discovery",
20
  icon: Microscope,
 
 
 
 
 
 
 
 
 
 
21
  },
22
  {
23
- title: "Explorer",
24
- href: "/explorer",
25
  icon: Dna,
 
 
 
 
 
 
 
 
 
 
26
  },
27
  {
28
- title: "Data",
29
- href: "/data",
30
  icon: BarChart2,
 
 
 
 
 
 
 
 
 
 
31
  },
32
  {
33
- title: "Settings",
34
- href: "/settings",
35
  icon: Settings,
 
 
 
 
 
 
 
 
 
 
36
  },
37
- ]
38
 
39
- export function Sidebar() {
40
- const pathname = usePathname()
 
 
 
 
 
 
 
41
 
42
  return (
43
- <div className="flex h-screen w-[250px] flex-col border-r bg-card pb-4 pt-6">
44
- <div className="px-6 mb-8 flex items-center gap-2">
45
- <div className="h-8 w-8 rounded-lg bg-primary/20 flex items-center justify-center text-primary">
46
- <Dna className="h-5 w-5" />
47
- </div>
48
- <div className="font-bold text-xl tracking-tight">
49
- Bio<span className="text-primary">Flow</span>
50
- </div>
51
- </div>
52
-
53
- <div className="px-4 py-2">
54
- <div className="text-xs font-semibold text-muted-foreground mb-4 px-2 uppercase tracking-wider">
55
- Navigation
56
- </div>
57
- <nav className="space-y-1">
58
- {sidebarItems.map((item) => (
59
- <Link
60
- key={item.href}
61
- href={item.href}
62
- >
63
- <Button
64
- variant={pathname === item.href ? "secondary" : "ghost"}
65
- className={cn(
66
- "w-full justify-start gap-3",
67
- pathname === item.href && "bg-secondary font-medium"
68
- )}
69
- >
70
- <item.icon className="h-4 w-4" />
71
- {item.title}
72
- </Button>
73
- </Link>
74
- ))}
75
- </nav>
76
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
- <div className="mt-auto px-4">
79
- <div className="rounded-lg border bg-muted/50 p-4">
80
- <div className="flex items-center gap-2 mb-2">
 
 
 
81
  <Terminal className="h-4 w-4 text-muted-foreground" />
82
  <span className="text-xs font-medium">Status</span>
83
- </div>
84
- <div className="flex items-center gap-2">
85
  <span className="relative flex h-2 w-2">
86
  <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
87
  <span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
88
  </span>
89
  <span className="text-xs text-muted-foreground">System Online</span>
 
90
  </div>
91
- </div>
92
- </div>
93
- </div>
94
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  }
 
1
  // ui/components/sidebar.tsx
2
+ 'use client';
3
 
4
+ import * as React from 'react';
5
+ import Link from 'next/link';
6
+ import { usePathname } from 'next/navigation';
7
+ import {
8
+ Home,
9
+ Microscope,
10
+ Dna,
11
+ BarChart2,
12
+ Settings,
13
+ Terminal,
14
+ ChevronRight,
15
+ ChevronsUpDown,
16
+ BadgeCheck,
17
+ Bell,
18
+ CreditCard,
19
+ LogOut,
20
+ Sparkles,
21
+ User,
22
+ } from 'lucide-react';
23
 
24
+ import {
25
+ Sidebar,
26
+ SidebarHeader,
27
+ SidebarContent,
28
+ SidebarFooter,
29
+ SidebarRail,
30
+ SidebarGroup,
31
+ SidebarGroupLabel,
32
+ SidebarMenu,
33
+ SidebarMenuItem,
34
+ SidebarMenuButton,
35
+ SidebarMenuSub,
36
+ SidebarMenuSubItem,
37
+ SidebarMenuSubButton,
38
+ } from '@/components/animate-ui/components/radix/sidebar';
39
+ import {
40
+ Collapsible,
41
+ CollapsibleContent,
42
+ CollapsibleTrigger,
43
+ } from '@/components/animate-ui/primitives/radix/collapsible';
44
+ import {
45
+ DropdownMenu,
46
+ DropdownMenuContent,
47
+ DropdownMenuGroup,
48
+ DropdownMenuItem,
49
+ DropdownMenuLabel,
50
+ DropdownMenuSeparator,
51
+ DropdownMenuTrigger,
52
+ } from '@/components/animate-ui/components/radix/dropdown-menu';
53
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
54
+ import { useIsMobile } from '@/hooks/use-mobile';
55
 
56
+ const navMain = [
57
  {
58
+ title: 'Home',
59
+ url: '/',
60
  icon: Home,
61
+ isActive: true,
62
  },
63
  {
64
+ title: 'Discovery',
65
+ url: '/discovery',
66
  icon: Microscope,
67
+ items: [
68
+ {
69
+ title: 'Drug Discovery',
70
+ url: '/discovery',
71
+ },
72
+ {
73
+ title: 'Molecule Search',
74
+ url: '/discovery#search',
75
+ },
76
+ ],
77
  },
78
  {
79
+ title: 'Explorer',
80
+ url: '/explorer',
81
  icon: Dna,
82
+ items: [
83
+ {
84
+ title: 'Embeddings',
85
+ url: '/explorer',
86
+ },
87
+ {
88
+ title: 'Predictions',
89
+ url: '/explorer#predictions',
90
+ },
91
+ ],
92
  },
93
  {
94
+ title: 'Data',
95
+ url: '/data',
96
  icon: BarChart2,
97
+ items: [
98
+ {
99
+ title: 'Datasets',
100
+ url: '/data',
101
+ },
102
+ {
103
+ title: 'Analytics',
104
+ url: '/data#analytics',
105
+ },
106
+ ],
107
  },
108
  {
109
+ title: 'Settings',
110
+ url: '/settings',
111
  icon: Settings,
112
+ items: [
113
+ {
114
+ title: 'General',
115
+ url: '/settings',
116
+ },
117
+ {
118
+ title: 'Models',
119
+ url: '/settings#models',
120
+ },
121
+ ],
122
  },
123
+ ];
124
 
125
+ const userData = {
126
+ name: 'BioFlow User',
127
+ email: 'user@bioflow.ai',
128
+ avatar: '',
129
+ };
130
+
131
+ export function AppSidebar() {
132
+ const isMobile = useIsMobile();
133
+ const pathname = usePathname();
134
 
135
  return (
136
+ <Sidebar collapsible="icon">
137
+ <SidebarHeader>
138
+ {/* App Header */}
139
+ <SidebarMenu>
140
+ <SidebarMenuItem>
141
+ <SidebarMenuButton size="lg" asChild>
142
+ <Link href="/">
143
+ <div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
144
+ <Dna className="size-4" />
145
+ </div>
146
+ <div className="grid flex-1 text-left text-sm leading-tight">
147
+ <span className="truncate font-semibold">BioFlow</span>
148
+ <span className="truncate text-xs">AI Drug Discovery</span>
149
+ </div>
150
+ </Link>
151
+ </SidebarMenuButton>
152
+ </SidebarMenuItem>
153
+ </SidebarMenu>
154
+ {/* App Header */}
155
+ </SidebarHeader>
156
+
157
+ <SidebarContent>
158
+ {/* Nav Main */}
159
+ <SidebarGroup>
160
+ <SidebarGroupLabel>Platform</SidebarGroupLabel>
161
+ <SidebarMenu>
162
+ {navMain.map((item) => {
163
+ const isActive = pathname === item.url || pathname?.startsWith(item.url + '/');
164
+
165
+ if (!item.items || item.items.length === 0) {
166
+ return (
167
+ <SidebarMenuItem key={item.title}>
168
+ <SidebarMenuButton asChild isActive={isActive} tooltip={item.title}>
169
+ <Link href={item.url}>
170
+ {item.icon && <item.icon />}
171
+ <span>{item.title}</span>
172
+ </Link>
173
+ </SidebarMenuButton>
174
+ </SidebarMenuItem>
175
+ );
176
+ }
177
+
178
+ return (
179
+ <Collapsible
180
+ key={item.title}
181
+ asChild
182
+ defaultOpen={isActive}
183
+ className="group/collapsible"
184
+ >
185
+ <SidebarMenuItem>
186
+ <CollapsibleTrigger asChild>
187
+ <SidebarMenuButton tooltip={item.title} isActive={isActive}>
188
+ {item.icon && <item.icon />}
189
+ <span>{item.title}</span>
190
+ <ChevronRight className="ml-auto transition-transform duration-300 group-data-[state=open]/collapsible:rotate-90" />
191
+ </SidebarMenuButton>
192
+ </CollapsibleTrigger>
193
+ <CollapsibleContent>
194
+ <SidebarMenuSub>
195
+ {item.items?.map((subItem) => (
196
+ <SidebarMenuSubItem key={subItem.title}>
197
+ <SidebarMenuSubButton asChild isActive={pathname === subItem.url}>
198
+ <Link href={subItem.url}>
199
+ <span>{subItem.title}</span>
200
+ </Link>
201
+ </SidebarMenuSubButton>
202
+ </SidebarMenuSubItem>
203
+ ))}
204
+ </SidebarMenuSub>
205
+ </CollapsibleContent>
206
+ </SidebarMenuItem>
207
+ </Collapsible>
208
+ );
209
+ })}
210
+ </SidebarMenu>
211
+ </SidebarGroup>
212
+ {/* Nav Main */}
213
 
214
+ {/* Status Section */}
215
+ <SidebarGroup className="group-data-[collapsible=icon]:hidden mt-auto">
216
+ <SidebarGroupLabel>System Status</SidebarGroupLabel>
217
+ <div className="px-3 py-2">
218
+ <div className="rounded-lg border bg-muted/50 p-3">
219
+ <div className="flex items-center gap-2 mb-2">
220
  <Terminal className="h-4 w-4 text-muted-foreground" />
221
  <span className="text-xs font-medium">Status</span>
222
+ </div>
223
+ <div className="flex items-center gap-2">
224
  <span className="relative flex h-2 w-2">
225
  <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
226
  <span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
227
  </span>
228
  <span className="text-xs text-muted-foreground">System Online</span>
229
+ </div>
230
  </div>
231
+ </div>
232
+ </SidebarGroup>
233
+ {/* Status Section */}
234
+ </SidebarContent>
235
+
236
+ <SidebarFooter>
237
+ {/* Nav User */}
238
+ <SidebarMenu>
239
+ <SidebarMenuItem>
240
+ <DropdownMenu>
241
+ <DropdownMenuTrigger asChild>
242
+ <SidebarMenuButton
243
+ size="lg"
244
+ className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
245
+ >
246
+ <Avatar className="h-8 w-8 rounded-lg">
247
+ <AvatarImage src={userData.avatar} alt={userData.name} />
248
+ <AvatarFallback className="rounded-lg bg-primary text-primary-foreground">
249
+ <User className="h-4 w-4" />
250
+ </AvatarFallback>
251
+ </Avatar>
252
+ <div className="grid flex-1 text-left text-sm leading-tight">
253
+ <span className="truncate font-semibold">{userData.name}</span>
254
+ <span className="truncate text-xs">{userData.email}</span>
255
+ </div>
256
+ <ChevronsUpDown className="ml-auto size-4" />
257
+ </SidebarMenuButton>
258
+ </DropdownMenuTrigger>
259
+ <DropdownMenuContent
260
+ className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
261
+ side={isMobile ? 'bottom' : 'right'}
262
+ align="end"
263
+ sideOffset={4}
264
+ >
265
+ <DropdownMenuLabel className="p-0 font-normal">
266
+ <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
267
+ <Avatar className="h-8 w-8 rounded-lg">
268
+ <AvatarImage src={userData.avatar} alt={userData.name} />
269
+ <AvatarFallback className="rounded-lg bg-primary text-primary-foreground">
270
+ <User className="h-4 w-4" />
271
+ </AvatarFallback>
272
+ </Avatar>
273
+ <div className="grid flex-1 text-left text-sm leading-tight">
274
+ <span className="truncate font-semibold">{userData.name}</span>
275
+ <span className="truncate text-xs">{userData.email}</span>
276
+ </div>
277
+ </div>
278
+ </DropdownMenuLabel>
279
+ <DropdownMenuSeparator />
280
+ <DropdownMenuGroup>
281
+ <DropdownMenuItem>
282
+ <Sparkles />
283
+ Upgrade to Pro
284
+ </DropdownMenuItem>
285
+ </DropdownMenuGroup>
286
+ <DropdownMenuSeparator />
287
+ <DropdownMenuGroup>
288
+ <DropdownMenuItem>
289
+ <BadgeCheck />
290
+ Account
291
+ </DropdownMenuItem>
292
+ <DropdownMenuItem>
293
+ <CreditCard />
294
+ Billing
295
+ </DropdownMenuItem>
296
+ <DropdownMenuItem>
297
+ <Bell />
298
+ Notifications
299
+ </DropdownMenuItem>
300
+ </DropdownMenuGroup>
301
+ <DropdownMenuSeparator />
302
+ <DropdownMenuItem>
303
+ <LogOut />
304
+ Log out
305
+ </DropdownMenuItem>
306
+ </DropdownMenuContent>
307
+ </DropdownMenu>
308
+ </SidebarMenuItem>
309
+ </SidebarMenu>
310
+ {/* Nav User */}
311
+ </SidebarFooter>
312
+ <SidebarRail />
313
+ </Sidebar>
314
+ );
315
  }
ui/components/ui/avatar.tsx ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Avatar = React.forwardRef<
9
+ React.ElementRef<typeof AvatarPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <AvatarPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ ))
21
+ Avatar.displayName = AvatarPrimitive.Root.displayName
22
+
23
+ const AvatarImage = React.forwardRef<
24
+ React.ElementRef<typeof AvatarPrimitive.Image>,
25
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
26
+ >(({ className, ...props }, ref) => (
27
+ <AvatarPrimitive.Image
28
+ ref={ref}
29
+ className={cn("aspect-square h-full w-full", className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
34
+
35
+ const AvatarFallback = React.forwardRef<
36
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
37
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
38
+ >(({ className, ...props }, ref) => (
39
+ <AvatarPrimitive.Fallback
40
+ ref={ref}
41
+ className={cn(
42
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ ))
48
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49
+
50
+ export { Avatar, AvatarImage, AvatarFallback }
ui/components/ui/breadcrumb.tsx ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Breadcrumb = React.forwardRef<
8
+ HTMLElement,
9
+ React.ComponentPropsWithoutRef<"nav"> & {
10
+ separator?: React.ReactNode
11
+ }
12
+ >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
13
+ Breadcrumb.displayName = "Breadcrumb"
14
+
15
+ const BreadcrumbList = React.forwardRef<
16
+ HTMLOListElement,
17
+ React.ComponentPropsWithoutRef<"ol">
18
+ >(({ className, ...props }, ref) => (
19
+ <ol
20
+ ref={ref}
21
+ className={cn(
22
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ BreadcrumbList.displayName = "BreadcrumbList"
29
+
30
+ const BreadcrumbItem = React.forwardRef<
31
+ HTMLLIElement,
32
+ React.ComponentPropsWithoutRef<"li">
33
+ >(({ className, ...props }, ref) => (
34
+ <li
35
+ ref={ref}
36
+ className={cn("inline-flex items-center gap-1.5", className)}
37
+ {...props}
38
+ />
39
+ ))
40
+ BreadcrumbItem.displayName = "BreadcrumbItem"
41
+
42
+ const BreadcrumbLink = React.forwardRef<
43
+ HTMLAnchorElement,
44
+ React.ComponentPropsWithoutRef<"a"> & {
45
+ asChild?: boolean
46
+ }
47
+ >(({ asChild, className, ...props }, ref) => {
48
+ const Comp = asChild ? Slot : "a"
49
+
50
+ return (
51
+ <Comp
52
+ ref={ref}
53
+ className={cn("transition-colors hover:text-foreground", className)}
54
+ {...props}
55
+ />
56
+ )
57
+ })
58
+ BreadcrumbLink.displayName = "BreadcrumbLink"
59
+
60
+ const BreadcrumbPage = React.forwardRef<
61
+ HTMLSpanElement,
62
+ React.ComponentPropsWithoutRef<"span">
63
+ >(({ className, ...props }, ref) => (
64
+ <span
65
+ ref={ref}
66
+ role="link"
67
+ aria-disabled="true"
68
+ aria-current="page"
69
+ className={cn("font-normal text-foreground", className)}
70
+ {...props}
71
+ />
72
+ ))
73
+ BreadcrumbPage.displayName = "BreadcrumbPage"
74
+
75
+ const BreadcrumbSeparator = ({
76
+ children,
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<"li">) => (
80
+ <li
81
+ role="presentation"
82
+ aria-hidden="true"
83
+ className={cn("[&>svg]:size-3.5", className)}
84
+ {...props}
85
+ >
86
+ {children ?? <ChevronRight />}
87
+ </li>
88
+ )
89
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
90
+
91
+ const BreadcrumbEllipsis = ({
92
+ className,
93
+ ...props
94
+ }: React.ComponentProps<"span">) => (
95
+ <span
96
+ role="presentation"
97
+ aria-hidden="true"
98
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
99
+ {...props}
100
+ >
101
+ <MoreHorizontal className="h-4 w-4" />
102
+ <span className="sr-only">More</span>
103
+ </span>
104
+ )
105
+ BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
106
+
107
+ export {
108
+ Breadcrumb,
109
+ BreadcrumbList,
110
+ BreadcrumbItem,
111
+ BreadcrumbLink,
112
+ BreadcrumbPage,
113
+ BreadcrumbSeparator,
114
+ BreadcrumbEllipsis,
115
+ }
ui/components/ui/button.tsx CHANGED
@@ -1,4 +1,3 @@
1
- // ui/components/ui/button.tsx
2
  import * as React from "react"
3
  import { Slot } from "@radix-ui/react-slot"
4
  import { cva, type VariantProps } from "class-variance-authority"
@@ -6,26 +5,30 @@ import { cva, type VariantProps } from "class-variance-authority"
6
  import { cn } from "@/lib/utils"
7
 
8
  const buttonVariants = cva(
9
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
10
  {
11
  variants: {
12
  variant: {
13
- default:
14
- "bg-primary text-primary-foreground shadow hover:bg-primary/90",
15
  destructive:
16
- "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
17
  outline:
18
- "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
19
  secondary:
20
- "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
21
- ghost: "hover:bg-accent hover:text-accent-foreground",
 
22
  link: "text-primary underline-offset-4 hover:underline",
23
  },
24
  size: {
25
- default: "h-9 px-4 py-2",
26
- sm: "h-8 rounded-md px-3 text-xs",
27
- lg: "h-10 rounded-md px-8",
28
- icon: "h-9 w-9",
 
 
 
 
29
  },
30
  },
31
  defaultVariants: {
@@ -35,24 +38,27 @@ const buttonVariants = cva(
35
  }
36
  )
37
 
38
- export interface ButtonProps
39
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
40
- VariantProps<typeof buttonVariants> {
41
- asChild?: boolean
42
- }
 
 
 
 
 
 
43
 
44
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
45
- ({ className, variant, size, asChild = false, ...props }, ref) => {
46
- const Comp = asChild ? Slot : "button"
47
- return (
48
- <Comp
49
- className={cn(buttonVariants({ variant, size, className }))}
50
- ref={ref}
51
- {...props}
52
- />
53
- )
54
- }
55
- )
56
- Button.displayName = "Button"
57
 
58
  export { Button, buttonVariants }
 
 
1
  import * as React from "react"
2
  import { Slot } from "@radix-ui/react-slot"
3
  import { cva, type VariantProps } from "class-variance-authority"
 
5
  import { cn } from "@/lib/utils"
6
 
7
  const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
  {
10
  variants: {
11
  variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
 
13
  destructive:
14
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
15
  outline:
16
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
17
  secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost:
20
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
21
  link: "text-primary underline-offset-4 hover:underline",
22
  },
23
  size: {
24
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
25
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
26
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
27
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28
+ icon: "size-9",
29
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
30
+ "icon-sm": "size-8",
31
+ "icon-lg": "size-10",
32
  },
33
  },
34
  defaultVariants: {
 
38
  }
39
  )
40
 
41
+ function Button({
42
+ className,
43
+ variant = "default",
44
+ size = "default",
45
+ asChild = false,
46
+ ...props
47
+ }: React.ComponentProps<"button"> &
48
+ VariantProps<typeof buttonVariants> & {
49
+ asChild?: boolean
50
+ }) {
51
+ const Comp = asChild ? Slot : "button"
52
 
53
+ return (
54
+ <Comp
55
+ data-slot="button"
56
+ data-variant={variant}
57
+ data-size={size}
58
+ className={cn(buttonVariants({ variant, size, className }))}
59
+ {...props}
60
+ />
61
+ )
62
+ }
 
 
 
63
 
64
  export { Button, buttonVariants }
ui/components/ui/empty.tsx ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { cva, type VariantProps } from "class-variance-authority"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Empty({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="empty"
9
+ className={cn(
10
+ "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="empty-header"
22
+ className={cn(
23
+ "flex max-w-sm flex-col items-center gap-2 text-center",
24
+ className
25
+ )}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ const emptyMediaVariants = cva(
32
+ "flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0",
33
+ {
34
+ variants: {
35
+ variant: {
36
+ default: "bg-transparent",
37
+ icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
38
+ },
39
+ },
40
+ defaultVariants: {
41
+ variant: "default",
42
+ },
43
+ }
44
+ )
45
+
46
+ function EmptyMedia({
47
+ className,
48
+ variant = "default",
49
+ ...props
50
+ }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
51
+ return (
52
+ <div
53
+ data-slot="empty-icon"
54
+ data-variant={variant}
55
+ className={cn(emptyMediaVariants({ variant, className }))}
56
+ {...props}
57
+ />
58
+ )
59
+ }
60
+
61
+ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
62
+ return (
63
+ <div
64
+ data-slot="empty-title"
65
+ className={cn("text-lg font-medium tracking-tight", className)}
66
+ {...props}
67
+ />
68
+ )
69
+ }
70
+
71
+ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
72
+ return (
73
+ <div
74
+ data-slot="empty-description"
75
+ className={cn(
76
+ "text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4",
77
+ className
78
+ )}
79
+ {...props}
80
+ />
81
+ )
82
+ }
83
+
84
+ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
85
+ return (
86
+ <div
87
+ data-slot="empty-content"
88
+ className={cn(
89
+ "flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
90
+ className
91
+ )}
92
+ {...props}
93
+ />
94
+ )
95
+ }
96
+
97
+ export {
98
+ Empty,
99
+ EmptyHeader,
100
+ EmptyTitle,
101
+ EmptyDescription,
102
+ EmptyContent,
103
+ EmptyMedia,
104
+ }
ui/eslint.config.mjs CHANGED
@@ -1,18 +1,76 @@
 
1
  import { defineConfig, globalIgnores } from "eslint/config";
2
  import nextVitals from "eslint-config-next/core-web-vitals";
3
  import nextTs from "eslint-config-next/typescript";
4
 
5
- const eslintConfig = defineConfig([
 
 
 
 
 
6
  ...nextVitals,
7
  ...nextTs,
8
- // Override default ignores of eslint-config-next.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  globalIgnores([
10
- // Default ignores of eslint-config-next:
11
  ".next/**",
12
  "out/**",
13
  "build/**",
 
 
 
14
  "next-env.d.ts",
15
  ]),
16
  ]);
17
-
18
- export default eslintConfig;
 
1
+ // eslint.config.ts
2
  import { defineConfig, globalIgnores } from "eslint/config";
3
  import nextVitals from "eslint-config-next/core-web-vitals";
4
  import nextTs from "eslint-config-next/typescript";
5
 
6
+ import reactHooks from "eslint-plugin-react-hooks";
7
+ import unusedImports from "eslint-plugin-unused-imports";
8
+ import simpleImportSort from "eslint-plugin-simple-import-sort";
9
+ import tailwindcss from "eslint-plugin-tailwindcss";
10
+
11
+ export default defineConfig([
12
  ...nextVitals,
13
  ...nextTs,
14
+
15
+ {
16
+ plugins: {
17
+ "react-hooks": reactHooks,
18
+ "unused-imports": unusedImports,
19
+ "simple-import-sort": simpleImportSort,
20
+ tailwindcss,
21
+ },
22
+
23
+ settings: {
24
+ // Helps eslint-plugin-tailwindcss understand cn()/cva() patterns (common with shadcn)
25
+ tailwindcss: {
26
+ callees: ["cn", "cva"],
27
+ // If you still have a config file, keep it here; otherwise it’s harmless.
28
+ config: "./tailwind.config.ts",
29
+ },
30
+ },
31
+
32
+ rules: {
33
+ // React hooks (Next includes some of this, but this keeps it explicit)
34
+ "react-hooks/rules-of-hooks": "error",
35
+ "react-hooks/exhaustive-deps": "warn",
36
+
37
+ // Prefer removing unused imports entirely
38
+ "unused-imports/no-unused-imports": "warn",
39
+ "unused-imports/no-unused-vars": [
40
+ "warn",
41
+ {
42
+ argsIgnorePattern: "^_",
43
+ varsIgnorePattern: "^_",
44
+ caughtErrorsIgnorePattern: "^_",
45
+ },
46
+ ],
47
+
48
+ // If unused-imports handles it, avoid double-reporting from TS ESLint
49
+ "@typescript-eslint/no-unused-vars": "off",
50
+
51
+ // Practical console policy
52
+ "no-console": ["warn", { allow: ["info", "warn", "error"] }],
53
+
54
+ // Clean, deterministic import ordering
55
+ "simple-import-sort/imports": "warn",
56
+ "simple-import-sort/exports": "warn",
57
+
58
+ // Tailwind + shadcn friendly defaults
59
+ "tailwindcss/classnames-order": "warn",
60
+ "tailwindcss/no-contradicting-classname": "error",
61
+ // shadcn often uses design-token classes (e.g. bg-background, text-foreground)
62
+ "tailwindcss/no-custom-classname": "off",
63
+ },
64
+ },
65
+
66
+ // Ignore generated/build output (and override eslint-config-next defaults explicitly)
67
  globalIgnores([
 
68
  ".next/**",
69
  "out/**",
70
  "build/**",
71
+ "dist/**",
72
+ "coverage/**",
73
+ "node_modules/**",
74
  "next-env.d.ts",
75
  ]),
76
  ]);
 
 
ui/package.json CHANGED
@@ -10,6 +10,10 @@
10
  "type-check": "tsc --noEmit"
11
  },
12
  "dependencies": {
 
 
 
 
13
  "@radix-ui/react-label": "^2.1.8",
14
  "@radix-ui/react-select": "^2.2.6",
15
  "@radix-ui/react-separator": "^1.1.8",
@@ -31,12 +35,17 @@
31
  "zod": "^4.3.6"
32
  },
33
  "devDependencies": {
 
34
  "@tailwindcss/postcss": "^4.1.18",
35
  "@types/node": "^25.0.10",
36
  "@types/react": "^19.2.9",
37
  "@types/react-dom": "^19.2.3",
38
  "eslint": "^9.39.2",
39
  "eslint-config-next": "^16.1.4",
 
 
 
 
40
  "shadcn": "^3.7.0",
41
  "tailwindcss": "^4.1.18",
42
  "tw-animate-css": "^1.4.0",
 
10
  "type-check": "tsc --noEmit"
11
  },
12
  "dependencies": {
13
+ "@floating-ui/react": "^0.27.16",
14
+ "@radix-ui/react-avatar": "^1.1.11",
15
+ "@radix-ui/react-collapsible": "^1.1.12",
16
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
17
  "@radix-ui/react-label": "^2.1.8",
18
  "@radix-ui/react-select": "^2.2.6",
19
  "@radix-ui/react-separator": "^1.1.8",
 
35
  "zod": "^4.3.6"
36
  },
37
  "devDependencies": {
38
+ "@eslint/js": "^9.39.2",
39
  "@tailwindcss/postcss": "^4.1.18",
40
  "@types/node": "^25.0.10",
41
  "@types/react": "^19.2.9",
42
  "@types/react-dom": "^19.2.3",
43
  "eslint": "^9.39.2",
44
  "eslint-config-next": "^16.1.4",
45
+ "eslint-plugin-react-hooks": "^7.0.1",
46
+ "eslint-plugin-simple-import-sort": "^12.1.1",
47
+ "eslint-plugin-tailwindcss": "^3.18.2",
48
+ "eslint-plugin-unused-imports": "^4.3.0",
49
  "shadcn": "^3.7.0",
50
  "tailwindcss": "^4.1.18",
51
  "tw-animate-css": "^1.4.0",
ui/pnpm-lock.yaml CHANGED
@@ -8,6 +8,18 @@ importers:
8
 
9
  .:
10
  dependencies:
 
 
 
 
 
 
 
 
 
 
 
 
11
  '@radix-ui/react-label':
12
  specifier: ^2.1.8
13
  version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -66,6 +78,9 @@ importers:
66
  specifier: ^4.3.6
67
  version: 4.3.6
68
  devDependencies:
 
 
 
69
  '@tailwindcss/postcss':
70
  specifier: ^4.1.18
71
  version: 4.1.18
@@ -84,6 +99,18 @@ importers:
84
  eslint-config-next:
85
  specifier: ^16.1.4
86
  version: 16.1.4(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
 
 
 
 
 
 
 
 
 
 
 
 
87
  shadcn:
88
  specifier: ^3.7.0
89
  version: 3.7.0(@types/node@25.0.10)(hono@4.11.5)(typescript@5.9.3)
@@ -305,6 +332,12 @@ packages:
305
  react: '>=16.8.0'
306
  react-dom: '>=16.8.0'
307
 
 
 
 
 
 
 
308
  '@floating-ui/utils@0.2.10':
309
  resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
310
 
@@ -718,6 +751,19 @@ packages:
718
  '@types/react-dom':
719
  optional: true
720
 
 
 
 
 
 
 
 
 
 
 
 
 
 
721
  '@radix-ui/react-checkbox@1.3.3':
722
  resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
723
  peerDependencies:
@@ -788,6 +834,15 @@ packages:
788
  '@types/react':
789
  optional: true
790
 
 
 
 
 
 
 
 
 
 
791
  '@radix-ui/react-dialog@1.1.15':
792
  resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==}
793
  peerDependencies:
@@ -2275,6 +2330,26 @@ packages:
2275
  peerDependencies:
2276
  eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
2277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2278
  eslint-scope@8.4.0:
2279
  resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
2280
  engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3681,6 +3756,9 @@ packages:
3681
  resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
3682
  engines: {node: '>= 0.4'}
3683
 
 
 
 
3684
  tagged-tag@1.0.0:
3685
  resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
3686
  engines: {node: '>=20'}
@@ -4243,6 +4321,14 @@ snapshots:
4243
  react: 19.2.3
4244
  react-dom: 19.2.3(react@19.2.3)
4245
 
 
 
 
 
 
 
 
 
4246
  '@floating-ui/utils@0.2.10': {}
4247
 
4248
  '@hono/node-server@1.19.9(hono@4.11.5)':
@@ -4584,6 +4670,19 @@ snapshots:
4584
  '@types/react': 19.2.9
4585
  '@types/react-dom': 19.2.3(@types/react@19.2.9)
4586
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4587
  '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
4588
  dependencies:
4589
  '@radix-ui/primitive': 1.1.3
@@ -4654,6 +4753,12 @@ snapshots:
4654
  optionalDependencies:
4655
  '@types/react': 19.2.9
4656
 
 
 
 
 
 
 
4657
  '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
4658
  dependencies:
4659
  '@radix-ui/primitive': 1.1.3
@@ -6262,6 +6367,22 @@ snapshots:
6262
  string.prototype.matchall: 4.0.12
6263
  string.prototype.repeat: 1.0.0
6264
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6265
  eslint-scope@8.4.0:
6266
  dependencies:
6267
  esrecurse: 4.3.0
@@ -7830,6 +7951,8 @@ snapshots:
7830
 
7831
  supports-preserve-symlinks-flag@1.0.0: {}
7832
 
 
 
7833
  tagged-tag@1.0.0: {}
7834
 
7835
  tailwind-merge@3.4.0: {}
 
8
 
9
  .:
10
  dependencies:
11
+ '@floating-ui/react':
12
+ specifier: ^0.27.16
13
+ version: 0.27.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
14
+ '@radix-ui/react-avatar':
15
+ specifier: ^1.1.11
16
+ version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
17
+ '@radix-ui/react-collapsible':
18
+ specifier: ^1.1.12
19
+ version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
20
+ '@radix-ui/react-dropdown-menu':
21
+ specifier: ^2.1.16
22
+ version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
23
  '@radix-ui/react-label':
24
  specifier: ^2.1.8
25
  version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
 
78
  specifier: ^4.3.6
79
  version: 4.3.6
80
  devDependencies:
81
+ '@eslint/js':
82
+ specifier: ^9.39.2
83
+ version: 9.39.2
84
  '@tailwindcss/postcss':
85
  specifier: ^4.1.18
86
  version: 4.1.18
 
99
  eslint-config-next:
100
  specifier: ^16.1.4
101
  version: 16.1.4(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
102
+ eslint-plugin-react-hooks:
103
+ specifier: ^7.0.1
104
+ version: 7.0.1(eslint@9.39.2(jiti@2.6.1))
105
+ eslint-plugin-simple-import-sort:
106
+ specifier: ^12.1.1
107
+ version: 12.1.1(eslint@9.39.2(jiti@2.6.1))
108
+ eslint-plugin-tailwindcss:
109
+ specifier: ^3.18.2
110
+ version: 3.18.2(tailwindcss@4.1.18)
111
+ eslint-plugin-unused-imports:
112
+ specifier: ^4.3.0
113
+ version: 4.3.0(@typescript-eslint/eslint-plugin@8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))
114
  shadcn:
115
  specifier: ^3.7.0
116
  version: 3.7.0(@types/node@25.0.10)(hono@4.11.5)(typescript@5.9.3)
 
332
  react: '>=16.8.0'
333
  react-dom: '>=16.8.0'
334
 
335
+ '@floating-ui/react@0.27.16':
336
+ resolution: {integrity: sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==}
337
+ peerDependencies:
338
+ react: '>=17.0.0'
339
+ react-dom: '>=17.0.0'
340
+
341
  '@floating-ui/utils@0.2.10':
342
  resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
343
 
 
751
  '@types/react-dom':
752
  optional: true
753
 
754
+ '@radix-ui/react-avatar@1.1.11':
755
+ resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==}
756
+ peerDependencies:
757
+ '@types/react': '*'
758
+ '@types/react-dom': '*'
759
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
760
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
761
+ peerDependenciesMeta:
762
+ '@types/react':
763
+ optional: true
764
+ '@types/react-dom':
765
+ optional: true
766
+
767
  '@radix-ui/react-checkbox@1.3.3':
768
  resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
769
  peerDependencies:
 
834
  '@types/react':
835
  optional: true
836
 
837
+ '@radix-ui/react-context@1.1.3':
838
+ resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==}
839
+ peerDependencies:
840
+ '@types/react': '*'
841
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
842
+ peerDependenciesMeta:
843
+ '@types/react':
844
+ optional: true
845
+
846
  '@radix-ui/react-dialog@1.1.15':
847
  resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==}
848
  peerDependencies:
 
2330
  peerDependencies:
2331
  eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
2332
 
2333
+ eslint-plugin-simple-import-sort@12.1.1:
2334
+ resolution: {integrity: sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==}
2335
+ peerDependencies:
2336
+ eslint: '>=5.0.0'
2337
+
2338
+ eslint-plugin-tailwindcss@3.18.2:
2339
+ resolution: {integrity: sha512-QbkMLDC/OkkjFQ1iz/5jkMdHfiMu/uwujUHLAJK5iwNHD8RTxVTlsUezE0toTZ6VhybNBsk+gYGPDq2agfeRNA==}
2340
+ engines: {node: '>=18.12.0'}
2341
+ peerDependencies:
2342
+ tailwindcss: ^3.4.0
2343
+
2344
+ eslint-plugin-unused-imports@4.3.0:
2345
+ resolution: {integrity: sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==}
2346
+ peerDependencies:
2347
+ '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0
2348
+ eslint: ^9.0.0 || ^8.0.0
2349
+ peerDependenciesMeta:
2350
+ '@typescript-eslint/eslint-plugin':
2351
+ optional: true
2352
+
2353
  eslint-scope@8.4.0:
2354
  resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
2355
  engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
3756
  resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
3757
  engines: {node: '>= 0.4'}
3758
 
3759
+ tabbable@6.4.0:
3760
+ resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
3761
+
3762
  tagged-tag@1.0.0:
3763
  resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
3764
  engines: {node: '>=20'}
 
4321
  react: 19.2.3
4322
  react-dom: 19.2.3(react@19.2.3)
4323
 
4324
+ '@floating-ui/react@0.27.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
4325
+ dependencies:
4326
+ '@floating-ui/react-dom': 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
4327
+ '@floating-ui/utils': 0.2.10
4328
+ react: 19.2.3
4329
+ react-dom: 19.2.3(react@19.2.3)
4330
+ tabbable: 6.4.0
4331
+
4332
  '@floating-ui/utils@0.2.10': {}
4333
 
4334
  '@hono/node-server@1.19.9(hono@4.11.5)':
 
4670
  '@types/react': 19.2.9
4671
  '@types/react-dom': 19.2.3(@types/react@19.2.9)
4672
 
4673
+ '@radix-ui/react-avatar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
4674
+ dependencies:
4675
+ '@radix-ui/react-context': 1.1.3(@types/react@19.2.9)(react@19.2.3)
4676
+ '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
4677
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.9)(react@19.2.3)
4678
+ '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.9)(react@19.2.3)
4679
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.9)(react@19.2.3)
4680
+ react: 19.2.3
4681
+ react-dom: 19.2.3(react@19.2.3)
4682
+ optionalDependencies:
4683
+ '@types/react': 19.2.9
4684
+ '@types/react-dom': 19.2.3(@types/react@19.2.9)
4685
+
4686
  '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
4687
  dependencies:
4688
  '@radix-ui/primitive': 1.1.3
 
4753
  optionalDependencies:
4754
  '@types/react': 19.2.9
4755
 
4756
+ '@radix-ui/react-context@1.1.3(@types/react@19.2.9)(react@19.2.3)':
4757
+ dependencies:
4758
+ react: 19.2.3
4759
+ optionalDependencies:
4760
+ '@types/react': 19.2.9
4761
+
4762
  '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
4763
  dependencies:
4764
  '@radix-ui/primitive': 1.1.3
 
6367
  string.prototype.matchall: 4.0.12
6368
  string.prototype.repeat: 1.0.0
6369
 
6370
+ eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.2(jiti@2.6.1)):
6371
+ dependencies:
6372
+ eslint: 9.39.2(jiti@2.6.1)
6373
+
6374
+ eslint-plugin-tailwindcss@3.18.2(tailwindcss@4.1.18):
6375
+ dependencies:
6376
+ fast-glob: 3.3.3
6377
+ postcss: 8.5.6
6378
+ tailwindcss: 4.1.18
6379
+
6380
+ eslint-plugin-unused-imports@4.3.0(@typescript-eslint/eslint-plugin@8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)):
6381
+ dependencies:
6382
+ eslint: 9.39.2(jiti@2.6.1)
6383
+ optionalDependencies:
6384
+ '@typescript-eslint/eslint-plugin': 8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
6385
+
6386
  eslint-scope@8.4.0:
6387
  dependencies:
6388
  esrecurse: 4.3.0
 
7951
 
7952
  supports-preserve-symlinks-flag@1.0.0: {}
7953
 
7954
+ tabbable@6.4.0: {}
7955
+
7956
  tagged-tag@1.0.0: {}
7957
 
7958
  tailwind-merge@3.4.0: {}