cortena-ui 1.3.0 → 1.4.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 (280) hide show
  1. package/README.md +74 -3
  2. package/dist/a2ui/catalogue-doc.d.ts +44 -0
  3. package/dist/a2ui/catalogue-doc.js +169 -0
  4. package/dist/a2ui/catalogue-doc.js.map +1 -0
  5. package/dist/a2ui/catalogue.d.ts +44 -0
  6. package/dist/a2ui/catalogue.js +942 -0
  7. package/dist/a2ui/catalogue.js.map +1 -0
  8. package/dist/a2ui/index.d.ts +7 -0
  9. package/dist/a2ui/message.d.ts +232 -0
  10. package/dist/a2ui/message.js +365 -0
  11. package/dist/a2ui/message.js.map +1 -0
  12. package/dist/a2ui/renderer.d.ts +46 -0
  13. package/dist/a2ui/renderer.js +252 -0
  14. package/dist/a2ui/renderer.js.map +1 -0
  15. package/dist/a2ui/views.d.ts +80 -0
  16. package/dist/a2ui/views.js +596 -0
  17. package/dist/a2ui/views.js.map +1 -0
  18. package/dist/a2ui.d.ts +8 -0
  19. package/dist/a2ui.js +7 -0
  20. package/dist/chart.d.ts +9 -0
  21. package/dist/chart.js +9 -0
  22. package/dist/components/accordion.d.ts +22 -0
  23. package/dist/components/accordion.js +63 -0
  24. package/dist/components/accordion.js.map +1 -0
  25. package/dist/components/alert.d.ts +15 -0
  26. package/dist/components/alert.js +64 -0
  27. package/dist/components/alert.js.map +1 -0
  28. package/dist/components/avatar.d.ts +18 -0
  29. package/dist/components/avatar.js +40 -0
  30. package/dist/components/avatar.js.map +1 -0
  31. package/dist/components/badge.d.ts +12 -0
  32. package/dist/components/badge.js +47 -0
  33. package/dist/components/badge.js.map +1 -0
  34. package/dist/components/breadcrumb.d.ts +71 -0
  35. package/dist/components/breadcrumb.js +123 -0
  36. package/dist/components/breadcrumb.js.map +1 -0
  37. package/dist/components/button-link.d.ts +28 -0
  38. package/dist/components/button-link.js +24 -0
  39. package/dist/components/button-link.js.map +1 -0
  40. package/dist/components/button.d.ts +24 -0
  41. package/dist/components/button.js +65 -0
  42. package/dist/components/button.js.map +1 -0
  43. package/dist/components/calendar.d.ts +28 -0
  44. package/dist/components/calendar.js +123 -0
  45. package/dist/components/calendar.js.map +1 -0
  46. package/dist/components/card.d.ts +32 -0
  47. package/dist/components/card.js +57 -0
  48. package/dist/components/card.js.map +1 -0
  49. package/dist/components/chart/chart.d.ts +71 -0
  50. package/dist/components/chart/chart.js +167 -0
  51. package/dist/components/chart/chart.js.map +1 -0
  52. package/dist/components/chart/container.d.ts +88 -0
  53. package/dist/components/chart/container.js +189 -0
  54. package/dist/components/chart/container.js.map +1 -0
  55. package/dist/components/chart/data.js +128 -0
  56. package/dist/components/chart/data.js.map +1 -0
  57. package/dist/components/chart/index.d.ts +9 -0
  58. package/dist/components/chart/index.js +10 -0
  59. package/dist/components/chart/nivo-charts.js +225 -0
  60. package/dist/components/chart/nivo-charts.js.map +1 -0
  61. package/dist/components/chart/nivo-lazy.js +50 -0
  62. package/dist/components/chart/nivo-lazy.js.map +1 -0
  63. package/dist/components/chart/nivo-theme.d.ts +156 -0
  64. package/dist/components/chart/nivo-theme.js +161 -0
  65. package/dist/components/chart/nivo-theme.js.map +1 -0
  66. package/dist/components/chart/recharts-charts.js +276 -0
  67. package/dist/components/chart/recharts-charts.js.map +1 -0
  68. package/dist/components/chart/recharts-lazy.js +58 -0
  69. package/dist/components/chart/recharts-lazy.js.map +1 -0
  70. package/dist/components/chart/recharts-parts.d.ts +10 -0
  71. package/dist/components/chart/recharts-parts.js +25 -0
  72. package/dist/components/chart/recharts-parts.js.map +1 -0
  73. package/dist/components/chart/tokens.d.ts +62 -0
  74. package/dist/components/chart/tokens.js +192 -0
  75. package/dist/components/chart/tokens.js.map +1 -0
  76. package/dist/components/chart/types.d.ts +114 -0
  77. package/dist/components/chart/types.js +27 -0
  78. package/dist/components/chart/types.js.map +1 -0
  79. package/dist/components/checkbox.d.ts +20 -0
  80. package/dist/components/checkbox.js +24 -0
  81. package/dist/components/checkbox.js.map +1 -0
  82. package/dist/components/chip.d.ts +17 -0
  83. package/dist/components/chip.js +47 -0
  84. package/dist/components/chip.js.map +1 -0
  85. package/dist/components/combobox.d.ts +76 -0
  86. package/dist/components/combobox.js +201 -0
  87. package/dist/components/combobox.js.map +1 -0
  88. package/dist/components/command.d.ts +50 -0
  89. package/dist/components/command.js +117 -0
  90. package/dist/components/command.js.map +1 -0
  91. package/dist/components/data-table/data-table.d.ts +65 -0
  92. package/dist/components/data-table/data-table.js +588 -0
  93. package/dist/components/data-table/data-table.js.map +1 -0
  94. package/dist/components/data-table/export.d.ts +23 -0
  95. package/dist/components/data-table/export.js +95 -0
  96. package/dist/components/data-table/export.js.map +1 -0
  97. package/dist/components/data-table/features.d.ts +66 -0
  98. package/dist/components/data-table/features.js +103 -0
  99. package/dist/components/data-table/features.js.map +1 -0
  100. package/dist/components/data-table/index.d.ts +8 -0
  101. package/dist/components/data-table/parts.d.ts +79 -0
  102. package/dist/components/data-table/parts.js +400 -0
  103. package/dist/components/data-table/parts.js.map +1 -0
  104. package/dist/components/data-table/system-columns.js +84 -0
  105. package/dist/components/data-table/system-columns.js.map +1 -0
  106. package/dist/components/data-table/types.d.ts +172 -0
  107. package/dist/components/data-table/use-data-table.d.ts +19 -0
  108. package/dist/components/data-table/use-data-table.js +171 -0
  109. package/dist/components/data-table/use-data-table.js.map +1 -0
  110. package/dist/components/data-table/use-server-source.js +200 -0
  111. package/dist/components/data-table/use-server-source.js.map +1 -0
  112. package/dist/components/date-field.d.ts +54 -0
  113. package/dist/components/date-field.js +235 -0
  114. package/dist/components/date-field.js.map +1 -0
  115. package/dist/components/date-picker.d.ts +42 -0
  116. package/dist/components/date-picker.js +125 -0
  117. package/dist/components/date-picker.js.map +1 -0
  118. package/dist/components/dialog.d.ts +36 -0
  119. package/dist/components/dialog.js +97 -0
  120. package/dist/components/dialog.js.map +1 -0
  121. package/dist/components/dropdown-menu.d.ts +54 -0
  122. package/dist/components/dropdown-menu.js +191 -0
  123. package/dist/components/dropdown-menu.js.map +1 -0
  124. package/dist/components/dropzone.d.ts +48 -0
  125. package/dist/components/dropzone.js +185 -0
  126. package/dist/components/dropzone.js.map +1 -0
  127. package/dist/components/empty-state.d.ts +22 -0
  128. package/dist/components/empty-state.js +46 -0
  129. package/dist/components/empty-state.js.map +1 -0
  130. package/dist/components/error-banner.d.ts +17 -0
  131. package/dist/components/error-banner.js +52 -0
  132. package/dist/components/error-banner.js.map +1 -0
  133. package/dist/components/field.d.ts +64 -0
  134. package/dist/components/field.js +133 -0
  135. package/dist/components/field.js.map +1 -0
  136. package/dist/components/form.d.ts +37 -0
  137. package/dist/components/form.js +103 -0
  138. package/dist/components/form.js.map +1 -0
  139. package/dist/components/input.d.ts +17 -0
  140. package/dist/components/input.js +26 -0
  141. package/dist/components/input.js.map +1 -0
  142. package/dist/components/kbd.d.ts +11 -0
  143. package/dist/components/kbd.js +20 -0
  144. package/dist/components/kbd.js.map +1 -0
  145. package/dist/components/label.d.ts +14 -0
  146. package/dist/components/label.js +28 -0
  147. package/dist/components/label.js.map +1 -0
  148. package/dist/components/loading-skeleton.d.ts +22 -0
  149. package/dist/components/loading-skeleton.js +123 -0
  150. package/dist/components/loading-skeleton.js.map +1 -0
  151. package/dist/components/markdown.d.ts +44 -0
  152. package/dist/components/markdown.js +231 -0
  153. package/dist/components/markdown.js.map +1 -0
  154. package/dist/components/page-header.d.ts +26 -0
  155. package/dist/components/page-header.js +53 -0
  156. package/dist/components/page-header.js.map +1 -0
  157. package/dist/components/popover.d.ts +29 -0
  158. package/dist/components/popover.js +74 -0
  159. package/dist/components/popover.js.map +1 -0
  160. package/dist/components/progress.d.ts +22 -0
  161. package/dist/components/progress.js +49 -0
  162. package/dist/components/progress.js.map +1 -0
  163. package/dist/components/radio-group.d.ts +20 -0
  164. package/dist/components/radio-group.js +29 -0
  165. package/dist/components/radio-group.js.map +1 -0
  166. package/dist/components/scroll-area.d.ts +25 -0
  167. package/dist/components/scroll-area.js +39 -0
  168. package/dist/components/scroll-area.js.map +1 -0
  169. package/dist/components/section-card.d.ts +29 -0
  170. package/dist/components/section-card.js +55 -0
  171. package/dist/components/section-card.js.map +1 -0
  172. package/dist/components/segmented.d.ts +27 -0
  173. package/dist/components/segmented.js +32 -0
  174. package/dist/components/segmented.js.map +1 -0
  175. package/dist/components/select.d.ts +62 -0
  176. package/dist/components/select.js +97 -0
  177. package/dist/components/select.js.map +1 -0
  178. package/dist/components/separator.d.ts +19 -0
  179. package/dist/components/separator.js +23 -0
  180. package/dist/components/separator.js.map +1 -0
  181. package/dist/components/sheet.d.ts +35 -0
  182. package/dist/components/sheet.js +131 -0
  183. package/dist/components/sheet.js.map +1 -0
  184. package/dist/components/skeleton.d.ts +12 -0
  185. package/dist/components/skeleton.js +22 -0
  186. package/dist/components/skeleton.js.map +1 -0
  187. package/dist/components/sortable-list.d.ts +67 -0
  188. package/dist/components/sortable-list.js +206 -0
  189. package/dist/components/sortable-list.js.map +1 -0
  190. package/dist/components/spinner.d.ts +29 -0
  191. package/dist/components/spinner.js +34 -0
  192. package/dist/components/spinner.js.map +1 -0
  193. package/dist/components/status-dot.d.ts +26 -0
  194. package/dist/components/status-dot.js +47 -0
  195. package/dist/components/status-dot.js.map +1 -0
  196. package/dist/components/switch.d.ts +21 -0
  197. package/dist/components/switch.js +21 -0
  198. package/dist/components/switch.js.map +1 -0
  199. package/dist/components/tabs.d.ts +24 -0
  200. package/dist/components/tabs.js +60 -0
  201. package/dist/components/tabs.js.map +1 -0
  202. package/dist/components/textarea.d.ts +19 -0
  203. package/dist/components/textarea.js +47 -0
  204. package/dist/components/textarea.js.map +1 -0
  205. package/dist/components/toast.d.ts +47 -0
  206. package/dist/components/toast.js +106 -0
  207. package/dist/components/toast.js.map +1 -0
  208. package/dist/components/toolbar.d.ts +19 -0
  209. package/dist/components/toolbar.js +45 -0
  210. package/dist/components/toolbar.js.map +1 -0
  211. package/dist/components/tooltip.d.ts +28 -0
  212. package/dist/components/tooltip.js +59 -0
  213. package/dist/components/tooltip.js.map +1 -0
  214. package/dist/core.d.ts +44 -0
  215. package/dist/core.js +44 -0
  216. package/dist/data-table.d.ts +9 -0
  217. package/dist/data-table.js +7 -0
  218. package/dist/form.d.ts +7 -0
  219. package/dist/form.js +7 -0
  220. package/dist/hooks/use-cortena-theme.d.ts +98 -0
  221. package/dist/hooks/use-cortena-theme.js +145 -0
  222. package/dist/hooks/use-cortena-theme.js.map +1 -0
  223. package/dist/index.d.ts +75 -2678
  224. package/dist/index.js +66 -8598
  225. package/dist/lib/cn.d.ts +10 -0
  226. package/dist/lib/cn.js +15 -0
  227. package/dist/lib/cn.js.map +1 -0
  228. package/dist/lib/render.d.ts +23 -0
  229. package/dist/lib/render.js +20 -0
  230. package/dist/lib/render.js.map +1 -0
  231. package/dist/lib/shakeable.js +39 -0
  232. package/dist/lib/shakeable.js.map +1 -0
  233. package/dist/markdown.d.ts +3 -0
  234. package/dist/markdown.js +3 -0
  235. package/dist/sortable-list.d.ts +3 -0
  236. package/dist/sortable-list.js +3 -0
  237. package/package.json +37 -14
  238. package/src/a2ui/catalogue.ts +140 -103
  239. package/src/a2ui/views.tsx +5 -2
  240. package/src/components/alert.tsx +1 -1
  241. package/src/components/badge.tsx +1 -1
  242. package/src/components/button.tsx +1 -1
  243. package/src/components/chart/chart.tsx +72 -25
  244. package/src/components/chart/container.tsx +24 -39
  245. package/src/components/chart/index.tsx +4 -2
  246. package/src/components/chart/nivo-charts.tsx +41 -13
  247. package/src/components/chart/nivo-lazy.tsx +52 -0
  248. package/src/components/chart/recharts-charts.tsx +98 -3
  249. package/src/components/chart/recharts-lazy.tsx +72 -0
  250. package/src/components/chart/recharts-parts.tsx +34 -0
  251. package/src/components/chart/tokens.ts +1 -1
  252. package/src/components/chart/types.ts +14 -0
  253. package/src/components/chip.tsx +1 -1
  254. package/src/components/combobox.tsx +2 -2
  255. package/src/components/data-table/export.ts +18 -2
  256. package/src/components/data-table/features.ts +77 -45
  257. package/src/components/data-table/parts.tsx +15 -0
  258. package/src/components/data-table/use-data-table.ts +7 -2
  259. package/src/components/data-table/use-server-source.ts +17 -4
  260. package/src/components/date-field.tsx +1 -1
  261. package/src/components/error-banner.tsx +1 -1
  262. package/src/components/field.tsx +2 -2
  263. package/src/components/form.tsx +2 -2
  264. package/src/components/input.tsx +1 -1
  265. package/src/components/label.tsx +1 -1
  266. package/src/components/markdown.tsx +5 -4
  267. package/src/components/sheet.tsx +2 -2
  268. package/src/components/sortable-list.tsx +2 -2
  269. package/src/components/textarea.tsx +1 -1
  270. package/src/components/toast.tsx +1 -1
  271. package/src/entries/a2ui.ts +11 -0
  272. package/src/entries/chart.ts +10 -0
  273. package/src/entries/core.ts +91 -0
  274. package/src/entries/data-table.ts +9 -0
  275. package/src/entries/form.ts +24 -0
  276. package/src/entries/markdown.ts +9 -0
  277. package/src/entries/sortable-list.ts +13 -0
  278. package/src/index.ts +32 -98
  279. package/src/lib/shakeable.ts +33 -0
  280. package/dist/index.js.map +0 -1
