@health-samurai/react-components 0.0.0-alpha.4 → 0.0.0-alpha.5

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 (239) hide show
  1. package/dist/bundle.css +687 -446
  2. package/dist/src/components/code-editor/http/grammar/http.d.ts +3 -0
  3. package/dist/src/components/code-editor/http/grammar/http.d.ts.map +1 -0
  4. package/dist/src/components/code-editor/http/grammar/http.grammar +74 -0
  5. package/dist/src/components/code-editor/http/grammar/http.js +38 -0
  6. package/dist/src/components/code-editor/http/grammar/http.js.map +1 -0
  7. package/dist/src/components/code-editor/http/grammar/http.terms.d.ts +2 -0
  8. package/dist/src/components/code-editor/http/grammar/http.terms.d.ts.map +1 -0
  9. package/dist/src/components/code-editor/http/grammar/http.terms.js +4 -0
  10. package/dist/src/components/code-editor/http/grammar/http.terms.js.map +1 -0
  11. package/dist/src/components/code-editor/http/grammar/http.test.d.ts +2 -0
  12. package/dist/src/components/code-editor/http/grammar/http.test.d.ts.map +1 -0
  13. package/dist/src/components/code-editor/http/grammar/http.test.js +80 -0
  14. package/dist/src/components/code-editor/http/grammar/http.test.js.map +1 -0
  15. package/dist/src/components/code-editor/http/index.d.ts +4 -0
  16. package/dist/src/components/code-editor/http/index.d.ts.map +1 -0
  17. package/dist/src/components/code-editor/http/index.js +66 -0
  18. package/dist/src/components/code-editor/http/index.js.map +1 -0
  19. package/dist/src/components/code-editor/index.d.ts +13 -2
  20. package/dist/src/components/code-editor/index.d.ts.map +1 -1
  21. package/dist/src/components/code-editor/index.js +161 -20
  22. package/dist/src/components/code-editor/index.js.map +1 -1
  23. package/dist/src/components/code-editor.stories.js +3 -1
  24. package/dist/src/components/code-editor.stories.js.map +1 -1
  25. package/dist/src/components/request-line-editor.d.ts +11 -35
  26. package/dist/src/components/request-line-editor.d.ts.map +1 -1
  27. package/dist/src/components/request-line-editor.js +51 -49
  28. package/dist/src/components/request-line-editor.js.map +1 -1
  29. package/dist/src/components/request-line-editor.stories.d.ts.map +1 -1
  30. package/dist/src/components/request-line-editor.stories.js +17 -53
  31. package/dist/src/components/request-line-editor.stories.js.map +1 -1
  32. package/dist/src/components/tree-view.d.ts +16 -0
  33. package/dist/src/components/tree-view.d.ts.map +1 -0
  34. package/dist/src/components/tree-view.js +67 -0
  35. package/dist/src/components/tree-view.js.map +1 -0
  36. package/dist/src/components/tree-view.stories.d.ts +13 -0
  37. package/dist/src/components/tree-view.stories.d.ts.map +1 -0
  38. package/dist/src/components/tree-view.stories.js +274 -0
  39. package/dist/src/components/tree-view.stories.js.map +1 -0
  40. package/dist/src/icons.d.ts +3 -0
  41. package/dist/src/icons.d.ts.map +1 -0
  42. package/dist/src/icons.js +47 -0
  43. package/dist/src/icons.js.map +1 -0
  44. package/dist/src/index.css +42 -3
  45. package/dist/src/index.d.ts +1 -1
  46. package/dist/src/index.d.ts.map +1 -1
  47. package/dist/src/index.js +1 -1
  48. package/dist/src/index.js.map +1 -1
  49. package/dist/src/shadcn/components/ui/accordion.d.ts.map +1 -1
  50. package/dist/src/shadcn/components/ui/accordion.js +23 -5
  51. package/dist/src/shadcn/components/ui/accordion.js.map +1 -1
  52. package/dist/src/shadcn/components/ui/alert.d.ts.map +1 -1
  53. package/dist/src/shadcn/components/ui/alert.js +12 -5
  54. package/dist/src/shadcn/components/ui/alert.js.map +1 -1
  55. package/dist/src/shadcn/components/ui/avatar.d.ts.map +1 -1
  56. package/dist/src/shadcn/components/ui/avatar.js +4 -3
  57. package/dist/src/shadcn/components/ui/avatar.js.map +1 -1
  58. package/dist/src/shadcn/components/ui/badge.d.ts.map +1 -1
  59. package/dist/src/shadcn/components/ui/badge.js +16 -5
  60. package/dist/src/shadcn/components/ui/badge.js.map +1 -1
  61. package/dist/src/shadcn/components/ui/breadcrumb.d.ts.map +1 -1
  62. package/dist/src/shadcn/components/ui/breadcrumb.js +6 -6
  63. package/dist/src/shadcn/components/ui/breadcrumb.js.map +1 -1
  64. package/dist/src/shadcn/components/ui/button.d.ts.map +1 -1
  65. package/dist/src/shadcn/components/ui/button.js +19 -11
  66. package/dist/src/shadcn/components/ui/button.js.map +1 -1
  67. package/dist/src/shadcn/components/ui/card.d.ts.map +1 -1
  68. package/dist/src/shadcn/components/ui/card.js +14 -6
  69. package/dist/src/shadcn/components/ui/card.js.map +1 -1
  70. package/dist/src/shadcn/components/ui/checkbox.d.ts.map +1 -1
  71. package/dist/src/shadcn/components/ui/checkbox.js +20 -5
  72. package/dist/src/shadcn/components/ui/checkbox.js.map +1 -1
  73. package/dist/src/shadcn/components/ui/checkbox.stories.d.ts.map +1 -1
  74. package/dist/src/shadcn/components/ui/checkbox.stories.js +44 -35
  75. package/dist/src/shadcn/components/ui/checkbox.stories.js.map +1 -1
  76. package/dist/src/shadcn/components/ui/combobox.d.ts +18 -0
  77. package/dist/src/shadcn/components/ui/combobox.d.ts.map +1 -0
  78. package/dist/src/shadcn/components/ui/combobox.js +121 -0
  79. package/dist/src/shadcn/components/ui/combobox.js.map +1 -0
  80. package/dist/src/shadcn/components/ui/combobox.stories.d.ts +11 -0
  81. package/dist/src/shadcn/components/ui/combobox.stories.d.ts.map +1 -0
  82. package/dist/src/shadcn/components/ui/combobox.stories.js +16 -0
  83. package/dist/src/shadcn/components/ui/combobox.stories.js.map +1 -0
  84. package/dist/src/shadcn/components/ui/command.d.ts.map +1 -1
  85. package/dist/src/shadcn/components/ui/command.js +73 -12
  86. package/dist/src/shadcn/components/ui/command.js.map +1 -1
  87. package/dist/src/shadcn/components/ui/command.stories.js +0 -1
  88. package/dist/src/shadcn/components/ui/command.stories.js.map +1 -1
  89. package/dist/src/shadcn/components/ui/dialog.d.ts.map +1 -1
  90. package/dist/src/shadcn/components/ui/dialog.js +35 -7
  91. package/dist/src/shadcn/components/ui/dialog.js.map +1 -1
  92. package/dist/src/shadcn/components/ui/drawer.d.ts.map +1 -1
  93. package/dist/src/shadcn/components/ui/drawer.js +26 -5
  94. package/dist/src/shadcn/components/ui/drawer.js.map +1 -1
  95. package/dist/src/shadcn/components/ui/dropdown-menu.d.ts.map +1 -1
  96. package/dist/src/shadcn/components/ui/dropdown-menu.js +12 -1
  97. package/dist/src/shadcn/components/ui/dropdown-menu.js.map +1 -1
  98. package/dist/src/shadcn/components/ui/form.d.ts.map +1 -1
  99. package/dist/src/shadcn/components/ui/form.js +12 -4
  100. package/dist/src/shadcn/components/ui/form.js.map +1 -1
  101. package/dist/src/shadcn/components/ui/input.d.ts.map +1 -1
  102. package/dist/src/shadcn/components/ui/input.js +87 -16
  103. package/dist/src/shadcn/components/ui/input.js.map +1 -1
  104. package/dist/src/shadcn/components/ui/label.d.ts.map +1 -1
  105. package/dist/src/shadcn/components/ui/label.js +8 -1
  106. package/dist/src/shadcn/components/ui/label.js.map +1 -1
  107. package/dist/src/shadcn/components/ui/menubar.d.ts.map +1 -1
  108. package/dist/src/shadcn/components/ui/menubar.js +35 -13
  109. package/dist/src/shadcn/components/ui/menubar.js.map +1 -1
  110. package/dist/src/shadcn/components/ui/pagination.d.ts.map +1 -1
  111. package/dist/src/shadcn/components/ui/pagination.js +6 -6
  112. package/dist/src/shadcn/components/ui/pagination.js.map +1 -1
  113. package/dist/src/shadcn/components/ui/popover.d.ts.map +1 -1
  114. package/dist/src/shadcn/components/ui/popover.js +12 -1
  115. package/dist/src/shadcn/components/ui/popover.js.map +1 -1
  116. package/dist/src/shadcn/components/ui/progress.d.ts.map +1 -1
  117. package/dist/src/shadcn/components/ui/progress.js +6 -2
  118. package/dist/src/shadcn/components/ui/progress.js.map +1 -1
  119. package/dist/src/shadcn/components/ui/radio-group.d.ts.map +1 -1
  120. package/dist/src/shadcn/components/ui/radio-group.js +11 -6
  121. package/dist/src/shadcn/components/ui/radio-group.js.map +1 -1
  122. package/dist/src/shadcn/components/ui/radio-group.stories.d.ts.map +1 -1
  123. package/dist/src/shadcn/components/ui/radio-group.stories.js +57 -34
  124. package/dist/src/shadcn/components/ui/radio-group.stories.js.map +1 -1
  125. package/dist/src/shadcn/components/ui/scroll-area.d.ts.map +1 -1
  126. package/dist/src/shadcn/components/ui/scroll-area.js +9 -3
  127. package/dist/src/shadcn/components/ui/scroll-area.js.map +1 -1
  128. package/dist/src/shadcn/components/ui/select.d.ts.map +1 -1
  129. package/dist/src/shadcn/components/ui/select.js +49 -14
  130. package/dist/src/shadcn/components/ui/select.js.map +1 -1
  131. package/dist/src/shadcn/components/ui/select.stories.d.ts.map +1 -1
  132. package/dist/src/shadcn/components/ui/select.stories.js +1 -4
  133. package/dist/src/shadcn/components/ui/select.stories.js.map +1 -1
  134. package/dist/src/shadcn/components/ui/separator.d.ts.map +1 -1
  135. package/dist/src/shadcn/components/ui/separator.js +7 -1
  136. package/dist/src/shadcn/components/ui/separator.js.map +1 -1
  137. package/dist/src/shadcn/components/ui/sidebar.d.ts.map +1 -1
  138. package/dist/src/shadcn/components/ui/sidebar.js +20 -6
  139. package/dist/src/shadcn/components/ui/sidebar.js.map +1 -1
  140. package/dist/src/shadcn/components/ui/skeleton.d.ts.map +1 -1
  141. package/dist/src/shadcn/components/ui/skeleton.js +3 -1
  142. package/dist/src/shadcn/components/ui/skeleton.js.map +1 -1
  143. package/dist/src/shadcn/components/ui/slider.d.ts.map +1 -1
  144. package/dist/src/shadcn/components/ui/slider.js +34 -4
  145. package/dist/src/shadcn/components/ui/slider.js.map +1 -1
  146. package/dist/src/shadcn/components/ui/sonner.d.ts +16 -1
  147. package/dist/src/shadcn/components/ui/sonner.d.ts.map +1 -1
  148. package/dist/src/shadcn/components/ui/sonner.js +23 -3
  149. package/dist/src/shadcn/components/ui/sonner.js.map +1 -1
  150. package/dist/src/shadcn/components/ui/sonner.stories.d.ts.map +1 -1
  151. package/dist/src/shadcn/components/ui/sonner.stories.js +19 -11
  152. package/dist/src/shadcn/components/ui/sonner.stories.js.map +1 -1
  153. package/dist/src/shadcn/components/ui/switch.d.ts.map +1 -1
  154. package/dist/src/shadcn/components/ui/switch.js +18 -2
  155. package/dist/src/shadcn/components/ui/switch.js.map +1 -1
  156. package/dist/src/shadcn/components/ui/table.d.ts.map +1 -1
  157. package/dist/src/shadcn/components/ui/table.js +12 -8
  158. package/dist/src/shadcn/components/ui/table.js.map +1 -1
  159. package/dist/src/shadcn/components/ui/tabs.d.ts +21 -3
  160. package/dist/src/shadcn/components/ui/tabs.d.ts.map +1 -1
  161. package/dist/src/shadcn/components/ui/tabs.js +314 -9
  162. package/dist/src/shadcn/components/ui/tabs.js.map +1 -1
  163. package/dist/src/shadcn/components/ui/tabs.stories.d.ts.map +1 -1
  164. package/dist/src/shadcn/components/ui/tabs.stories.js +50 -1
  165. package/dist/src/shadcn/components/ui/tabs.stories.js.map +1 -1
  166. package/dist/src/shadcn/components/ui/textarea.d.ts.map +1 -1
  167. package/dist/src/shadcn/components/ui/textarea.js +15 -1
  168. package/dist/src/shadcn/components/ui/textarea.js.map +1 -1
  169. package/dist/src/shadcn/components/ui/toggle-group.d.ts.map +1 -1
  170. package/dist/src/shadcn/components/ui/toggle-group.js +6 -2
  171. package/dist/src/shadcn/components/ui/toggle-group.js.map +1 -1
  172. package/dist/src/shadcn/components/ui/toggle.d.ts.map +1 -1
  173. package/dist/src/shadcn/components/ui/toggle.js +18 -6
  174. package/dist/src/shadcn/components/ui/toggle.js.map +1 -1
  175. package/dist/src/shadcn/components/ui/tooltip.d.ts.map +1 -1
  176. package/dist/src/shadcn/components/ui/tooltip.js +11 -1
  177. package/dist/src/shadcn/components/ui/tooltip.js.map +1 -1
  178. package/dist/src/shadcn/components/ui/tree.d.ts +20 -0
  179. package/dist/src/shadcn/components/ui/tree.d.ts.map +1 -0
  180. package/dist/src/shadcn/components/ui/tree.js +111 -0
  181. package/dist/src/shadcn/components/ui/tree.js.map +1 -0
  182. package/package.json +9 -2
  183. package/src/components/code-editor/http/grammar/http.grammar +74 -0
  184. package/src/components/code-editor/http/grammar/http.terms.ts +9 -0
  185. package/src/components/code-editor/http/grammar/http.test.ts +110 -0
  186. package/src/components/code-editor/http/grammar/http.ts +21 -0
  187. package/src/components/code-editor/http/index.ts +87 -0
  188. package/src/components/code-editor/index.tsx +182 -21
  189. package/src/components/code-editor.stories.tsx +1 -1
  190. package/src/components/request-line-editor.stories.tsx +17 -27
  191. package/src/components/request-line-editor.tsx +72 -61
  192. package/src/components/tree-view.stories.tsx +260 -0
  193. package/src/components/tree-view.tsx +101 -0
  194. package/src/icons.tsx +45 -0
  195. package/src/index.css +42 -3
  196. package/src/index.tsx +1 -1
  197. package/src/shadcn/components/ui/accordion.tsx +66 -8
  198. package/src/shadcn/components/ui/alert.tsx +53 -15
  199. package/src/shadcn/components/ui/avatar.tsx +17 -6
  200. package/src/shadcn/components/ui/badge.tsx +67 -18
  201. package/src/shadcn/components/ui/breadcrumb.tsx +35 -7
  202. package/src/shadcn/components/ui/button.tsx +118 -57
  203. package/src/shadcn/components/ui/card.tsx +44 -13
  204. package/src/shadcn/components/ui/checkbox.stories.tsx +20 -24
  205. package/src/shadcn/components/ui/checkbox.tsx +45 -4
  206. package/src/shadcn/components/ui/combobox.stories.tsx +19 -0
  207. package/src/shadcn/components/ui/combobox.tsx +142 -0
  208. package/src/shadcn/components/ui/command.stories.tsx +1 -1
  209. package/src/shadcn/components/ui/command.tsx +192 -36
  210. package/src/shadcn/components/ui/dialog.tsx +101 -13
  211. package/src/shadcn/components/ui/drawer.tsx +93 -18
  212. package/src/shadcn/components/ui/dropdown-menu.tsx +38 -9
  213. package/src/shadcn/components/ui/form.tsx +16 -4
  214. package/src/shadcn/components/ui/input.tsx +281 -29
  215. package/src/shadcn/components/ui/label.tsx +21 -4
  216. package/src/shadcn/components/ui/menubar.tsx +188 -43
  217. package/src/shadcn/components/ui/pagination.tsx +12 -6
  218. package/src/shadcn/components/ui/popover.tsx +35 -4
  219. package/src/shadcn/components/ui/progress.tsx +21 -5
  220. package/src/shadcn/components/ui/radio-group.stories.tsx +22 -14
  221. package/src/shadcn/components/ui/radio-group.tsx +42 -5
  222. package/src/shadcn/components/ui/scroll-area.tsx +33 -5
  223. package/src/shadcn/components/ui/select.stories.tsx +0 -2
  224. package/src/shadcn/components/ui/select.tsx +184 -33
  225. package/src/shadcn/components/ui/separator.tsx +15 -5
  226. package/src/shadcn/components/ui/sidebar.tsx +68 -26
  227. package/src/shadcn/components/ui/skeleton.tsx +4 -1
  228. package/src/shadcn/components/ui/slider.tsx +82 -11
  229. package/src/shadcn/components/ui/sonner.stories.tsx +19 -15
  230. package/src/shadcn/components/ui/sonner.tsx +53 -3
  231. package/src/shadcn/components/ui/switch.tsx +53 -7
  232. package/src/shadcn/components/ui/table.tsx +38 -11
  233. package/src/shadcn/components/ui/tabs.stories.tsx +32 -0
  234. package/src/shadcn/components/ui/tabs.tsx +455 -17
  235. package/src/shadcn/components/ui/textarea.tsx +42 -4
  236. package/src/shadcn/components/ui/toggle-group.tsx +27 -5
  237. package/src/shadcn/components/ui/toggle.tsx +59 -18
  238. package/src/shadcn/components/ui/tooltip.tsx +33 -8
  239. package/src/shadcn/components/ui/tree.tsx +200 -0
