@blurrow/ui 0.2.0 → 0.2.1

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 (271) hide show
  1. package/README.md +48 -36
  2. package/dist/Accordion.d.ts +17 -0
  3. package/dist/Accordion.js +138 -0
  4. package/dist/Accordion.js.map +1 -0
  5. package/dist/Alert.d.ts +13 -0
  6. package/dist/Alert.js +69 -0
  7. package/dist/Alert.js.map +1 -0
  8. package/dist/AuthorCard.d.ts +12 -0
  9. package/dist/AuthorCard.js +57 -0
  10. package/dist/AuthorCard.js.map +1 -0
  11. package/dist/Avatar.d.ts +21 -0
  12. package/dist/Avatar.js +96 -0
  13. package/dist/Avatar.js.map +1 -0
  14. package/dist/Badge.d.ts +12 -0
  15. package/dist/Badge.js +46 -0
  16. package/dist/Badge.js.map +1 -0
  17. package/dist/Banner.d.ts +15 -0
  18. package/dist/Banner.js +68 -0
  19. package/dist/Banner.js.map +1 -0
  20. package/dist/Breadcrumb.d.ts +13 -0
  21. package/dist/Breadcrumb.js +45 -0
  22. package/dist/Breadcrumb.js.map +1 -0
  23. package/dist/Button.d.ts +19 -0
  24. package/dist/Button.js +282 -0
  25. package/dist/Button.js.map +1 -0
  26. package/dist/ButtonGroup.d.ts +19 -0
  27. package/dist/ButtonGroup.js +62 -0
  28. package/dist/ButtonGroup.js.map +1 -0
  29. package/dist/Card.d.ts +40 -0
  30. package/dist/Card.js +175 -0
  31. package/dist/Card.js.map +1 -0
  32. package/dist/CardShell.d.ts +18 -0
  33. package/dist/CardShell.js +58 -0
  34. package/dist/CardShell.js.map +1 -0
  35. package/dist/Carousel.d.ts +22 -0
  36. package/dist/Carousel.js +247 -0
  37. package/dist/Carousel.js.map +1 -0
  38. package/dist/Checkbox.d.ts +15 -0
  39. package/dist/Checkbox.js +84 -0
  40. package/dist/Checkbox.js.map +1 -0
  41. package/dist/CloseButton.d.ts +12 -0
  42. package/dist/CloseButton.js +63 -0
  43. package/dist/CloseButton.js.map +1 -0
  44. package/dist/CodeBlock.d.ts +12 -0
  45. package/dist/CodeBlock.js +221 -0
  46. package/dist/CodeBlock.js.map +1 -0
  47. package/dist/ColorPicker.d.ts +24 -0
  48. package/dist/ColorPicker.js +366 -0
  49. package/dist/ColorPicker.js.map +1 -0
  50. package/dist/Combobox.d.ts +34 -0
  51. package/dist/Combobox.js +255 -0
  52. package/dist/Combobox.js.map +1 -0
  53. package/dist/CommandPalette.d.ts +25 -0
  54. package/dist/CommandPalette.js +304 -0
  55. package/dist/CommandPalette.js.map +1 -0
  56. package/dist/ContextMenu.d.ts +16 -0
  57. package/dist/ContextMenu.js +209 -0
  58. package/dist/ContextMenu.js.map +1 -0
  59. package/dist/DataList.d.ts +15 -0
  60. package/dist/DataList.js +42 -0
  61. package/dist/DataList.js.map +1 -0
  62. package/dist/DatePicker.d.ts +65 -0
  63. package/dist/DatePicker.js +391 -0
  64. package/dist/DatePicker.js.map +1 -0
  65. package/dist/DateRangePicker.d.ts +34 -0
  66. package/dist/DateRangePicker.js +180 -0
  67. package/dist/DateRangePicker.js.map +1 -0
  68. package/dist/Drawer.d.ts +17 -0
  69. package/dist/Drawer.js +132 -0
  70. package/dist/Drawer.js.map +1 -0
  71. package/dist/DropdownMenu.d.ts +23 -0
  72. package/dist/DropdownMenu.js +185 -0
  73. package/dist/DropdownMenu.js.map +1 -0
  74. package/dist/EmptyState.d.ts +13 -0
  75. package/dist/EmptyState.js +55 -0
  76. package/dist/EmptyState.js.map +1 -0
  77. package/dist/FieldMessage.d.ts +26 -0
  78. package/dist/FieldMessage.js +95 -0
  79. package/dist/FieldMessage.js.map +1 -0
  80. package/dist/FileUpload.d.ts +32 -0
  81. package/dist/FileUpload.js +253 -0
  82. package/dist/FileUpload.js.map +1 -0
  83. package/dist/Floating.d.ts +32 -0
  84. package/dist/Floating.js +95 -0
  85. package/dist/Floating.js.map +1 -0
  86. package/dist/HoverCard.d.ts +18 -0
  87. package/dist/HoverCard.js +90 -0
  88. package/dist/HoverCard.js.map +1 -0
  89. package/dist/Input.d.ts +17 -0
  90. package/dist/Input.js +96 -0
  91. package/dist/Input.js.map +1 -0
  92. package/dist/Kbd.d.ts +9 -0
  93. package/dist/Kbd.js +24 -0
  94. package/dist/Kbd.js.map +1 -0
  95. package/dist/Modal.d.ts +15 -0
  96. package/dist/Modal.js +109 -0
  97. package/dist/Modal.js.map +1 -0
  98. package/dist/NumberInput.d.ts +23 -0
  99. package/dist/NumberInput.js +162 -0
  100. package/dist/NumberInput.js.map +1 -0
  101. package/dist/OtpInput.d.ts +26 -0
  102. package/dist/OtpInput.js +173 -0
  103. package/dist/OtpInput.js.map +1 -0
  104. package/dist/Pagination.d.ts +11 -0
  105. package/dist/Pagination.js +148 -0
  106. package/dist/Pagination.js.map +1 -0
  107. package/dist/Popover.d.ts +13 -0
  108. package/dist/Popover.js +98 -0
  109. package/dist/Popover.js.map +1 -0
  110. package/dist/Progress.d.ts +12 -0
  111. package/dist/Progress.js +61 -0
  112. package/dist/Progress.js.map +1 -0
  113. package/dist/QuoteCard.d.ts +13 -0
  114. package/dist/QuoteCard.js +92 -0
  115. package/dist/QuoteCard.js.map +1 -0
  116. package/dist/Radio.d.ts +19 -0
  117. package/dist/Radio.js +161 -0
  118. package/dist/Radio.js.map +1 -0
  119. package/dist/RadioCards.d.ts +27 -0
  120. package/dist/RadioCards.js +205 -0
  121. package/dist/RadioCards.js.map +1 -0
  122. package/dist/ReactionBar.d.ts +15 -0
  123. package/dist/ReactionBar.js +92 -0
  124. package/dist/ReactionBar.js.map +1 -0
  125. package/dist/ReadingProgress.d.ts +8 -0
  126. package/dist/ReadingProgress.js +51 -0
  127. package/dist/ReadingProgress.js.map +1 -0
  128. package/dist/ReadingTime.d.ts +9 -0
  129. package/dist/ReadingTime.js +39 -0
  130. package/dist/ReadingTime.js.map +1 -0
  131. package/dist/Select.d.ts +20 -0
  132. package/dist/Select.js +244 -0
  133. package/dist/Select.js.map +1 -0
  134. package/dist/Separator.d.ts +14 -0
  135. package/dist/Separator.js +33 -0
  136. package/dist/Separator.js.map +1 -0
  137. package/dist/ShareButtons.d.ts +10 -0
  138. package/dist/ShareButtons.js +107 -0
  139. package/dist/ShareButtons.js.map +1 -0
  140. package/dist/Sidebar.d.ts +30 -0
  141. package/dist/Sidebar.js +140 -0
  142. package/dist/Sidebar.js.map +1 -0
  143. package/dist/Skeleton.d.ts +15 -0
  144. package/dist/Skeleton.js +67 -0
  145. package/dist/Skeleton.js.map +1 -0
  146. package/dist/Slider.d.ts +27 -0
  147. package/dist/Slider.js +182 -0
  148. package/dist/Slider.js.map +1 -0
  149. package/dist/Spinner.d.ts +12 -0
  150. package/dist/Spinner.js +24 -0
  151. package/dist/Spinner.js.map +1 -0
  152. package/dist/SpotlightCard.d.ts +17 -0
  153. package/dist/SpotlightCard.js +87 -0
  154. package/dist/SpotlightCard.js.map +1 -0
  155. package/dist/Stat.d.ts +19 -0
  156. package/dist/Stat.js +71 -0
  157. package/dist/Stat.js.map +1 -0
  158. package/dist/StatCard.d.ts +24 -0
  159. package/dist/StatCard.js +212 -0
  160. package/dist/StatCard.js.map +1 -0
  161. package/dist/Steps.d.ts +14 -0
  162. package/dist/Steps.js +94 -0
  163. package/dist/Steps.js.map +1 -0
  164. package/dist/Table.d.ts +21 -0
  165. package/dist/Table.js +93 -0
  166. package/dist/Table.js.map +1 -0
  167. package/dist/TableOfContents.d.ts +17 -0
  168. package/dist/TableOfContents.js +92 -0
  169. package/dist/TableOfContents.js.map +1 -0
  170. package/dist/Tabs.d.ts +17 -0
  171. package/dist/Tabs.js +123 -0
  172. package/dist/Tabs.js.map +1 -0
  173. package/dist/TagInput.d.ts +16 -0
  174. package/dist/TagInput.js +169 -0
  175. package/dist/TagInput.js.map +1 -0
  176. package/dist/Textarea.d.ts +13 -0
  177. package/dist/Textarea.js +74 -0
  178. package/dist/Textarea.js.map +1 -0
  179. package/dist/TimePicker.d.ts +28 -0
  180. package/dist/TimePicker.js +264 -0
  181. package/dist/TimePicker.js.map +1 -0
  182. package/dist/Timeline.d.ts +19 -0
  183. package/dist/Timeline.js +76 -0
  184. package/dist/Timeline.js.map +1 -0
  185. package/dist/Toast.d.ts +18 -0
  186. package/dist/Toast.js +318 -0
  187. package/dist/Toast.js.map +1 -0
  188. package/dist/Toggle.d.ts +13 -0
  189. package/dist/Toggle.js +84 -0
  190. package/dist/Toggle.js.map +1 -0
  191. package/dist/Tooltip.d.ts +13 -0
  192. package/dist/Tooltip.js +138 -0
  193. package/dist/Tooltip.js.map +1 -0
  194. package/dist/cli.js +172 -5
  195. package/dist/index.d.ts +68 -631
  196. package/dist/index.js +67 -5558
  197. package/dist/index.js.map +1 -1
  198. package/dist/registry/Accordion.tsx +6 -6
  199. package/dist/registry/Alert.tsx +13 -10
  200. package/dist/registry/AuthorCard.tsx +7 -7
  201. package/dist/registry/Avatar.tsx +5 -5
  202. package/dist/registry/Badge.tsx +12 -15
  203. package/dist/registry/Banner.tsx +15 -18
  204. package/dist/registry/Breadcrumb.tsx +3 -3
  205. package/dist/registry/Button.tsx +58 -69
  206. package/dist/registry/ButtonGroup.tsx +9 -9
  207. package/dist/registry/Card.tsx +15 -15
  208. package/dist/registry/CardShell.tsx +6 -6
  209. package/dist/registry/Carousel.tsx +19 -15
  210. package/dist/registry/Checkbox.tsx +14 -9
  211. package/dist/registry/CloseButton.tsx +2 -2
  212. package/dist/registry/CodeBlock.tsx +15 -15
  213. package/dist/registry/ColorPicker.tsx +404 -0
  214. package/dist/registry/Combobox.tsx +318 -0
  215. package/dist/registry/CommandPalette.tsx +31 -24
  216. package/dist/registry/ContextMenu.tsx +241 -0
  217. package/dist/registry/DataList.tsx +4 -4
  218. package/dist/registry/DatePicker.tsx +494 -0
  219. package/dist/registry/DateRangePicker.tsx +232 -0
  220. package/dist/registry/Drawer.tsx +17 -15
  221. package/dist/registry/DropdownMenu.tsx +39 -77
  222. package/dist/registry/EmptyState.tsx +8 -8
  223. package/dist/registry/FieldMessage.tsx +123 -0
  224. package/dist/registry/FileUpload.tsx +318 -0
  225. package/dist/registry/Floating.tsx +134 -0
  226. package/dist/registry/HoverCard.tsx +113 -0
  227. package/dist/registry/Input.tsx +54 -43
  228. package/dist/registry/Kbd.tsx +4 -5
  229. package/dist/registry/Modal.tsx +17 -15
  230. package/dist/registry/NumberInput.tsx +183 -0
  231. package/dist/registry/OtpInput.tsx +219 -0
  232. package/dist/registry/Pagination.tsx +15 -18
  233. package/dist/registry/Popover.tsx +40 -45
  234. package/dist/registry/Progress.tsx +4 -4
  235. package/dist/registry/QuoteCard.tsx +7 -7
  236. package/dist/registry/Radio.tsx +24 -21
  237. package/dist/registry/RadioCards.tsx +247 -0
  238. package/dist/registry/ReactionBar.tsx +5 -5
  239. package/dist/registry/ReadingProgress.tsx +2 -2
  240. package/dist/registry/ReadingTime.tsx +2 -2
  241. package/dist/registry/Select.tsx +28 -53
  242. package/dist/registry/Separator.tsx +52 -0
  243. package/dist/registry/ShareButtons.tsx +2 -2
  244. package/dist/registry/Sidebar.tsx +18 -18
  245. package/dist/registry/Skeleton.tsx +1 -1
  246. package/dist/registry/Slider.tsx +229 -0
  247. package/dist/registry/Spinner.tsx +32 -0
  248. package/dist/registry/SpotlightCard.tsx +6 -6
  249. package/dist/registry/Stat.tsx +4 -4
  250. package/dist/registry/StatCard.tsx +19 -19
  251. package/dist/registry/Steps.tsx +7 -7
  252. package/dist/registry/Table.tsx +12 -10
  253. package/dist/registry/TableOfContents.tsx +8 -8
  254. package/dist/registry/Tabs.tsx +6 -6
  255. package/dist/registry/TagInput.tsx +9 -9
  256. package/dist/registry/Textarea.tsx +5 -5
  257. package/dist/registry/TimePicker.tsx +315 -0
  258. package/dist/registry/Timeline.tsx +14 -14
  259. package/dist/registry/Toast.tsx +15 -12
  260. package/dist/registry/Toggle.tsx +7 -10
  261. package/dist/registry/Tooltip.tsx +21 -73
  262. package/dist/registry/styles.css +868 -62
  263. package/dist/registry/useFloatingPanel.ts +112 -0
  264. package/dist/styles.css +868 -62
  265. package/dist/useFloatingPanel.d.ts +24 -0
  266. package/dist/useFloatingPanel.js +89 -0
  267. package/dist/useFloatingPanel.js.map +1 -0
  268. package/dist/useModalBehavior.d.ts +15 -0
  269. package/dist/useModalBehavior.js +57 -0
  270. package/dist/useModalBehavior.js.map +1 -0
  271. package/package.json +1 -1
