@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
package/dist/Badge.js ADDED
@@ -0,0 +1,46 @@
1
+ // ../../components/ui/Badge.tsx
2
+ import { jsx } from "react/jsx-runtime";
3
+ var variantStyles = {
4
+ default: "text-ink-inverse",
5
+ muted: "text-ink-body",
6
+ accent: "text-accent"
7
+ };
8
+ var variantSurfaces = {
9
+ default: {
10
+ background: "var(--face-inverse)",
11
+ boxShadow: "var(--badge-inverse-shadow)",
12
+ textShadow: "var(--emboss-inverse)"
13
+ },
14
+ muted: {
15
+ background: "var(--badge)",
16
+ boxShadow: "var(--badge-shadow)",
17
+ textShadow: "var(--emboss)"
18
+ },
19
+ accent: {
20
+ background: "var(--badge-accent)",
21
+ boxShadow: "var(--badge-accent-shadow)",
22
+ textShadow: "var(--emboss-soft)"
23
+ }
24
+ };
25
+ function Badge({
26
+ variant = "muted",
27
+ children,
28
+ className = ""
29
+ }) {
30
+ return /* @__PURE__ */ jsx(
31
+ "span",
32
+ {
33
+ className: [
34
+ "inline-flex items-center px-2 py-0.5 rounded-md text-[11px] font-medium tracking-wide",
35
+ variantStyles[variant],
36
+ className
37
+ ].filter(Boolean).join(" "),
38
+ style: variantSurfaces[variant],
39
+ children
40
+ }
41
+ );
42
+ }
43
+ export {
44
+ Badge
45
+ };
46
+ //# sourceMappingURL=Badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/Badge.tsx"],"sourcesContent":["import React from \"react\";\n\ntype BadgeVariant = \"default\" | \"muted\" | \"accent\";\n\ninterface BadgeProps {\n variant?: BadgeVariant;\n children: React.ReactNode;\n className?: string;\n}\n\nconst variantStyles: Record<BadgeVariant, string> = {\n default: \"text-ink-inverse\",\n muted: \"text-ink-body\",\n accent: \"text-accent\",\n};\n\n/* Badges are small raised faces, lit from the same top edge as buttons. */\nconst variantSurfaces: Record<BadgeVariant, React.CSSProperties> = {\n default: {\n background: \"var(--face-inverse)\",\n boxShadow: \"var(--badge-inverse-shadow)\",\n textShadow: \"var(--emboss-inverse)\",\n },\n muted: {\n background: \"var(--badge)\",\n boxShadow: \"var(--badge-shadow)\",\n textShadow: \"var(--emboss)\",\n },\n accent: {\n background: \"var(--badge-accent)\",\n boxShadow: \"var(--badge-accent-shadow)\",\n textShadow: \"var(--emboss-soft)\",\n },\n};\n\nexport function Badge({\n variant = \"muted\",\n children,\n className = \"\",\n}: BadgeProps) {\n return (\n <span\n className={[\n \"inline-flex items-center px-2 py-0.5 rounded-md text-[11px] font-medium tracking-wide\",\n variantStyles[variant],\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={variantSurfaces[variant]}\n >\n {children}\n </span>\n );\n}\n"],"mappings":";AAyCI;AA/BJ,IAAM,gBAA8C;AAAA,EAClD,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AACV;AAGA,IAAM,kBAA6D;AAAA,EACjE,SAAS;AAAA,IACP,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,YAAY;AAAA,EACd;AAAA,EACA,OAAO;AAAA,IACL,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,YAAY;AAAA,EACd;AAAA,EACA,QAAQ;AAAA,IACN,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,YAAY;AAAA,EACd;AACF;AAEO,SAAS,MAAM;AAAA,EACpB,UAAU;AAAA,EACV;AAAA,EACA,YAAY;AACd,GAAe;AACb,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,cAAc,OAAO;AAAA,QACrB;AAAA,MACF,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,MACX,OAAO,gBAAgB,OAAO;AAAA,MAE7B;AAAA;AAAA,EACH;AAEJ;","names":[]}
@@ -0,0 +1,15 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ type BannerVariant = "default" | "accent" | "warning";
5
+ declare function Banner({ variant, children, icon, dismissible, onDismiss, action, className, }: {
6
+ variant?: BannerVariant;
7
+ children: ReactNode;
8
+ icon?: ReactNode;
9
+ dismissible?: boolean;
10
+ onDismiss?: () => void;
11
+ action?: ReactNode;
12
+ className?: string;
13
+ }): react_jsx_runtime.JSX.Element;
14
+
15
+ export { Banner };
package/dist/Banner.js ADDED
@@ -0,0 +1,68 @@
1
+ "use client";
2
+
3
+ // ../../components/ui/Banner.tsx
4
+ import { CloseButton } from "./CloseButton.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ var variantStyles = {
7
+ default: {
8
+ bezel: "var(--banner-bezel)",
9
+ bezelShadow: "var(--banner-bezel-shadow)",
10
+ face: "var(--banner-face)",
11
+ faceShadow: "var(--banner-face-shadow)",
12
+ text: "text-white",
13
+ tone: "dark"
14
+ },
15
+ accent: {
16
+ bezel: "var(--bezel-violet)",
17
+ bezelShadow: "var(--bezel-violet-shadow)",
18
+ face: "var(--face-violet)",
19
+ faceShadow: "var(--face-violet-shadow)",
20
+ text: "text-white",
21
+ tone: "dark"
22
+ },
23
+ warning: {
24
+ bezel: "var(--bezel-warning)",
25
+ bezelShadow: "var(--bezel-warning-shadow)",
26
+ face: "var(--face-warning)",
27
+ faceShadow: "var(--face-warning-shadow)",
28
+ text: "text-warning-ink",
29
+ tone: "light"
30
+ }
31
+ };
32
+ function Banner({
33
+ variant = "default",
34
+ children,
35
+ icon,
36
+ dismissible = false,
37
+ onDismiss,
38
+ action,
39
+ className = ""
40
+ }) {
41
+ const s = variantStyles[variant];
42
+ return /* @__PURE__ */ jsx(
43
+ "div",
44
+ {
45
+ role: "region",
46
+ "aria-label": "Announcement",
47
+ className: `w-full rounded-[18px] p-1 ${className}`,
48
+ style: { background: s.bezel, boxShadow: s.bezelShadow },
49
+ children: /* @__PURE__ */ jsxs(
50
+ "div",
51
+ {
52
+ className: `rounded-[14px] pl-4 pr-2 py-2.5 flex items-center gap-3 ${s.text}`,
53
+ style: { background: s.face, boxShadow: s.faceShadow },
54
+ children: [
55
+ icon && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "flex-shrink-0 opacity-80", children: icon }),
56
+ /* @__PURE__ */ jsx("span", { className: "text-[13px] font-medium flex-1 min-w-0 leading-relaxed", children }),
57
+ action && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: action }),
58
+ dismissible && /* @__PURE__ */ jsx(CloseButton, { size: "sm", tone: s.tone, label: "Dismiss announcement", onClick: onDismiss })
59
+ ]
60
+ }
61
+ )
62
+ }
63
+ );
64
+ }
65
+ export {
66
+ Banner
67
+ };
68
+ //# sourceMappingURL=Banner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/Banner.tsx"],"sourcesContent":["\"use client\";\n\nimport { ReactNode } from \"react\";\nimport { CloseButton } from \"./CloseButton\";\n\ntype BannerVariant = \"default\" | \"accent\" | \"warning\";\n\n/* A banner is a dark bezel holding a dark face — the same build as the\n primary button, stretched to full width. On a dark page it stays a\n dark strip (the mid material) rather than a full-width pale bar. */\nconst variantStyles: Record<\n BannerVariant,\n { bezel: string; bezelShadow: string; face: string; faceShadow: string; text: string; tone: \"light\" | \"dark\" }\n> = {\n default: {\n bezel: \"var(--banner-bezel)\",\n bezelShadow: \"var(--banner-bezel-shadow)\",\n face: \"var(--banner-face)\",\n faceShadow: \"var(--banner-face-shadow)\",\n text: \"text-white\",\n tone: \"dark\",\n },\n accent: {\n bezel: \"var(--bezel-violet)\",\n bezelShadow: \"var(--bezel-violet-shadow)\",\n face: \"var(--face-violet)\",\n faceShadow: \"var(--face-violet-shadow)\",\n text: \"text-white\",\n tone: \"dark\",\n },\n warning: {\n bezel: \"var(--bezel-warning)\",\n bezelShadow: \"var(--bezel-warning-shadow)\",\n face: \"var(--face-warning)\",\n faceShadow: \"var(--face-warning-shadow)\",\n text: \"text-warning-ink\",\n tone: \"light\",\n },\n};\n\nexport function Banner({\n variant = \"default\",\n children,\n icon,\n dismissible = false,\n onDismiss,\n action,\n className = \"\",\n}: {\n variant?: BannerVariant;\n children: ReactNode;\n icon?: ReactNode;\n dismissible?: boolean;\n onDismiss?: () => void;\n action?: ReactNode;\n className?: string;\n}) {\n const s = variantStyles[variant];\n\n return (\n <div\n role=\"region\"\n aria-label=\"Announcement\"\n className={`w-full rounded-[18px] p-1 ${className}`}\n style={{ background: s.bezel, boxShadow: s.bezelShadow }}\n >\n <div\n className={`rounded-[14px] pl-4 pr-2 py-2.5 flex items-center gap-3 ${s.text}`}\n style={{ background: s.face, boxShadow: s.faceShadow }}\n >\n {icon && <span aria-hidden className=\"flex-shrink-0 opacity-80\">{icon}</span>}\n\n {/* Left-aligned so it reads in one sweep with the icon beside it */}\n <span className=\"text-[13px] font-medium flex-1 min-w-0 leading-relaxed\">\n {children}\n </span>\n\n {action && <span className=\"flex-shrink-0\">{action}</span>}\n\n {dismissible && (\n <CloseButton size=\"sm\" tone={s.tone} label=\"Dismiss announcement\" onClick={onDismiss} />\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;AAGA,SAAS,mBAAmB;AA+DtB,SAIW,KAJX;AAxDN,IAAM,gBAGF;AAAA,EACF,SAAS;AAAA,IACP,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AAAA,EACA,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AAAA,EACA,SAAS;AAAA,IACP,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AACF;AAEO,SAAS,OAAO;AAAA,EACrB,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,YAAY;AACd,GAQG;AACD,QAAM,IAAI,cAAc,OAAO;AAE/B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAW;AAAA,MACX,WAAW,6BAA6B,SAAS;AAAA,MACjD,OAAO,EAAE,YAAY,EAAE,OAAO,WAAW,EAAE,YAAY;AAAA,MAEvD;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,2DAA2D,EAAE,IAAI;AAAA,UAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,EAAE,WAAW;AAAA,UAEpD;AAAA,oBAAQ,oBAAC,UAAK,eAAW,MAAC,WAAU,4BAA4B,gBAAK;AAAA,YAGtE,oBAAC,UAAK,WAAU,0DACb,UACH;AAAA,YAEC,UAAU,oBAAC,UAAK,WAAU,iBAAiB,kBAAO;AAAA,YAElD,eACC,oBAAC,eAAY,MAAK,MAAK,MAAM,EAAE,MAAM,OAAM,wBAAuB,SAAS,WAAW;AAAA;AAAA;AAAA,MAE1F;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,13 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface BreadcrumbItem {
4
+ label: string;
5
+ href?: string;
6
+ }
7
+ interface BreadcrumbProps {
8
+ items: BreadcrumbItem[];
9
+ className?: string;
10
+ }
11
+ declare function Breadcrumb({ items, className }: BreadcrumbProps): react_jsx_runtime.JSX.Element;
12
+
13
+ export { Breadcrumb };
@@ -0,0 +1,45 @@
1
+ // ../../components/ui/Breadcrumb.tsx
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ function Breadcrumb({ items, className = "" }) {
4
+ return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className, children: /* @__PURE__ */ jsx("ol", { className: "flex items-center gap-1 flex-wrap", children: items.map((item, i) => {
5
+ const isLast = i === items.length - 1;
6
+ return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1 min-w-0", children: [
7
+ i > 0 && /* @__PURE__ */ jsx(
8
+ "svg",
9
+ {
10
+ "aria-hidden": true,
11
+ width: "12",
12
+ height: "12",
13
+ fill: "none",
14
+ stroke: "currentColor",
15
+ strokeWidth: "2",
16
+ viewBox: "0 0 24 24",
17
+ className: "text-ink-ghost shrink-0",
18
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" })
19
+ }
20
+ ),
21
+ isLast || !item.href ? /* @__PURE__ */ jsx(
22
+ "span",
23
+ {
24
+ "aria-current": isLast ? "page" : void 0,
25
+ className: [
26
+ "text-[12.5px] px-1.5 py-0.5 truncate",
27
+ isLast ? "font-medium text-ink" : "text-ink-muted"
28
+ ].join(" "),
29
+ children: item.label
30
+ }
31
+ ) : /* @__PURE__ */ jsx(
32
+ "a",
33
+ {
34
+ href: item.href,
35
+ className: "text-[12.5px] text-ink-muted hover:text-ink px-1.5 py-0.5 rounded-[6px] hover:bg-ink/[0.05] transition-colors truncate",
36
+ children: item.label
37
+ }
38
+ )
39
+ ] }, i);
40
+ }) }) });
41
+ }
42
+ export {
43
+ Breadcrumb
44
+ };
45
+ //# sourceMappingURL=Breadcrumb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/Breadcrumb.tsx"],"sourcesContent":["import React from \"react\";\n\ninterface BreadcrumbItem {\n label: string;\n href?: string;\n}\n\ninterface BreadcrumbProps {\n items: BreadcrumbItem[];\n className?: string;\n}\n\nexport function Breadcrumb({ items, className = \"\" }: BreadcrumbProps) {\n return (\n <nav aria-label=\"Breadcrumb\" className={className}>\n <ol className=\"flex items-center gap-1 flex-wrap\">\n {items.map((item, i) => {\n const isLast = i === items.length - 1;\n\n return (\n <li key={i} className=\"flex items-center gap-1 min-w-0\">\n {i > 0 && (\n <svg\n aria-hidden\n width=\"12\"\n height=\"12\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n className=\"text-ink-ghost shrink-0\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9 5l7 7-7 7\" />\n </svg>\n )}\n {isLast || !item.href ? (\n <span\n aria-current={isLast ? \"page\" : undefined}\n className={[\n \"text-[12.5px] px-1.5 py-0.5 truncate\",\n isLast ? \"font-medium text-ink\" : \"text-ink-muted\",\n ].join(\" \")}\n >\n {item.label}\n </span>\n ) : (\n <a\n href={item.href}\n className=\"text-[12.5px] text-ink-muted hover:text-ink px-1.5 py-0.5 rounded-[6px] hover:bg-ink/[0.05] transition-colors truncate\"\n >\n {item.label}\n </a>\n )}\n </li>\n );\n })}\n </ol>\n </nav>\n );\n}\n"],"mappings":";AAoBY,SAYM,KAZN;AARL,SAAS,WAAW,EAAE,OAAO,YAAY,GAAG,GAAoB;AACrE,SACE,oBAAC,SAAI,cAAW,cAAa,WAC3B,8BAAC,QAAG,WAAU,qCACX,gBAAM,IAAI,CAAC,MAAM,MAAM;AACtB,UAAM,SAAS,MAAM,MAAM,SAAS;AAEpC,WACE,qBAAC,QAAW,WAAU,mCACnB;AAAA,UAAI,KACH;AAAA,QAAC;AAAA;AAAA,UACC,eAAW;AAAA,UACX,OAAM;AAAA,UACN,QAAO;AAAA,UACP,MAAK;AAAA,UACL,QAAO;AAAA,UACP,aAAY;AAAA,UACZ,SAAQ;AAAA,UACR,WAAU;AAAA,UAEV,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,GAAE,gBAAe;AAAA;AAAA,MACtE;AAAA,MAED,UAAU,CAAC,KAAK,OACf;AAAA,QAAC;AAAA;AAAA,UACC,gBAAc,SAAS,SAAS;AAAA,UAChC,WAAW;AAAA,YACT;AAAA,YACA,SAAS,yBAAyB;AAAA,UACpC,EAAE,KAAK,GAAG;AAAA,UAET,eAAK;AAAA;AAAA,MACR,IAEA;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,KAAK;AAAA,UACX,WAAU;AAAA,UAET,eAAK;AAAA;AAAA,MACR;AAAA,SA/BK,CAiCT;AAAA,EAEJ,CAAC,GACH,GACF;AAEJ;","names":[]}
@@ -0,0 +1,19 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+
4
+ type ButtonVariant = "primary" | "secondary" | "ghost" | "outline";
5
+ type ButtonSize = "sm" | "md" | "lg" | "icon";
6
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
7
+ variant?: ButtonVariant;
8
+ size?: ButtonSize;
9
+ children: React.ReactNode;
10
+ fullWidth?: boolean;
11
+ /** Surface the button sits on. On dark cards, secondary/outline switch to a
12
+ dark face so they don't glare against the card. */
13
+ tone?: "light" | "dark";
14
+ /** Equal width and height, for icon-only layered buttons. */
15
+ square?: boolean;
16
+ }
17
+ declare function Button({ variant, size, fullWidth, tone, square, className, children, disabled, onClick, onPointerDown, onPointerUp, onPointerLeave, onPointerCancel, onPointerEnter, style, ...rest }: ButtonProps): react_jsx_runtime.JSX.Element;
18
+
19
+ export { Button };
package/dist/Button.js ADDED
@@ -0,0 +1,282 @@
1
+ "use client";
2
+
3
+ // ../../components/ui/Button.tsx
4
+ import { useState, useRef, useCallback } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ var shellConfig = {
7
+ primary: {
8
+ bezel: "var(--bezel-inverse)",
9
+ bezelShadow: "var(--bezel-inverse-shadow)",
10
+ face: "var(--face-inverse)",
11
+ faceShadow: "var(--face-inverse-shadow)",
12
+ faceHoverShadow: "var(--face-inverse-shadow-hover)",
13
+ facePressedShadow: "var(--face-inverse-pressed)",
14
+ text: "text-ink-inverse",
15
+ textShadow: "var(--emboss-inverse-deep)",
16
+ fill: "bg-ink-inverse/[0.05]"
17
+ },
18
+ secondary: {
19
+ bezel: "var(--bezel)",
20
+ bezelShadow: "var(--bezel-shadow)",
21
+ face: "var(--face)",
22
+ faceShadow: "var(--face-shadow)",
23
+ faceHoverShadow: "var(--face-shadow-hover)",
24
+ facePressedShadow: "var(--face-pressed)",
25
+ text: "text-ink-soft",
26
+ textShadow: "var(--emboss)",
27
+ fill: "bg-ink/[0.025]"
28
+ },
29
+ outline: {
30
+ bezel: "var(--outline-bezel)",
31
+ bezelShadow: "var(--outline-bezel-shadow)",
32
+ face: "var(--face)",
33
+ faceShadow: "var(--outline-face-shadow)",
34
+ faceHoverShadow: "var(--outline-face-shadow-hover)",
35
+ facePressedShadow: "var(--face-pressed)",
36
+ text: "text-ink-label",
37
+ textShadow: "var(--emboss)",
38
+ fill: "bg-ink/[0.025]"
39
+ }
40
+ };
41
+ var darkSecondary = {
42
+ bezel: "var(--bezel-mid)",
43
+ bezelShadow: "var(--bezel-mid-shadow)",
44
+ face: "var(--face-mid)",
45
+ faceShadow: "var(--face-mid-shadow)",
46
+ faceHoverShadow: "var(--face-mid-shadow-hover)",
47
+ facePressedShadow: "var(--face-dark-pressed)",
48
+ text: "text-neutral-100",
49
+ textShadow: "var(--emboss-inverse)",
50
+ fill: "bg-white/[0.04]"
51
+ };
52
+ var flatConfig = {
53
+ primary: {
54
+ bg: "text-ink-inverse",
55
+ background: "var(--face-inverse)",
56
+ fill: "bg-ink-inverse/[0.08]",
57
+ shadow: "var(--face-inverse-shadow)",
58
+ hoverShadow: "var(--face-inverse-float-shadow)",
59
+ pressedShadow: "var(--face-inverse-pressed)"
60
+ },
61
+ secondary: {
62
+ bg: "text-ink-soft",
63
+ background: "var(--face)",
64
+ fill: "bg-ink/[0.035]",
65
+ shadow: "var(--face-shadow)",
66
+ hoverShadow: "var(--face-float-shadow)",
67
+ pressedShadow: "var(--face-pressed)"
68
+ },
69
+ ghost: {
70
+ bg: "bg-transparent text-ink-muted hover:text-ink-soft",
71
+ fill: "bg-ink/[0.05]",
72
+ shadow: "none",
73
+ hoverShadow: "var(--ghost-shadow-hover)",
74
+ pressedShadow: "var(--ghost-shadow-pressed)"
75
+ },
76
+ outline: {
77
+ bg: "bg-transparent text-ink-label",
78
+ fill: "bg-ink/[0.035]",
79
+ shadow: "var(--outline-shadow)",
80
+ hoverShadow: "var(--outline-shadow-hover)",
81
+ pressedShadow: "var(--outline-shadow-pressed)"
82
+ }
83
+ };
84
+ var shellRadius = {
85
+ sm: "rounded-[12px] p-[3px]",
86
+ md: "rounded-[13px] p-[3px]",
87
+ lg: "rounded-[15px] p-[3.5px]",
88
+ icon: ""
89
+ };
90
+ var faceRadius = {
91
+ sm: "rounded-[9px]",
92
+ md: "rounded-[10px]",
93
+ lg: "rounded-[11.5px]",
94
+ icon: ""
95
+ };
96
+ var innerPadding = {
97
+ sm: "px-4 py-2 text-[13px]",
98
+ md: "px-5 py-2.5 text-sm",
99
+ lg: "px-7 py-3 text-[15px]",
100
+ icon: "w-7 h-7 p-0 text-[13px]"
101
+ };
102
+ var squarePadding = {
103
+ sm: "w-9 h-9 p-0 text-[13px]",
104
+ md: "w-10 h-10 p-0 text-sm",
105
+ lg: "w-12 h-12 p-0 text-[15px]",
106
+ icon: ""
107
+ };
108
+ var flatSizeStyles = {
109
+ sm: "px-4 py-2.5 text-[13px] rounded-[10px]",
110
+ md: "px-5 py-3 text-sm rounded-[10px]",
111
+ lg: "px-7 py-3.5 text-[15px] rounded-xl",
112
+ icon: "w-8 h-8 p-0 rounded-[10px] text-[13px]"
113
+ };
114
+ function Button({
115
+ variant = "primary",
116
+ size = "md",
117
+ fullWidth = false,
118
+ tone = "light",
119
+ square = false,
120
+ className = "",
121
+ children,
122
+ disabled,
123
+ onClick,
124
+ onPointerDown,
125
+ onPointerUp,
126
+ onPointerLeave,
127
+ onPointerCancel,
128
+ onPointerEnter,
129
+ style,
130
+ ...rest
131
+ }) {
132
+ const [pressed, setPressed] = useState(false);
133
+ const [hovered, setHovered] = useState(false);
134
+ const timerRef = useRef(null);
135
+ const handlePointerDown = useCallback(
136
+ (e) => {
137
+ if (timerRef.current) clearTimeout(timerRef.current);
138
+ setPressed(true);
139
+ onPointerDown?.(e);
140
+ },
141
+ [onPointerDown]
142
+ );
143
+ const handlePointerUp = useCallback(
144
+ (e) => {
145
+ timerRef.current = setTimeout(() => setPressed(false), 150);
146
+ onPointerUp?.(e);
147
+ },
148
+ [onPointerUp]
149
+ );
150
+ const handlePointerCancel = useCallback(
151
+ (e) => {
152
+ timerRef.current = setTimeout(() => setPressed(false), 150);
153
+ onPointerCancel?.(e);
154
+ },
155
+ [onPointerCancel]
156
+ );
157
+ const handlePointerLeave = useCallback(
158
+ (e) => {
159
+ setHovered(false);
160
+ if (timerRef.current) clearTimeout(timerRef.current);
161
+ timerRef.current = setTimeout(() => setPressed(false), 150);
162
+ onPointerLeave?.(e);
163
+ },
164
+ [onPointerLeave]
165
+ );
166
+ const layered = variant !== "ghost" && size !== "icon";
167
+ const handlers = {
168
+ onClick,
169
+ onPointerDown: handlePointerDown,
170
+ onPointerUp: handlePointerUp,
171
+ onPointerLeave: handlePointerLeave,
172
+ onPointerCancel: handlePointerCancel,
173
+ onPointerEnter: (e) => {
174
+ setHovered(true);
175
+ onPointerEnter?.(e);
176
+ }
177
+ };
178
+ if (layered) {
179
+ const cfg2 = tone === "dark" && (variant === "secondary" || variant === "outline") ? darkSecondary : shellConfig[variant];
180
+ const faceShadow = pressed ? cfg2.facePressedShadow : hovered ? cfg2.faceHoverShadow : cfg2.faceShadow;
181
+ return /* @__PURE__ */ jsx(
182
+ "button",
183
+ {
184
+ "data-btn": true,
185
+ className: [
186
+ "group/btn relative inline-flex cursor-pointer select-none whitespace-nowrap",
187
+ shellRadius[size],
188
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink",
189
+ "disabled:opacity-40 disabled:pointer-events-none",
190
+ fullWidth ? "w-full" : "",
191
+ className
192
+ ].filter(Boolean).join(" "),
193
+ style: {
194
+ background: cfg2.bezel,
195
+ boxShadow: cfg2.bezelShadow,
196
+ ...style
197
+ },
198
+ disabled,
199
+ ...handlers,
200
+ ...rest,
201
+ children: /* @__PURE__ */ jsxs(
202
+ "span",
203
+ {
204
+ className: [
205
+ "btn-face relative overflow-hidden flex items-center justify-center gap-1.5 font-medium",
206
+ faceRadius[size],
207
+ cfg2.text,
208
+ square ? squarePadding[size] : ["w-full", innerPadding[size]].join(" ")
209
+ ].join(" "),
210
+ style: {
211
+ background: cfg2.face,
212
+ boxShadow: faceShadow,
213
+ textShadow: cfg2.textShadow,
214
+ transform: pressed ? "translateY(0.5px) scale(0.985)" : hovered ? "translateY(-0.5px)" : "none",
215
+ transition: "transform 240ms var(--ease-spring), box-shadow 200ms ease-out"
216
+ },
217
+ children: [
218
+ /* @__PURE__ */ jsx(
219
+ "span",
220
+ {
221
+ "aria-hidden": "true",
222
+ className: [
223
+ "pointer-events-none absolute inset-0 origin-bottom transition-transform duration-300 ease-out",
224
+ pressed ? "scale-y-100" : "scale-y-0 group-hover/btn:scale-y-100",
225
+ cfg2.fill
226
+ ].join(" ")
227
+ }
228
+ ),
229
+ /* @__PURE__ */ jsx("span", { className: "btn-icon-nudge relative z-10 inline-flex items-center gap-1.5", children })
230
+ ]
231
+ }
232
+ )
233
+ }
234
+ );
235
+ }
236
+ const cfg = flatConfig[variant];
237
+ const currentShadow = pressed ? cfg.pressedShadow : hovered ? cfg.hoverShadow : cfg.shadow;
238
+ return /* @__PURE__ */ jsxs(
239
+ "button",
240
+ {
241
+ "data-btn": true,
242
+ className: [
243
+ "btn-face group/btn relative overflow-hidden inline-flex items-center justify-center gap-1.5 font-medium cursor-pointer select-none whitespace-nowrap",
244
+ "transition-colors duration-200 ease-out",
245
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink",
246
+ "disabled:opacity-40 disabled:pointer-events-none",
247
+ cfg.bg,
248
+ flatSizeStyles[size],
249
+ fullWidth ? "w-full" : "",
250
+ className
251
+ ].filter(Boolean).join(" "),
252
+ style: {
253
+ background: cfg.background,
254
+ boxShadow: currentShadow,
255
+ transform: pressed ? "translateY(0.5px) scale(0.97)" : hovered && cfg.background ? "translateY(-0.5px)" : "none",
256
+ transition: "transform 240ms var(--ease-spring), box-shadow 200ms ease-out, color 200ms ease-out",
257
+ ...style
258
+ },
259
+ disabled,
260
+ ...handlers,
261
+ ...rest,
262
+ children: [
263
+ /* @__PURE__ */ jsx(
264
+ "span",
265
+ {
266
+ "aria-hidden": "true",
267
+ className: [
268
+ "pointer-events-none absolute inset-0 origin-bottom transition-transform duration-300 ease-out",
269
+ pressed ? "scale-y-100" : "scale-y-0 group-hover/btn:scale-y-100",
270
+ cfg.fill
271
+ ].join(" ")
272
+ }
273
+ ),
274
+ /* @__PURE__ */ jsx("span", { className: "relative z-10 inline-flex items-center gap-1.5", children })
275
+ ]
276
+ }
277
+ );
278
+ }
279
+ export {
280
+ Button
281
+ };
282
+ //# sourceMappingURL=Button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/Button.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useState, useRef, useCallback } from \"react\";\n\ntype ButtonVariant = \"primary\" | \"secondary\" | \"ghost\" | \"outline\";\ntype ButtonSize = \"sm\" | \"md\" | \"lg\" | \"icon\";\n\ninterface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n children: React.ReactNode;\n fullWidth?: boolean;\n /** Surface the button sits on. On dark cards, secondary/outline switch to a\n dark face so they don't glare against the card. */\n tone?: \"light\" | \"dark\";\n /** Equal width and height, for icon-only layered buttons. */\n square?: boolean;\n}\n\n/* Layered buttons are a raised face seated in a machined bezel. The bezel\n never moves; the face lifts on hover and sinks flush when pressed. */\nconst shellConfig: Record<\n string,\n {\n bezel: string;\n bezelShadow: string;\n face: string;\n faceShadow: string;\n faceHoverShadow: string;\n facePressedShadow: string;\n text: string;\n textShadow: string;\n fill: string;\n }\n> = {\n primary: {\n bezel: \"var(--bezel-inverse)\",\n bezelShadow: \"var(--bezel-inverse-shadow)\",\n face: \"var(--face-inverse)\",\n faceShadow: \"var(--face-inverse-shadow)\",\n faceHoverShadow: \"var(--face-inverse-shadow-hover)\",\n facePressedShadow: \"var(--face-inverse-pressed)\",\n text: \"text-ink-inverse\",\n textShadow: \"var(--emboss-inverse-deep)\",\n fill: \"bg-ink-inverse/[0.05]\",\n },\n secondary: {\n bezel: \"var(--bezel)\",\n bezelShadow: \"var(--bezel-shadow)\",\n face: \"var(--face)\",\n faceShadow: \"var(--face-shadow)\",\n faceHoverShadow: \"var(--face-shadow-hover)\",\n facePressedShadow: \"var(--face-pressed)\",\n text: \"text-ink-soft\",\n textShadow: \"var(--emboss)\",\n fill: \"bg-ink/[0.025]\",\n },\n outline: {\n bezel: \"var(--outline-bezel)\",\n bezelShadow: \"var(--outline-bezel-shadow)\",\n face: \"var(--face)\",\n faceShadow: \"var(--outline-face-shadow)\",\n faceHoverShadow: \"var(--outline-face-shadow-hover)\",\n facePressedShadow: \"var(--face-pressed)\",\n text: \"text-ink-label\",\n textShadow: \"var(--emboss)\",\n fill: \"bg-ink/[0.025]\",\n },\n};\n\n/* Secondary on a dark surface: a raised face a few steps lighter than the\n card — present, but in the same family of tones. It's the mid material,\n the same one secondary keys wear everywhere in the dark theme. */\nconst darkSecondary = {\n bezel: \"var(--bezel-mid)\",\n bezelShadow: \"var(--bezel-mid-shadow)\",\n face: \"var(--face-mid)\",\n faceShadow: \"var(--face-mid-shadow)\",\n faceHoverShadow: \"var(--face-mid-shadow-hover)\",\n facePressedShadow: \"var(--face-dark-pressed)\",\n text: \"text-neutral-100\",\n textShadow: \"var(--emboss-inverse)\",\n fill: \"bg-white/[0.04]\",\n};\n\n/* Flat buttons (ghost, icon) are a single face without a bezel. */\nconst flatConfig: Record<\n ButtonVariant,\n {\n bg: string;\n background?: string;\n fill: string;\n shadow: string;\n hoverShadow: string;\n pressedShadow: string;\n }\n> = {\n primary: {\n bg: \"text-ink-inverse\",\n background: \"var(--face-inverse)\",\n fill: \"bg-ink-inverse/[0.08]\",\n shadow: \"var(--face-inverse-shadow)\",\n hoverShadow: \"var(--face-inverse-float-shadow)\",\n pressedShadow: \"var(--face-inverse-pressed)\",\n },\n secondary: {\n bg: \"text-ink-soft\",\n background: \"var(--face)\",\n fill: \"bg-ink/[0.035]\",\n shadow: \"var(--face-shadow)\",\n hoverShadow: \"var(--face-float-shadow)\",\n pressedShadow: \"var(--face-pressed)\",\n },\n ghost: {\n bg: \"bg-transparent text-ink-muted hover:text-ink-soft\",\n fill: \"bg-ink/[0.05]\",\n shadow: \"none\",\n hoverShadow: \"var(--ghost-shadow-hover)\",\n pressedShadow: \"var(--ghost-shadow-pressed)\",\n },\n outline: {\n bg: \"bg-transparent text-ink-label\",\n fill: \"bg-ink/[0.035]\",\n shadow: \"var(--outline-shadow)\",\n hoverShadow: \"var(--outline-shadow-hover)\",\n pressedShadow: \"var(--outline-shadow-pressed)\",\n },\n};\n\n/* Bezel radius minus bezel padding = face radius, so the curves stay concentric. */\nconst shellRadius: Record<ButtonSize, string> = {\n sm: \"rounded-[12px] p-[3px]\",\n md: \"rounded-[13px] p-[3px]\",\n lg: \"rounded-[15px] p-[3.5px]\",\n icon: \"\",\n};\n\nconst faceRadius: Record<ButtonSize, string> = {\n sm: \"rounded-[9px]\",\n md: \"rounded-[10px]\",\n lg: \"rounded-[11.5px]\",\n icon: \"\",\n};\n\nconst innerPadding: Record<ButtonSize, string> = {\n sm: \"px-4 py-2 text-[13px]\",\n md: \"px-5 py-2.5 text-sm\",\n lg: \"px-7 py-3 text-[15px]\",\n icon: \"w-7 h-7 p-0 text-[13px]\",\n};\n\nconst squarePadding: Record<ButtonSize, string> = {\n sm: \"w-9 h-9 p-0 text-[13px]\",\n md: \"w-10 h-10 p-0 text-sm\",\n lg: \"w-12 h-12 p-0 text-[15px]\",\n icon: \"\",\n};\n\nconst flatSizeStyles: Record<ButtonSize, string> = {\n sm: \"px-4 py-2.5 text-[13px] rounded-[10px]\",\n md: \"px-5 py-3 text-sm rounded-[10px]\",\n lg: \"px-7 py-3.5 text-[15px] rounded-xl\",\n icon: \"w-8 h-8 p-0 rounded-[10px] text-[13px]\",\n};\n\nexport function Button({\n variant = \"primary\",\n size = \"md\",\n fullWidth = false,\n tone = \"light\",\n square = false,\n className = \"\",\n children,\n disabled,\n onClick,\n onPointerDown,\n onPointerUp,\n onPointerLeave,\n onPointerCancel,\n onPointerEnter,\n style,\n ...rest\n}: ButtonProps) {\n const [pressed, setPressed] = useState(false);\n const [hovered, setHovered] = useState(false);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n /* The press state is the button's own; handlers passed in run alongside\n it rather than replacing it */\n const handlePointerDown = useCallback(\n (e: React.PointerEvent<HTMLButtonElement>) => {\n if (timerRef.current) clearTimeout(timerRef.current);\n setPressed(true);\n onPointerDown?.(e);\n },\n [onPointerDown],\n );\n\n const handlePointerUp = useCallback(\n (e: React.PointerEvent<HTMLButtonElement>) => {\n timerRef.current = setTimeout(() => setPressed(false), 150);\n onPointerUp?.(e);\n },\n [onPointerUp],\n );\n\n const handlePointerCancel = useCallback(\n (e: React.PointerEvent<HTMLButtonElement>) => {\n timerRef.current = setTimeout(() => setPressed(false), 150);\n onPointerCancel?.(e);\n },\n [onPointerCancel],\n );\n\n const handlePointerLeave = useCallback(\n (e: React.PointerEvent<HTMLButtonElement>) => {\n setHovered(false);\n if (timerRef.current) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => setPressed(false), 150);\n onPointerLeave?.(e);\n },\n [onPointerLeave],\n );\n\n const layered = variant !== \"ghost\" && size !== \"icon\";\n\n const handlers = {\n onClick,\n onPointerDown: handlePointerDown,\n onPointerUp: handlePointerUp,\n onPointerLeave: handlePointerLeave,\n onPointerCancel: handlePointerCancel,\n onPointerEnter: (e: React.PointerEvent<HTMLButtonElement>) => {\n setHovered(true);\n onPointerEnter?.(e);\n },\n };\n\n if (layered) {\n const cfg =\n tone === \"dark\" && (variant === \"secondary\" || variant === \"outline\")\n ? darkSecondary\n : shellConfig[variant];\n const faceShadow = pressed\n ? cfg.facePressedShadow\n : hovered\n ? cfg.faceHoverShadow\n : cfg.faceShadow;\n\n return (\n <button\n data-btn\n className={[\n \"group/btn relative inline-flex cursor-pointer select-none whitespace-nowrap\",\n shellRadius[size],\n \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink\",\n \"disabled:opacity-40 disabled:pointer-events-none\",\n fullWidth ? \"w-full\" : \"\",\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={{\n background: cfg.bezel,\n boxShadow: cfg.bezelShadow,\n ...style,\n }}\n disabled={disabled}\n {...handlers}\n {...rest}\n >\n <span\n className={[\n \"btn-face relative overflow-hidden flex items-center justify-center gap-1.5 font-medium\",\n faceRadius[size],\n cfg.text,\n square ? squarePadding[size] : [\"w-full\", innerPadding[size]].join(\" \"),\n ].join(\" \")}\n style={{\n background: cfg.face,\n boxShadow: faceShadow,\n textShadow: cfg.textShadow,\n transform: pressed\n ? \"translateY(0.5px) scale(0.985)\"\n : hovered\n ? \"translateY(-0.5px)\"\n : \"none\",\n transition:\n \"transform 240ms var(--ease-spring), box-shadow 200ms ease-out\",\n }}\n >\n <span\n aria-hidden=\"true\"\n className={[\n \"pointer-events-none absolute inset-0 origin-bottom transition-transform duration-300 ease-out\",\n pressed\n ? \"scale-y-100\"\n : \"scale-y-0 group-hover/btn:scale-y-100\",\n cfg.fill,\n ].join(\" \")}\n />\n <span className=\"btn-icon-nudge relative z-10 inline-flex items-center gap-1.5\">\n {children}\n </span>\n </span>\n </button>\n );\n }\n\n const cfg = flatConfig[variant];\n const currentShadow = pressed\n ? cfg.pressedShadow\n : hovered\n ? cfg.hoverShadow\n : cfg.shadow;\n\n return (\n <button\n data-btn\n className={[\n \"btn-face group/btn relative overflow-hidden inline-flex items-center justify-center gap-1.5 font-medium cursor-pointer select-none whitespace-nowrap\",\n \"transition-colors duration-200 ease-out\",\n \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink\",\n \"disabled:opacity-40 disabled:pointer-events-none\",\n cfg.bg,\n flatSizeStyles[size],\n fullWidth ? \"w-full\" : \"\",\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={{\n background: cfg.background,\n boxShadow: currentShadow,\n transform: pressed\n ? \"translateY(0.5px) scale(0.97)\"\n : hovered && cfg.background\n ? \"translateY(-0.5px)\"\n : \"none\",\n transition:\n \"transform 240ms var(--ease-spring), box-shadow 200ms ease-out, color 200ms ease-out\",\n ...style,\n }}\n disabled={disabled}\n {...handlers}\n {...rest}\n >\n <span\n aria-hidden=\"true\"\n className={[\n \"pointer-events-none absolute inset-0 origin-bottom transition-transform duration-300 ease-out\",\n pressed ? \"scale-y-100\" : \"scale-y-0 group-hover/btn:scale-y-100\",\n cfg.fill,\n ].join(\" \")}\n />\n <span className=\"relative z-10 inline-flex items-center gap-1.5\">\n {children}\n </span>\n </button>\n );\n}\n"],"mappings":"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","names":["cfg"]}
@@ -0,0 +1,19 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+
4
+ interface ButtonGroupItem {
5
+ label: string;
6
+ content: React.ReactNode;
7
+ onClick?: () => void;
8
+ href?: string;
9
+ /** Sized to its content; otherwise a square key for a lone icon */
10
+ wide?: boolean;
11
+ }
12
+ interface ButtonGroupProps {
13
+ items: ButtonGroupItem[];
14
+ label: string;
15
+ className?: string;
16
+ }
17
+ declare function ButtonGroup({ items, label, className }: ButtonGroupProps): react_jsx_runtime.JSX.Element;
18
+
19
+ export { ButtonGroup, type ButtonGroupItem };
@@ -0,0 +1,62 @@
1
+ // ../../components/ui/ButtonGroup.tsx
2
+ import React from "react";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ function ButtonGroup({ items, label, className = "" }) {
5
+ return /* @__PURE__ */ jsx(
6
+ "div",
7
+ {
8
+ role: "group",
9
+ "aria-label": label,
10
+ className: ["inline-flex w-fit rounded-[12px] p-[3px]", className].join(" "),
11
+ style: { background: "var(--bezel)", boxShadow: "var(--bezel-shadow)" },
12
+ children: /* @__PURE__ */ jsx(
13
+ "div",
14
+ {
15
+ className: "flex rounded-[9px] overflow-hidden",
16
+ style: { background: "var(--face)", boxShadow: "var(--face-shadow)" },
17
+ children: items.map((item, i) => {
18
+ const keyClass = [
19
+ "group/key relative h-9 inline-flex items-center justify-center gap-1.5 cursor-pointer",
20
+ "text-[13px] font-medium text-ink-soft outline-none",
21
+ "transition-[box-shadow,background] duration-150",
22
+ "active:bg-ink/[0.04] active:[box-shadow:var(--ghost-shadow-pressed)]",
23
+ "focus-visible:[box-shadow:var(--focus-ring-inset)]",
24
+ item.wide ? "px-4" : "w-10"
25
+ ].join(" ");
26
+ const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
27
+ /* @__PURE__ */ jsx(
28
+ "span",
29
+ {
30
+ "aria-hidden": true,
31
+ className: "pointer-events-none absolute inset-0 origin-bottom scale-y-0 bg-ink/[0.025] transition-transform duration-300 ease-out group-hover/key:scale-y-100"
32
+ }
33
+ ),
34
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex items-center gap-1.5", children: item.content })
35
+ ] });
36
+ const common = {
37
+ "aria-label": item.label,
38
+ title: item.wide ? void 0 : item.label,
39
+ className: keyClass,
40
+ style: { textShadow: "var(--emboss)" }
41
+ };
42
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
43
+ i > 0 && /* @__PURE__ */ jsx(
44
+ "span",
45
+ {
46
+ "aria-hidden": true,
47
+ className: "w-px self-stretch my-2 bg-groove",
48
+ style: { boxShadow: "var(--groove-lip)" }
49
+ }
50
+ ),
51
+ item.href ? /* @__PURE__ */ jsx("a", { href: item.href, ...common, children: inner }) : /* @__PURE__ */ jsx("button", { type: "button", onClick: item.onClick, ...common, children: inner })
52
+ ] }, item.label);
53
+ })
54
+ }
55
+ )
56
+ }
57
+ );
58
+ }
59
+ export {
60
+ ButtonGroup
61
+ };
62
+ //# sourceMappingURL=ButtonGroup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/ButtonGroup.tsx"],"sourcesContent":["import React from \"react\";\n\nexport interface ButtonGroupItem {\n label: string;\n content: React.ReactNode;\n onClick?: () => void;\n href?: string;\n /** Sized to its content; otherwise a square key for a lone icon */\n wide?: boolean;\n}\n\ninterface ButtonGroupProps {\n items: ButtonGroupItem[];\n label: string;\n className?: string;\n}\n\n/* One bezel, one face, several keys: a linked control in the same material\n as Button (sm), split by grooves rather than gaps. Each key fills from the\n bottom on hover and sinks when pressed, like a layered Button's face. */\nexport function ButtonGroup({ items, label, className = \"\" }: ButtonGroupProps) {\n return (\n <div\n role=\"group\"\n aria-label={label}\n className={[\"inline-flex w-fit rounded-[12px] p-[3px]\", className].join(\" \")}\n style={{ background: \"var(--bezel)\", boxShadow: \"var(--bezel-shadow)\" }}\n >\n <div\n className=\"flex rounded-[9px] overflow-hidden\"\n style={{ background: \"var(--face)\", boxShadow: \"var(--face-shadow)\" }}\n >\n {items.map((item, i) => {\n const keyClass = [\n \"group/key relative h-9 inline-flex items-center justify-center gap-1.5 cursor-pointer\",\n \"text-[13px] font-medium text-ink-soft outline-none\",\n \"transition-[box-shadow,background] duration-150\",\n \"active:bg-ink/[0.04] active:[box-shadow:var(--ghost-shadow-pressed)]\",\n \"focus-visible:[box-shadow:var(--focus-ring-inset)]\",\n item.wide ? \"px-4\" : \"w-10\",\n ].join(\" \");\n const inner = (\n <>\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-0 origin-bottom scale-y-0 bg-ink/[0.025] transition-transform duration-300 ease-out group-hover/key:scale-y-100\"\n />\n <span className=\"relative inline-flex items-center gap-1.5\">{item.content}</span>\n </>\n );\n const common = {\n \"aria-label\": item.label,\n title: item.wide ? undefined : item.label,\n className: keyClass,\n style: { textShadow: \"var(--emboss)\" },\n };\n\n return (\n <React.Fragment key={item.label}>\n {i > 0 && (\n <span\n aria-hidden\n className=\"w-px self-stretch my-2 bg-groove\"\n style={{ boxShadow: \"var(--groove-lip)\" }}\n />\n )}\n {item.href ? (\n <a href={item.href} {...common}>\n {inner}\n </a>\n ) : (\n <button type=\"button\" onClick={item.onClick} {...common}>\n {inner}\n </button>\n )}\n </React.Fragment>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":";AAAA,OAAO,WAAW;AA0CN,mBACE,KADF;AAtBL,SAAS,YAAY,EAAE,OAAO,OAAO,YAAY,GAAG,GAAqB;AAC9E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,WAAW,CAAC,4CAA4C,SAAS,EAAE,KAAK,GAAG;AAAA,MAC3E,OAAO,EAAE,YAAY,gBAAgB,WAAW,sBAAsB;AAAA,MAEtE;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO,EAAE,YAAY,eAAe,WAAW,qBAAqB;AAAA,UAEnE,gBAAM,IAAI,CAAC,MAAM,MAAM;AACtB,kBAAM,WAAW;AAAA,cACf;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,KAAK,OAAO,SAAS;AAAA,YACvB,EAAE,KAAK,GAAG;AACV,kBAAM,QACJ,iCACE;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAW;AAAA,kBACX,WAAU;AAAA;AAAA,cACZ;AAAA,cACA,oBAAC,UAAK,WAAU,6CAA6C,eAAK,SAAQ;AAAA,eAC5E;AAEF,kBAAM,SAAS;AAAA,cACb,cAAc,KAAK;AAAA,cACnB,OAAO,KAAK,OAAO,SAAY,KAAK;AAAA,cACpC,WAAW;AAAA,cACX,OAAO,EAAE,YAAY,gBAAgB;AAAA,YACvC;AAEA,mBACE,qBAAC,MAAM,UAAN,EACE;AAAA,kBAAI,KACH;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAW;AAAA,kBACX,WAAU;AAAA,kBACV,OAAO,EAAE,WAAW,oBAAoB;AAAA;AAAA,cAC1C;AAAA,cAED,KAAK,OACJ,oBAAC,OAAE,MAAM,KAAK,MAAO,GAAG,QACrB,iBACH,IAEA,oBAAC,YAAO,MAAK,UAAS,SAAS,KAAK,SAAU,GAAG,QAC9C,iBACH;AAAA,iBAfiB,KAAK,KAiB1B;AAAA,UAEJ,CAAC;AAAA;AAAA,MACH;AAAA;AAAA,EACF;AAEJ;","names":[]}