@@ -1,10 +1,89 @@
1
1
  "use client";
2
2
  import * as TabsPrimitive from "@radix-ui/react-tabs";
3
3
  import { cva, type VariantProps } from "class-variance-authority";
4
- import { Plus, X } from "lucide-react";
5
- import type * as React from "react";
4
+ import {
5
+ ChevronDownIcon,
6
+ ChevronLeft,
7
+ ChevronRight,
8
+ Plus,
9
+ X,
10
+ } from "lucide-react";
11
+ import * as React from "react";
6
12
  import { cn } from "#shadcn/lib/utils";
7
13
  import { Button } from "./button";
14
+ import {
15
+ Command,
16
+ CommandEmpty,
17
+ CommandInput,
18
+ CommandItem,
19
+ CommandList,
20
+ } from "./command";
21
+ import { Popover, PopoverContent, PopoverTrigger } from "./popover";
22
+
23
+ // Base tabs styles
24
+ const baseTabsStyles = cn("flex", "flex-col", "h-full");
25
+
26
+ // Tabs add button container styles
27
+ const tabsAddButtonContainerStyles = cn(
28
+ "grow",
29
+ "h-full",
30
+ "bg-bg-secondary",
31
+ "border-l",
32
+ "border-b",
33
+ );
34
+
35
+ // Tabs list styles
36
+ const tabsListStyles = cn(
37
+ "inline-flex",
38
+ "w-fit",
39
+ "items-center",
40
+ "no-scrollbar",
41
+ );
42
+
43
+ // Base tabs trigger styles
44
+ const baseTabsTriggerStyles = cn(
45
+ // Layout & Sizing
46
+ "box-border",
47
+ "flex-1",
48
+ "h-10",
49
+ "inline-flex",
50
+ "items-center",
51
+ "justify-center",
52
+ "px-3",
53
+ "whitespace-nowrap",
54
+ // Spacing & Padding
55
+ "pb-2",
56
+ "pt-2.5",
57
+ // Typography
58
+ "typo-body",
59
+ // Colors & States
60
+ "cursor-pointer",
61
+ "text-text-tertiary",
62
+ "hover:bg-bg-secondary/60",
63
+ "hover:text-text-tertiary_hover",
64
+ "data-[state=active]:text-text-primary",
65
+ "data-[state=active]:border-b-border-brand",
66
+ "disabled:opacity-50",
67
+ "disabled:pointer-events-none",
68
+ // Borders
69
+ "border-b-2",
70
+ "border-b-transparent",
71
+ // Focus & Accessibility
72
+ "focus-visible:ring-2",
73
+ "focus-visible:ring-utility-blue/70",
74
+ "focus-visible:outline-1",
75
+ // Transitions
76
+ "transition-[color,box-shadow]",
77
+ // Icons
78
+ "[&_svg]:pointer-events-none",
79
+ "[&_svg]:shrink-0",
80
+ "[&_svg:not([class*='size-'])]:size-4",
81
+ // Groups
82
+ "group/tabs-trigger",
83
+ );
84
+
85
+ // Tabs content styles
86
+ const tabsContentStyles = cn("grow", "outline-none", "overflow-auto");
8
87
 