@@ -0,0 +1,206 @@
1
+ "use client";
2
+ "use client";
3
+ import { cn } from "../lib/cn.js";
4
+ import { GripVertical } from "lucide-react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import * as React from "react";
7
+ import { DndContext, DragOverlay, KeyboardSensor, MouseSensor, PointerSensor, TouchSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
8
+ import { SortableContext, arrayMove, horizontalListSortingStrategy, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
9
+ import { CSS } from "@dnd-kit/utilities";
10
+ //#region src/components/sortable-list.tsx
11
+ /**
12
+ * SortableList.
13
+ *
14
+ * A reorderable list over dnd-kit: `DndContext` + `SortableContext` around one
15
+ * `useSortable` item per entry. The list owns nothing — it calls `onReorder`
16
+ * with the next array and the consumer stores it. Pointer and keyboard sensors
17
+ * are wired (Space picks up, arrows move, Space drops, Escape cancels) and a
18
+ * `DragOverlay` paints the lifted copy with `--ds-shadow-lg` while the item
19
+ * left behind dims and takes the ring colour.
20
+ *
21
+ * By default the whole row is the drag activator. Pass `handle` to restrict
22
+ * dragging to a `<SortableHandle>` (or any element given `handleProps`) so
23
+ * buttons and inputs inside a row stay clickable.
24
+ *
25
+ * ## Touch
26
+ *
27
+ * A pointer drag and a touch scroll start the same way, so something has to
28
+ * say which one a finger means. In handle mode only the small handle is
29
+ * `touch-none`, so the rest of the row scrolls: nothing to decide. In
30
+ * whole-row mode the whole row is `touch-none`, which is why a touch list
31
+ * whose rows are the activators cannot be scrolled by dragging over them.
32
+ *
33
+ * `touchScroll` is the way out, and it defaults to whichever of those is
34
+ * already true — `true` in handle mode, `false` in whole-row mode, so existing
35
+ * lists behave exactly as before. Setting it `true` on a whole-row list drops
36
+ * `touch-none` and swaps the PointerSensor for a MouseSensor plus a
37
+ * TouchSensor with delay activation: a quick swipe scrolls, a press held for
38
+ * {@link TOUCH_DELAY_MS} picks the row up. `false` in handle mode is the
39
+ * opposite trade — the row never scrolls under a finger.
40
+ */
41
+ /** How long a finger must rest on a row before it becomes a drag. */
42
+ const TOUCH_DELAY_MS = 250;
43
+ /** How far it may slip in that time and still count as a press, not a scroll. */
44
+ const TOUCH_TOLERANCE_PX = 8;
45
+ /** dnd-kit types its listeners as a bag of `Function`s; narrow them to the handlers they are. */
46
+ function asHandlers(listeners) {
47
+ return listeners ?? {};
48
+ }
49
+ const noopRef = () => {};
50
+ const inertHandle = { ref: noopRef };
51
+ const SortableItemContext = /* @__PURE__ */ React.createContext({
52
+ handleProps: inertHandle,
53
+ isDragging: false,
54
+ disabled: false
55
+ });
56
+ const itemClassName = /* @__PURE__ */ cn("relative rounded-[var(--ds-radius-md)] outline-none", "focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40", "data-[dragging]:opacity-50 data-[dragging]:ring-2 data-[dragging]:ring-[var(--ds-ring)]");
57
+ function SortableItem({ id, item, handle, disabled, touchScroll, renderItem, className }) {
58
+ const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({
59
+ id,
60
+ disabled
61
+ });
62
+ const handleProps = React.useMemo(() => ({
63
+ ...attributes,
64
+ ...asHandlers(listeners),
65
+ ref: setActivatorNodeRef
66
+ }), [
67
+ attributes,
68
+ listeners,
69
+ setActivatorNodeRef
70
+ ]);
71
+ const context = React.useMemo(() => ({
72
+ handleProps,
73
+ isDragging,
74
+ disabled
75
+ }), [
76
+ handleProps,
77
+ isDragging,
78
+ disabled
79
+ ]);
80
+ const rowActivator = handle ? void 0 : {
81
+ ...attributes,
82
+ ...asHandlers(listeners)
83
+ };
84
+ return /* @__PURE__ */ jsx(SortableItemContext.Provider, {
85
+ value: context,
86
+ children: /* @__PURE__ */ jsx("li", {
87
+ ref: setNodeRef,
88
+ "data-slot": "sortable-item",
89
+ "data-dragging": isDragging || void 0,
90
+ "data-disabled": disabled || void 0,
91
+ style: {
92
+ transform: CSS.Translate.toString(transform),
93
+ transition
94
+ },
95
+ className: cn(itemClassName, !handle && !disabled && "cursor-grab data-[dragging]:cursor-grabbing", !touchScroll && !disabled && "touch-none", className),
96
+ ...rowActivator,
97
+ children: renderItem(item, {
98
+ handleProps,
99
+ isDragging
100
+ })
101
+ })
102
+ });
103
+ }
104
+ /**
105
+ * A grip button that starts the drag for its row. Reads the row's activator
106
+ * props from context, so it needs no wiring beyond being rendered inside
107
+ * `renderItem`. Hidden (kept in layout) when the list is disabled.
108
+ */
109
+ function SortableHandle({ className, children, ...props }) {
110
+ const { handleProps, disabled } = React.useContext(SortableItemContext);
111
+ return /* @__PURE__ */ jsx("button", {
112
+ type: "button",
113
+ "data-slot": "sortable-handle",
114
+ "aria-label": "Drag to reorder",
115
+ className: cn("inline-flex size-7 shrink-0 cursor-grab touch-none items-center justify-center", "rounded-[var(--ds-radius-sm)] text-[color:var(--ds-text-tertiary)]", "hover:bg-[var(--ds-hover)] hover:text-[color:var(--ds-muted-foreground)]", "outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40", "active:cursor-grabbing disabled:pointer-events-none disabled:opacity-50", "[&_svg]:size-4 [&_svg]:pointer-events-none", disabled && "invisible", className),
116
+ disabled,
117
+ ...handleProps,
118
+ ...props,
119
+ children: children ?? /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": true })
120
+ });
121
+ }
122
+ function SortableList({ items, getId, onReorder, renderItem, orientation = "vertical", handle = false, disabled = false, touchScroll = handle, itemClassName: itemClass, className, ...props }) {
123
+ const [activeId, setActiveId] = React.useState(null);
124
+ const delayTouch = touchScroll && !handle;
125
+ const pointerSensor = useSensor(PointerSensor, { activationConstraint: { distance: 4 } });
126
+ const mouseSensor = useSensor(MouseSensor, { activationConstraint: { distance: 4 } });
127
+ const touchSensor = useSensor(TouchSensor, { activationConstraint: {
128
+ delay: TOUCH_DELAY_MS,
129
+ tolerance: TOUCH_TOLERANCE_PX
130
+ } });
131
+ const keyboardSensor = useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates });
132
+ const pointerSensors = useSensors(pointerSensor, keyboardSensor);
133
+ const splitSensors = useSensors(mouseSensor, touchSensor, keyboardSensor);
134
+ const sensors = delayTouch ? splitSensors : pointerSensors;
135
+ const ids = React.useMemo(() => items.map(getId), [items, getId]);
136
+ const activeItem = activeId === null ? void 0 : items[ids.indexOf(activeId)];
137
+ const onDragStart = React.useCallback((event) => {
138
+ setActiveId(event.active.id);
139
+ }, []);
140
+ const onDragEnd = React.useCallback((event) => {
141
+ setActiveId(null);
142
+ const { active, over } = event;
143
+ if (!over || active.id === over.id) return;
144
+ const from = ids.indexOf(active.id);
145
+ const to = ids.indexOf(over.id);
146
+ if (from < 0 || to < 0) return;
147
+ onReorder(arrayMove([...items], from, to), {
148
+ from,
149
+ to,
150
+ activeId: active.id,
151
+ overId: over.id
152
+ });
153
+ }, [
154
+ ids,
155
+ items,
156
+ onReorder
157
+ ]);
158
+ const onDragCancel = React.useCallback(() => setActiveId(null), []);
159
+ return /* @__PURE__ */ jsxs(DndContext, {
160
+ sensors,
161
+ collisionDetection: closestCenter,
162
+ onDragStart,
163
+ onDragEnd,
164
+ onDragCancel,
165
+ children: [/* @__PURE__ */ jsx(SortableContext, {
166
+ items: ids,
167
+ strategy: orientation === "horizontal" ? horizontalListSortingStrategy : verticalListSortingStrategy,
168
+ disabled,
169
+ children: /* @__PURE__ */ jsx("ul", {
170
+ "data-slot": "sortable-list",
171
+ "data-orientation": orientation,
172
+ "data-disabled": disabled || void 0,
173
+ "data-touch-scroll": touchScroll ? "" : void 0,
174
+ className: cn("flex list-none gap-2 p-0", orientation === "horizontal" ? "flex-row flex-wrap" : "flex-col", className),
175
+ ...props,
176
+ children: items.map((item, index) => /* @__PURE__ */ jsx(SortableItem, {
177
+ id: ids[index] ?? index,
178
+ item,
179
+ handle,
180
+ disabled,
181
+ touchScroll,
182
+ renderItem,
183
+ className: itemClass
184
+ }, ids[index] ?? index))
185
+ })
186
+ }), /* @__PURE__ */ jsx(DragOverlay, { children: activeItem === void 0 ? null : /* @__PURE__ */ jsx(SortableItemContext.Provider, {
187
+ value: {
188
+ handleProps: inertHandle,
189
+ isDragging: true,
190
+ disabled: false
191
+ },
192
+ children: /* @__PURE__ */ jsx("div", {
193
+ "data-slot": "sortable-overlay",
194
+ className: cn("cursor-grabbing rounded-[var(--ds-radius-md)] bg-[var(--ds-card)]", "shadow-[var(--ds-shadow-lg)] ring-1 ring-[var(--ds-ring)]", itemClass),
195
+ children: renderItem(activeItem, {
196
+ handleProps: inertHandle,
197
+ isDragging: true
198
+ })
199
+ })
200
+ }) })]
201
+ });
202
+ }
203
+ //#endregion
204
+ export { SortableHandle, SortableList, arrayMove };
205
+
206
+ //# sourceMappingURL=sortable-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sortable-list.js","names":[],"sources":["../../src/components/sortable-list.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n DndContext,\n DragOverlay,\n KeyboardSensor,\n MouseSensor,\n PointerSensor,\n TouchSensor,\n closestCenter,\n useSensor,\n useSensors,\n type DragEndEvent,\n type DragStartEvent,\n type DraggableAttributes,\n type DraggableSyntheticListeners,\n type UniqueIdentifier,\n} from \"@dnd-kit/core\";\nimport {\n SortableContext,\n arrayMove,\n horizontalListSortingStrategy,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { GripVertical } from \"lucide-react\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * SortableList.\n *\n * A reorderable list over dnd-kit: `DndContext` + `SortableContext` around one\n * `useSortable` item per entry. The list owns nothing — it calls `onReorder`\n * with the next array and the consumer stores it. Pointer and keyboard sensors\n * are wired (Space picks up, arrows move, Space drops, Escape cancels) and a\n * `DragOverlay` paints the lifted copy with `--ds-shadow-lg` while the item\n * left behind dims and takes the ring colour.\n *\n * By default the whole row is the drag activator. Pass `handle` to restrict\n * dragging to a `<SortableHandle>` (or any element given `handleProps`) so\n * buttons and inputs inside a row stay clickable.\n *\n * ## Touch\n *\n * A pointer drag and a touch scroll start the same way, so something has to\n * say which one a finger means. In handle mode only the small handle is\n * `touch-none`, so the rest of the row scrolls: nothing to decide. In\n * whole-row mode the whole row is `touch-none`, which is why a touch list\n * whose rows are the activators cannot be scrolled by dragging over them.\n *\n * `touchScroll` is the way out, and it defaults to whichever of those is\n * already true — `true` in handle mode, `false` in whole-row mode, so existing\n * lists behave exactly as before. Setting it `true` on a whole-row list drops\n * `touch-none` and swaps the PointerSensor for a MouseSensor plus a\n * TouchSensor with delay activation: a quick swipe scrolls, a press held for\n * {@link TOUCH_DELAY_MS} picks the row up. `false` in handle mode is the\n * opposite trade — the row never scrolls under a finger.\n */\n\n/** How long a finger must rest on a row before it becomes a drag. */\nconst TOUCH_DELAY_MS = 250;\n/** How far it may slip in that time and still count as a press, not a scroll. */\nconst TOUCH_TOLERANCE_PX = 8;\n\n/** Spread onto the element that should start a drag. */\nexport interface SortableHandleProps extends Partial<DraggableAttributes> {\n ref: (element: HTMLElement | null) => void;\n onKeyDown?: React.KeyboardEventHandler<HTMLElement>;\n onPointerDown?: React.PointerEventHandler<HTMLElement>;\n onMouseDown?: React.MouseEventHandler<HTMLElement>;\n onTouchStart?: React.TouchEventHandler<HTMLElement>;\n}\n\n/** dnd-kit types its listeners as a bag of `Function`s; narrow them to the handlers they are. */\nfunction asHandlers(listeners: DraggableSyntheticListeners): Partial<SortableHandleProps> {\n return (listeners ?? {}) as Partial<SortableHandleProps>;\n}\n\nexport interface SortableRenderState {\n /** Attributes, listeners and ref for a custom drag handle. */\n handleProps: SortableHandleProps;\n /** True for the item being dragged (and for the overlay copy). */\n isDragging: boolean;\n}\n\n/** What moved where, passed to `onReorder` alongside the reordered array. */\nexport interface SortableReorderDetails {\n /** Index the item came from, in the array as it was passed in. */\n from: number;\n /** Index it landed on. */\n to: number;\n /** Id of the item that moved (`getId` of the dragged item). */\n activeId: UniqueIdentifier;\n /** Id of the item it was dropped onto. */\n overId: UniqueIdentifier;\n}\n\nexport interface SortableListProps<T> extends Omit<React.ComponentProps<\"ul\">, \"children\"> {\n items: readonly T[];\n /** Stable id per item; must be unique within the list. */\n getId: (item: T) => UniqueIdentifier;\n /**\n * Called with the reordered array after a successful drop, and with the move\n * itself — `{ from, to, activeId, overId }` — for a server that persists a\n * position rather than the whole list.\n */\n onReorder: (next: T[], details: SortableReorderDetails) => void;\n renderItem: (item: T, state: SortableRenderState) => React.ReactNode;\n /** Layout axis; also selects the sorting strategy. */\n orientation?: \"vertical\" | \"horizontal\";\n /** Only a `<SortableHandle>` (or an element given `handleProps`) starts a drag. */\n handle?: boolean;\n /**\n * Let a finger scroll the page over the rows. Defaults to `handle`: `true`\n * in handle mode, `false` in whole-row mode, which is what each already did.\n * `true` on a whole-row list drops `touch-none` and uses a TouchSensor with\n * delay activation, so a swipe scrolls and a held press drags.\n */\n touchScroll?: boolean;\n /** Disables every sensor and hides the handles. */\n disabled?: boolean;\n /** Class name for each item wrapper. */\n itemClassName?: string;\n}\n\ninterface ItemContextValue {\n handleProps: SortableHandleProps;\n isDragging: boolean;\n disabled: boolean;\n}\n\nconst noopRef = () => {};\nconst inertHandle: SortableHandleProps = { ref: noopRef };\n\nconst SortableItemContext = /* @__PURE__ */ React.createContext<ItemContextValue>({\n handleProps: inertHandle,\n isDragging: false,\n disabled: false,\n});\n\nconst itemClassName = /* @__PURE__ */ cn(\n \"relative rounded-[var(--ds-radius-md)] outline-none\",\n \"focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n \"data-[dragging]:opacity-50 data-[dragging]:ring-2 data-[dragging]:ring-[var(--ds-ring)]\",\n);\n\ninterface SortableItemProps<T> {\n id: UniqueIdentifier;\n item: T;\n handle: boolean;\n disabled: boolean;\n touchScroll: boolean;\n renderItem: SortableListProps<T>[\"renderItem\"];\n className: string | undefined;\n}\n\nfunction SortableItem<T>({ id, item, handle, disabled, touchScroll, renderItem, className }: SortableItemProps<T>) {\n const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } =\n useSortable({ id, disabled });\n\n const handleProps = React.useMemo<SortableHandleProps>(\n () => ({ ...attributes, ...asHandlers(listeners), ref: setActivatorNodeRef }),\n [attributes, listeners, setActivatorNodeRef],\n );\n const context = React.useMemo<ItemContextValue>(\n () => ({ handleProps, isDragging, disabled }),\n [handleProps, isDragging, disabled],\n );\n const rowActivator = handle ? undefined : { ...attributes, ...asHandlers(listeners) };\n\n return (\n <SortableItemContext.Provider value={context}>\n <li\n ref={setNodeRef}\n data-slot=\"sortable-item\"\n data-dragging={isDragging || undefined}\n data-disabled={disabled || undefined}\n style={{ transform: CSS.Translate.toString(transform), transition }}\n className={cn(\n itemClassName,\n !handle && !disabled && \"cursor-grab data-[dragging]:cursor-grabbing\",\n // touch-action is what decides whether a finger scrolls or drags.\n !touchScroll && !disabled && \"touch-none\",\n className,\n )}\n {...rowActivator}\n >\n {renderItem(item, { handleProps, isDragging })}\n </li>\n </SortableItemContext.Provider>\n );\n}\n\n/**\n * A grip button that starts the drag for its row. Reads the row's activator\n * props from context, so it needs no wiring beyond being rendered inside\n * `renderItem`. Hidden (kept in layout) when the list is disabled.\n */\nfunction SortableHandle({ className, children, ...props }: React.ComponentProps<\"button\">) {\n const { handleProps, disabled } = React.useContext(SortableItemContext);\n return (\n <button\n type=\"button\"\n data-slot=\"sortable-handle\"\n aria-label=\"Drag to reorder\"\n className={cn(\n \"inline-flex size-7 shrink-0 cursor-grab touch-none items-center justify-center\",\n \"rounded-[var(--ds-radius-sm)] text-[color:var(--ds-text-tertiary)]\",\n \"hover:bg-[var(--ds-hover)] hover:text-[color:var(--ds-muted-foreground)]\",\n \"outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n \"active:cursor-grabbing disabled:pointer-events-none disabled:opacity-50\",\n \"[&_svg]:size-4 [&_svg]:pointer-events-none\",\n disabled && \"invisible\",\n className,\n )}\n disabled={disabled}\n {...handleProps}\n {...props}\n >\n {children ?? <GripVertical aria-hidden />}\n </button>\n );\n}\n\nfunction SortableList<T>({\n items,\n getId,\n onReorder,\n renderItem,\n orientation = \"vertical\",\n handle = false,\n disabled = false,\n touchScroll = handle,\n itemClassName: itemClass,\n className,\n ...props\n}: SortableListProps<T>) {\n const [activeId, setActiveId] = React.useState<UniqueIdentifier | null>(null);\n // Only a whole-row list needs the split sensors: in handle mode the row is\n // already scrollable because only the handle is touch-none, and swapping\n // sensors there would put a 250 ms delay on the handle for no gain.\n const delayTouch = touchScroll && !handle;\n // Every sensor is created on every render (they are hooks); which ones are\n // handed to DndContext is what changes.\n const pointerSensor = useSensor(PointerSensor, { activationConstraint: { distance: 4 } });\n const mouseSensor = useSensor(MouseSensor, { activationConstraint: { distance: 4 } });\n const touchSensor = useSensor(TouchSensor, {\n activationConstraint: { delay: TOUCH_DELAY_MS, tolerance: TOUCH_TOLERANCE_PX },\n });\n const keyboardSensor = useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates });\n const pointerSensors = useSensors(pointerSensor, keyboardSensor);\n const splitSensors = useSensors(mouseSensor, touchSensor, keyboardSensor);\n const sensors = delayTouch ? splitSensors : pointerSensors;\n const ids = React.useMemo(() => items.map(getId), [items, getId]);\n const activeItem = activeId === null ? undefined : items[ids.indexOf(activeId)];\n\n const onDragStart = React.useCallback((event: DragStartEvent) => {\n setActiveId(event.active.id);\n }, []);\n const onDragEnd = React.useCallback(\n (event: DragEndEvent) => {\n setActiveId(null);\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n const from = ids.indexOf(active.id);\n const to = ids.indexOf(over.id);\n if (from < 0 || to < 0) return;\n onReorder(arrayMove([...items], from, to), {\n from,\n to,\n activeId: active.id,\n overId: over.id,\n });\n },\n [ids, items, onReorder],\n );\n const onDragCancel = React.useCallback(() => setActiveId(null), []);\n\n return (\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragStart={onDragStart}\n onDragEnd={onDragEnd}\n onDragCancel={onDragCancel}\n >\n <SortableContext\n items={ids}\n strategy={orientation === \"horizontal\" ? horizontalListSortingStrategy : verticalListSortingStrategy}\n disabled={disabled}\n >\n <ul\n data-slot=\"sortable-list\"\n data-orientation={orientation}\n data-disabled={disabled || undefined}\n data-touch-scroll={touchScroll ? \"\" : undefined}\n className={cn(\n \"flex list-none gap-2 p-0\",\n orientation === \"horizontal\" ? \"flex-row flex-wrap\" : \"flex-col\",\n className,\n )}\n {...props}\n >\n {items.map((item, index) => (\n <SortableItem\n key={ids[index] ?? index}\n id={ids[index] ?? index}\n item={item}\n handle={handle}\n disabled={disabled}\n touchScroll={touchScroll}\n renderItem={renderItem}\n className={itemClass}\n />\n ))}\n </ul>\n </SortableContext>\n <DragOverlay>\n {activeItem === undefined ? null : (\n <SortableItemContext.Provider\n value={{ handleProps: inertHandle, isDragging: true, disabled: false }}\n >\n <div\n data-slot=\"sortable-overlay\"\n className={cn(\n \"cursor-grabbing rounded-[var(--ds-radius-md)] bg-[var(--ds-card)]\",\n \"shadow-[var(--ds-shadow-lg)] ring-1 ring-[var(--ds-ring)]\",\n itemClass,\n )}\n >\n {renderItem(activeItem, { handleProps: inertHandle, isDragging: true })}\n </div>\n </SortableItemContext.Provider>\n )}\n </DragOverlay>\n </DndContext>\n );\n}\n\nexport { SortableList, SortableHandle, arrayMove };\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ //#region src/components/spinner.d.ts
4
+ export interface SpinnerProps extends React.ComponentProps<"span"> {
5
+ /** Diameter in px. Defaults to 16. */
6
+ size?: number;
7
+ /**
8
+ * `inverse` for a spinner on a filled surface — inside a primary or
9
+ * destructive Button, on a toast, on any `-foreground` pairing. The default
10
+ * ring is `--ds-border` with a `--ds-primary` leading edge, which all but
11
+ * disappears on a primary fill; `inverse` draws both from
12
+ * `--ds-primary-foreground` instead.
13
+ */
14
+ tone?: "default" | "inverse";
15
+ }
16
+ /**
17
+ * Spinner — a ring with a coloured leading edge. Announces itself as
18
+ * `role="status"` with a "Loading" label; pass `aria-label` to override.
19
+ *
20
+ * ```tsx
21
+ * <Button disabled>
22
+ * <Spinner tone="inverse" /> Saving…
23
+ * </Button>
24
+ * ```
25
+ */
26
+ declare function Spinner({ className, size, tone, style, ...props }: SpinnerProps): React.JSX.Element;
27
+ //#endregion
28
+ export { Spinner };
29
+ //# sourceMappingURL=spinner.d.ts.map
@@ -0,0 +1,34 @@
1
+ "use client";
2
+ "use client";
3
+ import { cn } from "../lib/cn.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/components/spinner.tsx
6
+ /**
7
+ * Spinner — a ring with a coloured leading edge. Announces itself as
8
+ * `role="status"` with a "Loading" label; pass `aria-label` to override.
9
+ *
10
+ * ```tsx
11
+ * <Button disabled>
12
+ * <Spinner tone="inverse" /> Saving…
13
+ * </Button>
14
+ * ```
15
+ */
16
+ function Spinner({ className, size = 16, tone = "default", style, ...props }) {
17
+ return /* @__PURE__ */ jsx("span", {
18
+ "data-slot": "spinner",
19
+ "data-tone": tone,
20
+ role: "status",
21
+ "aria-label": "Loading",
22
+ className: cn("inline-block shrink-0 animate-spin rounded-[var(--ds-radius-full)] border-2", tone === "inverse" ? "border-[var(--ds-primary-foreground)]/35 border-t-[var(--ds-primary-foreground)]" : "border-[var(--ds-border)] border-t-[var(--ds-primary)]", className),
23
+ style: {
24
+ width: size,
25
+ height: size,
26
+ ...style
27
+ },
28
+ ...props
29
+ });
30
+ }
31
+ //#endregion
32
+ export { Spinner };
33
+
34
+ //# sourceMappingURL=spinner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spinner.js","names":[],"sources":["../../src/components/spinner.tsx"],"sourcesContent":["\"use client\";\n\nimport type * as React from \"react\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface SpinnerProps extends React.ComponentProps<\"span\"> {\n /** Diameter in px. Defaults to 16. */\n size?: number;\n /**\n * `inverse` for a spinner on a filled surface — inside a primary or\n * destructive Button, on a toast, on any `-foreground` pairing. The default\n * ring is `--ds-border` with a `--ds-primary` leading edge, which all but\n * disappears on a primary fill; `inverse` draws both from\n * `--ds-primary-foreground` instead.\n */\n tone?: \"default\" | \"inverse\";\n}\n\n/**\n * Spinner — a ring with a coloured leading edge. Announces itself as\n * `role=\"status\"` with a \"Loading\" label; pass `aria-label` to override.\n *\n * ```tsx\n * <Button disabled>\n * <Spinner tone=\"inverse\" /> Saving…\n * </Button>\n * ```\n */\nfunction Spinner({ className, size = 16, tone = \"default\", style, ...props }: SpinnerProps) {\n return (\n <span\n data-slot=\"spinner\"\n data-tone={tone}\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\n \"inline-block shrink-0 animate-spin rounded-[var(--ds-radius-full)] border-2\",\n tone === \"inverse\"\n ? \"border-[var(--ds-primary-foreground)]/35 border-t-[var(--ds-primary-foreground)]\"\n : \"border-[var(--ds-border)] border-t-[var(--ds-primary)]\",\n className,\n )}\n style={{ width: size, height: size, ...style }}\n {...props}\n />\n );\n}\n\nexport { Spinner };\n"],"mappings":";;;;;;;;;;;;;;;AA4BA,SAAS,QAAQ,EAAE,WAAW,OAAO,IAAI,OAAO,WAAW,OAAO,GAAG,SAAuB;CAC1F,OACE,oBAAC,QAAD;EACE,aAAU;EACV,aAAW;EACX,MAAK;EACL,cAAW;EACX,WAAW,GACT,+EACA,SAAS,YACL,qFACA,0DACJ,SACF;EACA,OAAO;GAAE,OAAO;GAAM,QAAQ;GAAM,GAAG;EAAM;EAC7C,GAAI;CACL,CAAA;AAEL"}
@@ -0,0 +1,26 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ //#region src/components/status-dot.d.ts
4
+ /**
5
+ * The error tone is `destructive`, the same spelling Button, Alert and Badge
6
+ * use. `danger` is a deprecated alias of it and paints the same.
7
+ */
8
+ type StatusDotTone = "accent" | "success" | "warning" | "destructive" |
9
+ /** @deprecated Use `destructive`; this alias paints the same. */
10
+ "danger" | "info" | "neutral";
11
+ export interface StatusDotProps extends Omit<React.ComponentProps<"span">, "children"> {
12
+ tone?: StatusDotTone;
13
+ /** Adds a pinging halo behind the dot for "currently active" states. */
14
+ live?: boolean;
15
+ label?: React.ReactNode;
16
+ /** Dot diameter in px. Defaults to 7. */
17
+ size?: number;
18
+ }
19
+ /**
20
+ * StatusDot — a coloured dot with an optional caption. `live` layers a ping
21
+ * ring in the same tone behind the dot.
22
+ */
23
+ declare function StatusDot({ className, tone, live, label, size, style, ...props }: StatusDotProps): React.JSX.Element;
24
+ //#endregion
25
+ export { StatusDot, type StatusDotTone };
26
+ //# sourceMappingURL=status-dot.d.ts.map
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ "use client";
3
+ import { cn } from "../lib/cn.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/components/status-dot.tsx
6
+ const TONE_CLASS = {
7
+ accent: "bg-[var(--ds-primary)]",
8
+ success: "bg-[var(--ds-success)]",
9
+ warning: "bg-[var(--ds-warning)]",
10
+ destructive: "bg-[var(--ds-destructive)]",
11
+ danger: "bg-[var(--ds-destructive)]",
12
+ info: "bg-[var(--ds-info)]",
13
+ neutral: "bg-[var(--ds-text-tertiary)]"
14
+ };
15
+ /**
16
+ * StatusDot — a coloured dot with an optional caption. `live` layers a ping
17
+ * ring in the same tone behind the dot.
18
+ */
19
+ function StatusDot({ className, tone = "accent", live, label, size = 7, style, ...props }) {
20
+ return /* @__PURE__ */ jsxs("span", {
21
+ "data-slot": "status-dot",
22
+ "data-tone": tone,
23
+ className: cn("inline-flex items-center gap-1.5", "text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]", className),
24
+ ...props,
25
+ children: [/* @__PURE__ */ jsxs("span", {
26
+ "aria-hidden": true,
27
+ "data-slot": "status-dot-indicator",
28
+ className: "relative inline-flex shrink-0",
29
+ style: {
30
+ width: size,
31
+ height: size,
32
+ ...style
33
+ },
34
+ children: [live ? /* @__PURE__ */ jsx("span", {
35
+ "data-slot": "status-dot-ping",
36
+ className: cn("absolute inset-0 animate-ping rounded-[var(--ds-radius-full)] opacity-60", TONE_CLASS[tone])
37
+ }) : null, /* @__PURE__ */ jsx("span", {
38
+ "data-slot": "status-dot-fill",
39
+ className: cn("relative inline-block size-full rounded-[var(--ds-radius-full)]", TONE_CLASS[tone])
40
+ })]
41
+ }), label]
42
+ });
43
+ }
44
+ //#endregion
45
+ export { StatusDot };
46
+
47
+ //# sourceMappingURL=status-dot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-dot.js","names":[],"sources":["../../src/components/status-dot.tsx"],"sourcesContent":["\"use client\";\n\nimport type * as React from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * The error tone is `destructive`, the same spelling Button, Alert and Badge\n * use. `danger` is a deprecated alias of it and paints the same.\n */\ntype StatusDotTone =\n | \"accent\"\n | \"success\"\n | \"warning\"\n | \"destructive\"\n /** @deprecated Use `destructive`; this alias paints the same. */\n | \"danger\"\n | \"info\"\n | \"neutral\";\n\nconst TONE_CLASS: Record<StatusDotTone, string> = {\n accent: \"bg-[var(--ds-primary)]\",\n success: \"bg-[var(--ds-success)]\",\n warning: \"bg-[var(--ds-warning)]\",\n destructive: \"bg-[var(--ds-destructive)]\",\n danger: \"bg-[var(--ds-destructive)]\",\n info: \"bg-[var(--ds-info)]\",\n neutral: \"bg-[var(--ds-text-tertiary)]\",\n};\n\nexport interface StatusDotProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n tone?: StatusDotTone;\n /** Adds a pinging halo behind the dot for \"currently active\" states. */\n live?: boolean;\n label?: React.ReactNode;\n /** Dot diameter in px. Defaults to 7. */\n size?: number;\n}\n\n/**\n * StatusDot — a coloured dot with an optional caption. `live` layers a ping\n * ring in the same tone behind the dot.\n */\nfunction StatusDot({\n className,\n tone = \"accent\",\n live,\n label,\n size = 7,\n style,\n ...props\n}: StatusDotProps) {\n return (\n <span\n data-slot=\"status-dot\"\n data-tone={tone}\n className={cn(\n \"inline-flex items-center gap-1.5\",\n \"text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\",\n className,\n )}\n {...props}\n >\n <span\n aria-hidden\n data-slot=\"status-dot-indicator\"\n className=\"relative inline-flex shrink-0\"\n style={{ width: size, height: size, ...style }}\n >\n {live ? (\n <span\n data-slot=\"status-dot-ping\"\n className={cn(\n \"absolute inset-0 animate-ping rounded-[var(--ds-radius-full)] opacity-60\",\n TONE_CLASS[tone],\n )}\n />\n ) : null}\n <span\n data-slot=\"status-dot-fill\"\n className={cn(\n \"relative inline-block size-full rounded-[var(--ds-radius-full)]\",\n TONE_CLASS[tone],\n )}\n />\n </span>\n {label}\n </span>\n );\n}\n\nexport { StatusDot, type StatusDotTone };\n"],"mappings":";;;;;AAmBA,MAAM,aAA4C;CAChD,QAAQ;CACR,SAAS;CACT,SAAS;CACT,aAAa;CACb,QAAQ;CACR,MAAM;CACN,SAAS;AACX;;;;;AAeA,SAAS,UAAU,EACjB,WACA,OAAO,UACP,MACA,OACA,OAAO,GACP,OACA,GAAG,SACc;CACjB,OACE,qBAAC,QAAD;EACE,aAAU;EACV,aAAW;EACX,WAAW,GACT,oCACA,gFACA,SACF;EACA,GAAI;EARN,UAAA,CAUE,qBAAC,QAAD;GACE,eAAA;GACA,aAAU;GACV,WAAU;GACV,OAAO;IAAE,OAAO;IAAM,QAAQ;IAAM,GAAG;GAAM;GAJ/C,UAAA,CAMG,OACC,oBAAC,QAAD;IACE,aAAU;IACV,WAAW,GACT,4EACA,WAAW,KACb;GACD,CAAA,IACC,MACJ,oBAAC,QAAD;IACE,aAAU;IACV,WAAW,GACT,mEACA,WAAW,KACb;GACD,CAAA,CACG;EACL,CAAA,GAAA,KACG;;AAEV"}
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { Switch } from "@base-ui/react/switch";
4
+ //#region src/components/switch.d.ts
5
+ /**
6
+ * Switch.
7
+ *
8
+ * Base UI renders a `<span role="switch">` with a hidden `<input>` beside it,
9
+ * so the control submits with forms and takes `checked`/`defaultChecked`
10
+ * exactly like a checkbox. State is exposed as `data-checked` /
11
+ * `data-unchecked` / `data-disabled`, which is what the classes key on.
12
+ *
13
+ * cortenaweb users: `checked` and `onCheckedChange` keep their names, but the
14
+ * control is now uncontrolled by default; pass `defaultChecked` if you do not
15
+ * hold the state yourself.
16
+ */
17
+ export interface SwitchProps extends React.ComponentProps<typeof Switch.Root> {}
18
+ declare function Switch$1({ className, ...props }: SwitchProps): React.JSX.Element;
19
+ //#endregion
20
+ export { Switch$1 as Switch };
21
+ //# sourceMappingURL=switch.d.ts.map
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ "use client";
3
+ import { cn } from "../lib/cn.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ import { Switch } from "@base-ui/react/switch";
6
+ //#region src/components/switch.tsx
7
+ function Switch$1({ className, ...props }) {
8
+ return /* @__PURE__ */ jsx(Switch.Root, {
9
+ "data-slot": "switch",
10
+ className: cn("relative inline-flex h-5 w-9 shrink-0 items-center rounded-[var(--ds-radius-full)] border", "transition-[background-color,border-color,box-shadow] duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]", "outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40", "data-[unchecked]:border-[var(--ds-border)] data-[unchecked]:bg-[var(--ds-input)]", "data-[checked]:border-[var(--ds-primary)] data-[checked]:bg-[var(--ds-primary)]", "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className),
11
+ ...props,
12
+ children: /* @__PURE__ */ jsx(Switch.Thumb, {
13
+ "data-slot": "switch-thumb",
14
+ className: cn("pointer-events-none block size-3.5 rounded-[var(--ds-radius-full)] bg-[var(--ds-primary-foreground)]", "transition-transform duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]", "data-[unchecked]:translate-x-0.5 data-[checked]:translate-x-4.5")
15
+ })
16
+ });
17
+ }
18
+ //#endregion
19
+ export { Switch$1 as Switch };
20
+
21
+ //# sourceMappingURL=switch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"switch.js","names":["Switch","BaseSwitch"],"sources":["../../src/components/switch.tsx"],"sourcesContent":["\"use client\";\n\nimport { Switch as BaseSwitch } from \"@base-ui/react/switch\";\nimport type * as React from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Switch.\n *\n * Base UI renders a `<span role=\"switch\">` with a hidden `<input>` beside it,\n * so the control submits with forms and takes `checked`/`defaultChecked`\n * exactly like a checkbox. State is exposed as `data-checked` /\n * `data-unchecked` / `data-disabled`, which is what the classes key on.\n *\n * cortenaweb users: `checked` and `onCheckedChange` keep their names, but the\n * control is now uncontrolled by default; pass `defaultChecked` if you do not\n * hold the state yourself.\n */\nexport interface SwitchProps extends React.ComponentProps<typeof BaseSwitch.Root> {}\n\nfunction Switch({ className, ...props }: SwitchProps) {\n return (\n <BaseSwitch.Root\n data-slot=\"switch\"\n className={cn(\n \"relative inline-flex h-5 w-9 shrink-0 items-center rounded-[var(--ds-radius-full)] border\",\n \"transition-[background-color,border-color,box-shadow] duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]\",\n \"outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n \"data-[unchecked]:border-[var(--ds-border)] data-[unchecked]:bg-[var(--ds-input)]\",\n \"data-[checked]:border-[var(--ds-primary)] data-[checked]:bg-[var(--ds-primary)]\",\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n )}\n {...props}\n >\n <BaseSwitch.Thumb\n data-slot=\"switch-thumb\"\n className={cn(\n \"pointer-events-none block size-3.5 rounded-[var(--ds-radius-full)] bg-[var(--ds-primary-foreground)]\",\n \"transition-transform duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]\",\n \"data-[unchecked]:translate-x-0.5 data-[checked]:translate-x-4.5\",\n )}\n />\n </BaseSwitch.Root>\n );\n}\n\nexport { Switch };\n"],"mappings":";;;;;;AAoBA,SAASA,SAAO,EAAE,WAAW,GAAG,SAAsB;CACpD,OACE,oBAACC,OAAW,MAAZ;EACE,aAAU;EACV,WAAW,GACT,6FACA,sHACA,gFACA,oFACA,mFACA,kEACA,SACF;EACA,GAAI;EAEJ,UAAA,oBAACA,OAAW,OAAZ;GACE,aAAU;GACV,WAAW,GACT,wGACA,qFACA,iEACF;EACD,CAAA;CACc,CAAA;AAErB"}
@@ -0,0 +1,24 @@
1
+ "use client";
2
+ import { Tabs } from "@base-ui/react/tabs";
3
+ //#region src/components/tabs.d.ts
4
+ /**
5
+ * Tabs — the underlined pattern (primary-coloured underline under the active
6
+ * tab). For the inset segmented style use Segmented.
7
+ *
8
+ * The underline is a Base UI `Tabs.Indicator` rendered inside `TabsList`, so it
9
+ * slides between tabs instead of snapping. Base UI exposes the active tab's
10
+ * box as `--active-tab-left` / `--active-tab-width` on the indicator.
11
+ *
12
+ * Radix users: `data-state="active"` is now `data-active`; `TabsTrigger` and
13
+ * `TabsContent` are Base UI's `Tab` and `Panel` under the shadcn names. Arrow
14
+ * keys activate as they move (Radix's `activationMode="automatic"`); pass
15
+ * `activateOnFocus={false}` on `TabsList` for manual activation.
16
+ */
17
+ declare function Tabs$1({ className, ...props }: Tabs.Root.Props): import("react").JSX.Element;
18
+ declare function TabsList({ className, children, activateOnFocus, ...props }: Tabs.List.Props): import("react").JSX.Element;
19
+ declare function TabsIndicator({ className, ...props }: Tabs.Indicator.Props): import("react").JSX.Element;
20
+ declare function TabsTrigger({ className, ...props }: Tabs.Tab.Props): import("react").JSX.Element;
21
+ declare function TabsContent({ className, ...props }: Tabs.Panel.Props): import("react").JSX.Element;
22
+ //#endregion
23
+ export { Tabs$1 as Tabs, TabsContent, TabsIndicator, TabsList, TabsTrigger };
24
+ //# sourceMappingURL=tabs.d.ts.map
@@ -0,0 +1,60 @@
1
+ "use client";
2
+ "use client";
3
+ import { cn } from "../lib/cn.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { Tabs } from "@base-ui/react/tabs";
6
+ //#region src/components/tabs.tsx
7
+ /**
8
+ * Tabs — the underlined pattern (primary-coloured underline under the active
9
+ * tab). For the inset segmented style use Segmented.
10
+ *
11
+ * The underline is a Base UI `Tabs.Indicator` rendered inside `TabsList`, so it
12
+ * slides between tabs instead of snapping. Base UI exposes the active tab's
13
+ * box as `--active-tab-left` / `--active-tab-width` on the indicator.
14
+ *
15
+ * Radix users: `data-state="active"` is now `data-active`; `TabsTrigger` and
16
+ * `TabsContent` are Base UI's `Tab` and `Panel` under the shadcn names. Arrow
17
+ * keys activate as they move (Radix's `activationMode="automatic"`); pass
18
+ * `activateOnFocus={false}` on `TabsList` for manual activation.
19
+ */
20
+ function Tabs$1({ className, ...props }) {
21
+ return /* @__PURE__ */ jsx(Tabs.Root, {
22
+ "data-slot": "tabs",
23
+ className: cn("flex flex-col data-[orientation=vertical]:flex-row", className),
24
+ ...props
25
+ });
26
+ }
27
+ function TabsList({ className, children, activateOnFocus = true, ...props }) {
28
+ return /* @__PURE__ */ jsxs(Tabs.List, {
29
+ "data-slot": "tabs-list",
30
+ activateOnFocus,
31
+ className: cn("relative inline-flex items-center gap-0.5 self-start", "border-[var(--ds-border-subtle)] text-[color:var(--ds-muted-foreground)]", "data-[orientation=horizontal]:border-b", "data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch data-[orientation=vertical]:border-r", className),
32
+ ...props,
33
+ children: [children, /* @__PURE__ */ jsx(TabsIndicator, {})]
34
+ });
35
+ }
36
+ function TabsIndicator({ className, ...props }) {
37
+ return /* @__PURE__ */ jsx(Tabs.Indicator, {
38
+ "data-slot": "tabs-indicator",
39
+ className: cn("absolute rounded-[var(--ds-radius-full)] bg-[var(--ds-primary)]", "transition-[width,height,translate] duration-[var(--ds-duration-base)] ease-[var(--ds-ease-out)]", "data-[orientation=horizontal]:-bottom-px data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:h-0.5", "data-[orientation=horizontal]:w-[var(--active-tab-width,0px)] data-[orientation=horizontal]:translate-x-[var(--active-tab-left,0px)]", "data-[orientation=vertical]:-right-px data-[orientation=vertical]:top-0 data-[orientation=vertical]:w-0.5", "data-[orientation=vertical]:h-[var(--active-tab-height,0px)] data-[orientation=vertical]:translate-y-[var(--active-tab-top,0px)]", className),
40
+ ...props
41
+ });
42
+ }
43
+ function TabsTrigger({ className, ...props }) {
44
+ return /* @__PURE__ */ jsx(Tabs.Tab, {
45
+ "data-slot": "tabs-trigger",
46
+ className: cn("relative inline-flex items-center gap-1.5 whitespace-nowrap px-3.5 py-2", "text-[length:var(--ds-text-caption-lg)] font-medium text-[color:var(--ds-muted-foreground)]", "rounded-[var(--ds-radius-sm)] outline-none", "transition-colors duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]", "hover:text-[color:var(--ds-foreground)]", "focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40", "data-[active]:text-[color:var(--ds-primary)]", "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", className),
47
+ ...props
48
+ });
49
+ }
50
+ function TabsContent({ className, ...props }) {
51
+ return /* @__PURE__ */ jsx(Tabs.Panel, {
52
+ "data-slot": "tabs-content",
53
+ className: cn("mt-3 flex-1 rounded-[var(--ds-radius-sm)] outline-none", "focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40", className),
54
+ ...props
55
+ });
56
+ }
57
+ //#endregion
58
+ export { Tabs$1 as Tabs, TabsContent, TabsIndicator, TabsList, TabsTrigger };
59
+
60
+ //# sourceMappingURL=tabs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabs.js","names":["Tabs","TabsPrimitive"],"sources":["../../src/components/tabs.tsx"],"sourcesContent":["\"use client\";\n\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Tabs — the underlined pattern (primary-coloured underline under the active\n * tab). For the inset segmented style use Segmented.\n *\n * The underline is a Base UI `Tabs.Indicator` rendered inside `TabsList`, so it\n * slides between tabs instead of snapping. Base UI exposes the active tab's\n * box as `--active-tab-left` / `--active-tab-width` on the indicator.\n *\n * Radix users: `data-state=\"active\"` is now `data-active`; `TabsTrigger` and\n * `TabsContent` are Base UI's `Tab` and `Panel` under the shadcn names. Arrow\n * keys activate as they move (Radix's `activationMode=\"automatic\"`); pass\n * `activateOnFocus={false}` on `TabsList` for manual activation.\n */\nfunction Tabs({ className, ...props }: TabsPrimitive.Root.Props) {\n return (\n <TabsPrimitive.Root\n data-slot=\"tabs\"\n className={cn(\"flex flex-col data-[orientation=vertical]:flex-row\", className)}\n {...props}\n />\n );\n}\n\nfunction TabsList({\n className,\n children,\n activateOnFocus = true,\n ...props\n}: TabsPrimitive.List.Props) {\n return (\n <TabsPrimitive.List\n data-slot=\"tabs-list\"\n activateOnFocus={activateOnFocus}\n className={cn(\n \"relative inline-flex items-center gap-0.5 self-start\",\n \"border-[var(--ds-border-subtle)] text-[color:var(--ds-muted-foreground)]\",\n \"data-[orientation=horizontal]:border-b\",\n \"data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch data-[orientation=vertical]:border-r\",\n className,\n )}\n {...props}\n >\n {children}\n <TabsIndicator />\n </TabsPrimitive.List>\n );\n}\n\nfunction TabsIndicator({ className, ...props }: TabsPrimitive.Indicator.Props) {\n return (\n <TabsPrimitive.Indicator\n data-slot=\"tabs-indicator\"\n className={cn(\n \"absolute rounded-[var(--ds-radius-full)] bg-[var(--ds-primary)]\",\n \"transition-[width,height,translate] duration-[var(--ds-duration-base)] ease-[var(--ds-ease-out)]\",\n // Horizontal: an underline that slides along the bottom edge.\n \"data-[orientation=horizontal]:-bottom-px data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:h-0.5\",\n \"data-[orientation=horizontal]:w-[var(--active-tab-width,0px)] data-[orientation=horizontal]:translate-x-[var(--active-tab-left,0px)]\",\n // Vertical: a bar on the trailing edge that slides down the list.\n \"data-[orientation=vertical]:-right-px data-[orientation=vertical]:top-0 data-[orientation=vertical]:w-0.5\",\n \"data-[orientation=vertical]:h-[var(--active-tab-height,0px)] data-[orientation=vertical]:translate-y-[var(--active-tab-top,0px)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {\n return (\n <TabsPrimitive.Tab\n data-slot=\"tabs-trigger\"\n className={cn(\n \"relative inline-flex items-center gap-1.5 whitespace-nowrap px-3.5 py-2\",\n \"text-[length:var(--ds-text-caption-lg)] font-medium text-[color:var(--ds-muted-foreground)]\",\n \"rounded-[var(--ds-radius-sm)] outline-none\",\n \"transition-colors duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]\",\n \"hover:text-[color:var(--ds-foreground)]\",\n \"focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n \"data-[active]:text-[color:var(--ds-primary)]\",\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {\n return (\n <TabsPrimitive.Panel\n data-slot=\"tabs-content\"\n className={cn(\n \"mt-3 flex-1 rounded-[var(--ds-radius-sm)] outline-none\",\n \"focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, TabsIndicator };\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkBA,SAASA,OAAK,EAAE,WAAW,GAAG,SAAmC;CAC/D,OACE,oBAACC,KAAc,MAAf;EACE,aAAU;EACV,WAAW,GAAG,sDAAsD,SAAS;EAC7E,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,SAAS,EAChB,WACA,UACA,kBAAkB,MAClB,GAAG,SACwB;CAC3B,OACE,qBAACA,KAAc,MAAf;EACE,aAAU;EACO;EACjB,WAAW,GACT,wDACA,4EACA,0CACA,uHACA,SACF;EACA,GAAI;EAVN,UAAA,CAYG,UACD,oBAAC,eAAD,CAAgB,CAAA,CACE;;AAExB;AAEA,SAAS,cAAc,EAAE,WAAW,GAAG,SAAwC;CAC7E,OACE,oBAACA,KAAc,WAAf;EACE,aAAU;EACV,WAAW,GACT,mEACA,oGAEA,qHACA,wIAEA,6GACA,oIACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAAkC;CACrE,OACE,oBAACA,KAAc,KAAf;EACE,aAAU;EACV,WAAW,GACT,2EACA,+FACA,8CACA,kFACA,2CACA,mEACA,gDACA,kEACA,+DACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAAoC;CACvE,OACE,oBAACA,KAAc,OAAf;EACE,aAAU;EACV,WAAW,GACT,0DACA,mEACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ //#region src/components/textarea.d.ts
4
+ /**
5
+ * Textarea.
6
+ *
7
+ * Same surface, border, focus ring and invalid state as Input so the two sit
8
+ * together in a form. `autoGrow` resizes the element to its content on every
9
+ * change (and on mount), which is how chat composers and note fields use it.
10
+ */
11
+ declare const textareaClassName: string;
12
+ export interface TextareaProps extends React.ComponentProps<"textarea"> {
13
+ /** When true the textarea grows to fit its content instead of scrolling. */
14
+ autoGrow?: boolean;
15
+ }
16
+ declare function Textarea({ className, autoGrow, onChange, ref, ...props }: TextareaProps): React.JSX.Element;
17
+ //#endregion
18
+ export { Textarea, textareaClassName };
19
+ //# sourceMappingURL=textarea.d.ts.map
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ "use client";
3
+ import { cn } from "../lib/cn.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ import * as React from "react";
6
+ //#region src/components/textarea.tsx
7
+ /**
8
+ * Textarea.
9
+ *
10
+ * Same surface, border, focus ring and invalid state as Input so the two sit
11
+ * together in a form. `autoGrow` resizes the element to its content on every
12
+ * change (and on mount), which is how chat composers and note fields use it.
13
+ */
14
+ const textareaClassName = /* @__PURE__ */ cn("flex min-h-20 w-full min-w-0 rounded-[var(--ds-radius-md)] border border-[var(--ds-border)] bg-[var(--ds-muted)] px-3 py-2", "text-[length:var(--ds-text-caption-lg)] leading-[var(--ds-text-caption-lg--line-height)] text-[color:var(--ds-foreground)]", "placeholder:text-[color:var(--ds-text-tertiary)]", "transition-[border-color,box-shadow] duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]", "hover:border-[var(--ds-border-strong)]", "outline-none focus-visible:border-[var(--ds-ring)] focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40", "disabled:cursor-not-allowed disabled:opacity-50", "aria-invalid:border-[var(--ds-destructive)] aria-invalid:ring-[3px] aria-invalid:ring-[var(--ds-destructive)]/20");
15
+ function Textarea({ className, autoGrow = false, onChange, ref, ...props }) {
16
+ const internalRef = React.useRef(null);
17
+ const setRefs = React.useCallback((node) => {
18
+ internalRef.current = node;
19
+ if (typeof ref === "function") ref(node);
20
+ else if (ref) ref.current = node;
21
+ }, [ref]);
22
+ const fitToContent = React.useCallback(() => {
23
+ const el = internalRef.current;
24
+ if (el && autoGrow) {
25
+ el.style.height = "auto";
26
+ el.style.height = `${el.scrollHeight}px`;
27
+ }
28
+ }, [autoGrow]);
29
+ React.useEffect(() => {
30
+ fitToContent();
31
+ }, [fitToContent]);
32
+ const handleChange = React.useCallback((event) => {
33
+ onChange?.(event);
34
+ fitToContent();
35
+ }, [onChange, fitToContent]);
36
+ return /* @__PURE__ */ jsx("textarea", {
37
+ "data-slot": "textarea",
38
+ className: cn(textareaClassName, autoGrow && "resize-none overflow-hidden", className),
39
+ ref: setRefs,
40
+ onChange: handleChange,
41
+ ...props
42
+ });
43
+ }
44
+ //#endregion
45
+ export { Textarea, textareaClassName };
46
+
47
+ //# sourceMappingURL=textarea.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textarea.js","names":[],"sources":["../../src/components/textarea.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Textarea.\n *\n * Same surface, border, focus ring and invalid state as Input so the two sit\n * together in a form. `autoGrow` resizes the element to its content on every\n * change (and on mount), which is how chat composers and note fields use it.\n */\nconst textareaClassName = /* @__PURE__ */ cn(\n \"flex min-h-20 w-full min-w-0 rounded-[var(--ds-radius-md)] border border-[var(--ds-border)] bg-[var(--ds-muted)] px-3 py-2\",\n \"text-[length:var(--ds-text-caption-lg)] leading-[var(--ds-text-caption-lg--line-height)] text-[color:var(--ds-foreground)]\",\n \"placeholder:text-[color:var(--ds-text-tertiary)]\",\n \"transition-[border-color,box-shadow] duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]\",\n \"hover:border-[var(--ds-border-strong)]\",\n \"outline-none focus-visible:border-[var(--ds-ring)] focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"aria-invalid:border-[var(--ds-destructive)] aria-invalid:ring-[3px] aria-invalid:ring-[var(--ds-destructive)]/20\",\n);\n\nexport interface TextareaProps extends React.ComponentProps<\"textarea\"> {\n /** When true the textarea grows to fit its content instead of scrolling. */\n autoGrow?: boolean;\n}\n\nfunction Textarea({ className, autoGrow = false, onChange, ref, ...props }: TextareaProps) {\n const internalRef = React.useRef<HTMLTextAreaElement | null>(null);\n\n const setRefs = React.useCallback(\n (node: HTMLTextAreaElement | null) => {\n internalRef.current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref],\n );\n\n const fitToContent = React.useCallback(() => {\n const el = internalRef.current;\n if (el && autoGrow) {\n el.style.height = \"auto\";\n el.style.height = `${el.scrollHeight}px`;\n }\n }, [autoGrow]);\n\n React.useEffect(() => {\n fitToContent();\n }, [fitToContent]);\n\n const handleChange = React.useCallback(\n (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n onChange?.(event);\n fitToContent();\n },\n [onChange, fitToContent],\n );\n\n return (\n <textarea\n data-slot=\"textarea\"\n className={cn(textareaClassName, autoGrow && \"resize-none overflow-hidden\", className)}\n ref={setRefs}\n onChange={handleChange}\n {...props}\n />\n );\n}\n\nexport { Textarea, textareaClassName };\n"],"mappings":";;;;;;;;;;;;;AAYA,MAAM,oBAAoC,mBACxC,8HACA,8HACA,oDACA,qGACA,0CACA,sHACA,mDACA,kHACF;AAOA,SAAS,SAAS,EAAE,WAAW,WAAW,OAAO,UAAU,KAAK,GAAG,SAAwB;CACzF,MAAM,cAAc,MAAM,OAAmC,IAAI;CAEjE,MAAM,UAAU,MAAM,aACnB,SAAqC;EACpC,YAAY,UAAU;EACtB,IAAI,OAAO,QAAQ,YACjB,IAAI,IAAI;OACH,IAAI,KACT,IAAI,UAAU;CAElB,GACA,CAAC,GAAG,CACN;CAEA,MAAM,eAAe,MAAM,kBAAkB;EAC3C,MAAM,KAAK,YAAY;EACvB,IAAI,MAAM,UAAU;GAClB,GAAG,MAAM,SAAS;GAClB,GAAG,MAAM,SAAS,GAAG,GAAG,aAAa;EACvC;CACF,GAAG,CAAC,QAAQ,CAAC;CAEb,MAAM,gBAAgB;EACpB,aAAa;CACf,GAAG,CAAC,YAAY,CAAC;CAEjB,MAAM,eAAe,MAAM,aACxB,UAAkD;EACjD,WAAW,KAAK;EAChB,aAAa;CACf,GACA,CAAC,UAAU,YAAY,CACzB;CAEA,OACE,oBAAC,YAAD;EACE,aAAU;EACV,WAAW,GAAG,mBAAmB,YAAY,+BAA+B,SAAS;EACrF,KAAK;EACL,UAAU;EACV,GAAI;CACL,CAAA;AAEL"}