@robr0/design-system 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +33 -28
  2. package/components/Accordion/Accordion.js +0 -1
  3. package/components/AgentStatus/AgentStatus.css +436 -0
  4. package/components/AgentStatus/AgentStatus.d.ts +37 -0
  5. package/components/AgentStatus/AgentStatus.js +53 -0
  6. package/components/AgentStatus/AgentStatusPatterns.d.ts +17 -0
  7. package/components/AgentStatus/AgentStatusPatterns.js +20 -0
  8. package/components/AlertDialog/AlertDialog.css +5 -5
  9. package/components/Avatar/Avatar.js +1 -0
  10. package/components/Breadcrumb/Breadcrumb.d.ts +7 -1
  11. package/components/Breadcrumb/Breadcrumb.js +3 -2
  12. package/components/Button/Button.css +18 -0
  13. package/components/Button/Button.d.ts +20 -2
  14. package/components/Button/Button.js +13 -3
  15. package/components/ButtonGroup/ButtonGroup.js +2 -9
  16. package/components/Card/Card.css +6 -0
  17. package/components/Checkbox/Checkbox.d.ts +17 -3
  18. package/components/CircularButton/CircularButton.css +11 -0
  19. package/components/CircularButton/CircularButton.d.ts +15 -3
  20. package/components/CircularButton/CircularButton.js +19 -6
  21. package/components/CodeBlock/CodeBlock.js +10 -1
  22. package/components/ColorPicker/ColorPicker.css +278 -0
  23. package/components/ColorPicker/ColorPicker.d.ts +50 -0
  24. package/components/ColorPicker/ColorPicker.js +338 -0
  25. package/components/Combobox/Combobox.css +0 -36
  26. package/components/Combobox/Combobox.d.ts +10 -2
  27. package/components/Combobox/Combobox.js +90 -82
  28. package/components/CommandPalette/CommandPalette.css +1 -15
  29. package/components/CommandPalette/CommandPalette.js +6 -5
  30. package/components/ContextMenu/ContextMenu.css +258 -0
  31. package/components/ContextMenu/ContextMenu.d.ts +26 -0
  32. package/components/ContextMenu/ContextMenu.js +350 -0
  33. package/components/DateInput/DateInput.css +0 -36
  34. package/components/DateInput/DateInput.d.ts +5 -1
  35. package/components/DateInput/DateInput.js +37 -33
  36. package/components/DatePicker/DatePicker.js +45 -34
  37. package/components/Dialog/Dialog.css +5 -5
  38. package/components/Dialog/Dialog.js +5 -4
  39. package/components/Drawer/Drawer.js +7 -2
  40. package/components/Dropdown/Dropdown.css +2 -40
  41. package/components/Dropdown/Dropdown.d.ts +10 -2
  42. package/components/Dropdown/Dropdown.js +119 -109
  43. package/components/DropdownMenu/DropdownMenu.css +2 -4
  44. package/components/DropdownMenu/DropdownMenu.js +10 -7
  45. package/components/Field/Field.css +80 -0
  46. package/components/Field/Field.d.ts +50 -0
  47. package/components/Field/Field.js +58 -0
  48. package/components/Field/FieldContext.d.ts +42 -0
  49. package/components/Field/FieldContext.js +7 -0
  50. package/components/FileInput/FileInput.css +0 -36
  51. package/components/FileInput/FileInput.d.ts +5 -1
  52. package/components/FileInput/FileInput.js +111 -103
  53. package/components/Input/Input.css +0 -43
  54. package/components/Input/Input.d.ts +5 -1
  55. package/components/Input/Input.js +51 -46
  56. package/components/Kbd/Kbd.css +28 -0
  57. package/components/Kbd/Kbd.d.ts +19 -0
  58. package/components/Kbd/Kbd.js +14 -0
  59. package/components/LinkList/LinkList.d.ts +1 -0
  60. package/components/Popover/Popover.d.ts +0 -6
  61. package/components/Popover/Popover.js +22 -16
  62. package/components/ProgressBar/ProgressBar.js +1 -1
  63. package/components/RadioButton/RadioButton.d.ts +17 -3
  64. package/components/Reasoning/Reasoning.css +152 -0
  65. package/components/Reasoning/Reasoning.d.ts +36 -0
  66. package/components/Reasoning/Reasoning.js +69 -0
  67. package/components/SelectionCard/SelectionCard.css +6 -2
  68. package/components/SelectionCard/SelectionCard.d.ts +5 -1
  69. package/components/Skeleton/Skeleton.css +2 -0
  70. package/components/Slider/Slider.d.ts +5 -1
  71. package/components/Spinner/Spinner.css +11 -0
  72. package/components/Spinner/Spinner.d.ts +2 -2
  73. package/components/Swatch/Swatch.css +52 -0
  74. package/components/Swatch/Swatch.d.ts +34 -0
  75. package/components/Swatch/Swatch.js +44 -0
  76. package/components/Table/Table.css +19 -0
  77. package/components/Table/Table.js +1 -1
  78. package/components/Textarea/Textarea.css +0 -43
  79. package/components/Textarea/Textarea.d.ts +5 -1
  80. package/components/Textarea/Textarea.js +37 -35
  81. package/components/Timeline/Timeline.d.ts +2 -0
  82. package/components/ToggleGroup/ToggleGroup.d.ts +10 -2
  83. package/components/ToggleSwitch/ToggleSwitch.css +7 -2
  84. package/components/ToggleSwitch/ToggleSwitch.d.ts +10 -2
  85. package/components/ToolCall/ToolCall.css +213 -0
  86. package/components/ToolCall/ToolCall.d.ts +43 -0
  87. package/components/ToolCall/ToolCall.js +89 -0
  88. package/components/registry.d.ts +47 -10
  89. package/components/registry.js +5 -1
  90. package/components/registry.json +523 -56
  91. package/components/registry.json.d.ts +523 -56
  92. package/components/registry.json.js +4 -1
  93. package/index.d.ts +12 -1
  94. package/index.js +24 -1
  95. package/package.json +8 -4
  96. package/tokens/registry.json +1 -0
  97. package/tokens/registry.json.d.ts +1 -0
  98. package/tokens/registry.json.js +1 -1
  99. package/tokens/tokens-motion.css +3 -1
