@xemahq/ui-kernel 0.1.12 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/biome-host/errors.d.ts +2 -0
  6. package/dist/lib/biome-host/errors.d.ts.map +1 -0
  7. package/dist/lib/biome-host/errors.js +146 -0
  8. package/dist/lib/biome-host/errors.js.map +1 -0
  9. package/dist/lib/biome-host/host-bridge.d.ts +8 -0
  10. package/dist/lib/biome-host/host-bridge.d.ts.map +1 -1
  11. package/dist/lib/biome-host/host-bridge.js.map +1 -1
  12. package/dist/lib/biome-host/index.d.ts +4 -0
  13. package/dist/lib/biome-host/index.d.ts.map +1 -1
  14. package/dist/lib/biome-host/index.js +4 -0
  15. package/dist/lib/biome-host/index.js.map +1 -1
  16. package/dist/lib/biome-host/realtime-hooks.d.ts +5 -0
  17. package/dist/lib/biome-host/realtime-hooks.d.ts.map +1 -0
  18. package/dist/lib/biome-host/realtime-hooks.js +28 -0
  19. package/dist/lib/biome-host/realtime-hooks.js.map +1 -0
  20. package/dist/lib/biome-host/realtime-port.d.ts +30 -0
  21. package/dist/lib/biome-host/realtime-port.d.ts.map +1 -0
  22. package/dist/lib/biome-host/realtime-port.js +3 -0
  23. package/dist/lib/biome-host/realtime-port.js.map +1 -0
  24. package/dist/lib/biome-host/response-envelope.d.ts +3 -0
  25. package/dist/lib/biome-host/response-envelope.d.ts.map +1 -0
  26. package/dist/lib/biome-host/response-envelope.js +25 -0
  27. package/dist/lib/biome-host/response-envelope.js.map +1 -0
  28. package/dist/lib/capabilities/capability-provider.d.ts +15 -0
  29. package/dist/lib/capabilities/capability-provider.d.ts.map +1 -0
  30. package/dist/lib/capabilities/capability-provider.js +36 -0
  31. package/dist/lib/capabilities/capability-provider.js.map +1 -0
  32. package/dist/lib/capabilities/index.d.ts +4 -0
  33. package/dist/lib/capabilities/index.d.ts.map +1 -0
  34. package/dist/lib/capabilities/index.js +20 -0
  35. package/dist/lib/capabilities/index.js.map +1 -0
  36. package/dist/lib/capabilities/types.d.ts +18 -0
  37. package/dist/lib/capabilities/types.d.ts.map +1 -0
  38. package/dist/lib/capabilities/types.js +3 -0
  39. package/dist/lib/capabilities/types.js.map +1 -0
  40. package/dist/lib/capabilities/use-capability.d.ts +18 -0
  41. package/dist/lib/capabilities/use-capability.d.ts.map +1 -0
  42. package/dist/lib/capabilities/use-capability.js +21 -0
  43. package/dist/lib/capabilities/use-capability.js.map +1 -0
  44. package/dist/ui/chrome/AsyncBoundary.d.ts +22 -0
  45. package/dist/ui/chrome/AsyncBoundary.d.ts.map +1 -0
  46. package/dist/ui/chrome/AsyncBoundary.js +23 -0
  47. package/dist/ui/chrome/AsyncBoundary.js.map +1 -0
  48. package/dist/ui/chrome/EmptyState.d.ts +34 -0
  49. package/dist/ui/chrome/EmptyState.d.ts.map +1 -0
  50. package/dist/ui/chrome/EmptyState.js +27 -0
  51. package/dist/ui/chrome/EmptyState.js.map +1 -0
  52. package/dist/ui/chrome/ErrorCard.d.ts +11 -0
  53. package/dist/ui/chrome/ErrorCard.d.ts.map +1 -0
  54. package/dist/ui/chrome/ErrorCard.js +14 -0
  55. package/dist/ui/chrome/ErrorCard.js.map +1 -0
  56. package/dist/ui/chrome/LoadingState.d.ts +10 -0
  57. package/dist/ui/chrome/LoadingState.d.ts.map +1 -0
  58. package/dist/ui/chrome/LoadingState.js +17 -0
  59. package/dist/ui/chrome/LoadingState.js.map +1 -0
  60. package/dist/ui/chrome/PageHeader.d.ts +20 -0
  61. package/dist/ui/chrome/PageHeader.d.ts.map +1 -0
  62. package/dist/ui/chrome/PageHeader.js +26 -0
  63. package/dist/ui/chrome/PageHeader.js.map +1 -0
  64. package/dist/ui/chrome/StateCard.d.ts +24 -0
  65. package/dist/ui/chrome/StateCard.d.ts.map +1 -0
  66. package/dist/ui/chrome/StateCard.js +17 -0
  67. package/dist/ui/chrome/StateCard.js.map +1 -0
  68. package/dist/ui/cn.d.ts +3 -0
  69. package/dist/ui/cn.d.ts.map +1 -0
  70. package/dist/ui/cn.js +18 -0
  71. package/dist/ui/cn.js.map +1 -0
  72. package/dist/ui/index.d.ts +33 -0
  73. package/dist/ui/index.d.ts.map +1 -0
  74. package/dist/ui/index.js +61 -0
  75. package/dist/ui/index.js.map +1 -0
  76. package/dist/ui/primitives/alert-dialog.d.ts +21 -0
  77. package/dist/ui/primitives/alert-dialog.d.ts.map +1 -0
  78. package/dist/ui/primitives/alert-dialog.js +72 -0
  79. package/dist/ui/primitives/alert-dialog.js.map +1 -0
  80. package/dist/ui/primitives/badge.d.ts +10 -0
  81. package/dist/ui/primitives/badge.d.ts.map +1 -0
  82. package/dist/ui/primitives/badge.js +60 -0
  83. package/dist/ui/primitives/badge.js.map +1 -0
  84. package/dist/ui/primitives/button.d.ts +12 -0
  85. package/dist/ui/primitives/button.d.ts.map +1 -0
  86. package/dist/ui/primitives/button.js +71 -0
  87. package/dist/ui/primitives/button.js.map +1 -0
  88. package/dist/ui/primitives/card.d.ts +9 -0
  89. package/dist/ui/primitives/card.d.ts.map +1 -0
  90. package/dist/ui/primitives/card.js +58 -0
  91. package/dist/ui/primitives/card.js.map +1 -0
  92. package/dist/ui/primitives/checkbox.d.ts +5 -0
  93. package/dist/ui/primitives/checkbox.d.ts.map +1 -0
  94. package/dist/ui/primitives/checkbox.js +45 -0
  95. package/dist/ui/primitives/checkbox.js.map +1 -0
  96. package/dist/ui/primitives/collapsible.d.ts +6 -0
  97. package/dist/ui/primitives/collapsible.d.ts.map +1 -0
  98. package/dist/ui/primitives/collapsible.js +44 -0
  99. package/dist/ui/primitives/collapsible.js.map +1 -0
  100. package/dist/ui/primitives/dialog.d.ts +22 -0
  101. package/dist/ui/primitives/dialog.d.ts.map +1 -0
  102. package/dist/ui/primitives/dialog.js +68 -0
  103. package/dist/ui/primitives/dialog.js.map +1 -0
  104. package/dist/ui/primitives/dropdown-menu.d.ts +28 -0
  105. package/dist/ui/primitives/dropdown-menu.d.ts.map +1 -0
  106. package/dist/ui/primitives/dropdown-menu.js +83 -0
  107. package/dist/ui/primitives/dropdown-menu.js.map +1 -0
  108. package/dist/ui/primitives/input.d.ts +4 -0
  109. package/dist/ui/primitives/input.d.ts.map +1 -0
  110. package/dist/ui/primitives/input.js +45 -0
  111. package/dist/ui/primitives/input.js.map +1 -0
  112. package/dist/ui/primitives/label.d.ts +6 -0
  113. package/dist/ui/primitives/label.d.ts.map +1 -0
  114. package/dist/ui/primitives/label.js +46 -0
  115. package/dist/ui/primitives/label.js.map +1 -0
  116. package/dist/ui/primitives/overflow-tabs.d.ts +18 -0
  117. package/dist/ui/primitives/overflow-tabs.d.ts.map +1 -0
  118. package/dist/ui/primitives/overflow-tabs.js +84 -0
  119. package/dist/ui/primitives/overflow-tabs.js.map +1 -0
  120. package/dist/ui/primitives/popover.d.ts +9 -0
  121. package/dist/ui/primitives/popover.d.ts.map +1 -0
  122. package/dist/ui/primitives/popover.js +48 -0
  123. package/dist/ui/primitives/popover.js.map +1 -0
  124. package/dist/ui/primitives/radio-group.d.ts +6 -0
  125. package/dist/ui/primitives/radio-group.d.ts.map +1 -0
  126. package/dist/ui/primitives/radio-group.js +52 -0
  127. package/dist/ui/primitives/radio-group.js.map +1 -0
  128. package/dist/ui/primitives/resizable.d.ts +12 -0
  129. package/dist/ui/primitives/resizable.d.ts.map +1 -0
  130. package/dist/ui/primitives/resizable.js +18 -0
  131. package/dist/ui/primitives/resizable.js.map +1 -0
  132. package/dist/ui/primitives/scroll-area.d.ts +6 -0
  133. package/dist/ui/primitives/scroll-area.d.ts.map +1 -0
  134. package/dist/ui/primitives/scroll-area.js +47 -0
  135. package/dist/ui/primitives/scroll-area.js.map +1 -0
  136. package/dist/ui/primitives/select.d.ts +14 -0
  137. package/dist/ui/primitives/select.d.ts.map +1 -0
  138. package/dist/ui/primitives/select.js +71 -0
  139. package/dist/ui/primitives/select.js.map +1 -0
  140. package/dist/ui/primitives/separator.d.ts +5 -0
  141. package/dist/ui/primitives/separator.d.ts.map +1 -0
  142. package/dist/ui/primitives/separator.js +44 -0
  143. package/dist/ui/primitives/separator.js.map +1 -0
  144. package/dist/ui/primitives/sheet.d.ts +26 -0
  145. package/dist/ui/primitives/sheet.d.ts.map +1 -0
  146. package/dist/ui/primitives/sheet.js +82 -0
  147. package/dist/ui/primitives/sheet.js.map +1 -0
  148. package/dist/ui/primitives/skeleton.d.ts +13 -0
  149. package/dist/ui/primitives/skeleton.d.ts.map +1 -0
  150. package/dist/ui/primitives/skeleton.js +29 -0
  151. package/dist/ui/primitives/skeleton.js.map +1 -0
  152. package/dist/ui/primitives/switch.d.ts +5 -0
  153. package/dist/ui/primitives/switch.d.ts.map +1 -0
  154. package/dist/ui/primitives/switch.js +44 -0
  155. package/dist/ui/primitives/switch.js.map +1 -0
  156. package/dist/ui/primitives/table.d.ts +11 -0
  157. package/dist/ui/primitives/table.d.ts.map +1 -0
  158. package/dist/ui/primitives/table.js +64 -0
  159. package/dist/ui/primitives/table.js.map +1 -0
  160. package/dist/ui/primitives/tabs.d.ts +8 -0
  161. package/dist/ui/primitives/tabs.d.ts.map +1 -0
  162. package/dist/ui/primitives/tabs.js +52 -0
  163. package/dist/ui/primitives/tabs.js.map +1 -0
  164. package/dist/ui/primitives/tag-multi-select.d.ts +19 -0
  165. package/dist/ui/primitives/tag-multi-select.d.ts.map +1 -0
  166. package/dist/ui/primitives/tag-multi-select.js +92 -0
  167. package/dist/ui/primitives/tag-multi-select.js.map +1 -0
  168. package/dist/ui/primitives/textarea.d.ts +5 -0
  169. package/dist/ui/primitives/textarea.d.ts.map +1 -0
  170. package/dist/ui/primitives/textarea.js +45 -0
  171. package/dist/ui/primitives/textarea.js.map +1 -0
  172. package/dist/ui/primitives/tooltip.d.ts +8 -0
  173. package/dist/ui/primitives/tooltip.d.ts.map +1 -0
  174. package/dist/ui/primitives/tooltip.js +50 -0
  175. package/dist/ui/primitives/tooltip.js.map +1 -0
  176. package/package.json +24 -1
  177. package/src/index.ts +1 -0
  178. package/src/lib/biome-host/errors.ts +220 -0
  179. package/src/lib/biome-host/host-bridge.ts +54 -0
  180. package/src/lib/biome-host/index.ts +4 -0
  181. package/src/lib/biome-host/realtime-hooks.ts +74 -0
  182. package/src/lib/biome-host/realtime-port.ts +109 -0
  183. package/src/lib/biome-host/response-envelope.ts +69 -0
  184. package/src/lib/capabilities/capability-provider.tsx +95 -0
  185. package/src/lib/capabilities/index.ts +16 -0
  186. package/src/lib/capabilities/types.ts +69 -0
  187. package/src/lib/capabilities/use-capability.ts +72 -0
  188. package/src/ui/chrome/AsyncBoundary.tsx +66 -0
  189. package/src/ui/chrome/EmptyState.tsx +184 -0
  190. package/src/ui/chrome/ErrorCard.tsx +63 -0
  191. package/src/ui/chrome/LoadingState.tsx +61 -0
  192. package/src/ui/chrome/PageHeader.tsx +137 -0
  193. package/src/ui/chrome/StateCard.tsx +150 -0
  194. package/src/ui/cn.ts +32 -0
  195. package/src/ui/index.ts +53 -0
  196. package/src/ui/primitives/alert-dialog.tsx +104 -0
  197. package/src/ui/primitives/badge.tsx +32 -0
  198. package/src/ui/primitives/button.tsx +47 -0
  199. package/src/ui/primitives/card.tsx +43 -0
  200. package/src/ui/primitives/checkbox.tsx +26 -0
  201. package/src/ui/primitives/collapsible.tsx +9 -0
  202. package/src/ui/primitives/dialog.tsx +103 -0
  203. package/src/ui/primitives/dropdown-menu.tsx +179 -0
  204. package/src/ui/primitives/input.tsx +22 -0
  205. package/src/ui/primitives/label.tsx +17 -0
  206. package/src/ui/primitives/overflow-tabs.tsx +281 -0
  207. package/src/ui/primitives/popover.tsx +33 -0
  208. package/src/ui/primitives/radio-group.tsx +36 -0
  209. package/src/ui/primitives/resizable.tsx +67 -0
  210. package/src/ui/primitives/scroll-area.tsx +38 -0
  211. package/src/ui/primitives/select.tsx +143 -0
  212. package/src/ui/primitives/separator.tsx +20 -0
  213. package/src/ui/primitives/sheet.tsx +107 -0
  214. package/src/ui/primitives/skeleton.tsx +99 -0
  215. package/src/ui/primitives/switch.tsx +27 -0
  216. package/src/ui/primitives/table.tsx +72 -0
  217. package/src/ui/primitives/tabs.tsx +53 -0
  218. package/src/ui/primitives/tag-multi-select.tsx +241 -0
  219. package/src/ui/primitives/textarea.tsx +21 -0
  220. package/src/ui/primitives/tooltip.tsx +30 -0