@@ -0,0 +1,107 @@
1
+ "use client";
2
+
3
+ // ../../components/ui/ShareButtons.tsx
4
+ import { useState, useCallback } from "react";
5
+ import { ButtonGroup } from "./ButtonGroup.js";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ function LinkIcon() {
8
+ return /* @__PURE__ */ jsxs(
9
+ "svg",
10
+ {
11
+ width: "14",
12
+ height: "14",
13
+ viewBox: "0 0 24 24",
14
+ fill: "none",
15
+ stroke: "currentColor",
16
+ strokeWidth: "1.75",
17
+ strokeLinecap: "round",
18
+ strokeLinejoin: "round",
19
+ children: [
20
+ /* @__PURE__ */ jsx("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
21
+ /* @__PURE__ */ jsx("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
22
+ ]
23
+ }
24
+ );
25
+ }
26
+ function CheckIcon() {
27
+ return /* @__PURE__ */ jsx(
28
+ "svg",
29
+ {
30
+ width: "14",
31
+ height: "14",
32
+ viewBox: "0 0 24 24",
33
+ fill: "none",
34
+ stroke: "currentColor",
35
+ strokeWidth: "2",
36
+ strokeLinecap: "round",
37
+ strokeLinejoin: "round",
38
+ children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
39
+ }
40
+ );
41
+ }
42
+ function XIcon() {
43
+ return /* @__PURE__ */ jsx("svg", { width: "13", height: "13", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" }) });
44
+ }
45
+ function LinkedInIcon() {
46
+ return /* @__PURE__ */ jsx("svg", { width: "13", height: "13", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.063 2.063 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" }) });
47
+ }
48
+ function ShareButtons({
49
+ url,
50
+ title,
51
+ className = ""
52
+ }) {
53
+ const [copied, setCopied] = useState(false);
54
+ const shareUrl = url ?? (typeof window !== "undefined" ? window.location.href : "");
55
+ const shareTitle = title ?? "";
56
+ const handleCopy = useCallback(async () => {
57
+ try {
58
+ await navigator.clipboard.writeText(shareUrl);
59
+ setCopied(true);
60
+ setTimeout(() => setCopied(false), 2e3);
61
+ } catch {
62
+ }
63
+ }, [shareUrl]);
64
+ const handleTwitter = useCallback(() => {
65
+ const params = new URLSearchParams({
66
+ text: shareTitle,
67
+ url: shareUrl
68
+ });
69
+ window.open(
70
+ `https://x.com/intent/tweet?${params.toString()}`,
71
+ "_blank",
72
+ "noopener,noreferrer"
73
+ );
74
+ }, [shareUrl, shareTitle]);
75
+ const handleLinkedIn = useCallback(() => {
76
+ const params = new URLSearchParams({
77
+ url: shareUrl
78
+ });
79
+ window.open(
80
+ `https://www.linkedin.com/sharing/share-offsite/?${params.toString()}`,
81
+ "_blank",
82
+ "noopener,noreferrer"
83
+ );
84
+ }, [shareUrl]);
85
+ const keys = [
86
+ {
87
+ label: copied ? "Link copied" : "Copy link",
88
+ onClick: handleCopy,
89
+ content: /* @__PURE__ */ jsxs(Fragment, { children: [
90
+ /* @__PURE__ */ jsx("span", { className: ["animate-checkbox-in", copied ? "text-success-soft" : ""].join(" "), children: copied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(LinkIcon, {}) }, String(copied)),
91
+ /* @__PURE__ */ jsx("span", { className: "w-[62px] text-left", children: copied ? "copied" : "copy link" })
92
+ ] }),
93
+ wide: true
94
+ },
95
+ { label: "Share on X", onClick: handleTwitter, content: /* @__PURE__ */ jsx(XIcon, {}), wide: false },
96
+ { label: "Share on LinkedIn", onClick: handleLinkedIn, content: /* @__PURE__ */ jsx(LinkedInIcon, {}), wide: false }
97
+ ];
98
+ return /* @__PURE__ */ jsxs("div", { className, children: [
99
+ /* @__PURE__ */ jsx("p", { className: "text-[12px] font-medium text-ink-muted lowercase mb-3", children: "Share" }),
100
+ /* @__PURE__ */ jsx(ButtonGroup, { label: "Share", items: keys }),
101
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", children: copied ? "Link copied to clipboard" : "" })
102
+ ] });
103
+ }
104
+ export {
105
+ ShareButtons
106
+ };
107
+ //# sourceMappingURL=ShareButtons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/ShareButtons.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useState, useCallback } from \"react\";\nimport { ButtonGroup } from \"./ButtonGroup\";\n\ninterface ShareButtonsProps {\n url?: string;\n title?: string;\n className?: string;\n}\n\n\nfunction LinkIcon() {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\" />\n <path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\" />\n </svg>\n );\n}\n\nfunction CheckIcon() {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n );\n}\n\nfunction XIcon() {\n return (\n <svg width=\"13\" height=\"13\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z\" />\n </svg>\n );\n}\n\nfunction LinkedInIcon() {\n return (\n <svg width=\"13\" height=\"13\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.063 2.063 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z\" />\n </svg>\n );\n}\n\nexport function ShareButtons({\n url,\n title,\n className = \"\",\n}: ShareButtonsProps) {\n const [copied, setCopied] = useState(false);\n\n const shareUrl = url ?? (typeof window !== \"undefined\" ? window.location.href : \"\");\n const shareTitle = title ?? \"\";\n\n const handleCopy = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(shareUrl);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch {\n // Fallback silently\n }\n }, [shareUrl]);\n\n const handleTwitter = useCallback(() => {\n const params = new URLSearchParams({\n text: shareTitle,\n url: shareUrl,\n });\n window.open(\n `https://x.com/intent/tweet?${params.toString()}`,\n \"_blank\",\n \"noopener,noreferrer\"\n );\n }, [shareUrl, shareTitle]);\n\n const handleLinkedIn = useCallback(() => {\n const params = new URLSearchParams({\n url: shareUrl,\n });\n window.open(\n `https://www.linkedin.com/sharing/share-offsite/?${params.toString()}`,\n \"_blank\",\n \"noopener,noreferrer\"\n );\n }, [shareUrl]);\n\n const keys = [\n {\n label: copied ? \"Link copied\" : \"Copy link\",\n onClick: handleCopy as () => void,\n content: (\n <>\n <span key={String(copied)} className={[\"animate-checkbox-in\", copied ? \"text-success-soft\" : \"\"].join(\" \")}>\n {copied ? <CheckIcon /> : <LinkIcon />}\n </span>\n <span className=\"w-[62px] text-left\">{copied ? \"copied\" : \"copy link\"}</span>\n </>\n ),\n wide: true,\n },\n { label: \"Share on X\", onClick: handleTwitter, content: <XIcon />, wide: false },\n { label: \"Share on LinkedIn\", onClick: handleLinkedIn, content: <LinkedInIcon />, wide: false },\n ];\n\n return (\n <div className={className}>\n <p className=\"text-[12px] font-medium text-ink-muted lowercase mb-3\">\n Share\n </p>\n <ButtonGroup label=\"Share\" items={keys} />\n <span aria-live=\"polite\" className=\"sr-only\">{copied ? \"Link copied to clipboard\" : \"\"}</span>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,30 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+
4
+ interface SidebarItem {
5
+ key: string;
6
+ label: string;
7
+ icon?: React.ReactNode;
8
+ badge?: string | number;
9
+ disabled?: boolean;
10
+ }
11
+ interface SidebarSection {
12
+ title?: string;
13
+ items: SidebarItem[];
14
+ }
15
+ interface SidebarHeader {
16
+ logo?: React.ReactNode;
17
+ title: string;
18
+ subtitle?: string;
19
+ }
20
+ interface SidebarProps {
21
+ sections: SidebarSection[];
22
+ header?: SidebarHeader;
23
+ defaultActive?: string;
24
+ active?: string;
25
+ onSelect?: (key: string) => void;
26
+ className?: string;
27
+ }
28
+ declare function Sidebar({ sections, header, defaultActive, active, onSelect, className, }: SidebarProps): react_jsx_runtime.JSX.Element;
29
+
30
+ export { Sidebar };
@@ -0,0 +1,140 @@
1
+ "use client";
2
+
3
+ // ../../components/ui/Sidebar.tsx
4
+ import { useState, useRef, useEffect, useLayoutEffect } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ function Sidebar({
7
+ sections,
8
+ header,
9
+ defaultActive = "",
10
+ active,
11
+ onSelect,
12
+ className = ""
13
+ }) {
14
+ const [internal, setInternal] = useState(defaultActive);
15
+ const current = active !== void 0 ? active : internal;
16
+ const [pill, setPill] = useState({ top: 0, height: 0, ready: false });
17
+ const navRef = useRef(null);
18
+ const btnRefs = useRef({});
19
+ const measurePill = (key) => {
20
+ const btn = btnRefs.current[key];
21
+ const nav = navRef.current;
22
+ if (!btn || !nav) return;
23
+ const nr = nav.getBoundingClientRect();
24
+ const br = btn.getBoundingClientRect();
25
+ setPill({ top: br.top - nr.top, height: br.height, ready: true });
26
+ };
27
+ useLayoutEffect(() => {
28
+ measurePill(current);
29
+ }, [current]);
30
+ useEffect(() => {
31
+ const ro = new ResizeObserver(() => measurePill(current));
32
+ if (navRef.current) ro.observe(navRef.current);
33
+ return () => ro.disconnect();
34
+ }, [current]);
35
+ const handleSelect = (key, disabled) => {
36
+ if (disabled) return;
37
+ setInternal(key);
38
+ onSelect?.(key);
39
+ };
40
+ return /* @__PURE__ */ jsx(
41
+ "div",
42
+ {
43
+ className: ["rounded-[20px] p-1.5", className].join(" "),
44
+ style: { background: "var(--panel-bezel)", boxShadow: "var(--panel-shadow)" },
45
+ children: /* @__PURE__ */ jsxs("div", { className: "bg-panel-face rounded-[15px] overflow-hidden", style: { boxShadow: "var(--panel-face-shadow)" }, children: [
46
+ header && /* @__PURE__ */ jsxs("div", { className: "px-3 py-3 border-b border-line flex items-center gap-2.5", children: [
47
+ header.logo && /* @__PURE__ */ jsx(
48
+ "div",
49
+ {
50
+ className: "w-7 h-7 rounded-[8px] flex items-center justify-center shrink-0 text-ink-inverse",
51
+ style: { background: "var(--face-inverse)", boxShadow: "var(--face-inverse-shadow)" },
52
+ children: header.logo
53
+ }
54
+ ),
55
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
56
+ /* @__PURE__ */ jsx("p", { className: "text-[13px] font-semibold text-ink leading-none truncate", children: header.title }),
57
+ header.subtitle && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-ink-faint mt-0.5 leading-none truncate", children: header.subtitle })
58
+ ] }),
59
+ /* @__PURE__ */ jsx(
60
+ "button",
61
+ {
62
+ type: "button",
63
+ "aria-label": "Switch workspace",
64
+ className: "shrink-0 w-6 h-6 flex items-center justify-center text-ink-faint hover:text-ink transition-colors cursor-pointer rounded-[7px] hover:bg-surface-active",
65
+ children: /* @__PURE__ */ jsx("svg", { width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8 9l4-4 4 4M8 15l4 4 4-4" }) })
66
+ }
67
+ )
68
+ ] }),
69
+ /* @__PURE__ */ jsxs("nav", { ref: navRef, "aria-label": header?.title ?? "Sidebar", className: "relative p-2 flex flex-col gap-4", children: [
70
+ pill.ready && /* @__PURE__ */ jsx(
71
+ "span",
72
+ {
73
+ "aria-hidden": true,
74
+ className: "absolute left-2 right-2 rounded-[11px] pointer-events-none z-0",
75
+ style: {
76
+ top: pill.top,
77
+ height: pill.height,
78
+ background: "var(--face-inverse)",
79
+ boxShadow: "var(--face-inverse-shadow)",
80
+ transition: "top 300ms var(--ease-spring), height 300ms var(--ease-spring)"
81
+ }
82
+ }
83
+ ),
84
+ sections.map((section, si) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
85
+ section.title && /* @__PURE__ */ jsx("p", { className: "relative z-10 px-2.5 pt-1 pb-1.5 text-[11.5px] font-medium text-ink-faint lowercase", children: section.title }),
86
+ section.items.map((item) => {
87
+ const isActive = current === item.key;
88
+ return /* @__PURE__ */ jsxs(
89
+ "button",
90
+ {
91
+ ref: (el) => {
92
+ btnRefs.current[item.key] = el;
93
+ },
94
+ type: "button",
95
+ onClick: () => handleSelect(item.key, item.disabled),
96
+ disabled: item.disabled,
97
+ "aria-current": isActive ? "page" : void 0,
98
+ className: [
99
+ "relative z-10 w-full flex items-center gap-2.5 px-2.5 py-2 rounded-[11px] text-[13px] font-medium text-left",
100
+ "transition-colors duration-200 cursor-pointer",
101
+ "disabled:opacity-35 disabled:pointer-events-none",
102
+ isActive ? "text-ink-inverse" : "text-ink-body hover:text-ink hover:bg-surface-active/80"
103
+ ].join(" "),
104
+ children: [
105
+ item.icon && /* @__PURE__ */ jsx(
106
+ "span",
107
+ {
108
+ className: [
109
+ "shrink-0 w-[22px] h-[22px] rounded-md flex items-center justify-center transition-colors duration-200",
110
+ isActive ? "bg-ink-inverse/12 shadow-[inset_0_1px_0_var(--inverse-lip)]" : "text-ink-muted [background:var(--face)] [box-shadow:var(--face-shadow)]"
111
+ ].join(" "),
112
+ children: item.icon
113
+ }
114
+ ),
115
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: item.label }),
116
+ item.badge !== void 0 && /* @__PURE__ */ jsx(
117
+ "span",
118
+ {
119
+ className: [
120
+ "shrink-0 text-[11px] font-medium px-1.5 py-0.5 rounded-md tabular-nums leading-none transition-colors duration-200",
121
+ isActive ? "bg-ink-inverse/20 text-ink-inverse/80" : "bg-surface-active text-ink-muted"
122
+ ].join(" "),
123
+ children: item.badge
124
+ }
125
+ )
126
+ ]
127
+ },
128
+ item.key
129
+ );
130
+ })
131
+ ] }, si))
132
+ ] })
133
+ ] })
134
+ }
135
+ );
136
+ }
137
+ export {
138
+ Sidebar
139
+ };
140
+ //# sourceMappingURL=Sidebar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/Sidebar.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useState, useRef, useEffect, useLayoutEffect } from \"react\";\n\ninterface SidebarItem {\n key: string;\n label: string;\n icon?: React.ReactNode;\n badge?: string | number;\n disabled?: boolean;\n}\n\ninterface SidebarSection {\n title?: string;\n items: SidebarItem[];\n}\n\ninterface SidebarHeader {\n logo?: React.ReactNode;\n title: string;\n subtitle?: string;\n}\n\ninterface SidebarProps {\n sections: SidebarSection[];\n header?: SidebarHeader;\n defaultActive?: string;\n active?: string;\n onSelect?: (key: string) => void;\n className?: string;\n}\n\nexport function Sidebar({\n sections,\n header,\n defaultActive = \"\",\n active,\n onSelect,\n className = \"\",\n}: SidebarProps) {\n const [internal, setInternal] = useState(defaultActive);\n const current = active !== undefined ? active : internal;\n const [pill, setPill] = useState({ top: 0, height: 0, ready: false });\n const navRef = useRef<HTMLElement>(null);\n const btnRefs = useRef<Record<string, HTMLButtonElement | null>>({});\n\n const measurePill = (key: string) => {\n const btn = btnRefs.current[key];\n const nav = navRef.current;\n if (!btn || !nav) return;\n const nr = nav.getBoundingClientRect();\n const br = btn.getBoundingClientRect();\n setPill({ top: br.top - nr.top, height: br.height, ready: true });\n };\n\n // Before paint, so the pill is never seen out of place\n useLayoutEffect(() => { measurePill(current); }, [current]);\n\n useEffect(() => {\n const ro = new ResizeObserver(() => measurePill(current));\n if (navRef.current) ro.observe(navRef.current);\n return () => ro.disconnect();\n }, [current]);\n\n const handleSelect = (key: string, disabled?: boolean) => {\n if (disabled) return;\n setInternal(key);\n onSelect?.(key);\n };\n\n return (\n <div\n className={[\"rounded-[20px] p-1.5\", className].join(\" \")}\n style={{ background: \"var(--panel-bezel)\", boxShadow: \"var(--panel-shadow)\" }}\n >\n <div className=\"bg-panel-face rounded-[15px] overflow-hidden\" style={{ boxShadow: \"var(--panel-face-shadow)\" }}>\n {header && (\n <div className=\"px-3 py-3 border-b border-line flex items-center gap-2.5\">\n {header.logo && (\n <div\n className=\"w-7 h-7 rounded-[8px] flex items-center justify-center shrink-0 text-ink-inverse\"\n style={{ background: \"var(--face-inverse)\", boxShadow: \"var(--face-inverse-shadow)\" }}\n >\n {header.logo}\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-[13px] font-semibold text-ink leading-none truncate\">{header.title}</p>\n {header.subtitle && (\n <p className=\"text-[11px] text-ink-faint mt-0.5 leading-none truncate\">{header.subtitle}</p>\n )}\n </div>\n <button\n type=\"button\"\n aria-label=\"Switch workspace\"\n className=\"shrink-0 w-6 h-6 flex items-center justify-center text-ink-faint hover:text-ink transition-colors cursor-pointer rounded-[7px] hover:bg-surface-active\"\n >\n <svg width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M8 9l4-4 4 4M8 15l4 4 4-4\" />\n </svg>\n </button>\n </div>\n )}\n\n <nav ref={navRef} aria-label={header?.title ?? \"Sidebar\"} className=\"relative p-2 flex flex-col gap-4\">\n {pill.ready && (\n <span\n aria-hidden\n className=\"absolute left-2 right-2 rounded-[11px] pointer-events-none z-0\"\n style={{\n top: pill.top,\n height: pill.height,\n background: \"var(--face-inverse)\",\n boxShadow: \"var(--face-inverse-shadow)\",\n transition: \"top 300ms var(--ease-spring), height 300ms var(--ease-spring)\",\n }}\n />\n )}\n\n {sections.map((section, si) => (\n <div key={si} className=\"flex flex-col gap-0.5\">\n {section.title && (\n <p className=\"relative z-10 px-2.5 pt-1 pb-1.5 text-[11.5px] font-medium text-ink-faint lowercase\">\n {section.title}\n </p>\n )}\n {section.items.map((item) => {\n const isActive = current === item.key;\n return (\n <button\n key={item.key}\n ref={(el) => { btnRefs.current[item.key] = el; }}\n type=\"button\"\n onClick={() => handleSelect(item.key, item.disabled)}\n disabled={item.disabled}\n aria-current={isActive ? \"page\" : undefined}\n className={[\n \"relative z-10 w-full flex items-center gap-2.5 px-2.5 py-2 rounded-[11px] text-[13px] font-medium text-left\",\n \"transition-colors duration-200 cursor-pointer\",\n \"disabled:opacity-35 disabled:pointer-events-none\",\n isActive\n ? \"text-ink-inverse\"\n : \"text-ink-body hover:text-ink hover:bg-surface-active/80\",\n ].join(\" \")}\n >\n {item.icon && (\n <span\n className={[\n \"shrink-0 w-[22px] h-[22px] rounded-md flex items-center justify-center transition-colors duration-200\",\n isActive\n ? \"bg-ink-inverse/12 shadow-[inset_0_1px_0_var(--inverse-lip)]\"\n : \"text-ink-muted [background:var(--face)] [box-shadow:var(--face-shadow)]\",\n ].join(\" \")}\n >\n {item.icon}\n </span>\n )}\n\n <span className=\"flex-1 truncate\">{item.label}</span>\n\n {item.badge !== undefined && (\n <span\n className={[\n \"shrink-0 text-[11px] font-medium px-1.5 py-0.5 rounded-md tabular-nums leading-none transition-colors duration-200\",\n isActive\n ? \"bg-ink-inverse/20 text-ink-inverse/80\"\n : \"bg-surface-active text-ink-muted\",\n ].join(\" \")}\n >\n {item.badge}\n </span>\n )}\n </button>\n );\n })}\n </div>\n ))}\n </nav>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,15 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type SkeletonVariant = "text" | "circle" | "rect" | "card";
4
+ interface SkeletonProps {
5
+ variant?: SkeletonVariant;
6
+ width?: string | number;
7
+ height?: string | number;
8
+ lines?: number;
9
+ className?: string;
10
+ /** What is loading, for screen readers */
11
+ label?: string;
12
+ }
13
+ declare function Skeleton({ variant, width, height, lines, className, label, }: SkeletonProps): react_jsx_runtime.JSX.Element;
14
+
15
+ export { Skeleton };
@@ -0,0 +1,67 @@
1
+ // ../../components/ui/Skeleton.tsx
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ var base = "skeleton-sheen relative overflow-hidden rounded-[8px]";
4
+ var well = { background: "var(--well)", boxShadow: "var(--well-shadow)" };
5
+ function Skeleton({
6
+ variant = "text",
7
+ width,
8
+ height,
9
+ lines = 1,
10
+ className = "",
11
+ label = "Loading"
12
+ }) {
13
+ const a11y = { role: "status", "aria-busy": true, "aria-label": label };
14
+ if (variant === "circle") {
15
+ return /* @__PURE__ */ jsx(
16
+ "div",
17
+ {
18
+ ...a11y,
19
+ className: [base, "rounded-full", className].join(" "),
20
+ style: { ...well, width: width ?? 40, height: height ?? 40 }
21
+ }
22
+ );
23
+ }
24
+ if (variant === "card") {
25
+ return /* @__PURE__ */ jsx(
26
+ "div",
27
+ {
28
+ ...a11y,
29
+ className: ["rounded-[20px] p-1.5", className].join(" "),
30
+ style: { background: "var(--panel-bezel)", boxShadow: "var(--panel-shadow)" },
31
+ children: /* @__PURE__ */ jsxs("div", { className: "bg-panel-face rounded-[15px] p-1.5", style: { boxShadow: "var(--panel-face-shadow)" }, children: [
32
+ /* @__PURE__ */ jsx("div", { className: [base, "w-full rounded-[11px]"].join(" "), style: { ...well, height: height ?? 160 } }),
33
+ /* @__PURE__ */ jsxs("div", { className: "px-3.5 pt-4 pb-3.5 flex flex-col gap-2.5", children: [
34
+ /* @__PURE__ */ jsx("div", { className: [base, "h-3.5 w-3/5"].join(" "), style: well }),
35
+ /* @__PURE__ */ jsx("div", { className: [base, "h-2.5 w-full"].join(" "), style: well }),
36
+ /* @__PURE__ */ jsx("div", { className: [base, "h-2.5 w-4/5"].join(" "), style: well })
37
+ ] })
38
+ ] })
39
+ }
40
+ );
41
+ }
42
+ if (variant === "rect") {
43
+ return /* @__PURE__ */ jsx(
44
+ "div",
45
+ {
46
+ ...a11y,
47
+ className: [base, "rounded-[12px]", className].join(" "),
48
+ style: { ...well, width: width ?? "100%", height: height ?? 80 }
49
+ }
50
+ );
51
+ }
52
+ return /* @__PURE__ */ jsx("div", { ...a11y, className: ["flex flex-col gap-2", className].join(" "), children: Array.from({ length: lines }).map((_, i) => /* @__PURE__ */ jsx(
53
+ "div",
54
+ {
55
+ className: [base, "h-2.5 rounded-full"].join(" "),
56
+ style: {
57
+ ...well,
58
+ width: width ?? (i === lines - 1 && lines > 1 ? "60%" : "100%")
59
+ }
60
+ },
61
+ i
62
+ )) });
63
+ }
64
+ export {
65
+ Skeleton
66
+ };
67
+ //# sourceMappingURL=Skeleton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/Skeleton.tsx"],"sourcesContent":["import React from \"react\";\n\ntype SkeletonVariant = \"text\" | \"circle\" | \"rect\" | \"card\";\n\ninterface SkeletonProps {\n variant?: SkeletonVariant;\n width?: string | number;\n height?: string | number;\n lines?: number;\n className?: string;\n /** What is loading, for screen readers */\n label?: string;\n}\n\n/* Placeholders are empty sunken wells — the shape of what will fill them —\n with a light sweep travelling across, rather than a blink. */\nconst base = \"skeleton-sheen relative overflow-hidden rounded-[8px]\";\nconst well: React.CSSProperties = { background: \"var(--well)\", boxShadow: \"var(--well-shadow)\" };\n\nexport function Skeleton({\n variant = \"text\",\n width,\n height,\n lines = 1,\n className = \"\",\n label = \"Loading\",\n}: SkeletonProps) {\n const a11y = { role: \"status\", \"aria-busy\": true, \"aria-label\": label } as const;\n\n if (variant === \"circle\") {\n return (\n <div\n {...a11y}\n className={[base, \"rounded-full\", className].join(\" \")}\n style={{ ...well, width: width ?? 40, height: height ?? 40 }}\n />\n );\n }\n\n if (variant === \"card\") {\n return (\n <div\n {...a11y}\n className={[\"rounded-[20px] p-1.5\", className].join(\" \")}\n style={{ background: \"var(--panel-bezel)\", boxShadow: \"var(--panel-shadow)\" }}\n >\n <div className=\"bg-panel-face rounded-[15px] p-1.5\" style={{ boxShadow: \"var(--panel-face-shadow)\" }}>\n <div className={[base, \"w-full rounded-[11px]\"].join(\" \")} style={{ ...well, height: height ?? 160 }} />\n <div className=\"px-3.5 pt-4 pb-3.5 flex flex-col gap-2.5\">\n <div className={[base, \"h-3.5 w-3/5\"].join(\" \")} style={well} />\n <div className={[base, \"h-2.5 w-full\"].join(\" \")} style={well} />\n <div className={[base, \"h-2.5 w-4/5\"].join(\" \")} style={well} />\n </div>\n </div>\n </div>\n );\n }\n\n if (variant === \"rect\") {\n return (\n <div\n {...a11y}\n className={[base, \"rounded-[12px]\", className].join(\" \")}\n style={{ ...well, width: width ?? \"100%\", height: height ?? 80 }}\n />\n );\n }\n\n return (\n <div {...a11y} className={[\"flex flex-col gap-2\", className].join(\" \")}>\n {Array.from({ length: lines }).map((_, i) => (\n <div\n key={i}\n className={[base, \"h-2.5 rounded-full\"].join(\" \")}\n style={{\n ...well,\n width: width ?? (i === lines - 1 && lines > 1 ? \"60%\" : \"100%\"),\n }}\n />\n ))}\n </div>\n );\n}\n"],"mappings":";AA+BM,cAiBI,YAjBJ;AAfN,IAAM,OAAO;AACb,IAAM,OAA4B,EAAE,YAAY,eAAe,WAAW,qBAAqB;AAExF,SAAS,SAAS;AAAA,EACvB,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,QAAQ;AACV,GAAkB;AAChB,QAAM,OAAO,EAAE,MAAM,UAAU,aAAa,MAAM,cAAc,MAAM;AAEtE,MAAI,YAAY,UAAU;AACxB,WACE;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAW,CAAC,MAAM,gBAAgB,SAAS,EAAE,KAAK,GAAG;AAAA,QACrD,OAAO,EAAE,GAAG,MAAM,OAAO,SAAS,IAAI,QAAQ,UAAU,GAAG;AAAA;AAAA,IAC7D;AAAA,EAEJ;AAEA,MAAI,YAAY,QAAQ;AACtB,WACE;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAW,CAAC,wBAAwB,SAAS,EAAE,KAAK,GAAG;AAAA,QACvD,OAAO,EAAE,YAAY,sBAAsB,WAAW,sBAAsB;AAAA,QAE5E,+BAAC,SAAI,WAAU,sCAAqC,OAAO,EAAE,WAAW,2BAA2B,GACjG;AAAA,8BAAC,SAAI,WAAW,CAAC,MAAM,uBAAuB,EAAE,KAAK,GAAG,GAAG,OAAO,EAAE,GAAG,MAAM,QAAQ,UAAU,IAAI,GAAG;AAAA,UACtG,qBAAC,SAAI,WAAU,4CACb;AAAA,gCAAC,SAAI,WAAW,CAAC,MAAM,aAAa,EAAE,KAAK,GAAG,GAAG,OAAO,MAAM;AAAA,YAC9D,oBAAC,SAAI,WAAW,CAAC,MAAM,cAAc,EAAE,KAAK,GAAG,GAAG,OAAO,MAAM;AAAA,YAC/D,oBAAC,SAAI,WAAW,CAAC,MAAM,aAAa,EAAE,KAAK,GAAG,GAAG,OAAO,MAAM;AAAA,aAChE;AAAA,WACF;AAAA;AAAA,IACF;AAAA,EAEJ;AAEA,MAAI,YAAY,QAAQ;AACtB,WACE;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAW,CAAC,MAAM,kBAAkB,SAAS,EAAE,KAAK,GAAG;AAAA,QACvD,OAAO,EAAE,GAAG,MAAM,OAAO,SAAS,QAAQ,QAAQ,UAAU,GAAG;AAAA;AAAA,IACjE;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAK,GAAG,MAAM,WAAW,CAAC,uBAAuB,SAAS,EAAE,KAAK,GAAG,GAClE,gBAAM,KAAK,EAAE,QAAQ,MAAM,CAAC,EAAE,IAAI,CAAC,GAAG,MACrC;AAAA,IAAC;AAAA;AAAA,MAEC,WAAW,CAAC,MAAM,oBAAoB,EAAE,KAAK,GAAG;AAAA,MAChD,OAAO;AAAA,QACL,GAAG;AAAA,QACH,OAAO,UAAU,MAAM,QAAQ,KAAK,QAAQ,IAAI,QAAQ;AAAA,MAC1D;AAAA;AAAA,IALK;AAAA,EAMP,CACD,GACH;AAEJ;","names":[]}
@@ -0,0 +1,27 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+
4
+ type SliderValue = number | [number, number];
5
+ interface SliderProps<V extends SliderValue> {
6
+ /** One number for a slider, two for a range */
7
+ value?: V;
8
+ defaultValue?: V;
9
+ onChange?: (value: V) => void;
10
+ /** Fires once the knob is let go, for work too heavy to do while dragging */
11
+ onCommit?: (value: V) => void;
12
+ min?: number;
13
+ max?: number;
14
+ step?: number;
15
+ label?: string;
16
+ /** Show the value beside the label; pass a function to format it */
17
+ showValue?: boolean | ((value: V) => React.ReactNode);
18
+ /** Small labels under the track, at these values */
19
+ marks?: number[];
20
+ disabled?: boolean;
21
+ /** Name of a hidden input (two for a range: name[0], name[1]) */
22
+ name?: string;
23
+ className?: string;
24
+ }
25
+ declare function Slider<V extends SliderValue = number>({ value, defaultValue, onChange, onCommit, min, max, step, label, showValue, marks, disabled, name, className, }: SliderProps<V>): react_jsx_runtime.JSX.Element;
26
+
27
+ export { Slider };
package/dist/Slider.js ADDED
@@ -0,0 +1,182 @@
1
+ "use client";
2
+
3
+ // ../../components/ui/Slider.tsx
4
+ import { useId, useRef, useState } from "react";
5
+ import { FieldLabel } from "./FieldMessage.js";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ var clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, n));
8
+ var decimals = (n) => (String(n).split(".")[1] ?? "").length;
9
+ function Slider({
10
+ value,
11
+ defaultValue,
12
+ onChange,
13
+ onCommit,
14
+ min = 0,
15
+ max = 100,
16
+ step = 1,
17
+ label,
18
+ showValue = false,
19
+ marks,
20
+ disabled = false,
21
+ name,
22
+ className = ""
23
+ }) {
24
+ const uid = useId();
25
+ const trackRef = useRef(null);
26
+ const knobs = useRef([]);
27
+ const isRange = Array.isArray(value ?? defaultValue);
28
+ const [internal, setInternal] = useState(() => defaultValue ?? value ?? min);
29
+ const current = value !== void 0 ? value : internal;
30
+ const values = Array.isArray(current) ? [...current] : [current];
31
+ const [dragging, setDragging] = useState(null);
32
+ const digits = decimals(step);
33
+ const snap = (n) => Number(clamp(Math.round((n - min) / step) * step + min, min, max).toFixed(digits));
34
+ const pct = (n) => (n - min) / (max - min) * 100;
35
+ const emit = (next, commit = false) => {
36
+ const v = isRange ? [next[0], next[1]] : next[0];
37
+ setInternal(v);
38
+ if (next.some((n, i) => n !== values[i])) onChange?.(v);
39
+ if (commit) onCommit?.(v);
40
+ };
41
+ const setAt = (i, n, commit = false) => {
42
+ const next = [...values];
43
+ next[i] = snap(n);
44
+ if (isRange) next[i] = i === 0 ? Math.min(next[0], values[1]) : Math.max(next[1], values[0]);
45
+ emit(next, commit);
46
+ };
47
+ const fromPointer = (clientX) => {
48
+ const r = trackRef.current.getBoundingClientRect();
49
+ return min + clamp((clientX - r.left) / r.width, 0, 1) * (max - min);
50
+ };
51
+ const onPointerDown = (e) => {
52
+ if (disabled || e.button !== 0) return;
53
+ e.preventDefault();
54
+ const n = fromPointer(e.clientX);
55
+ let i = 0;
56
+ if (isRange) {
57
+ const d0 = Math.abs(n - values[0]);
58
+ const d1 = Math.abs(n - values[1]);
59
+ i = d1 < d0 || d0 === d1 && n > values[1] ? 1 : 0;
60
+ }
61
+ e.currentTarget.setPointerCapture(e.pointerId);
62
+ setDragging(i);
63
+ knobs.current[i]?.focus();
64
+ setAt(i, n);
65
+ };
66
+ const onKeyDown = (i) => (e) => {
67
+ const big = Math.max(step, (max - min) / 10);
68
+ const moves = {
69
+ ArrowRight: step * (e.shiftKey ? 10 : 1),
70
+ ArrowUp: step * (e.shiftKey ? 10 : 1),
71
+ ArrowLeft: -step * (e.shiftKey ? 10 : 1),
72
+ ArrowDown: -step * (e.shiftKey ? 10 : 1),
73
+ PageUp: big,
74
+ PageDown: -big
75
+ };
76
+ if (e.key in moves) {
77
+ e.preventDefault();
78
+ setAt(i, values[i] + moves[e.key], true);
79
+ } else if (e.key === "Home") {
80
+ e.preventDefault();
81
+ setAt(i, min, true);
82
+ } else if (e.key === "End") {
83
+ e.preventDefault();
84
+ setAt(i, max, true);
85
+ }
86
+ };
87
+ const shown = typeof showValue === "function" ? showValue(current) : showValue ? values.join(" \u2013 ") : null;
88
+ const lo = isRange ? pct(values[0]) : 0;
89
+ const hi = pct(values[isRange ? 1 : 0]);
90
+ return /* @__PURE__ */ jsxs("div", { className: ["flex w-full flex-col gap-2.5", disabled ? "opacity-40" : "", className].join(" "), children: [
91
+ (label || shown !== null) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
92
+ label && /* @__PURE__ */ jsx(FieldLabel, { id: `${uid}-label`, children: label }),
93
+ shown !== null && /* @__PURE__ */ jsx("span", { className: "text-[12.5px] tabular-nums text-ink-muted", children: shown })
94
+ ] }),
95
+ /* @__PURE__ */ jsxs(
96
+ "div",
97
+ {
98
+ className: ["relative flex h-6 touch-none items-center", disabled ? "pointer-events-none" : "cursor-pointer"].join(" "),
99
+ onPointerDown,
100
+ onPointerMove: (e) => dragging !== null && setAt(dragging, fromPointer(e.clientX)),
101
+ onPointerUp: () => {
102
+ if (dragging !== null) onCommit?.(current);
103
+ setDragging(null);
104
+ },
105
+ onPointerCancel: () => setDragging(null),
106
+ children: [
107
+ /* @__PURE__ */ jsx(
108
+ "div",
109
+ {
110
+ ref: trackRef,
111
+ className: "relative h-2 w-full rounded-full",
112
+ style: { background: "var(--track)", boxShadow: "var(--track-shadow)" },
113
+ children: /* @__PURE__ */ jsx(
114
+ "div",
115
+ {
116
+ className: "absolute inset-y-px rounded-full",
117
+ style: {
118
+ left: `calc(${lo}% + ${isRange ? 0 : 1}px)`,
119
+ width: `calc(${hi - lo}% - ${isRange ? 0 : 1}px)`,
120
+ background: "var(--face-inverse)",
121
+ boxShadow: "var(--fill-shadow)",
122
+ transition: dragging === null ? "left 200ms var(--ease-out-soft), width 200ms var(--ease-out-soft)" : "none"
123
+ }
124
+ }
125
+ )
126
+ }
127
+ ),
128
+ values.map((v, i) => /* @__PURE__ */ jsx(
129
+ "span",
130
+ {
131
+ ref: (el) => {
132
+ knobs.current[i] = el;
133
+ },
134
+ role: "slider",
135
+ tabIndex: disabled ? -1 : 0,
136
+ "aria-labelledby": label ? `${uid}-label` : void 0,
137
+ "aria-label": isRange ? i === 0 ? "minimum" : "maximum" : label ? void 0 : "value",
138
+ "aria-valuemin": isRange && i === 1 ? values[0] : min,
139
+ "aria-valuemax": isRange && i === 0 ? values[1] : max,
140
+ "aria-valuenow": v,
141
+ "aria-disabled": disabled || void 0,
142
+ onKeyDown: onKeyDown(i),
143
+ className: "absolute top-1/2 h-5 w-5 rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ink focus-visible:ring-offset-2 focus-visible:ring-offset-page",
144
+ style: {
145
+ left: `${pct(v)}%`,
146
+ // the last-dragged knob of a range sits on top, so two at one spot can be pulled apart
147
+ zIndex: dragging === i ? 2 : 1,
148
+ background: "var(--face)",
149
+ boxShadow: "var(--knob-shadow)",
150
+ transform: `translate(-50%, -50%) scale(${dragging === i ? 1.15 : 1})`,
151
+ transition: dragging === null ? "left 200ms var(--ease-out-soft), transform 220ms var(--ease-spring)" : "transform 220ms var(--ease-spring)"
152
+ },
153
+ children: /* @__PURE__ */ jsx(
154
+ "span",
155
+ {
156
+ "aria-hidden": true,
157
+ className: "absolute left-1/2 top-1/2 h-1.5 w-1.5 -translate-x-1/2 -translate-y-1/2 rounded-full",
158
+ style: { background: "var(--track)", boxShadow: "var(--track-shadow)" }
159
+ }
160
+ )
161
+ },
162
+ i
163
+ ))
164
+ ]
165
+ }
166
+ ),
167
+ marks && marks.length > 0 && /* @__PURE__ */ jsx("div", { "aria-hidden": true, className: "relative h-4", children: marks.map((m) => /* @__PURE__ */ jsx(
168
+ "span",
169
+ {
170
+ className: "absolute -translate-x-1/2 text-[11px] tabular-nums text-ink-faint",
171
+ style: { left: `${pct(m)}%` },
172
+ children: m
173
+ },
174
+ m
175
+ )) }),
176
+ name && values.map((v, i) => /* @__PURE__ */ jsx("input", { type: "hidden", name: isRange ? `${name}[${i}]` : name, value: v }, i))
177
+ ] });
178
+ }
179
+ export {
180
+ Slider
181
+ };
182
+ //# sourceMappingURL=Slider.js.map