@blurrow/ui 0.1.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 (272) hide show
  1. package/README.md +60 -20
  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 +1644 -0
  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 +140 -0
  199. package/dist/registry/Alert.tsx +95 -0
  200. package/dist/registry/AuthorCard.tsx +67 -0
  201. package/dist/registry/Avatar.tsx +116 -0
  202. package/dist/registry/Badge.tsx +55 -0
  203. package/dist/registry/Banner.tsx +86 -0
  204. package/dist/registry/Breadcrumb.tsx +60 -0
  205. package/dist/registry/Button.tsx +361 -0
  206. package/dist/registry/ButtonGroup.tsx +82 -0
  207. package/dist/registry/Card.tsx +241 -0
  208. package/dist/registry/CardShell.tsx +78 -0
  209. package/dist/registry/Carousel.tsx +296 -0
  210. package/dist/registry/Checkbox.tsx +104 -0
  211. package/dist/registry/CloseButton.tsx +68 -0
  212. package/dist/registry/CodeBlock.tsx +202 -0
  213. package/dist/registry/ColorPicker.tsx +404 -0
  214. package/dist/registry/Combobox.tsx +318 -0
  215. package/dist/registry/CommandPalette.tsx +356 -0
  216. package/dist/registry/ContextMenu.tsx +241 -0
  217. package/dist/registry/DataList.tsx +49 -0
  218. package/dist/registry/DatePicker.tsx +494 -0
  219. package/dist/registry/DateRangePicker.tsx +232 -0
  220. package/dist/registry/Drawer.tsx +166 -0
  221. package/dist/registry/DropdownMenu.tsx +219 -0
  222. package/dist/registry/EmptyState.tsx +55 -0
  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 +108 -0
  228. package/dist/registry/Kbd.tsx +23 -0
  229. package/dist/registry/Modal.tsx +130 -0
  230. package/dist/registry/NumberInput.tsx +183 -0
  231. package/dist/registry/OtpInput.tsx +219 -0
  232. package/dist/registry/Pagination.tsx +160 -0
  233. package/dist/registry/Popover.tsx +116 -0
  234. package/dist/registry/Progress.tsx +66 -0
  235. package/dist/registry/QuoteCard.tsx +93 -0
  236. package/dist/registry/Radio.tsx +205 -0
  237. package/dist/registry/RadioCards.tsx +247 -0
  238. package/dist/registry/ReactionBar.tsx +101 -0
  239. package/dist/registry/ReadingProgress.tsx +51 -0
  240. package/dist/registry/ReadingTime.tsx +35 -0
  241. package/dist/registry/Select.tsx +265 -0
  242. package/dist/registry/Separator.tsx +52 -0
  243. package/dist/registry/ShareButtons.tsx +134 -0
  244. package/dist/registry/Sidebar.tsx +182 -0
  245. package/dist/registry/Skeleton.tsx +83 -0
  246. package/dist/registry/Slider.tsx +229 -0
  247. package/dist/registry/Spinner.tsx +32 -0
  248. package/dist/registry/SpotlightCard.tsx +106 -0
  249. package/dist/registry/Stat.tsx +82 -0
  250. package/dist/registry/StatCard.tsx +256 -0
  251. package/dist/registry/Steps.tsx +109 -0
  252. package/dist/registry/Table.tsx +123 -0
  253. package/dist/registry/TableOfContents.tsx +116 -0
  254. package/dist/registry/Tabs.tsx +136 -0
  255. package/dist/registry/TagInput.tsx +191 -0
  256. package/dist/registry/Textarea.tsx +84 -0
  257. package/dist/registry/TimePicker.tsx +315 -0
  258. package/dist/registry/Timeline.tsx +102 -0
  259. package/dist/registry/Toast.tsx +388 -0
  260. package/dist/registry/Toggle.tsx +96 -0
  261. package/dist/registry/Tooltip.tsx +160 -0
  262. package/dist/registry/styles.css +1207 -0
  263. package/dist/registry/useFloatingPanel.ts +112 -0
  264. package/dist/registry/useModalBehavior.ts +77 -0
  265. package/dist/styles.css +868 -62
  266. package/dist/useFloatingPanel.d.ts +24 -0
  267. package/dist/useFloatingPanel.js +89 -0
  268. package/dist/useFloatingPanel.js.map +1 -0
  269. package/dist/useModalBehavior.d.ts +15 -0
  270. package/dist/useModalBehavior.js +57 -0
  271. package/dist/useModalBehavior.js.map +1 -0
  272. package/package.json +23 -6