@@ -0,0 +1,26 @@
1
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
2
+ import { Check } from "lucide-react";
3
+ import * as React from "react";
4
+
5
+ import { cn } from '../cn';
6
+
7
+ const Checkbox = React.forwardRef<
8
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <CheckboxPrimitive.Root
12
+ ref={ref}
13
+ className={cn(
14
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
15
+ className,
16
+ )}
17
+ {...props}
18
+ >
19
+ <CheckboxPrimitive.Indicator className={cn("flex items-center justify-center text-current")}>
20
+ <Check className="h-4 w-4" />
21
+ </CheckboxPrimitive.Indicator>
22
+ </CheckboxPrimitive.Root>
23
+ ));
24
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName;
25
+
26
+ export { Checkbox };
@@ -0,0 +1,9 @@
1
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
2
+
3
+ const Collapsible = CollapsiblePrimitive.Root;
4
+
5
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
6
+
7
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
8
+
9
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent };
@@ -0,0 +1,103 @@
1
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
2
+ import { X } from "lucide-react";
3
+ import * as React from "react";
4
+
5
+ import { cn } from '../cn';
6
+
7
+ const Dialog = DialogPrimitive.Root;
8
+
9
+ const DialogTrigger = DialogPrimitive.Trigger;
10
+
11
+ const DialogPortal = DialogPrimitive.Portal;
12
+
13
+ const DialogClose = DialogPrimitive.Close;
14
+
15
+ const DialogOverlay = React.forwardRef<
16
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
17
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
18
+ >(({ className, ...props }, ref) => (
19
+ <DialogPrimitive.Overlay
20
+ ref={ref}
21
+ className={cn(
22
+ "modal-scrim fixed inset-0 z-50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
23
+ className,
24
+ )}
25
+ {...props}
26
+ />
27
+ ));
28
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
29
+
30
+ const DialogContent = React.forwardRef<
31
+ React.ElementRef<typeof DialogPrimitive.Content>,
32
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { 'data-hide-close'?: boolean }
33
+ >(({ className, children, 'data-hide-close': hideClose, 'aria-describedby': ariaDescribedBy, ...props }, ref) => (
34
+ <DialogPortal>
35
+ <DialogOverlay />
36
+ <DialogPrimitive.Content
37
+ ref={ref}
38
+ // Pulling `aria-describedby` out of props and re-passing it (even
39
+ // when undefined) silences Radix's "Missing Description or
40
+ // aria-describedby={undefined}" warning for dialogs that
41
+ // intentionally have no description. Dialogs that DO include a
42
+ // <DialogDescription> still set this through the spread.
43
+ aria-describedby={ariaDescribedBy}
44
+ className={cn(
45
+ "modal-surface fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg max-h-[calc(100dvh-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto p-6 duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
46
+ className,
47
+ )}
48
+ {...props}
49
+ >
50
+ {children}
51
+ {!hideClose && (
52
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm text-ink-3 ring-offset-background transition-colors hover:text-ink focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
53
+ <X className="h-4 w-4" />
54
+ <span className="sr-only">Close</span>
55
+ </DialogPrimitive.Close>
56
+ )}
57
+ </DialogPrimitive.Content>
58
+ </DialogPortal>
59
+ ));
60
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
61
+
62
+ const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
63
+ <div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
64
+ );
65
+ DialogHeader.displayName = "DialogHeader";
66
+
67
+ const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
68
+ <div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
69
+ );
70
+ DialogFooter.displayName = "DialogFooter";
71
+
72
+ const DialogTitle = React.forwardRef<
73
+ React.ElementRef<typeof DialogPrimitive.Title>,
74
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
75
+ >(({ className, ...props }, ref) => (
76
+ <DialogPrimitive.Title
77
+ ref={ref}
78
+ className={cn("text-subtitle font-semibold leading-none tracking-tight", className)}
79
+ {...props}
80
+ />
81
+ ));
82
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
83
+
84
+ const DialogDescription = React.forwardRef<
85
+ React.ElementRef<typeof DialogPrimitive.Description>,
86
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
87
+ >(({ className, ...props }, ref) => (
88
+ <DialogPrimitive.Description ref={ref} className={cn("text-body-1 text-ink-3", className)} {...props} />
89
+ ));
90
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
91
+
92
+ export {
93
+ Dialog,
94
+ DialogPortal,
95
+ DialogOverlay,
96
+ DialogClose,
97
+ DialogTrigger,
98
+ DialogContent,
99
+ DialogHeader,
100
+ DialogFooter,
101
+ DialogTitle,
102
+ DialogDescription,
103
+ };
@@ -0,0 +1,179 @@
1
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
2
+ import { Check, ChevronRight, Circle } from "lucide-react";
3
+ import * as React from "react";
4
+
5
+ import { cn } from '../cn';
6
+
7
+ const DropdownMenu = DropdownMenuPrimitive.Root;
8
+
9
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
10
+
11
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group;
12
+
13
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
14
+
15
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
16
+
17
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
18
+
19
+ const DropdownMenuSubTrigger = React.forwardRef<
20
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
21
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
22
+ inset?: boolean;
23
+ }
24
+ >(({ className, inset, children, ...props }, ref) => (
25
+ <DropdownMenuPrimitive.SubTrigger
26
+ ref={ref}
27
+ className={cn(
28
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-body-1 outline-none data-[state=open]:bg-accent focus:bg-accent",
29
+ inset && "pl-8",
30
+ className,
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronRight className="ml-auto h-4 w-4" />
36
+ </DropdownMenuPrimitive.SubTrigger>
37
+ ));
38
+ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
39
+
40
+ const DropdownMenuSubContent = React.forwardRef<
41
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
42
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
43
+ >(({ className, ...props }, ref) => (
44
+ <DropdownMenuPrimitive.SubContent
45
+ ref={ref}
46
+ className={cn(
47
+ "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",
48
+ className,
49
+ )}
50
+ {...props}
51
+ />
52
+ ));
53
+ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
54
+
55
+ const DropdownMenuContent = React.forwardRef<
56
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
57
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
58
+ >(({ className, sideOffset = 4, ...props }, ref) => (
59
+ <DropdownMenuPrimitive.Portal>
60
+ <DropdownMenuPrimitive.Content
61
+ ref={ref}
62
+ sideOffset={sideOffset}
63
+ className={cn(
64
+ "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",
65
+ className,
66
+ )}
67
+ {...props}
68
+ />
69
+ </DropdownMenuPrimitive.Portal>
70
+ ));
71
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
72
+
73
+ const DropdownMenuItem = React.forwardRef<
74
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
75
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
76
+ inset?: boolean;
77
+ }
78
+ >(({ className, inset, ...props }, ref) => (
79
+ <DropdownMenuPrimitive.Item
80
+ ref={ref}
81
+ className={cn(
82
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-body-1 outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
83
+ inset && "pl-8",
84
+ className,
85
+ )}
86
+ {...props}
87
+ />
88
+ ));
89
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
90
+
91
+ const DropdownMenuCheckboxItem = React.forwardRef<
92
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
93
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
94
+ >(({ className, children, checked, ...props }, ref) => (
95
+ <DropdownMenuPrimitive.CheckboxItem
96
+ ref={ref}
97
+ className={cn(
98
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-body-1 outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
99
+ className,
100
+ )}
101
+ checked={checked}
102
+ {...props}
103
+ >
104
+ <span className="absolute left-2 flex h-4 w-4 items-center justify-center">
105
+ <DropdownMenuPrimitive.ItemIndicator>
106
+ <Check className="h-4 w-4" />
107
+ </DropdownMenuPrimitive.ItemIndicator>
108
+ </span>
109
+ {children}
110
+ </DropdownMenuPrimitive.CheckboxItem>
111
+ ));
112
+ DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
113
+
114
+ const DropdownMenuRadioItem = React.forwardRef<
115
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
116
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
117
+ >(({ className, children, ...props }, ref) => (
118
+ <DropdownMenuPrimitive.RadioItem
119
+ ref={ref}
120
+ className={cn(
121
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-body-1 outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
122
+ className,
123
+ )}
124
+ {...props}
125
+ >
126
+ <span className="absolute left-2 flex h-4 w-4 items-center justify-center">
127
+ <DropdownMenuPrimitive.ItemIndicator>
128
+ <Circle className="h-2 w-2 fill-current" />
129
+ </DropdownMenuPrimitive.ItemIndicator>
130
+ </span>
131
+ {children}
132
+ </DropdownMenuPrimitive.RadioItem>
133
+ ));
134
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
135
+
136
+ const DropdownMenuLabel = React.forwardRef<
137
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
138
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
139
+ inset?: boolean;
140
+ }
141
+ >(({ className, inset, ...props }, ref) => (
142
+ <DropdownMenuPrimitive.Label
143
+ ref={ref}
144
+ className={cn("px-2 py-1.5 text-body-1 font-semibold", inset && "pl-8", className)}
145
+ {...props}
146
+ />
147
+ ));
148
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
149
+
150
+ const DropdownMenuSeparator = React.forwardRef<
151
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
152
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
153
+ >(({ className, ...props }, ref) => (
154
+ <DropdownMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
155
+ ));
156
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
157
+
158
+ const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
159
+ return <span className={cn("ml-auto text-body-1 tracking-widest opacity-60", className)} {...props} />;
160
+ };
161
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
162
+
163
+ export {
164
+ DropdownMenu,
165
+ DropdownMenuTrigger,
166
+ DropdownMenuContent,
167
+ DropdownMenuItem,
168
+ DropdownMenuCheckboxItem,
169
+ DropdownMenuRadioItem,
170
+ DropdownMenuLabel,
171
+ DropdownMenuSeparator,
172
+ DropdownMenuShortcut,
173
+ DropdownMenuGroup,
174
+ DropdownMenuPortal,
175
+ DropdownMenuSub,
176
+ DropdownMenuSubContent,
177
+ DropdownMenuSubTrigger,
178
+ DropdownMenuRadioGroup,
179
+ };
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from '../cn';
4
+
5
+ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
+ ({ className, type, ...props }, ref) => {
7
+ return (
8
+ <input
9
+ type={type}
10
+ className={cn(
11
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-body-1 file:font-medium file:text-foreground placeholder:text-ink-3 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-body-1",
12
+ className,
13
+ )}
14
+ ref={ref}
15
+ {...props}
16
+ />
17
+ );
18
+ },
19
+ );
20
+ Input.displayName = "Input";
21
+
22
+ export { Input };
@@ -0,0 +1,17 @@
1
+ import * as LabelPrimitive from "@radix-ui/react-label";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import * as React from "react";
4
+
5
+ import { cn } from '../cn';
6
+
7
+ const labelVariants = cva("text-body-1 font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
8
+
9
+ const Label = React.forwardRef<
10
+ React.ElementRef<typeof LabelPrimitive.Root>,
11
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
12
+ >(({ className, ...props }, ref) => (
13
+ <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
14
+ ));
15
+ Label.displayName = LabelPrimitive.Root.displayName;
16
+
17
+ export { Label };
@@ -0,0 +1,281 @@
1
+ /**
2
+ * OverflowTabs — editorial tab strip that auto-collapses overflowing items
3
+ * into a "More…" dropdown instead of scrolling horizontally.
4
+ *
5
+ * Behavior:
6
+ * 1. All tabs render off-screen for measurement (visibility: hidden, but
7
+ * still laid out so widths are real).
8
+ * 2. A ResizeObserver tracks the container width; once known we walk the
9
+ * tab widths left-to-right and pick the cutoff where they still fit
10
+ * alongside a fixed "More…" trigger.
11
+ * 3. Visible tabs render inline; overflow tabs go into a dropdown.
12
+ *
13
+ * The active tab is always pulled into the visible set, even if its index
14
+ * would be in the overflow bucket — users should never lose sight of "where
15
+ * am I." Same for tabs marked `pinned: true` (indicator dot is sticky).
16
+ */
17
+ import { ChevronDown, X } from 'lucide-react';
18
+ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
19
+
20
+ import {
21
+ DropdownMenu,
22
+ DropdownMenuContent,
23
+ DropdownMenuItem,
24
+ DropdownMenuTrigger,
25
+ } from './dropdown-menu';
26
+ import { cn } from '../cn';
27
+
28
+ export interface OverflowTab {
29
+ readonly value: string;
30
+ readonly label: string;
31
+ /** Optional warning/indicator dot rendered next to the label. */
32
+ readonly indicator?: boolean;
33
+ /** Optional numeric badge. */
34
+ readonly count?: number;
35
+ }
36
+
37
+ interface OverflowTabsProps {
38
+ readonly tabs: readonly OverflowTab[];
39
+ readonly value: string;
40
+ readonly onChange: (value: string) => void;
41
+ /** Reserved px for the "More…" trigger when measuring. Default 88. */
42
+ readonly moreTriggerWidth?: number;
43
+ readonly className?: string;
44
+ /** Each tab's px gap from the next. Matches the visual rendering. Default 4. */
45
+ readonly gap?: number;
46
+ /**
47
+ * When provided, every tab renders a hover `×` that calls this with the
48
+ * tab value. Closable tabs (multi-thread surfaces, etc.) opt in by
49
+ * passing it; surfaces that just navigate omit it.
50
+ */
51
+ readonly onClose?: (value: string) => void;
52
+ }
53
+
54
+ export function OverflowTabs({
55
+ tabs,
56
+ value,
57
+ onChange,
58
+ moreTriggerWidth = 88,
59
+ className,
60
+ gap = 4,
61
+ onClose,
62
+ }: OverflowTabsProps) {
63
+ const containerRef = useRef<HTMLDivElement>(null);
64
+ const measureRef = useRef<HTMLDivElement>(null);
65
+ const [containerWidth, setContainerWidth] = useState(0);
66
+ const [tabWidths, setTabWidths] = useState<readonly number[]>([]);
67
+
68
+ // Measure container width and react to resizes.
69
+ useEffect(() => {
70
+ const el = containerRef.current;
71
+ if (!el) return;
72
+ const ro = new ResizeObserver((entries) => {
73
+ const w = entries[0]?.contentRect.width ?? el.clientWidth;
74
+ setContainerWidth(w);
75
+ });
76
+ ro.observe(el);
77
+ setContainerWidth(el.clientWidth);
78
+ return () => ro.disconnect();
79
+ }, []);
80
+
81
+ // Measure each tab's natural width once after first render. We re-measure
82
+ // when the tab list itself changes (new tabs, label edits).
83
+ useLayoutEffect(() => {
84
+ const el = measureRef.current;
85
+ if (!el) return;
86
+ const children = Array.from(el.children) as HTMLElement[];
87
+ setTabWidths(children.map((c) => c.getBoundingClientRect().width));
88
+ }, [tabs]);
89
+
90
+ // Decide split point: walk left-to-right, summing widths + gaps, stopping
91
+ // when the next tab would exceed (container - moreTrigger). The active
92
+ // tab and any pinned tabs are always pulled into the visible set.
93
+ const { visible, overflow } = useMemo(() => {
94
+ if (tabWidths.length !== tabs.length || containerWidth === 0) {
95
+ return { visible: tabs, overflow: [] as readonly OverflowTab[] };
96
+ }
97
+ const totalNatural =
98
+ tabWidths.reduce((s, w) => s + w, 0) + gap * Math.max(0, tabs.length - 1);
99
+ if (totalNatural <= containerWidth) {
100
+ return { visible: tabs, overflow: [] as readonly OverflowTab[] };
101
+ }
102
+ const budget = containerWidth - moreTriggerWidth;
103
+ const vis: OverflowTab[] = [];
104
+ const over: OverflowTab[] = [];
105
+ let used = 0;
106
+ for (let i = 0; i < tabs.length; i++) {
107
+ const w = tabWidths[i] + (vis.length > 0 ? gap : 0);
108
+ if (used + w <= budget) {
109
+ vis.push(tabs[i]);
110
+ used += w;
111
+ } else {
112
+ over.push(tabs[i]);
113
+ }
114
+ }
115
+ // Force the active tab into the visible set if it ended up in overflow.
116
+ const activeIdx = tabs.findIndex((t) => t.value === value);
117
+ if (activeIdx !== -1) {
118
+ const activeTab = tabs[activeIdx];
119
+ const inVisible = vis.some((t) => t.value === activeTab.value);
120
+ if (!inVisible) {
121
+ // Swap the last visible for the active one to keep the budget OK.
122
+ const dropped = vis.pop();
123
+ if (dropped) over.unshift(dropped);
124
+ vis.push(activeTab);
125
+ const removeIdx = over.findIndex((t) => t.value === activeTab.value);
126
+ if (removeIdx !== -1) over.splice(removeIdx, 1);
127
+ }
128
+ }
129
+ return { visible: vis, overflow: over };
130
+ }, [tabs, tabWidths, containerWidth, gap, moreTriggerWidth, value]);
131
+
132
+ return (
133
+ <div className={cn('relative -mb-px flex items-end border-b border-rule', className)}>
134
+ {/* Off-screen measurement layer — renders ALL tabs so widths are real. */}
135
+ <div
136
+ ref={measureRef}
137
+ aria-hidden
138
+ className="pointer-events-none absolute left-0 top-0 flex items-center gap-1 opacity-0"
139
+ style={{ visibility: 'hidden' }}
140
+ >
141
+ {tabs.map((t) => (
142
+ <TabPill key={`measure-${t.value}`} tab={t} active={false} onClick={() => undefined} />
143
+ ))}
144
+ </div>
145
+
146
+ {/* Live visible row. */}
147
+ <div
148
+ ref={containerRef}
149
+ role="tablist"
150
+ className="flex flex-1 items-end gap-1 overflow-hidden"
151
+ >
152
+ {visible.map((t) => (
153
+ <TabPill
154
+ key={t.value}
155
+ tab={t}
156
+ active={t.value === value}
157
+ onClick={() => onChange(t.value)}
158
+ onClose={onClose ? () => onClose(t.value) : undefined}
159
+ />
160
+ ))}
161
+ </div>
162
+
163
+ {overflow.length > 0 && (
164
+ <DropdownMenu>
165
+ <DropdownMenuTrigger asChild>
166
+ <button
167
+ type="button"
168
+ className="ml-1 -mb-px inline-flex shrink-0 items-center gap-1 whitespace-nowrap px-2 py-2 text-body-1 text-ink-3 transition-colors hover:text-ink"
169
+ aria-label="More tabs"
170
+ >
171
+ More
172
+ <span className="rounded bg-paper-elev px-1 font-mono text-[10px] leading-4 text-ink-4">
173
+ {overflow.length}
174
+ </span>
175
+ <ChevronDown className="h-3 w-3" strokeWidth={1.6} />
176
+ </button>
177
+ </DropdownMenuTrigger>
178
+ <DropdownMenuContent align="end" className="min-w-[180px]">
179
+ {overflow.map((t) => (
180
+ <DropdownMenuItem
181
+ key={t.value}
182
+ onClick={() => onChange(t.value)}
183
+ className={cn(t.value === value && 'bg-accent/60 text-foreground')}
184
+ >
185
+ <span className="flex-1 truncate">{t.label}</span>
186
+ {t.count !== undefined && (
187
+ <span className="ml-2 text-caption tabular-nums text-ink-4">{t.count}</span>
188
+ )}
189
+ {t.indicator && (
190
+ <span
191
+ aria-hidden
192
+ className="ml-2 inline-block h-1.5 w-1.5 rounded-full bg-warning"
193
+ />
194
+ )}
195
+ {onClose && (
196
+ <button
197
+ type="button"
198
+ aria-label={`Close ${t.label}`}
199
+ className="ml-2 rounded p-0.5 text-ink-4 hover:bg-paper-elev hover:text-ink"
200
+ onClick={(e) => {
201
+ e.stopPropagation();
202
+ onClose(t.value);
203
+ }}
204
+ >
205
+ <X className="h-3 w-3" strokeWidth={1.8} />
206
+ </button>
207
+ )}
208
+ </DropdownMenuItem>
209
+ ))}
210
+ </DropdownMenuContent>
211
+ </DropdownMenu>
212
+ )}
213
+ </div>
214
+ );
215
+ }
216
+
217
+ function TabPill({
218
+ tab,
219
+ active,
220
+ onClick,
221
+ onClose,
222
+ }: Readonly<{
223
+ tab: OverflowTab;
224
+ active: boolean;
225
+ onClick: () => void;
226
+ onClose?: () => void;
227
+ }>) {
228
+ return (
229
+ <div
230
+ className={cn(
231
+ 'group relative -mb-px inline-flex items-center gap-1.5 whitespace-nowrap rounded-t-md border px-3 py-2 text-body-1 transition-colors',
232
+ active
233
+ ? 'border-rule border-b-transparent bg-paper-elev text-ink font-medium'
234
+ : 'border-transparent text-ink-3 hover:border-rule/40 hover:bg-paper-elev/60 hover:text-ink-2',
235
+ )}
236
+ >
237
+ <button
238
+ type="button"
239
+ role="tab"
240
+ aria-selected={active}
241
+ onClick={onClick}
242
+ className="inline-flex items-center gap-2 outline-none"
243
+ >
244
+ {tab.label}
245
+ {tab.count !== undefined && (
246
+ <span className="rounded bg-paper-elev px-1 font-mono text-[10px] tabular-nums leading-4 text-ink-4">
247
+ {tab.count}
248
+ </span>
249
+ )}
250
+ {tab.indicator && (
251
+ <span
252
+ aria-hidden
253
+ className="inline-block h-1.5 w-1.5 rounded-full bg-warning"
254
+ />
255
+ )}
256
+ </button>
257
+ {onClose && (
258
+ <button
259
+ type="button"
260
+ aria-label={`Close ${tab.label}`}
261
+ onClick={(e) => {
262
+ e.stopPropagation();
263
+ onClose();
264
+ }}
265
+ className={cn(
266
+ 'rounded p-0.5 text-ink-4 transition-opacity hover:bg-paper hover:text-ink',
267
+ active ? 'opacity-70' : 'opacity-0 group-hover:opacity-70',
268
+ )}
269
+ >
270
+ <X className="h-3 w-3" strokeWidth={1.8} />
271
+ </button>
272
+ )}
273
+ {active && (
274
+ <span
275
+ aria-hidden
276
+ className="absolute inset-x-2 bottom-[-1px] h-[2px] rounded-full bg-primary"
277
+ />
278
+ )}
279
+ </div>
280
+ );
281
+ }
@@ -0,0 +1,33 @@
1
+ import * as PopoverPrimitive from "@radix-ui/react-popover";
2
+ import * as React from "react";
3
+
4
+ import { cn } from '../cn';
5
+
6
+ const Popover = PopoverPrimitive.Root;
7
+
8
+ const PopoverTrigger = PopoverPrimitive.Trigger;
9
+
10
+ type PopoverContentProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
11
+ container?: HTMLElement | null;
12
+ };
13
+
14
+ const PopoverContent = React.forwardRef<
15
+ React.ElementRef<typeof PopoverPrimitive.Content>,
16
+ PopoverContentProps
17
+ >(({ className, align = "center", sideOffset = 4, container, ...props }, ref) => (
18
+ <PopoverPrimitive.Portal container={container ?? undefined}>
19
+ <PopoverPrimitive.Content
20
+ ref={ref}
21
+ align={align}
22
+ sideOffset={sideOffset}
23
+ className={cn(
24
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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",
25
+ className,
26
+ )}
27
+ {...props}
28
+ />
29
+ </PopoverPrimitive.Portal>
30
+ ));
31
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName;
32
+
33
+ export { Popover, PopoverTrigger, PopoverContent };