9
88
  const tabsVariants = cva("", {
10
89
  variants: {
@@ -20,6 +99,7 @@ const tabsVariants = cva("", {
20
99
  **:data-[slot=tabs-list]:divide-x`,
21
100
  // TabsTrigger
22
101
  `**:data-[slot=tabs-trigger]:max-w-80
102
+ **:data-[slot=tabs-trigger]:w-60
23
103
  **:data-[slot=tabs-trigger]:min-w-40
24
104
  **:data-[slot=tabs-trigger]:data-[state=inactive]:border-b-1
25
105
  **:data-[slot=tabs-trigger]:data-[state=inactive]:border-b-border-secondary
@@ -39,7 +119,7 @@ function Tabs({
39
119
  return (
40
120
  <TabsPrimitive.Root
41
121
  data-slot="tabs"
42
- className={cn("flex flex-col", tabsVariants({ variant }), className)}
122
+ className={cn(baseTabsStyles, tabsVariants({ variant }), className)}
43
123
  {...props}
44
124
  />
45
125
  );
@@ -47,7 +127,7 @@ function Tabs({
47
127
 
48
128
  export function TabsAddButton(props: React.ComponentProps<typeof Button>) {
49
129
  return (
50
- <div className="grow h-full bg-bg-secondary border-l border-b">
130
+ <div className={tabsAddButtonContainerStyles}>
51
131
  <Button
52
132
  data-slot="tabs-add-button"
53
133
  variant="link"
@@ -60,19 +140,384 @@ export function TabsAddButton(props: React.ComponentProps<typeof Button>) {
60
140
  );
61
141
  }
62
142
 
143
+ const horizontalScroll = (event: React.WheelEvent) => {
144
+ const mode = event.deltaMode;
145
+ let deltaPx = 0;
146
+
147
+ if (mode === 0) {
148
+ deltaPx = event.deltaY;
149
+ } else if (mode === 1) {
150
+ deltaPx = event.deltaY * 160;
151
+ } else if (mode === 2) {
152
+ deltaPx = event.currentTarget.clientWidth;
153
+ }
154
+
155
+ const newScrollLeft = event.currentTarget.scrollLeft + deltaPx;
156
+
157
+ event.currentTarget.scrollTo({
158
+ left: newScrollLeft,
159
+ behavior: "smooth",
160
+ });
161
+ };
162
+
163
+ const performHorizontalScroll = (
164
+ tabsListRef: React.RefObject<HTMLDivElement | null>,
165
+ direction: "left" | "right",
166
+ ) => {
167
+ if (!tabsListRef.current) return;
168
+ const scrollAmount = 160;
169
+ let newScrollLeft = tabsListRef.current.scrollLeft;
170
+
171
+ if (direction === "left") {
172
+ newScrollLeft -= scrollAmount;
173
+ newScrollLeft -= newScrollLeft % scrollAmount;
174
+ } else {
175
+ newScrollLeft += scrollAmount;
176
+
177
+ const rightCoord = newScrollLeft + tabsListRef.current.clientWidth;
178
+
179
+ if (rightCoord % scrollAmount !== 0) {
180
+ newScrollLeft += scrollAmount - (rightCoord % scrollAmount);
181
+ }
182
+ }
183
+
184
+ tabsListRef.current.scrollTo({
185
+ left: newScrollLeft,
186
+ behavior: "smooth",
187
+ });
188
+ };
189
+
190
+ type EdgeScrollPosition = "touch" | "depart";
191
+ type FlowType = "overflow" | "underflow";
192
+
193
+ type TabsListProps = {
194
+ onLeftEdge?: (position: EdgeScrollPosition) => void;
195
+ onRightEdge?: (position: EdgeScrollPosition) => void;
196
+ onFlow?: (flow: FlowType) => void;
197
+ onResize?: (entries: ResizeObserverEntry[]) => void;
198
+ onTabChange?: (mutationRecords: MutationRecord[]) => void;
199
+ } & React.ComponentProps<typeof TabsPrimitive.List>;
200
+
63
201
  function TabsList({
64
202
  className,
203
+ onLeftEdge,
204
+ onRightEdge,
205
+ onResize,
206
+ onFlow,
207
+ onTabChange,
65
208
  ...props
66
- }: React.ComponentProps<typeof TabsPrimitive.List>) {
209
+ }: TabsListProps) {
210
+ const tabListRef = React.useRef<HTMLDivElement | null>(null);
211
+
212
+ const onLeftEdgeRef = React.useRef(onLeftEdge);
213
+ React.useEffect(() => {
214
+ onLeftEdgeRef.current = onLeftEdge;
215
+ }, [onLeftEdge]);
216
+
217
+ const onResizeRef = React.useRef(onResize);
218
+ React.useEffect(() => {
219
+ onResizeRef.current = onResize;
220
+ }, [onResize]);
221
+
222
+ const onRightEdgeRef = React.useRef(onRightEdge);
223
+ React.useEffect(() => {
224
+ onRightEdgeRef.current = onRightEdge;
225
+ }, [onRightEdge]);
226
+
227
+ const onFlowRef = React.useRef(onFlow);
228
+ React.useEffect(() => {
229
+ onFlowRef.current = onFlow;
230
+ }, [onFlow]);
231
+
232
+ const onTabChangeRef = React.useRef(onTabChange);
233
+ React.useEffect(() => {
234
+ onTabChangeRef.current = onTabChange;
235
+ }, [onTabChange]);
236
+
237
+ React.useEffect(() => {
238
+ if (tabListRef.current === null) {
239
+ return;
240
+ }
241
+ const tabList = tabListRef.current;
242
+
243
+ let last: {
244
+ scrollLeft: number;
245
+ scrollWidth: number;
246
+ clientWidth: number;
247
+ } | null = null;
248
+
249
+ const handleScroll = () => {
250
+ if (onLeftEdgeRef.current) {
251
+ const newState: EdgeScrollPosition =
252
+ tabList.scrollLeft < 1 ? "touch" : "depart";
253
+
254
+ if (last === null) {
255
+ onLeftEdgeRef.current(newState);
256
+ } else {
257
+ const lastState: EdgeScrollPosition =
258
+ last.scrollLeft < 1 ? "touch" : "depart";
259
+
260
+ if (lastState !== newState) {
261
+ onLeftEdgeRef.current(newState);
262
+ }
263
+ }
264
+ }
265
+
266
+ if (onRightEdgeRef.current) {
267
+ const newState: EdgeScrollPosition =
268
+ tabList.scrollWidth - tabList.clientWidth - tabList.scrollLeft < 1
269
+ ? "touch"
270
+ : "depart";
271
+
272
+ if (last === null) {
273
+ onRightEdgeRef.current(newState);
274
+ } else {
275
+ const lastState: EdgeScrollPosition =
276
+ last.scrollWidth - last.clientWidth - last.scrollLeft < 1
277
+ ? "touch"
278
+ : "depart";
279
+
280
+ if (lastState !== newState) {
281
+ onRightEdgeRef.current(newState);
282
+ }
283
+ }
284
+ }
285
+
286
+ if (onFlowRef.current) {
287
+ const newState: FlowType =
288
+ tabList.scrollWidth > tabList.clientWidth ? "overflow" : "underflow";
289
+
290
+ if (last === null) {
291
+ onFlowRef.current(newState);
292
+ } else {
293
+ const lastState =
294
+ last.scrollWidth > last.clientWidth ? "overflow" : "underflow";
295
+ if (lastState !== newState) {
296
+ onFlowRef.current(newState);
297
+ }
298
+ }
299
+ }
300
+
301
+ last = {
302
+ scrollLeft: tabList.scrollLeft,
303
+ scrollWidth: tabList.scrollWidth,
304
+ clientWidth: tabList.clientWidth,
305
+ };
306
+ };
307
+
308
+ const scrollCallback = (_ev: unknown) => handleScroll();
309
+ const resizeObserver = new ResizeObserver((entries) => {
310
+ handleScroll();
311
+ if (onResizeRef.current) {
312
+ onResizeRef.current(entries);
313
+ }
314
+ });
315
+ const mutationObserver = new MutationObserver((mutationRecords) => {
316
+ handleScroll();
317
+ if (onTabChangeRef.current) {
318
+ onTabChangeRef.current(mutationRecords);
319
+ }
320
+ });
321
+
322
+ tabList.addEventListener("scroll", scrollCallback, { passive: true });
323
+ resizeObserver.observe(tabList);
324
+ mutationObserver.observe(tabList, { childList: true });
325
+
326
+ return () => {
327
+ tabList.removeEventListener("scroll", scrollCallback);
328
+ resizeObserver.disconnect();
329
+ mutationObserver.disconnect();
330
+ };
331
+ }, []);
332
+
67
333
  return (
68
334
  <TabsPrimitive.List
69
335
  data-slot="tabs-list"
70
336
  className={cn("inline-flex w-fit items-center", className)}
71
337
  {...props}
338
+ ref={(element) => {
339
+ tabListRef.current = element;
340
+ if (props.ref !== undefined && props.ref !== null) {
341
+ if (typeof props.ref === "function") {
342
+ props.ref(element);
343
+ } else {
344
+ props.ref.current = element;
345
+ }
346
+ }
347
+ }}
72
348
  />
73
349
  );
74
350
  }
75
351
 
352
+ type TabScrollButtonProps = {
353
+ disabled: boolean;
354
+ onClick: () => void;
355
+ };
356
+
357
+ function TabScrollLeftButton({
358
+ disabled,
359
+ onClick,
360
+ }: TabScrollButtonProps): React.ReactElement {
361
+ return (
362
+ <Button
363
+ variant="link"
364
+ size="small"
365
+ disabled={disabled}
366
+ className="h-full border-r border-b bg-bg-secondary"
367
+ onClick={onClick}
368
+ >
369
+ <ChevronLeft />
370
+ </Button>
371
+ );
372
+ }
373
+
374
+ function TabScrollRightButton({
375
+ disabled,
376
+ onClick,
377
+ }: TabScrollButtonProps): React.ReactElement {
378
+ return (
379
+ <Button
380
+ variant="link"
381
+ size="small"
382
+ disabled={disabled}
383
+ className="h-full border-l border-b bg-bg-secondary"
384
+ onClick={onClick}
385
+ >
386
+ <ChevronRight />
387
+ </Button>
388
+ );
389
+ }
390
+
391
+ function TabsBrowserList({
392
+ className,
393
+ children,
394
+ ...props
395
+ }: React.ComponentProps<typeof TabsPrimitive.List>) {
396
+ const tabsListRef = React.useRef<HTMLDivElement | null>(null);
397
+
398
+ const [showScrollButtons, setShowScrollButtons] = React.useState(false);
399
+ const [canScrollLeft, setCanScrollLeft] = React.useState(false);
400
+ const [canScrollRight, setCanScrollRight] = React.useState(false);
401
+
402
+ return (
403
+ <React.Fragment>
404
+ {showScrollButtons && (
405
+ <TabScrollLeftButton
406
+ disabled={!canScrollLeft}
407
+ onClick={() => performHorizontalScroll(tabsListRef, "left")}
408
+ />
409
+ )}
410
+
411
+ <TabsList
412
+ onLeftEdge={(edgeState) => {
413
+ if (edgeState === "touch") {
414
+ setCanScrollLeft(false);
415
+ } else {
416
+ setCanScrollLeft(true);
417
+ }
418
+ }}
419
+ onRightEdge={(edgeState) => {
420
+ if (edgeState === "touch") {
421
+ setCanScrollRight(false);
422
+ } else {
423
+ setCanScrollRight(true);
424
+ }
425
+ }}
426
+ onFlow={(flow) => {
427
+ if (flow === "overflow") {
428
+ setShowScrollButtons(true);
429
+ } else {
430
+ setShowScrollButtons(false);
431
+ }
432
+ }}
433
+ onResize={() => {
434
+ tabsListRef.current
435
+ ?.querySelector<HTMLButtonElement>('button[data-state="active"]')
436
+ ?.scrollIntoView();
437
+ }}
438
+ onTabChange={(entries) => {
439
+ if (
440
+ entries.filter((entry) => entry.addedNodes.length !== 0).length !==
441
+ 0
442
+ ) {
443
+ tabsListRef.current
444
+ ?.querySelector<HTMLButtonElement>('button[data-state="active"]')
445
+ ?.scrollIntoView();
446
+ }
447
+ }}
448
+ data-slot="tabs-list"
449
+ className={cn(tabsListStyles, className)}
450
+ onWheel={(event) => horizontalScroll(event)}
451
+ {...props}
452
+ ref={tabsListRef}
453
+ >
454
+ {children}
455
+ </TabsList>
456
+
457
+ {showScrollButtons && (
458
+ <TabScrollRightButton
459
+ disabled={!canScrollRight}
460
+ onClick={() => performHorizontalScroll(tabsListRef, "right")}
461
+ />
462
+ )}
463
+ </React.Fragment>
464
+ );
465
+ }
466
+
467
+ export function TabsListDropdown({
468
+ tabs,
469
+ handleTabSelect,
470
+ handleCloseTab,
471
+ }: {
472
+ tabs: { id: string; content: React.ReactNode }[];
473
+ handleTabSelect?: (tabId: string) => void;
474
+ handleCloseTab?: (tabId: string) => void;
475
+ }) {
476
+ const [isMenuOpen, setIsMenuOpen] = React.useState(false);
477
+ return (
478
+ <Popover open={isMenuOpen} onOpenChange={setIsMenuOpen}>
479
+ <PopoverTrigger asChild>
480
+ <Button variant="link" className="bg-bg-secondary h-full border-b pr-6">
481
+ <ChevronDownIcon className="size-4" />
482
+ </Button>
483
+ </PopoverTrigger>
484
+ <PopoverContent className="w-80 p-0 mr-3" align="end">
485
+ <Command>
486
+ <CommandInput placeholder="Search tabs..." />
487
+ <CommandList>
488
+ <CommandEmpty>Not tabs found.</CommandEmpty>
489
+ {tabs.map((tab) => (
490
+ <CommandItem
491
+ key={tab.id}
492
+ onSelect={() => {
493
+ handleTabSelect?.(tab.id);
494
+ setIsMenuOpen(false);
495
+ }}
496
+ className="group flex items-center justify-between"
497
+ >
498
+ {tab.content}
499
+ {tabs.length > 1 && (
500
+ <Button
501
+ variant="ghost"
502
+ size="small"
503
+ className="opacity-0 group-hover:opacity-100 transition-opacity p-1 ml-2"
504
+ onClick={(e) => {
505
+ e.stopPropagation();
506
+ handleCloseTab?.(tab.id);
507
+ }}
508
+ >
509
+ <X className="size-3" />
510
+ </Button>
511
+ )}
512
+ </CommandItem>
513
+ ))}
514
+ </CommandList>
515
+ </Command>
516
+ </PopoverContent>
517
+ </Popover>
518
+ );
519
+ }
520
+
76
521
  function TabsTrigger({
77
522
  className,
78
523
  onClose,
@@ -84,15 +529,8 @@ function TabsTrigger({
84
529
  <TabsPrimitive.Trigger
85
530
  data-slot="tabs-trigger"
86
531
  className={cn(
87
- "group/tabs-trigger",
88
- "box-border h-10 typo-body px-3 pb-2 pt-2.5 cursor-pointer text-text-tertiary data-[state=active]:text-text-primary",
89
- "data-[state=active]:border-b-border-brand border-b-2 border-b-transparent hover:text-text-tertiary-hover focus-visible:border-ring",
90
- "focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:text-muted-foreground",
91
- "inline-flex flex-1 items-center justify-center whitespace-nowrap transition-[color,box-shadow]",
92
- "focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50",
93
- "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
94
- "hover:bg-bg-secondary/60",
95
- onClose ? "justify-between" : "",
532
+ baseTabsTriggerStyles,
533
+ onClose ? "justify-between" : "justify-start",
96
534
  className,
97
535
  )}
98
536
  {...props}
@@ -107,7 +545,7 @@ function TabsTrigger({
107
545
  }}
108
546
  variant="link"
109
547
  size="small"
110
- className="p-0 ml-2"
548
+ className="p-0 ml-2 opacity-0 group-hover/tabs-trigger:opacity-100 transition-opacity"
111
549
  asChild
112
550
  >
113
551
  <span>
@@ -126,10 +564,10 @@ function TabsContent({
126
564
  return (
127
565
  <TabsPrimitive.Content
128
566
  data-slot="tabs-content"
129
- className={cn("flex-1 outline-none", className)}
567
+ className={cn(tabsContentStyles, className)}
130
568
  {...props}
131
569
  />
132
570
  );
133
571
  }
134
572
 
135
- export { Tabs, TabsList, TabsTrigger, TabsContent };
573
+ export { Tabs, TabsList, TabsTrigger, TabsContent, TabsBrowserList };
@@ -2,14 +2,52 @@ import type * as React from "react";
2
2
 
3
3
  import { cn } from "#shadcn/lib/utils";
4
4
 
5
+ // Textarea styles
6
+ const textareaStyles = cn(
7
+ // Layout
8
+ "flex",
9
+ "w-full",
10
+ "min-h-16",
11
+ "field-sizing-content",
12
+ // Shape
13
+ "rounded-md",
14
+ // Borders
15
+ "border",
16
+ "border-border-primary",
17
+ // Background & Colors
18
+ "bg-transparent",
19
+ "text-base",
20
+ // Spacing
21
+ "px-3",
22
+ "py-2",
23
+ // Typography
24
+ "md:text-sm",
25
+ // Placeholder
26
+ "placeholder:text-text-tertiary",
27
+ // Transitions
28
+ "transition-[color,box-shadow]",
29
+ "outline-none",
30
+ // Hover
31
+ "hover:border-border-primary_hover",
32
+ // Focus
33
+ "focus-visible:border-border-link",
34
+ "focus-visible:ring-2",
35
+ "focus-visible:ring-utility-blue/70",
36
+ // Invalid
37
+ "aria-invalid:border-border-error",
38
+ "aria-invalid:ring-2",
39
+ "aria-invalid:ring-utility-red/70",
40
+ // Disabled
41
+ "disabled:cursor-not-allowed",
42
+ "disabled:opacity-50",
43
+ "disabled:border-border-disabled",
44
+ );
45
+
5
46
  function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6
47
  return (
7
48
  <textarea
8
49
  data-slot="textarea"
9
- className={cn(
10
- "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
11
- className,
12
- )}
50
+ className={cn(textareaStyles, className)}
13
51
  {...props}
14
52
  />
15
53
  );
@@ -5,6 +5,31 @@ import * as React from "react";
5
5
  import { toggleVariants } from "#shadcn/components/ui/toggle";
6
6
  import { cn } from "#shadcn/lib/utils";
7
7
 
8
+ // Toggle group styles
9
+ const toggleGroupStyles = cn(
10
+ "group/toggle-group",
11
+ "flex",
12
+ "w-fit",
13
+ "items-center",
14
+ "rounded-md",
15
+ "data-[variant=outline]:shadow-xs",
16
+ );
17
+
18
+ // Toggle group item styles
19
+ const toggleGroupItemStyles = cn(
20
+ "min-w-0",
21
+ "flex-1",
22
+ "shrink-0",
23
+ "rounded-none",
24
+ "shadow-none",
25
+ "first:rounded-l-md",
26
+ "last:rounded-r-md",
27
+ "focus:z-10",
28
+ "focus-visible:z-10",
29
+ "data-[variant=outline]:border-l-0",
30
+ "data-[variant=outline]:first:border-l",
31
+ );
32
+
8
33
  const ToggleGroupContext = React.createContext<
9
34
  VariantProps<typeof toggleVariants>
10
35
  >({
@@ -25,10 +50,7 @@ function ToggleGroup({
25
50
  data-slot="toggle-group"
26
51
  data-variant={variant}
27
52
  data-size={size}
28
- className={cn(
29
- "group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
30
- className,
31
- )}
53
+ className={cn(toggleGroupStyles, className)}
32
54
  {...props}
33
55
  >
34
56
  <ToggleGroupContext.Provider value={{ variant, size }}>
@@ -58,7 +80,7 @@ function ToggleGroupItem({
58
80
  variant: context.variant || variant,
59
81
  size: context.size || size,
60
82
  }),
61
- "min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
83
+ toggleGroupItemStyles,
62
84
  className,
63
85
  )}
64
86
  {...props}