@@ -0,0 +1,366 @@
1
+ "use client";
2
+
3
+ // ../../components/ui/ColorPicker.tsx
4
+ import { useId, useRef, useState } from "react";
5
+ import { Portal } from "./Floating.js";
6
+ import { FieldLabel, FieldMessage } from "./FieldMessage.js";
7
+ import { useFloatingPanel } from "./useFloatingPanel.js";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ var clamp = (n, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, n));
10
+ function normalizeHex(input) {
11
+ const s = input.trim().replace(/^#/, "").toLowerCase();
12
+ if (/^[0-9a-f]{3}$/.test(s)) return "#" + s.split("").map((c) => c + c).join("");
13
+ if (/^[0-9a-f]{6}$/.test(s)) return "#" + s;
14
+ return null;
15
+ }
16
+ function hexToHsv(hex) {
17
+ const n = parseInt(hex.slice(1), 16);
18
+ const r = (n >> 16 & 255) / 255;
19
+ const g = (n >> 8 & 255) / 255;
20
+ const b = (n & 255) / 255;
21
+ const max = Math.max(r, g, b);
22
+ const d = max - Math.min(r, g, b);
23
+ let h = 0;
24
+ if (d) {
25
+ if (max === r) h = (g - b) / d % 6;
26
+ else if (max === g) h = (b - r) / d + 2;
27
+ else h = (r - g) / d + 4;
28
+ h = (h * 60 + 360) % 360;
29
+ }
30
+ return { h, s: max ? d / max : 0, v: max };
31
+ }
32
+ function hsvToHex({ h, s, v }) {
33
+ const f = (n) => {
34
+ const k = (n + h / 60) % 6;
35
+ return Math.round((v - v * s * Math.max(0, Math.min(k, 4 - k, 1))) * 255);
36
+ };
37
+ return "#" + [f(5), f(3), f(1)].map((x) => x.toString(16).padStart(2, "0")).join("");
38
+ }
39
+ var DEFAULT_SWATCHES = [
40
+ "#171717",
41
+ "#737373",
42
+ "#dc2626",
43
+ "#ea580c",
44
+ "#ca8a04",
45
+ "#16a34a",
46
+ "#0891b2",
47
+ "#2563eb",
48
+ "#7c3aed",
49
+ "#db2777"
50
+ ];
51
+ function ColorPanel({ value, onChange, swatches = DEFAULT_SWATCHES, autoFocus = false }) {
52
+ const [hsv, setHsv] = useState(() => hexToHsv(value));
53
+ const [draft, setDraft] = useState(value);
54
+ const [seen, setSeen] = useState(value);
55
+ if (value !== seen) {
56
+ setSeen(value);
57
+ if (value !== hsvToHex(hsv)) {
58
+ setHsv(hexToHsv(value));
59
+ setDraft(value);
60
+ }
61
+ }
62
+ const update = (next) => {
63
+ setHsv(next);
64
+ const hex = hsvToHex(next);
65
+ setDraft(hex);
66
+ if (hex !== value) onChange(hex);
67
+ };
68
+ const pickHex = (hex) => {
69
+ setHsv(hexToHsv(hex));
70
+ setDraft(hex);
71
+ if (hex !== value) onChange(hex);
72
+ };
73
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-[236px] flex-col gap-3", children: [
74
+ /* @__PURE__ */ jsx(SaturationArea, { hsv, onChange: update, autoFocus }),
75
+ /* @__PURE__ */ jsx(HueStrip, { hue: hsv.h, onChange: (h) => update({ ...hsv, h }) }),
76
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
77
+ /* @__PURE__ */ jsx(
78
+ "span",
79
+ {
80
+ "aria-hidden": true,
81
+ className: "h-9 w-9 shrink-0 rounded-[10px]",
82
+ style: { background: hsvToHex(hsv), boxShadow: "var(--tile-shadow), inset 0 0 0 1px var(--hairline)" }
83
+ }
84
+ ),
85
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
86
+ /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute left-3 top-1/2 z-20 -translate-y-1/2 text-[12.5px] text-ink-faint", children: "#" }),
87
+ /* @__PURE__ */ jsx(
88
+ "input",
89
+ {
90
+ "aria-label": "hex colour",
91
+ value: draft.replace(/^#/, ""),
92
+ maxLength: 7,
93
+ spellCheck: false,
94
+ autoComplete: "off",
95
+ onChange: (e) => {
96
+ setDraft("#" + e.target.value.replace(/^#/, ""));
97
+ const hex = normalizeHex(e.target.value);
98
+ if (hex && e.target.value.replace(/^#/, "").length === 6) pickHex(hex);
99
+ },
100
+ onBlur: () => {
101
+ const hex = normalizeHex(draft);
102
+ if (hex) pickHex(hex);
103
+ else setDraft(hsvToHex(hsv));
104
+ },
105
+ onKeyDown: (e) => {
106
+ if (e.key === "Enter") {
107
+ e.preventDefault();
108
+ e.target.blur();
109
+ }
110
+ },
111
+ className: "field relative z-10 w-full rounded-[10px] py-2 pl-6 pr-3 font-mono text-[12.5px] uppercase text-ink caret-ink"
112
+ }
113
+ )
114
+ ] })
115
+ ] }),
116
+ swatches.length > 0 && /* @__PURE__ */ jsx("div", { role: "group", "aria-label": "swatches", className: "grid grid-cols-10 gap-1.5 border-t border-line-strong pt-3", children: swatches.map((s) => {
117
+ const hex = normalizeHex(s) ?? s;
118
+ const on = hex === hsvToHex(hsv);
119
+ return /* @__PURE__ */ jsx(
120
+ "button",
121
+ {
122
+ type: "button",
123
+ "aria-label": hex,
124
+ "aria-pressed": on,
125
+ onClick: () => pickHex(hex),
126
+ className: "relative aspect-square cursor-pointer rounded-full transition-transform duration-200 ease-[var(--ease-spring)] hover:scale-115 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink",
127
+ style: {
128
+ background: hex,
129
+ boxShadow: on ? "0 0 0 2px var(--panel-face), 0 0 0 3.5px var(--ink)" : "inset 0 0 0 1px var(--hairline), inset 0 1px 1px rgba(255,255,255,0.3), var(--dot-shadow)"
130
+ }
131
+ },
132
+ s
133
+ );
134
+ }) })
135
+ ] });
136
+ }
137
+ function useDrag(onMove) {
138
+ const ref = useRef(null);
139
+ const [dragging, setDragging] = useState(false);
140
+ const at = (e) => {
141
+ const r = ref.current.getBoundingClientRect();
142
+ onMove(clamp((e.clientX - r.left) / r.width), clamp((e.clientY - r.top) / r.height));
143
+ };
144
+ const handlers = {
145
+ onPointerDown: (e) => {
146
+ if (e.button !== 0) return;
147
+ e.preventDefault();
148
+ e.currentTarget.setPointerCapture(e.pointerId);
149
+ e.currentTarget.querySelector("[data-thumb]")?.focus();
150
+ setDragging(true);
151
+ at(e);
152
+ },
153
+ onPointerMove: (e) => dragging && at(e),
154
+ onPointerUp: () => setDragging(false),
155
+ onPointerCancel: () => setDragging(false)
156
+ };
157
+ return { ref, dragging, handlers };
158
+ }
159
+ var thumbStyle = (color, dragging) => ({
160
+ background: color,
161
+ boxShadow: "0 0 0 2.5px #fff, 0 0 0 3px rgba(17,17,17,0.1), 0 2px 6px rgba(17,17,17,0.3), inset 0 0 0 1px rgba(17,17,17,0.1)",
162
+ transform: `translate(-50%, -50%) scale(${dragging ? 1.15 : 1})`,
163
+ transition: "transform 200ms var(--ease-spring)"
164
+ });
165
+ function SaturationArea({ hsv, onChange, autoFocus }) {
166
+ const { ref, dragging, handlers } = useDrag((x, y) => onChange({ ...hsv, s: x, v: 1 - y }));
167
+ const onKeyDown = (e) => {
168
+ const d = e.shiftKey ? 0.1 : 0.01;
169
+ const moves = {
170
+ ArrowLeft: { s: clamp(hsv.s - d) },
171
+ ArrowRight: { s: clamp(hsv.s + d) },
172
+ ArrowUp: { v: clamp(hsv.v + d) },
173
+ ArrowDown: { v: clamp(hsv.v - d) }
174
+ };
175
+ if (moves[e.key]) {
176
+ e.preventDefault();
177
+ onChange({ ...hsv, ...moves[e.key] });
178
+ }
179
+ };
180
+ return /* @__PURE__ */ jsx(
181
+ "div",
182
+ {
183
+ ref,
184
+ ...handlers,
185
+ className: "relative h-[148px] cursor-crosshair touch-none rounded-[10px]",
186
+ style: {
187
+ background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(${hsv.h} 100% 50%))`,
188
+ /* Cut into the face like a field: shade inside, lit lip below */
189
+ boxShadow: "inset 0 0 0 1px var(--hairline), inset 0 1px 3px rgba(0,0,0,0.25)"
190
+ },
191
+ children: /* @__PURE__ */ jsx(
192
+ "span",
193
+ {
194
+ "data-thumb": true,
195
+ ref: autoFocus ? (el) => el?.focus({ preventScroll: true }) : void 0,
196
+ role: "slider",
197
+ tabIndex: 0,
198
+ "aria-label": "saturation and brightness",
199
+ "aria-valuetext": `saturation ${Math.round(hsv.s * 100)}%, brightness ${Math.round(hsv.v * 100)}%`,
200
+ "aria-valuenow": Math.round(hsv.s * 100),
201
+ "aria-valuemin": 0,
202
+ "aria-valuemax": 100,
203
+ onKeyDown,
204
+ className: "absolute h-3.5 w-3.5 rounded-full focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-ink",
205
+ style: { left: `${hsv.s * 100}%`, top: `${(1 - hsv.v) * 100}%`, ...thumbStyle(hsvToHex(hsv), dragging) }
206
+ }
207
+ )
208
+ }
209
+ );
210
+ }
211
+ function HueStrip({ hue, onChange }) {
212
+ const { ref, dragging, handlers } = useDrag((x) => onChange(Math.min(359.9, x * 360)));
213
+ const onKeyDown = (e) => {
214
+ const d = e.shiftKey ? 10 : 1;
215
+ if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
216
+ e.preventDefault();
217
+ onChange((hue - d + 360) % 360);
218
+ }
219
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") {
220
+ e.preventDefault();
221
+ onChange((hue + d) % 360);
222
+ }
223
+ };
224
+ return /* @__PURE__ */ jsxs(
225
+ "div",
226
+ {
227
+ ref,
228
+ ...handlers,
229
+ className: "relative mx-[7px] h-3 cursor-pointer touch-none",
230
+ children: [
231
+ /* @__PURE__ */ jsx(
232
+ "span",
233
+ {
234
+ "aria-hidden": true,
235
+ className: "absolute -inset-x-[7px] inset-y-0 rounded-full",
236
+ style: {
237
+ background: "linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%)",
238
+ boxShadow: "inset 0 0 0 1px var(--hairline), inset 0 1px 2px rgba(0,0,0,0.3)"
239
+ }
240
+ }
241
+ ),
242
+ /* @__PURE__ */ jsx(
243
+ "span",
244
+ {
245
+ "data-thumb": true,
246
+ role: "slider",
247
+ tabIndex: 0,
248
+ "aria-label": "hue",
249
+ "aria-valuenow": Math.round(hue),
250
+ "aria-valuemin": 0,
251
+ "aria-valuemax": 360,
252
+ onKeyDown,
253
+ className: "absolute top-1/2 h-3.5 w-3.5 rounded-full focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-ink",
254
+ style: { left: `${hue / 360 * 100}%`, ...thumbStyle(`hsl(${hue} 100% 50%)`, dragging) }
255
+ }
256
+ )
257
+ ]
258
+ }
259
+ );
260
+ }
261
+ function ColorPicker({
262
+ value,
263
+ defaultValue = "#2563eb",
264
+ onChange,
265
+ label,
266
+ hint,
267
+ swatches,
268
+ inline = false,
269
+ name,
270
+ disabled = false,
271
+ fullWidth = false,
272
+ className = ""
273
+ }) {
274
+ const uid = useId();
275
+ const ref = useRef(null);
276
+ const buttonRef = useRef(null);
277
+ const [internal, setInternal] = useState(normalizeHex(defaultValue) ?? "#000000");
278
+ const color = (value !== void 0 ? normalizeHex(value) : null) ?? internal;
279
+ const panel = useFloatingPanel(ref, { onEscape: () => buttonRef.current?.focus() });
280
+ const { setFloating, panelStyle } = panel;
281
+ const set = (hex) => {
282
+ setInternal(hex);
283
+ onChange?.(hex);
284
+ };
285
+ const labelEl = label && /* @__PURE__ */ jsx(FieldLabel, { id: `${uid}-label`, children: label });
286
+ const hintEl = /* @__PURE__ */ jsx(FieldMessage, { hint });
287
+ const hidden = name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: color });
288
+ if (inline) {
289
+ return /* @__PURE__ */ jsxs("div", { role: "group", "aria-labelledby": label ? `${uid}-label` : void 0, className: ["flex flex-col gap-1.5", className].join(" "), children: [
290
+ labelEl,
291
+ /* @__PURE__ */ jsx("div", { className: "w-fit rounded-[16px] p-1.5", style: { background: "var(--panel-bezel)", boxShadow: "var(--panel-shadow)" }, children: /* @__PURE__ */ jsx("div", { className: "rounded-[11px] bg-panel-face p-3", style: { boxShadow: "var(--panel-face-shadow)" }, children: /* @__PURE__ */ jsx(ColorPanel, { value: color, onChange: set, swatches }) }) }),
292
+ hintEl,
293
+ hidden
294
+ ] });
295
+ }
296
+ return /* @__PURE__ */ jsxs("div", { className: ["group/field flex flex-col gap-1.5", fullWidth ? "w-full" : ""].join(" "), children: [
297
+ labelEl,
298
+ /* @__PURE__ */ jsxs("div", { ref, className: ["relative", fullWidth ? "w-full" : "inline-block w-48", className].join(" "), children: [
299
+ /* @__PURE__ */ jsxs(
300
+ "button",
301
+ {
302
+ ref: buttonRef,
303
+ type: "button",
304
+ disabled,
305
+ onClick: panel.toggle,
306
+ "aria-haspopup": "dialog",
307
+ "aria-expanded": panel.open,
308
+ "aria-controls": panel.open ? `${uid}-panel` : void 0,
309
+ "aria-labelledby": label ? `${uid}-label ${uid}-value` : void 0,
310
+ "data-open": panel.open || void 0,
311
+ className: "field group/color relative isolate flex w-full cursor-pointer items-center gap-2.5 rounded-xl py-2.5 pl-2.5 pr-4 text-left focus:outline-none disabled:opacity-40 disabled:pointer-events-none",
312
+ children: [
313
+ /* @__PURE__ */ jsx(
314
+ "span",
315
+ {
316
+ "aria-hidden": true,
317
+ className: "h-6 w-6 shrink-0 rounded-[7px] transition-transform duration-300 ease-[var(--ease-spring)] group-hover/color:scale-105",
318
+ style: {
319
+ background: color,
320
+ boxShadow: "var(--key-shadow), inset 0 1px 1px rgba(255,255,255,0.3), inset 0 0 0 1px var(--hairline)"
321
+ }
322
+ }
323
+ ),
324
+ /* @__PURE__ */ jsx("span", { id: `${uid}-value`, className: "flex-1 font-mono text-[13px] uppercase tracking-[0.02em] text-ink", children: color }),
325
+ /* @__PURE__ */ jsx(
326
+ "svg",
327
+ {
328
+ "aria-hidden": true,
329
+ width: "14",
330
+ height: "14",
331
+ fill: "none",
332
+ stroke: "currentColor",
333
+ strokeWidth: "2",
334
+ viewBox: "0 0 24 24",
335
+ className: [
336
+ "shrink-0 transition-[transform,color] duration-300 ease-[var(--ease-spring)]",
337
+ panel.open ? "rotate-180 text-ink" : "text-ink-faint group-hover/color:text-ink-label"
338
+ ].join(" "),
339
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" })
340
+ }
341
+ )
342
+ ]
343
+ }
344
+ ),
345
+ hidden
346
+ ] }),
347
+ panel.mounted && /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
348
+ "div",
349
+ {
350
+ ref: setFloating,
351
+ id: `${uid}-panel`,
352
+ role: "dialog",
353
+ "aria-label": label ? `choose ${label}` : "choose a colour",
354
+ className: "rounded-[16px] p-1.5",
355
+ style: panelStyle,
356
+ children: /* @__PURE__ */ jsx("div", { className: "rounded-[11px] bg-panel-face p-3", style: { boxShadow: "var(--panel-face-shadow)" }, children: /* @__PURE__ */ jsx(ColorPanel, { value: color, onChange: set, swatches, autoFocus: true }) })
357
+ }
358
+ ) }),
359
+ hintEl
360
+ ] });
361
+ }
362
+ export {
363
+ ColorPicker,
364
+ normalizeHex
365
+ };
366
+ //# sourceMappingURL=ColorPicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/ColorPicker.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useId, useRef, useState } from \"react\";\nimport { Portal } from \"./Floating\";\nimport { FieldLabel, FieldMessage } from \"./FieldMessage\";\nimport { useFloatingPanel } from \"./useFloatingPanel\";\n\n/* ── Colour maths ─────────────────────────────────────────────────── */\ntype HSV = { h: number; s: number; v: number };\n\nconst clamp = (n: number, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, n));\n\n/** Accepts #rgb, #rrggbb, with or without the hash. Returns #rrggbb or null */\nexport function normalizeHex(input: string): string | null {\n const s = input.trim().replace(/^#/, \"\").toLowerCase();\n if (/^[0-9a-f]{3}$/.test(s)) return \"#\" + s.split(\"\").map((c) => c + c).join(\"\");\n if (/^[0-9a-f]{6}$/.test(s)) return \"#\" + s;\n return null;\n}\n\nfunction hexToHsv(hex: string): HSV {\n const n = parseInt(hex.slice(1), 16);\n const r = ((n >> 16) & 255) / 255;\n const g = ((n >> 8) & 255) / 255;\n const b = (n & 255) / 255;\n const max = Math.max(r, g, b);\n const d = max - Math.min(r, g, b);\n let h = 0;\n if (d) {\n if (max === r) h = ((g - b) / d) % 6;\n else if (max === g) h = (b - r) / d + 2;\n else h = (r - g) / d + 4;\n h = (h * 60 + 360) % 360;\n }\n return { h, s: max ? d / max : 0, v: max };\n}\n\nfunction hsvToHex({ h, s, v }: HSV): string {\n const f = (n: number) => {\n const k = (n + h / 60) % 6;\n return Math.round((v - v * s * Math.max(0, Math.min(k, 4 - k, 1))) * 255);\n };\n return \"#\" + [f(5), f(3), f(1)].map((x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nconst DEFAULT_SWATCHES = [\n \"#171717\", \"#737373\", \"#dc2626\", \"#ea580c\", \"#ca8a04\",\n \"#16a34a\", \"#0891b2\", \"#2563eb\", \"#7c3aed\", \"#db2777\",\n];\n\n/* ── The panel: area, hue strip, hex field, swatches ──────────────── */\ninterface ColorPanelProps {\n value: string;\n onChange: (hex: string) => void;\n swatches?: string[];\n /** Focus the saturation knob as soon as the panel mounts */\n autoFocus?: boolean;\n}\n\nfunction ColorPanel({ value, onChange, swatches = DEFAULT_SWATCHES, autoFocus = false }: ColorPanelProps) {\n // Hue and saturation are kept apart from the hex, so dragging through\n // grey or black doesn't forget which colour you were on\n const [hsv, setHsv] = useState<HSV>(() => hexToHsv(value));\n const [draft, setDraft] = useState(value);\n\n // A colour set from outside (not one we just sent up) resets the panel\n const [seen, setSeen] = useState(value);\n if (value !== seen) {\n setSeen(value);\n if (value !== hsvToHex(hsv)) {\n setHsv(hexToHsv(value));\n setDraft(value);\n }\n }\n\n const update = (next: HSV) => {\n setHsv(next);\n const hex = hsvToHex(next);\n setDraft(hex);\n if (hex !== value) onChange(hex);\n };\n\n const pickHex = (hex: string) => {\n setHsv(hexToHsv(hex));\n setDraft(hex);\n if (hex !== value) onChange(hex);\n };\n\n return (\n <div className=\"flex w-[236px] flex-col gap-3\">\n <SaturationArea hsv={hsv} onChange={update} autoFocus={autoFocus} />\n <HueStrip hue={hsv.h} onChange={(h) => update({ ...hsv, h })} />\n\n <div className=\"flex items-center gap-2\">\n <span\n aria-hidden\n className=\"h-9 w-9 shrink-0 rounded-[10px]\"\n style={{ background: hsvToHex(hsv), boxShadow: \"var(--tile-shadow), inset 0 0 0 1px var(--hairline)\" }}\n />\n <div className=\"relative flex-1\">\n <span className=\"pointer-events-none absolute left-3 top-1/2 z-20 -translate-y-1/2 text-[12.5px] text-ink-faint\">#</span>\n <input\n aria-label=\"hex colour\"\n value={draft.replace(/^#/, \"\")}\n maxLength={7}\n spellCheck={false}\n autoComplete=\"off\"\n onChange={(e) => {\n setDraft(\"#\" + e.target.value.replace(/^#/, \"\"));\n const hex = normalizeHex(e.target.value);\n if (hex && e.target.value.replace(/^#/, \"\").length === 6) pickHex(hex);\n }}\n onBlur={() => {\n const hex = normalizeHex(draft);\n if (hex) pickHex(hex);\n else setDraft(hsvToHex(hsv));\n }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n (e.target as HTMLInputElement).blur();\n }\n }}\n className=\"field relative z-10 w-full rounded-[10px] py-2 pl-6 pr-3 font-mono text-[12.5px] uppercase text-ink caret-ink\"\n />\n </div>\n </div>\n\n {swatches.length > 0 && (\n <div role=\"group\" aria-label=\"swatches\" className=\"grid grid-cols-10 gap-1.5 border-t border-line-strong pt-3\">\n {swatches.map((s) => {\n const hex = normalizeHex(s) ?? s;\n const on = hex === hsvToHex(hsv);\n return (\n <button\n key={s}\n type=\"button\"\n aria-label={hex}\n aria-pressed={on}\n onClick={() => pickHex(hex)}\n className=\"relative aspect-square cursor-pointer rounded-full transition-transform duration-200 ease-[var(--ease-spring)] hover:scale-115 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink\"\n style={{\n background: hex,\n boxShadow: on\n ? \"0 0 0 2px var(--panel-face), 0 0 0 3.5px var(--ink)\"\n : \"inset 0 0 0 1px var(--hairline), inset 0 1px 1px rgba(255,255,255,0.3), var(--dot-shadow)\",\n }}\n />\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\n/* Drag anywhere on an element and get back where you are, 0…1 on each axis */\nfunction useDrag(onMove: (x: number, y: number) => void) {\n const ref = useRef<HTMLDivElement>(null);\n const [dragging, setDragging] = useState(false);\n const at = (e: React.PointerEvent) => {\n const r = ref.current!.getBoundingClientRect();\n onMove(clamp((e.clientX - r.left) / r.width), clamp((e.clientY - r.top) / r.height));\n };\n const handlers = {\n onPointerDown: (e: React.PointerEvent) => {\n if (e.button !== 0) return;\n e.preventDefault();\n (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);\n (e.currentTarget.querySelector(\"[data-thumb]\") as HTMLElement | null)?.focus();\n setDragging(true);\n at(e);\n },\n onPointerMove: (e: React.PointerEvent) => dragging && at(e),\n onPointerUp: () => setDragging(false),\n onPointerCancel: () => setDragging(false),\n };\n return { ref, dragging, handlers };\n}\n\n/* A raised white ring that rides on the area and the strip */\nconst thumbStyle = (color: string, dragging: boolean): React.CSSProperties => ({\n background: color,\n boxShadow:\n \"0 0 0 2.5px #fff, 0 0 0 3px rgba(17,17,17,0.1), 0 2px 6px rgba(17,17,17,0.3), inset 0 0 0 1px rgba(17,17,17,0.1)\",\n transform: `translate(-50%, -50%) scale(${dragging ? 1.15 : 1})`,\n transition: \"transform 200ms var(--ease-spring)\",\n});\n\nfunction SaturationArea({ hsv, onChange, autoFocus }: { hsv: HSV; onChange: (hsv: HSV) => void; autoFocus?: boolean }) {\n const { ref, dragging, handlers } = useDrag((x, y) => onChange({ ...hsv, s: x, v: 1 - y }));\n const onKeyDown = (e: React.KeyboardEvent) => {\n const d = e.shiftKey ? 0.1 : 0.01;\n const moves: Record<string, Partial<HSV>> = {\n ArrowLeft: { s: clamp(hsv.s - d) },\n ArrowRight: { s: clamp(hsv.s + d) },\n ArrowUp: { v: clamp(hsv.v + d) },\n ArrowDown: { v: clamp(hsv.v - d) },\n };\n if (moves[e.key]) {\n e.preventDefault();\n onChange({ ...hsv, ...moves[e.key] });\n }\n };\n return (\n <div\n ref={ref}\n {...handlers}\n className=\"relative h-[148px] cursor-crosshair touch-none rounded-[10px]\"\n style={{\n background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(${hsv.h} 100% 50%))`,\n /* Cut into the face like a field: shade inside, lit lip below */\n boxShadow: \"inset 0 0 0 1px var(--hairline), inset 0 1px 3px rgba(0,0,0,0.25)\",\n }}\n >\n <span\n data-thumb\n ref={autoFocus ? (el) => el?.focus({ preventScroll: true }) : undefined}\n role=\"slider\"\n tabIndex={0}\n aria-label=\"saturation and brightness\"\n aria-valuetext={`saturation ${Math.round(hsv.s * 100)}%, brightness ${Math.round(hsv.v * 100)}%`}\n aria-valuenow={Math.round(hsv.s * 100)}\n aria-valuemin={0}\n aria-valuemax={100}\n onKeyDown={onKeyDown}\n className=\"absolute h-3.5 w-3.5 rounded-full focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-ink\"\n style={{ left: `${hsv.s * 100}%`, top: `${(1 - hsv.v) * 100}%`, ...thumbStyle(hsvToHex(hsv), dragging) }}\n />\n </div>\n );\n}\n\nfunction HueStrip({ hue, onChange }: { hue: number; onChange: (h: number) => void }) {\n const { ref, dragging, handlers } = useDrag((x) => onChange(Math.min(359.9, x * 360)));\n const onKeyDown = (e: React.KeyboardEvent) => {\n const d = e.shiftKey ? 10 : 1;\n if (e.key === \"ArrowLeft\" || e.key === \"ArrowDown\") { e.preventDefault(); onChange((hue - d + 360) % 360); }\n if (e.key === \"ArrowRight\" || e.key === \"ArrowUp\") { e.preventDefault(); onChange((hue + d) % 360); }\n };\n return (\n <div\n ref={ref}\n {...handlers}\n className=\"relative mx-[7px] h-3 cursor-pointer touch-none\"\n >\n <span\n aria-hidden\n className=\"absolute -inset-x-[7px] inset-y-0 rounded-full\"\n style={{\n background: \"linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%)\",\n boxShadow: \"inset 0 0 0 1px var(--hairline), inset 0 1px 2px rgba(0,0,0,0.3)\",\n }}\n />\n <span\n data-thumb\n role=\"slider\"\n tabIndex={0}\n aria-label=\"hue\"\n aria-valuenow={Math.round(hue)}\n aria-valuemin={0}\n aria-valuemax={360}\n onKeyDown={onKeyDown}\n className=\"absolute top-1/2 h-3.5 w-3.5 rounded-full focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-ink\"\n style={{ left: `${(hue / 360) * 100}%`, ...thumbStyle(`hsl(${hue} 100% 50%)`, dragging) }}\n />\n </div>\n );\n}\n\n/* ── ColorPicker ──────────────────────────────────────────────────── */\ninterface ColorPickerProps {\n /** A hex colour, like \"#2563eb\" */\n value?: string;\n defaultValue?: string;\n onChange?: (hex: string) => void;\n label?: string;\n hint?: string;\n /** Preset colours under the picker; pass [] to hide them */\n swatches?: string[];\n /** Render the panel straight onto the page instead of behind a field */\n inline?: boolean;\n /** Submit the colour with a form */\n name?: string;\n disabled?: boolean;\n fullWidth?: boolean;\n className?: string;\n}\n\n/* Closed, it's a sunken field holding a small raised chip of the colour.\n Open, a floating panel with a saturation area cut into its face, a hue\n strip, a hex field and a row of swatches. */\nexport function ColorPicker({\n value,\n defaultValue = \"#2563eb\",\n onChange,\n label,\n hint,\n swatches,\n inline = false,\n name,\n disabled = false,\n fullWidth = false,\n className = \"\",\n}: ColorPickerProps) {\n const uid = useId();\n const ref = useRef<HTMLDivElement>(null);\n const buttonRef = useRef<HTMLButtonElement>(null);\n const [internal, setInternal] = useState(normalizeHex(defaultValue) ?? \"#000000\");\n const color = (value !== undefined ? normalizeHex(value) : null) ?? internal;\n const panel = useFloatingPanel(ref, { onEscape: () => buttonRef.current?.focus() });\n const { setFloating, panelStyle } = panel;\n\n const set = (hex: string) => {\n setInternal(hex);\n onChange?.(hex);\n };\n\n const labelEl = label && <FieldLabel id={`${uid}-label`}>{label}</FieldLabel>;\n const hintEl = <FieldMessage hint={hint} />;\n const hidden = name && <input type=\"hidden\" name={name} value={color} />;\n\n if (inline) {\n return (\n <div role=\"group\" aria-labelledby={label ? `${uid}-label` : undefined} className={[\"flex flex-col gap-1.5\", className].join(\" \")}>\n {labelEl}\n <div className=\"w-fit rounded-[16px] p-1.5\" style={{ background: \"var(--panel-bezel)\", boxShadow: \"var(--panel-shadow)\" }}>\n <div className=\"rounded-[11px] bg-panel-face p-3\" style={{ boxShadow: \"var(--panel-face-shadow)\" }}>\n <ColorPanel value={color} onChange={set} swatches={swatches} />\n </div>\n </div>\n {hintEl}\n {hidden}\n </div>\n );\n }\n\n return (\n <div className={[\"group/field flex flex-col gap-1.5\", fullWidth ? \"w-full\" : \"\"].join(\" \")}>\n {labelEl}\n <div ref={ref} className={[\"relative\", fullWidth ? \"w-full\" : \"inline-block w-48\", className].join(\" \")}>\n <button\n ref={buttonRef}\n type=\"button\"\n disabled={disabled}\n onClick={panel.toggle}\n aria-haspopup=\"dialog\"\n aria-expanded={panel.open}\n aria-controls={panel.open ? `${uid}-panel` : undefined}\n aria-labelledby={label ? `${uid}-label ${uid}-value` : undefined}\n data-open={panel.open || undefined}\n className=\"field group/color relative isolate flex w-full cursor-pointer items-center gap-2.5 rounded-xl py-2.5 pl-2.5 pr-4 text-left focus:outline-none disabled:opacity-40 disabled:pointer-events-none\"\n >\n <span\n aria-hidden\n className=\"h-6 w-6 shrink-0 rounded-[7px] transition-transform duration-300 ease-[var(--ease-spring)] group-hover/color:scale-105\"\n style={{\n background: color,\n boxShadow:\n \"var(--key-shadow), inset 0 1px 1px rgba(255,255,255,0.3), inset 0 0 0 1px var(--hairline)\",\n }}\n />\n <span id={`${uid}-value`} className=\"flex-1 font-mono text-[13px] uppercase tracking-[0.02em] text-ink\">\n {color}\n </span>\n <svg\n aria-hidden\n width=\"14\"\n height=\"14\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n className={[\n \"shrink-0 transition-[transform,color] duration-300 ease-[var(--ease-spring)]\",\n panel.open ? \"rotate-180 text-ink\" : \"text-ink-faint group-hover/color:text-ink-label\",\n ].join(\" \")}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n\n {hidden}\n </div>\n {panel.mounted && (\n <Portal>\n <div\n ref={setFloating}\n id={`${uid}-panel`}\n role=\"dialog\"\n aria-label={label ? `choose ${label}` : \"choose a colour\"}\n className=\"rounded-[16px] p-1.5\"\n style={panelStyle}\n >\n <div className=\"rounded-[11px] bg-panel-face p-3\" style={{ boxShadow: \"var(--panel-face-shadow)\" }}>\n <ColorPanel value={color} onChange={set} swatches={swatches} autoFocus />\n </div>\n </div>\n </Portal>\n )}\n {hintEl}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,34 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface ComboboxOption {
4
+ value: string;
5
+ label: string;
6
+ /** A second, quieter line under the label */
7
+ description?: string;
8
+ /** Extra words to match on that aren't shown */
9
+ keywords?: string[];
10
+ disabled?: boolean;
11
+ }
12
+ interface ComboboxProps {
13
+ options: ComboboxOption[];
14
+ value?: string | null;
15
+ defaultValue?: string | null;
16
+ onChange?: (value: string | null) => void;
17
+ placeholder?: string;
18
+ label?: string;
19
+ hint?: string;
20
+ error?: string;
21
+ /** Shown when nothing matches */
22
+ emptyText?: string;
23
+ /** Replace the default "contains" match */
24
+ filter?: (option: ComboboxOption, query: string) => boolean;
25
+ /** Show a key that empties the field */
26
+ clearable?: boolean;
27
+ disabled?: boolean;
28
+ fullWidth?: boolean;
29
+ name?: string;
30
+ className?: string;
31
+ }
32
+ declare function Combobox({ options, value, defaultValue, onChange, placeholder, label, hint, error, emptyText, filter, clearable, disabled, fullWidth, name, className, }: ComboboxProps): react_jsx_runtime.JSX.Element;
33
+
34
+ export { Combobox, type ComboboxOption };