@@ -0,0 +1,278 @@
1
+ /* ============================================
2
+ COLORPICKER COMPONENT
3
+ Swatch trigger + saturation/hue/alpha panel
4
+
5
+ Colour-mixing constants: the white/black
6
+ gradient overlays and the hue spectrum are
7
+ colour-space physics, not theme decisions —
8
+ they are written as hsl() literals because no
9
+ semantic token can represent them. Everything
10
+ themeable uses tokens as usual.
11
+
12
+ ds-allow-file(color): colour-space physics —
13
+ the gradients and spectrum cannot be theme
14
+ tokens.
15
+ ============================================ */
16
+
17
+ /* Base */
18
+
19
+ .ds-colorpicker {
20
+ position: relative;
21
+ /* Field owns no layout — the root class stacks label, trigger and helper */
22
+ display: inline-flex;
23
+ flex-direction: column;
24
+ align-items: flex-start;
25
+ gap: var(--gap-sm);
26
+ }
27
+
28
+ /* Trigger */
29
+
30
+ .ds-colorpicker__trigger {
31
+ display: inline-flex;
32
+ align-items: center;
33
+ gap: var(--gap-sm);
34
+ padding: var(--padding-xxs);
35
+ border: var(--border-xs) solid var(--color-input-border-primary);
36
+ border-radius: var(--radius-md);
37
+ background-color: transparent;
38
+ cursor: pointer;
39
+ transition: border-color var(--motion-duration-base) var(--motion-ease-standard);
40
+ }
41
+
42
+ .ds-colorpicker__trigger:hover:not(:disabled) {
43
+ border-color: var(--color-input-border-hover);
44
+ }
45
+
46
+ .ds-colorpicker__trigger:focus-visible {
47
+ outline: none;
48
+ border-color: var(--color-input-border-selected);
49
+ }
50
+
51
+ .ds-colorpicker--open .ds-colorpicker__trigger {
52
+ border-color: var(--color-input-border-selected);
53
+ }
54
+
55
+ .ds-colorpicker__trigger-swatch {
56
+ width: 24px;
57
+ height: 24px;
58
+ border-radius: var(--radius-sm);
59
+ /* Checkerboard shows through translucent colours */
60
+ background-color: var(--color-bg-page-primary);
61
+ background-image:
62
+ linear-gradient(var(--ds-cp-color), var(--ds-cp-color)),
63
+ repeating-conic-gradient(var(--color-bg-container-tertiary) 0% 25%, transparent 0% 50%);
64
+ background-size: 100% 100%, 8px 8px;
65
+ box-shadow: inset 0 0 0 var(--border-xs) var(--color-bg-container-border);
66
+ flex-shrink: 0;
67
+ }
68
+
69
+ .ds-colorpicker__trigger-text {
70
+ font-family: var(--font-paragraph-sm-family);
71
+ font-size: var(--font-paragraph-sm-size);
72
+ line-height: var(--font-paragraph-sm-line-height);
73
+ color: var(--color-text-primary);
74
+ font-variant-numeric: tabular-nums;
75
+ padding-right: var(--padding-xxs);
76
+ }
77
+
78
+ /* Sizes */
79
+
80
+ .ds-colorpicker--compact .ds-colorpicker__trigger-swatch {
81
+ width: 20px;
82
+ height: 20px;
83
+ }
84
+
85
+ /* Panel */
86
+
87
+ .ds-colorpicker__panel {
88
+ position: absolute;
89
+ top: 100%;
90
+ left: 0;
91
+ margin-top: var(--gap-xxs);
92
+ width: 240px;
93
+ padding: var(--padding-sm-md);
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: var(--gap-sm-md);
97
+ background-color: var(--color-bg-page-primary);
98
+ border: var(--border-xs) solid var(--color-input-border-primary);
99
+ border-radius: var(--radius-md);
100
+ box-shadow: var(--shadow-floating);
101
+ z-index: 10;
102
+ }
103
+
104
+ /* Saturation / brightness area */
105
+
106
+ .ds-colorpicker__saturation {
107
+ position: relative;
108
+ height: 150px;
109
+ border-radius: var(--radius-sm);
110
+ cursor: crosshair;
111
+ touch-action: none;
112
+ background:
113
+ linear-gradient(to top, hsl(0 0% 0%), transparent),
114
+ linear-gradient(to right, hsl(0 0% 100%), transparent),
115
+ hsl(var(--ds-cp-hue) 100% 50%);
116
+ }
117
+
118
+ .ds-colorpicker__saturation:focus-visible {
119
+ outline: var(--border-md) solid var(--color-input-border-selected);
120
+ outline-offset: var(--border-md);
121
+ }
122
+
123
+ .ds-colorpicker__handle {
124
+ position: absolute;
125
+ left: var(--ds-cp-x);
126
+ top: var(--ds-cp-y);
127
+ transform: translate(-50%, -50%);
128
+ width: 14px;
129
+ height: 14px;
130
+ border-radius: var(--radius-full);
131
+ border: var(--border-md) solid hsl(0 0% 100%);
132
+ box-shadow: var(--shadow-floating);
133
+ pointer-events: none;
134
+ }
135
+
136
+ /* Sliders + preview */
137
+
138
+ .ds-colorpicker__controls {
139
+ display: flex;
140
+ align-items: center;
141
+ gap: var(--gap-sm-md);
142
+ }
143
+
144
+ .ds-colorpicker__sliders {
145
+ flex: 1;
146
+ display: flex;
147
+ flex-direction: column;
148
+ gap: var(--gap-sm);
149
+ min-width: 0;
150
+ }
151
+
152
+ .ds-colorpicker__hue,
153
+ .ds-colorpicker__alpha {
154
+ appearance: none;
155
+ -webkit-appearance: none;
156
+ width: 100%;
157
+ height: 12px;
158
+ border-radius: var(--radius-full);
159
+ outline: none;
160
+ cursor: pointer;
161
+ }
162
+
163
+ .ds-colorpicker__hue {
164
+ background: linear-gradient(
165
+ to right,
166
+ hsl(0 100% 50%),
167
+ hsl(60 100% 50%),
168
+ hsl(120 100% 50%),
169
+ hsl(180 100% 50%),
170
+ hsl(240 100% 50%),
171
+ hsl(300 100% 50%),
172
+ hsl(360 100% 50%)
173
+ );
174
+ }
175
+
176
+ .ds-colorpicker__alpha {
177
+ background-color: var(--color-bg-page-primary);
178
+ background-image:
179
+ linear-gradient(to right, transparent, var(--ds-cp-solid)),
180
+ repeating-conic-gradient(var(--color-bg-container-tertiary) 0% 25%, transparent 0% 50%);
181
+ background-size: 100% 100%, 8px 8px;
182
+ }
183
+
184
+ .ds-colorpicker__hue::-webkit-slider-thumb,
185
+ .ds-colorpicker__alpha::-webkit-slider-thumb {
186
+ appearance: none;
187
+ -webkit-appearance: none;
188
+ width: 16px;
189
+ height: 16px;
190
+ border-radius: var(--radius-full);
191
+ background: hsl(0 0% 100%);
192
+ border: var(--border-xs) solid var(--color-bg-container-border);
193
+ box-shadow: var(--shadow-floating);
194
+ }
195
+
196
+ .ds-colorpicker__hue::-moz-range-thumb,
197
+ .ds-colorpicker__alpha::-moz-range-thumb {
198
+ width: 16px;
199
+ height: 16px;
200
+ border-radius: var(--radius-full);
201
+ background: hsl(0 0% 100%);
202
+ border: var(--border-xs) solid var(--color-bg-container-border);
203
+ box-shadow: var(--shadow-floating);
204
+ }
205
+
206
+ .ds-colorpicker__hue:focus-visible,
207
+ .ds-colorpicker__alpha:focus-visible {
208
+ outline: var(--border-md) solid var(--color-input-border-selected);
209
+ outline-offset: var(--border-md);
210
+ }
211
+
212
+ .ds-colorpicker__preview {
213
+ width: 32px;
214
+ height: 32px;
215
+ border-radius: var(--radius-sm);
216
+ background-color: var(--color-bg-page-primary);
217
+ background-image:
218
+ linear-gradient(var(--ds-cp-color), var(--ds-cp-color)),
219
+ repeating-conic-gradient(var(--color-bg-container-tertiary) 0% 25%, transparent 0% 50%);
220
+ background-size: 100% 100%, 8px 8px;
221
+ box-shadow: inset 0 0 0 var(--border-xs) var(--color-bg-container-border);
222
+ flex-shrink: 0;
223
+ }
224
+
225
+ /* Hex + alpha inputs */
226
+
227
+ .ds-colorpicker__inputs {
228
+ display: flex;
229
+ align-items: center;
230
+ gap: var(--gap-sm);
231
+ padding: var(--padding-xxs) var(--padding-sm);
232
+ border: var(--border-xs) solid var(--color-input-border-primary);
233
+ border-radius: var(--radius-md);
234
+ transition: border-color var(--motion-duration-base) var(--motion-ease-standard);
235
+ }
236
+
237
+ .ds-colorpicker__inputs:focus-within {
238
+ border-color: var(--color-input-border-selected);
239
+ }
240
+
241
+ .ds-colorpicker__hash {
242
+ font-family: var(--font-paragraph-sm-family);
243
+ font-size: var(--font-paragraph-sm-size);
244
+ color: var(--color-text-tertiary);
245
+ }
246
+
247
+ .ds-colorpicker__hex-field {
248
+ flex: 1;
249
+ min-width: 0;
250
+ border: none;
251
+ background: transparent;
252
+ outline: none;
253
+ padding: 0;
254
+ font-family: var(--font-paragraph-sm-family);
255
+ font-size: var(--font-paragraph-sm-size);
256
+ line-height: var(--font-paragraph-sm-line-height);
257
+ color: var(--color-text-primary);
258
+ text-transform: uppercase;
259
+ font-variant-numeric: tabular-nums;
260
+ }
261
+
262
+ .ds-colorpicker__alpha-value {
263
+ font-family: var(--font-paragraph-sm-family);
264
+ font-size: var(--font-paragraph-sm-size);
265
+ color: var(--color-text-secondary);
266
+ font-variant-numeric: tabular-nums;
267
+ }
268
+
269
+ /* States */
270
+
271
+ .ds-colorpicker--disabled .ds-colorpicker__trigger {
272
+ opacity: 0.4;
273
+ cursor: not-allowed;
274
+ }
275
+
276
+ .ds-colorpicker--error .ds-colorpicker__trigger {
277
+ border-color: var(--color-status-error-border);
278
+ }
@@ -0,0 +1,50 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by ColorPicker itself — everything else falls through to the trigger <button>. */
3
+ type ColorPickerOwnProps = {
4
+ /** Field label text */
5
+ label?: string;
6
+ /** Current colour as a hex string — 3, 6 or 8 digit, with or without `#` */
7
+ value?: string;
8
+ /** Initial colour for uncontrolled use */
9
+ defaultValue?: string;
10
+ /**
11
+ * Convenience callback receiving the colour as an uppercase hex string
12
+ * (`#RRGGBB`, or `#RRGGBBAA` when `showAlpha` and alpha < 100%). Fires
13
+ * live while dragging.
14
+ */
15
+ onValueChange?: (value: string) => void;
16
+ /** Show the current hex value as text inside the trigger */
17
+ showText?: boolean;
18
+ /** Add an alpha (opacity) slider and emit 8-digit hex when alpha < 100% */
19
+ showAlpha?: boolean;
20
+ /** Component size */
21
+ size?: 'default' | 'compact';
22
+ /** Whether the picker is disabled */
23
+ disabled?: boolean;
24
+ /** Whether the field is required */
25
+ required?: boolean;
26
+ /** Error state */
27
+ error?: boolean;
28
+ /** Helper or error message */
29
+ helperText?: string;
30
+ /**
31
+ * When set, a hidden `<input type="hidden">` carries the current hex value
32
+ * under this name so the picker participates in native form submission.
33
+ */
34
+ name?: string;
35
+ /** Additional CSS classes — applied to the wrapper, not the trigger */
36
+ className?: string;
37
+ };
38
+ export interface ColorPickerProps extends ColorPickerOwnProps, Omit<React.ComponentPropsWithoutRef<'button'>, keyof ColorPickerOwnProps | 'type'> {
39
+ }
40
+ /**
41
+ * Colour picker: a swatch trigger that opens a panel with a
42
+ * saturation/brightness area, hue slider, optional alpha slider and a hex
43
+ * field. Works controlled (`value` + `onValueChange`) or uncontrolled
44
+ * (`defaultValue`).
45
+ *
46
+ * Forwards a ref to the trigger `<button>` and spreads unrecognised props
47
+ * onto it.
48
+ */
49
+ export declare const ColorPicker: React.ForwardRefExoticComponent<ColorPickerProps & React.RefAttributes<HTMLButtonElement>>;
50
+ export {};
@@ -0,0 +1,338 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React, { useState, useRef, useEffect, useCallback } from "react";
3
+ import { Field } from "../Field/Field.js";
4
+ import "./ColorPicker.css";
5
+ const clamp = (n, min, max) => Math.min(max, Math.max(min, n));
6
+ function parseHex(input) {
7
+ const hex = input.trim().replace(/^#/, "");
8
+ if (!/^([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(hex)) return null;
9
+ let r, g, b;
10
+ let a = 255;
11
+ if (hex.length === 3) {
12
+ r = parseInt(hex[0] + hex[0], 16);
13
+ g = parseInt(hex[1] + hex[1], 16);
14
+ b = parseInt(hex[2] + hex[2], 16);
15
+ } else {
16
+ r = parseInt(hex.slice(0, 2), 16);
17
+ g = parseInt(hex.slice(2, 4), 16);
18
+ b = parseInt(hex.slice(4, 6), 16);
19
+ if (hex.length === 8) a = parseInt(hex.slice(6, 8), 16);
20
+ }
21
+ const max = Math.max(r, g, b);
22
+ const min = Math.min(r, g, b);
23
+ const d = max - min;
24
+ let h = 0;
25
+ if (d !== 0) {
26
+ if (max === r) h = 60 * ((g - b) / d % 6);
27
+ else if (max === g) h = 60 * ((b - r) / d + 2);
28
+ else h = 60 * ((r - g) / d + 4);
29
+ }
30
+ if (h < 0) h += 360;
31
+ return {
32
+ // h stays a float — rounding it here makes the hex → HSV → hex round
33
+ // trip drift by one on some colours.
34
+ h: clamp(h, 0, 359.999),
35
+ s: max === 0 ? 0 : d / max * 100,
36
+ v: max / 255 * 100,
37
+ a: a / 255 * 100
38
+ };
39
+ }
40
+ function hsvToRgb({ h, s, v }) {
41
+ const sf = s / 100;
42
+ const vf = v / 100;
43
+ const c = vf * sf;
44
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
45
+ const m = vf - c;
46
+ const [r, g, b] = h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] : h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x];
47
+ return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];
48
+ }
49
+ const channelHex = (n) => n.toString(16).padStart(2, "0").toUpperCase();
50
+ function formatHex(hsva, withAlpha) {
51
+ const [r, g, b] = hsvToRgb(hsva);
52
+ const base = `#${channelHex(r)}${channelHex(g)}${channelHex(b)}`;
53
+ if (withAlpha && hsva.a < 100) {
54
+ return base + channelHex(Math.round(hsva.a / 100 * 255));
55
+ }
56
+ return base;
57
+ }
58
+ function toCssColor(hsva) {
59
+ const [r, g, b] = hsvToRgb(hsva);
60
+ return `rgba(${r}, ${g}, ${b}, ${hsva.a / 100})`;
61
+ }
62
+ const ColorPicker = React.forwardRef(
63
+ ({
64
+ label,
65
+ value,
66
+ defaultValue = "#118AB2",
67
+ onValueChange,
68
+ showText = false,
69
+ showAlpha = false,
70
+ size = "default",
71
+ disabled = false,
72
+ required = false,
73
+ error = false,
74
+ helperText,
75
+ name,
76
+ className = "",
77
+ id,
78
+ ...rest
79
+ }, ref) => {
80
+ const baseClass = "ds-colorpicker";
81
+ const [isOpen, setIsOpen] = useState(false);
82
+ const [hsva, setHsva] = useState(
83
+ () => parseHex(value ?? defaultValue) ?? { h: 0, s: 0, v: 0, a: 100 }
84
+ );
85
+ const [hexDraft, setHexDraft] = useState(null);
86
+ const rootRef = useRef(null);
87
+ const triggerRef = useRef(null);
88
+ const satRef = useRef(null);
89
+ const hsvaRef = useRef(hsva);
90
+ hsvaRef.current = hsva;
91
+ const lastEmitted = useRef(null);
92
+ const setTriggerRef = (node) => {
93
+ triggerRef.current = node;
94
+ if (typeof ref === "function") ref(node);
95
+ else if (ref) ref.current = node;
96
+ };
97
+ useEffect(() => {
98
+ if (value == null || value === lastEmitted.current) return;
99
+ const parsed = parseHex(value);
100
+ if (parsed) setHsva(parsed);
101
+ }, [value]);
102
+ const commit = useCallback(
103
+ (next) => {
104
+ setHsva(next);
105
+ const hex = formatHex(next, showAlpha);
106
+ lastEmitted.current = hex;
107
+ onValueChange?.(hex);
108
+ },
109
+ [onValueChange, showAlpha]
110
+ );
111
+ useEffect(() => {
112
+ if (!isOpen) return;
113
+ const onMouseDown = (e) => {
114
+ if (rootRef.current && !rootRef.current.contains(e.target)) {
115
+ setIsOpen(false);
116
+ setHexDraft(null);
117
+ }
118
+ };
119
+ document.addEventListener("mousedown", onMouseDown);
120
+ return () => document.removeEventListener("mousedown", onMouseDown);
121
+ }, [isOpen]);
122
+ const handleRootKeyDown = (e) => {
123
+ if (e.key === "Escape" && isOpen) {
124
+ e.stopPropagation();
125
+ setIsOpen(false);
126
+ setHexDraft(null);
127
+ triggerRef.current?.focus();
128
+ }
129
+ };
130
+ const updateFromPointer = useCallback(
131
+ (clientX, clientY) => {
132
+ const el = satRef.current;
133
+ if (!el) return;
134
+ const rect = el.getBoundingClientRect();
135
+ const s = clamp((clientX - rect.left) / rect.width * 100, 0, 100);
136
+ const v = clamp((1 - (clientY - rect.top) / rect.height) * 100, 0, 100);
137
+ commit({ ...hsvaRef.current, s, v });
138
+ },
139
+ [commit]
140
+ );
141
+ const handleSatPointerDown = (e) => {
142
+ e.preventDefault();
143
+ e.currentTarget.setPointerCapture(e.pointerId);
144
+ e.currentTarget.focus();
145
+ updateFromPointer(e.clientX, e.clientY);
146
+ };
147
+ const handleSatPointerMove = (e) => {
148
+ if (e.currentTarget.hasPointerCapture(e.pointerId)) {
149
+ updateFromPointer(e.clientX, e.clientY);
150
+ }
151
+ };
152
+ const handleSatKeyDown = (e) => {
153
+ const step = e.shiftKey ? 10 : 2;
154
+ const { h, s, v, a } = hsvaRef.current;
155
+ switch (e.key) {
156
+ case "ArrowLeft":
157
+ e.preventDefault();
158
+ commit({ h, s: clamp(s - step, 0, 100), v, a });
159
+ break;
160
+ case "ArrowRight":
161
+ e.preventDefault();
162
+ commit({ h, s: clamp(s + step, 0, 100), v, a });
163
+ break;
164
+ case "ArrowUp":
165
+ e.preventDefault();
166
+ commit({ h, s, v: clamp(v + step, 0, 100), a });
167
+ break;
168
+ case "ArrowDown":
169
+ e.preventDefault();
170
+ commit({ h, s, v: clamp(v - step, 0, 100), a });
171
+ break;
172
+ }
173
+ };
174
+ const displayHex = formatHex(hsva, showAlpha);
175
+ const handleHexChange = (e) => {
176
+ const text = e.target.value;
177
+ setHexDraft(text);
178
+ const parsed = parseHex(text);
179
+ if (parsed) {
180
+ const keepAlpha = text.replace(/^#/, "").length < 8;
181
+ commit(keepAlpha ? { ...parsed, a: hsvaRef.current.a } : parsed);
182
+ }
183
+ };
184
+ const inputId = id || name || label?.toLowerCase().replace(/\s+/g, "-");
185
+ const cssColor = toCssColor(hsva);
186
+ const solidHex = formatHex({ ...hsva, a: 100 }, false);
187
+ const classes = [
188
+ baseClass,
189
+ size === "compact" ? `${baseClass}--compact` : "",
190
+ isOpen ? `${baseClass}--open` : "",
191
+ error ? `${baseClass}--error` : "",
192
+ disabled ? `${baseClass}--disabled` : "",
193
+ className
194
+ ].filter(Boolean).join(" ");
195
+ return /* @__PURE__ */ jsxs(
196
+ Field,
197
+ {
198
+ ref: rootRef,
199
+ className: classes,
200
+ label,
201
+ helperText,
202
+ error,
203
+ required,
204
+ disabled,
205
+ size,
206
+ id: inputId,
207
+ onKeyDown: handleRootKeyDown,
208
+ children: [
209
+ /* @__PURE__ */ jsxs(
210
+ "button",
211
+ {
212
+ ...rest,
213
+ ref: setTriggerRef,
214
+ type: "button",
215
+ id: inputId,
216
+ className: `${baseClass}__trigger`,
217
+ disabled,
218
+ "aria-haspopup": "dialog",
219
+ "aria-expanded": isOpen,
220
+ "aria-describedby": helperText ? `${inputId}-helper` : rest["aria-describedby"],
221
+ "aria-invalid": error || void 0,
222
+ "aria-label": rest["aria-label"] ?? (!label ? `Choose colour, ${displayHex}` : void 0),
223
+ onClick: () => {
224
+ if (!disabled) setIsOpen((prev) => !prev);
225
+ },
226
+ children: [
227
+ /* @__PURE__ */ jsx(
228
+ "span",
229
+ {
230
+ className: `${baseClass}__trigger-swatch`,
231
+ style: { "--ds-cp-color": cssColor },
232
+ "aria-hidden": "true"
233
+ }
234
+ ),
235
+ showText && /* @__PURE__ */ jsx("span", { className: `${baseClass}__trigger-text`, children: displayHex })
236
+ ]
237
+ }
238
+ ),
239
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: displayHex }),
240
+ isOpen && /* @__PURE__ */ jsxs(
241
+ "div",
242
+ {
243
+ className: `${baseClass}__panel`,
244
+ role: "dialog",
245
+ "aria-label": label ? `${label} colour picker` : "Colour picker",
246
+ style: {
247
+ "--ds-cp-hue": hsva.h,
248
+ "--ds-cp-color": cssColor,
249
+ "--ds-cp-solid": solidHex
250
+ },
251
+ children: [
252
+ /* @__PURE__ */ jsx(
253
+ "div",
254
+ {
255
+ ref: satRef,
256
+ className: `${baseClass}__saturation`,
257
+ role: "slider",
258
+ tabIndex: 0,
259
+ "aria-label": "Saturation and brightness",
260
+ "aria-valuemin": 0,
261
+ "aria-valuemax": 100,
262
+ "aria-valuenow": Math.round(hsva.v),
263
+ "aria-valuetext": `Saturation ${Math.round(hsva.s)}%, brightness ${Math.round(hsva.v)}%`,
264
+ onPointerDown: handleSatPointerDown,
265
+ onPointerMove: handleSatPointerMove,
266
+ onKeyDown: handleSatKeyDown,
267
+ style: {
268
+ "--ds-cp-x": `${hsva.s}%`,
269
+ "--ds-cp-y": `${100 - hsva.v}%`
270
+ },
271
+ children: /* @__PURE__ */ jsx("span", { className: `${baseClass}__handle` })
272
+ }
273
+ ),
274
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__controls`, children: [
275
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__sliders`, children: [
276
+ /* @__PURE__ */ jsx(
277
+ "input",
278
+ {
279
+ type: "range",
280
+ className: `${baseClass}__hue`,
281
+ min: 0,
282
+ max: 359,
283
+ step: 1,
284
+ value: Math.round(hsva.h),
285
+ "aria-label": "Hue",
286
+ onChange: (e) => commit({ ...hsvaRef.current, h: Number(e.target.value) })
287
+ }
288
+ ),
289
+ showAlpha && /* @__PURE__ */ jsx(
290
+ "input",
291
+ {
292
+ type: "range",
293
+ className: `${baseClass}__alpha`,
294
+ min: 0,
295
+ max: 100,
296
+ step: 1,
297
+ value: Math.round(hsva.a),
298
+ "aria-label": "Alpha",
299
+ onChange: (e) => commit({ ...hsvaRef.current, a: Number(e.target.value) })
300
+ }
301
+ )
302
+ ] }),
303
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__preview`, "aria-hidden": "true" })
304
+ ] }),
305
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__inputs`, children: [
306
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__hash`, "aria-hidden": "true", children: "#" }),
307
+ /* @__PURE__ */ jsx(
308
+ "input",
309
+ {
310
+ type: "text",
311
+ className: `${baseClass}__hex-field`,
312
+ value: (hexDraft ?? displayHex).replace(/^#/, ""),
313
+ maxLength: 8,
314
+ spellCheck: false,
315
+ autoComplete: "off",
316
+ "aria-label": "Hex colour value",
317
+ onChange: handleHexChange,
318
+ onFocus: () => setHexDraft(displayHex.replace(/^#/, "")),
319
+ onBlur: () => setHexDraft(null)
320
+ }
321
+ ),
322
+ showAlpha && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__alpha-value`, children: [
323
+ Math.round(hsva.a),
324
+ "%"
325
+ ] })
326
+ ] })
327
+ ]
328
+ }
329
+ )
330
+ ]
331
+ }
332
+ );
333
+ }
334
+ );
335
+ ColorPicker.displayName = "ColorPicker";
336
+ export {
337
+ ColorPicker
338
+ };