@flikk/ui 1.0.0-beta.21 → 1.0.0-beta.22

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 (177) hide show
  1. package/dist/components/ai/CodeBlock/CodeBlock.theme.js +2 -2
  2. package/dist/components/ai/PromptInput/PromptInput.theme.js +3 -3
  3. package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.js +1 -1
  4. package/dist/components/ai/StreamingResponse/StreamingResponse.theme.js +4 -4
  5. package/dist/components/ai/TokenCounter/TokenCounter.theme.js +6 -6
  6. package/dist/components/charts/ActivityRings/ActivityRings.theme.js +2 -2
  7. package/dist/components/charts/DonutChart/DonutChart.theme.js +2 -2
  8. package/dist/components/charts/DonutChart/MultiSegmentDonutChart.theme.js +1 -1
  9. package/dist/components/charts/Heatmap/Heatmap.theme.js +1 -1
  10. package/dist/components/charts/RadarChart/RadarChart.theme.js +2 -2
  11. package/dist/components/charts/ScatterPlot/ScatterPlot.theme.js +2 -2
  12. package/dist/components/charts/shared/ChartText/ChartText.theme.js +1 -1
  13. package/dist/components/charts/shared/ChartTooltip/ChartTooltip.theme.js +4 -4
  14. package/dist/components/core/Alert/Alert.theme.js +1 -1
  15. package/dist/components/core/Avatar/Avatar.theme.js +10 -10
  16. package/dist/components/core/AvatarGroup/AvatarGroup.theme.js +1 -1
  17. package/dist/components/core/Badge/Badge.js +2 -12
  18. package/dist/components/core/Badge/Badge.theme.js +2 -2
  19. package/dist/components/core/Button/Button.theme.js +6 -6
  20. package/dist/components/core/ButtonGroup/ButtonGroup.theme.js +1 -1
  21. package/dist/components/core/Calendar/Calendar.theme.js +5 -5
  22. package/dist/components/core/Card/Card.theme.js +2 -2
  23. package/dist/components/core/CommandPalette/CommandPalette.theme.js +4 -4
  24. package/dist/components/core/Drawer/Drawer.animations.js +5 -4
  25. package/dist/components/core/Drawer/Drawer.theme.js +2 -2
  26. package/dist/components/core/Dropdown/Dropdown.theme.js +1 -1
  27. package/dist/components/core/Empty/Empty.theme.js +2 -2
  28. package/dist/components/core/Icon/Icon.d.ts +3 -0
  29. package/dist/components/core/Icon/Icon.js +70 -0
  30. package/dist/components/core/Icon/Icon.theme.d.ts +2 -0
  31. package/dist/components/core/Icon/Icon.theme.js +19 -0
  32. package/dist/components/core/Icon/Icon.types.d.ts +32 -0
  33. package/dist/components/core/{FileTypeIcon → Icon/data}/fileTypes.d.ts +1 -1
  34. package/dist/components/core/Icon/data/paymentMethods.d.ts +27 -0
  35. package/dist/components/core/Icon/data/paymentMethods.js +169 -0
  36. package/dist/components/core/Icon/index.d.ts +4 -0
  37. package/dist/components/core/Kbd/Kbd.theme.js +2 -2
  38. package/dist/components/core/MenuItem/MenuItem.theme.js +2 -2
  39. package/dist/components/core/Message/Message.theme.js +6 -6
  40. package/dist/components/core/Modal/Modal.animations.js +3 -1
  41. package/dist/components/core/Modal/Modal.theme.js +2 -2
  42. package/dist/components/core/Modal/ModalBody.js +7 -8
  43. package/dist/components/core/NavItem/NavItem.theme.js +1 -1
  44. package/dist/components/core/OfflineIndicator/OfflineIndicator.theme.js +2 -2
  45. package/dist/components/core/PageHeading/PageHeading.theme.js +2 -2
  46. package/dist/components/core/Pagination/Pagination.theme.js +2 -2
  47. package/dist/components/core/Pill/Pill.theme.js +3 -3
  48. package/dist/components/core/Progress/Progress.theme.js +2 -2
  49. package/dist/components/core/Segmented/Segmented.theme.js +3 -3
  50. package/dist/components/core/Tabs/Tabs.theme.js +2 -2
  51. package/dist/components/core/Tag/Tag.theme.js +2 -2
  52. package/dist/components/core/Toast/Toast.theme.js +2 -2
  53. package/dist/components/core/Tooltip/Tooltip.theme.js +1 -1
  54. package/dist/components/core/Tree/Tree.theme.js +1 -1
  55. package/dist/components/core/index.d.ts +1 -2
  56. package/dist/components/core/index.js +2 -2
  57. package/dist/components/data-display/DescriptionList/DescriptionList.theme.js +2 -2
  58. package/dist/components/data-display/Feed/Feed.theme.js +1 -1
  59. package/dist/components/data-display/GanttChart/GanttChart.theme.js +2 -2
  60. package/dist/components/data-display/KPI/KPI.theme.js +9 -9
  61. package/dist/components/data-display/Metric/Metric.theme.js +9 -9
  62. package/dist/components/data-display/Table/Table.animations.js +16 -10
  63. package/dist/components/data-display/Table/Table.filterUtils.js +8 -1
  64. package/dist/components/data-display/Table/Table.js +10 -14
  65. package/dist/components/data-display/Table/Table.theme.js +3 -3
  66. package/dist/components/data-display/Table/Table.types.d.ts +2 -0
  67. package/dist/components/data-display/Table/TableActions.js +3 -3
  68. package/dist/components/data-display/Table/TableActionsMenu.js +2 -3
  69. package/dist/components/data-display/Table/TableCell.js +6 -3
  70. package/dist/components/data-display/Table/TableDeclarative.js +5 -7
  71. package/dist/components/data-display/Table/TableHeader.js +5 -2
  72. package/dist/components/data-display/Table/TablePagination.js +2 -6
  73. package/dist/components/data-display/Table/TableRow.js +2 -6
  74. package/dist/components/data-display/Table/index.js +1 -0
  75. package/dist/components/effects/CustomCursor/CustomCursor.theme.js +1 -1
  76. package/dist/components/effects/Overlay/Overlay.js +50 -21
  77. package/dist/components/forms/Checkbox/Checkbox.theme.js +3 -3
  78. package/dist/components/forms/ColorPicker/ColorPicker.d.ts +5 -9
  79. package/dist/components/forms/ColorPicker/ColorPicker.js +126 -22
  80. package/dist/components/forms/ColorPicker/ColorPicker.theme.js +9 -6
  81. package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +99 -0
  82. package/dist/components/forms/ColorPicker/ColorPicker2DCanvas.js +43 -6
  83. package/dist/components/forms/ColorPicker/ColorPickerBody.js +1 -1
  84. package/dist/components/forms/ColorPicker/ColorPickerContext.d.ts +4 -0
  85. package/dist/components/forms/ColorPicker/ColorPickerContext.js +12 -0
  86. package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +1 -1
  87. package/dist/components/forms/ColorPicker/ColorPickerFormatSelector.js +1 -1
  88. package/dist/components/forms/ColorPicker/ColorPickerGradient.d.ts +7 -0
  89. package/dist/components/forms/ColorPicker/ColorPickerGradient.js +186 -0
  90. package/dist/components/forms/ColorPicker/ColorPickerHeader.d.ts +14 -0
  91. package/dist/components/forms/ColorPicker/ColorPickerHeader.js +31 -0
  92. package/dist/components/forms/ColorPicker/ColorPickerImage.d.ts +6 -0
  93. package/dist/components/forms/ColorPicker/ColorPickerImage.js +154 -0
  94. package/dist/components/forms/ColorPicker/ColorPickerInput.d.ts +0 -4
  95. package/dist/components/forms/ColorPicker/ColorPickerInput.js +53 -61
  96. package/dist/components/forms/ColorPicker/ColorPickerPresets.js +13 -12
  97. package/dist/components/forms/ColorPicker/ColorPickerSavedColors.d.ts +3 -0
  98. package/dist/components/forms/ColorPicker/ColorPickerSavedColors.js +69 -0
  99. package/dist/components/forms/ColorPicker/ColorPickerSliders.d.ts +1 -2
  100. package/dist/components/forms/ColorPicker/ColorPickerSliders.js +84 -23
  101. package/dist/components/forms/ColorPicker/ColorPickerSwatch.d.ts +1 -1
  102. package/dist/components/forms/ColorPicker/ColorPickerSwatch.js +10 -9
  103. package/dist/components/forms/ColorPicker/ColorPickerTrigger.js +7 -3
  104. package/dist/components/forms/ColorPicker/index.d.ts +8 -2
  105. package/dist/components/forms/CronInput/CronInput.theme.js +8 -8
  106. package/dist/components/forms/DatePicker/DatePicker.theme.js +1 -1
  107. package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +3 -3
  108. package/dist/components/forms/FileUpload/FileUpload.theme.js +4 -4
  109. package/dist/components/forms/FileUpload/FileUploadProgress.js +3 -3
  110. package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +2 -2
  111. package/dist/components/forms/FormLabel/FormLabel.theme.js +1 -1
  112. package/dist/components/forms/Input/Input.js +3 -3
  113. package/dist/components/forms/Input/Input.theme.js +1 -1
  114. package/dist/components/forms/Input/Input.types.d.ts +2 -0
  115. package/dist/components/forms/InputCounter/InputCounter.theme.js +1 -1
  116. package/dist/components/forms/InputOTP/InputOTP.theme.js +1 -1
  117. package/dist/components/forms/Mention/Mention.theme.js +3 -3
  118. package/dist/components/forms/Radio/Radio.theme.js +3 -3
  119. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +1 -1
  120. package/dist/components/forms/Select/Select.theme.js +1 -1
  121. package/dist/components/forms/SelectableCard/SelectableCard.js +42 -27
  122. package/dist/components/forms/SelectableCard/SelectableCard.theme.js +15 -25
  123. package/dist/components/forms/SelectableCard/SelectableCard.types.d.ts +16 -3
  124. package/dist/components/forms/SelectableCard/SelectableCardContext.d.ts +1 -0
  125. package/dist/components/forms/SelectableCard/SelectableCardContext.js +4 -7
  126. package/dist/components/forms/SelectableCard/SelectableCardGroup.js +31 -10
  127. package/dist/components/forms/SelectableCard/index.d.ts +1 -1
  128. package/dist/components/forms/Signature/Signature.theme.js +1 -1
  129. package/dist/components/forms/Slider/Slider.theme.js +3 -3
  130. package/dist/components/forms/Switch/Switch.theme.js +1 -1
  131. package/dist/components/forms/TimePicker/TimePicker.theme.js +2 -2
  132. package/dist/components/forms/forms.theme.js +6 -6
  133. package/dist/components/forms/index.js +6 -1
  134. package/dist/components/layout/FormLayout/FormLayout.theme.js +2 -2
  135. package/dist/hooks/useOverlay.d.ts +2 -0
  136. package/dist/hooks/useOverlay.js +34 -4
  137. package/dist/index.js +8 -3
  138. package/dist/styles.css +1 -1
  139. package/package.json +2 -1
  140. package/src/global.scss +1 -1
  141. package/dist/bg/bg/1.jpg +0 -0
  142. package/dist/bg/bg/10.jpg +0 -0
  143. package/dist/bg/bg/11.jpg +0 -0
  144. package/dist/bg/bg/14.jpg +0 -0
  145. package/dist/bg/bg/15.jpg +0 -0
  146. package/dist/bg/bg/16.jpg +0 -0
  147. package/dist/bg/bg/17.jpg +0 -0
  148. package/dist/bg/bg/18.jpg +0 -0
  149. package/dist/bg/bg/19.jpg +0 -0
  150. package/dist/bg/bg/2.jpg +0 -0
  151. package/dist/bg/bg/20.jpg +0 -0
  152. package/dist/bg/bg/21.jpg +0 -0
  153. package/dist/bg/bg/22.jpg +0 -0
  154. package/dist/bg/bg/23.jpg +0 -0
  155. package/dist/bg/bg/24.jpg +0 -0
  156. package/dist/bg/bg/3.jpg +0 -0
  157. package/dist/bg/bg/4.jpg +0 -0
  158. package/dist/bg/bg/5.jpg +0 -0
  159. package/dist/bg/bg/6.jpg +0 -0
  160. package/dist/bg/bg/7.jpg +0 -0
  161. package/dist/bg/bg/8.jpg +0 -0
  162. package/dist/bg/bg/9.jpg +0 -0
  163. package/dist/components/core/FileTypeIcon/FileTypeIcon.d.ts +0 -3
  164. package/dist/components/core/FileTypeIcon/FileTypeIcon.js +0 -33
  165. package/dist/components/core/FileTypeIcon/FileTypeIcon.theme.d.ts +0 -2
  166. package/dist/components/core/FileTypeIcon/FileTypeIcon.theme.js +0 -12
  167. package/dist/components/core/FileTypeIcon/FileTypeIcon.types.d.ts +0 -19
  168. package/dist/components/core/FileTypeIcon/index.d.ts +0 -2
  169. package/dist/components/core/SocialIcon/SocialIcon.d.ts +0 -3
  170. package/dist/components/core/SocialIcon/SocialIcon.js +0 -30
  171. package/dist/components/core/SocialIcon/SocialIcon.theme.d.ts +0 -2
  172. package/dist/components/core/SocialIcon/SocialIcon.theme.js +0 -12
  173. package/dist/components/core/SocialIcon/SocialIcon.types.d.ts +0 -18
  174. package/dist/components/core/SocialIcon/index.d.ts +0 -2
  175. /package/dist/components/core/{FileTypeIcon → Icon/data}/fileTypes.js +0 -0
  176. /package/dist/components/core/{SocialIcon → Icon/data}/platforms.d.ts +0 -0
  177. /package/dist/components/core/{SocialIcon → Icon/data}/platforms.js +0 -0
@@ -0,0 +1,186 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { TrashIcon, PlusIcon, ClipboardDocumentCheckIcon, ClipboardDocumentIcon } from '@heroicons/react/20/solid';
3
+ import { useContext, useState, useRef, useCallback, useEffect } from 'react';
4
+ import { cn } from '../../../utils/cn.js';
5
+ import { Button } from '../../core/Button/Button.js';
6
+ import { Segmented } from '../../core/Segmented/Segmented.js';
7
+ import { Input } from '../Input/Input.js';
8
+ import '../Input/Input.theme.js';
9
+ import { ColorPickerContext } from './ColorPickerContext.js';
10
+ import { ColorPicker2DCanvas } from './ColorPicker2DCanvas.js';
11
+ import { ColorPickerSliders } from './ColorPickerSliders.js';
12
+ import { parseColor, formatHex } from './colorUtils.js';
13
+
14
+ /**
15
+ * ColorPickerGradient - Gradient editor with canvas, sliders, stop bar, type toggle, angle control.
16
+ * The 2D canvas and hue slider pick the color for the currently selected gradient stop.
17
+ */
18
+ const ColorPickerGradient = ({ className, ...props }) => {
19
+ var _a, _b, _c;
20
+ const context = useContext(ColorPickerContext);
21
+ if (!context) {
22
+ throw new Error('ColorPickerGradient must be used within a ColorPicker');
23
+ }
24
+ const { color, setColor, gradient, setGradient, updateGradientStop, addGradientStop, removeGradientStop, getOutputValue, showEyeDropper, } = context;
25
+ const [selectedStopId, setSelectedStopId] = useState((_b = (_a = gradient.stops[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null);
26
+ const [copied, setCopied] = useState(false);
27
+ const [dragStopId, setDragStopId] = useState(null);
28
+ const stopBarRef = useRef(null);
29
+ // Track whether color change originated from stop selection (to avoid update loops)
30
+ const isSelectingStopRef = useRef(false);
31
+ const selectedStop = gradient.stops.find(s => s.id === selectedStopId);
32
+ // --- Sync selected stop color → main color picker (when selecting a stop) ---
33
+ const selectStop = useCallback((stopId) => {
34
+ setSelectedStopId(stopId);
35
+ const stop = gradient.stops.find(s => s.id === stopId);
36
+ if (stop) {
37
+ const parsed = parseColor(stop.color);
38
+ if (parsed) {
39
+ isSelectingStopRef.current = true;
40
+ setColor(parsed);
41
+ // Reset after a tick so the next color change from canvas/slider will update the stop
42
+ requestAnimationFrame(() => { isSelectingStopRef.current = false; });
43
+ }
44
+ }
45
+ }, [gradient.stops, setColor]);
46
+ // Load first stop's color into picker on mount
47
+ useEffect(() => {
48
+ if (selectedStopId) {
49
+ const stop = gradient.stops.find(s => s.id === selectedStopId);
50
+ if (stop) {
51
+ const parsed = parseColor(stop.color);
52
+ if (parsed) {
53
+ isSelectingStopRef.current = true;
54
+ setColor(parsed);
55
+ requestAnimationFrame(() => { isSelectingStopRef.current = false; });
56
+ }
57
+ }
58
+ }
59
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
60
+ // --- Sync main color picker → selected stop (when user picks via canvas/slider) ---
61
+ useEffect(() => {
62
+ if (isSelectingStopRef.current || !selectedStopId)
63
+ return;
64
+ const hex = formatHex(color.h, color.s, color.l);
65
+ updateGradientStop(selectedStopId, { color: hex });
66
+ }, [color.h, color.s, color.l]); // eslint-disable-line react-hooks/exhaustive-deps
67
+ // Build the left-to-right preview gradient (ignoring angle for the bar)
68
+ const barGradientCSS = (() => {
69
+ const sorted = [...gradient.stops].sort((a, b) => a.position - b.position);
70
+ const stopStrings = sorted.map(s => `${s.color} ${s.position}%`).join(', ');
71
+ return `linear-gradient(to right, ${stopStrings})`;
72
+ })();
73
+ // Full output CSS
74
+ const outputCSS = getOutputValue();
75
+ // --- Type toggle ---
76
+ const handleTypeChange = (value) => {
77
+ setGradient({ ...gradient, type: value });
78
+ };
79
+ // --- Angle ---
80
+ const handleAngleChange = (e) => {
81
+ const val = parseInt(e.target.value, 10);
82
+ if (!isNaN(val)) {
83
+ setGradient({ ...gradient, angle: Math.max(0, Math.min(360, val)) });
84
+ }
85
+ };
86
+ // --- Stop bar interaction ---
87
+ const getPositionFromX = useCallback((clientX) => {
88
+ if (!stopBarRef.current)
89
+ return 0;
90
+ const rect = stopBarRef.current.getBoundingClientRect();
91
+ const ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
92
+ return Math.round(ratio * 100);
93
+ }, []);
94
+ const handleStopBarClick = (e) => {
95
+ if (e.target.closest('[data-stop-marker]'))
96
+ return;
97
+ const position = getPositionFromX(e.clientX);
98
+ addGradientStop(position);
99
+ };
100
+ const handleStopPointerDown = useCallback((e, stopId) => {
101
+ e.preventDefault();
102
+ e.stopPropagation();
103
+ setDragStopId(stopId);
104
+ selectStop(stopId);
105
+ }, [selectStop]);
106
+ const handlePointerMove = useCallback((e) => {
107
+ if (!dragStopId)
108
+ return;
109
+ const position = getPositionFromX(e.clientX);
110
+ updateGradientStop(dragStopId, { position });
111
+ }, [dragStopId, getPositionFromX, updateGradientStop]);
112
+ const handlePointerUp = useCallback(() => {
113
+ setDragStopId(null);
114
+ }, []);
115
+ useEffect(() => {
116
+ if (dragStopId) {
117
+ window.addEventListener('pointermove', handlePointerMove);
118
+ window.addEventListener('pointerup', handlePointerUp);
119
+ return () => {
120
+ window.removeEventListener('pointermove', handlePointerMove);
121
+ window.removeEventListener('pointerup', handlePointerUp);
122
+ };
123
+ }
124
+ }, [dragStopId, handlePointerMove, handlePointerUp]);
125
+ // --- Selected stop color input editing ---
126
+ const [colorInput, setColorInput] = useState((_c = selectedStop === null || selectedStop === void 0 ? void 0 : selectedStop.color) !== null && _c !== void 0 ? _c : '');
127
+ useEffect(() => {
128
+ if (selectedStop) {
129
+ setColorInput(selectedStop.color);
130
+ }
131
+ }, [selectedStop === null || selectedStop === void 0 ? void 0 : selectedStop.id, selectedStop === null || selectedStop === void 0 ? void 0 : selectedStop.color]); // eslint-disable-line react-hooks/exhaustive-deps
132
+ const handleColorInputChange = (e) => {
133
+ const val = e.target.value;
134
+ setColorInput(val);
135
+ if (selectedStop) {
136
+ const parsed = parseColor(val);
137
+ if (parsed) {
138
+ updateGradientStop(selectedStop.id, {
139
+ color: formatHex(parsed.h, parsed.s, parsed.l),
140
+ });
141
+ // Also update the canvas/sliders
142
+ isSelectingStopRef.current = true;
143
+ setColor(parsed);
144
+ requestAnimationFrame(() => { isSelectingStopRef.current = false; });
145
+ }
146
+ }
147
+ };
148
+ const handleColorInputBlur = () => {
149
+ if (selectedStop) {
150
+ setColorInput(selectedStop.color);
151
+ }
152
+ };
153
+ const handlePositionChange = (e) => {
154
+ const val = parseInt(e.target.value, 10);
155
+ if (selectedStop && !isNaN(val)) {
156
+ updateGradientStop(selectedStop.id, {
157
+ position: Math.max(0, Math.min(100, val)),
158
+ });
159
+ }
160
+ };
161
+ // --- Copy ---
162
+ const handleCopy = async () => {
163
+ try {
164
+ await navigator.clipboard.writeText(outputCSS);
165
+ setCopied(true);
166
+ setTimeout(() => setCopied(false), 1500);
167
+ }
168
+ catch (_a) { }
169
+ };
170
+ return (jsxs("div", { className: cn('space-y-3', className), ...props, children: [jsx(ColorPicker2DCanvas, {}), jsx(ColorPickerSliders, { className: "flex-1" }), jsxs("div", { className: "flex items-center gap-3", children: [jsxs(Segmented, { value: gradient.type, onChange: handleTypeChange, size: "sm", children: [jsx(Segmented.Item, { value: "linear", children: "Linear" }), jsx(Segmented.Item, { value: "radial", children: "Radial" })] }), gradient.type === 'linear' && (jsxs("div", { className: "flex items-center gap-1", children: [jsx(Input, { size: "sm", type: "number", value: String(gradient.angle), onChange: handleAngleChange, className: "!w-16 text-center", "aria-label": "Gradient angle" }), jsx("span", { className: "text-xs text-[var(--color-text-muted)]", children: "deg" })] }))] }), jsx("div", { className: "h-8 rounded-lg ring-1 ring-inset ring-black/5", style: { background: outputCSS }, "aria-label": "Gradient preview" }), jsxs("div", { className: "space-y-1", children: [jsx("div", { className: "text-xs font-medium text-[var(--color-text-muted)]", children: "Color Stops" }), jsx("div", { ref: stopBarRef, onClick: handleStopBarClick, className: "relative h-4 rounded-full cursor-pointer touch-none select-none", style: { backgroundImage: barGradientCSS }, "aria-label": "Gradient stops", children: gradient.stops.map(stop => (jsx("div", { "data-stop-marker": true, onPointerDown: (e) => handleStopPointerDown(e, stop.id), onClick: (e) => {
171
+ e.stopPropagation();
172
+ selectStop(stop.id);
173
+ }, className: cn('absolute top-1/2 size-4 rounded-full border-2 border-white shadow-md cursor-grab active:cursor-grabbing', selectedStopId === stop.id && 'ring-2 ring-[var(--color-primary)] ring-offset-1'), style: {
174
+ left: `${stop.position}%`,
175
+ transform: 'translate(-50%, -50%)',
176
+ backgroundColor: stop.color,
177
+ }, role: "slider", "aria-label": `Color stop at ${stop.position}%`, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": stop.position, tabIndex: 0 }, stop.id))) })] }), selectedStop && (jsxs("div", { className: "flex items-center gap-2", children: [jsx(Input, { size: "sm", value: colorInput, onChange: handleColorInputChange, onBlur: handleColorInputBlur, "aria-label": "Stop color", className: "flex-1 !font-mono", contentStart: jsx("div", { className: "size-4 rounded-full flex-shrink-0 border border-[var(--color-border)]", style: { backgroundColor: selectedStop.color } }) }), jsxs("div", { className: "flex items-center gap-0.5", children: [jsx(Input, { size: "sm", type: "number", value: String(selectedStop.position), onChange: handlePositionChange, className: "!w-16 text-center", "aria-label": "Stop position" }), jsx("span", { className: "text-xs text-[var(--color-text-muted)]", children: "%" })] }), gradient.stops.length > 2 && (jsx(Button, { variant: "ghost", size: "sm", color: "danger", iconOnly: true, onClick: () => {
178
+ removeGradientStop(selectedStop.id);
179
+ const remaining = gradient.stops.filter(s => s.id !== selectedStop.id);
180
+ if (remaining[0])
181
+ selectStop(remaining[0].id);
182
+ }, "aria-label": "Remove color stop", children: jsx(TrashIcon, { className: "size-4" }) })), jsx(Button, { variant: "ghost", size: "sm", color: "neutral", iconOnly: true, onClick: () => addGradientStop(50), "aria-label": "Add color stop", children: jsx(PlusIcon, { className: "size-4" }) })] })), jsxs("div", { className: "flex items-center gap-2 border border-[var(--color-border)] rounded-lg px-3 py-2", children: [jsx("code", { className: "flex-1 text-xs font-mono text-[var(--color-text-secondary)] truncate", children: outputCSS }), jsx(Button, { variant: "ghost", size: "sm", color: "neutral", iconOnly: true, onClick: handleCopy, "aria-label": "Copy gradient CSS", title: copied ? 'Copied!' : 'Copy CSS', children: copied ? (jsx(ClipboardDocumentCheckIcon, { className: "size-4 text-[var(--color-success)]" })) : (jsx(ClipboardDocumentIcon, { className: "size-4" })) })] })] }));
183
+ };
184
+ ColorPickerGradient.displayName = 'ColorPickerGradient';
185
+
186
+ export { ColorPickerGradient };
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import type { ColorPickerMode } from './ColorPicker.types';
3
+ export interface ColorPickerHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
4
+ /** Available modes/tabs to show */
5
+ modes?: ColorPickerMode[];
6
+ /** Currently active mode (overrides context) */
7
+ activeMode?: ColorPickerMode;
8
+ /** Callback when mode changes */
9
+ onModeChange?: (mode: ColorPickerMode) => void;
10
+ /** Whether to show the close button */
11
+ showClose?: boolean;
12
+ className?: string;
13
+ }
14
+ export declare const ColorPickerHeader: React.FC<ColorPickerHeaderProps>;
@@ -0,0 +1,31 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { XMarkIcon } from '@heroicons/react/20/solid';
3
+ import { useContext } from 'react';
4
+ import { cn } from '../../../utils/cn.js';
5
+ import { ColorPickerContext } from './ColorPickerContext.js';
6
+
7
+ const modeLabels = {
8
+ solid: 'Solid',
9
+ gradient: 'Gradient',
10
+ image: 'Image',
11
+ };
12
+ const ColorPickerHeader = ({ modes = ['solid'], activeMode: activeModeFromProp, onModeChange, showClose = true, className, ...props }) => {
13
+ const context = useContext(ColorPickerContext);
14
+ if (!context) {
15
+ throw new Error('ColorPickerHeader must be used within a ColorPicker');
16
+ }
17
+ const { setIsOpen, mode: contextMode, setMode: contextSetMode } = context;
18
+ const activeMode = activeModeFromProp !== null && activeModeFromProp !== void 0 ? activeModeFromProp : contextMode;
19
+ const handleModeChange = (mode) => {
20
+ if (onModeChange) {
21
+ onModeChange(mode);
22
+ }
23
+ contextSetMode(mode);
24
+ };
25
+ return (jsxs("div", { className: cn('flex items-center justify-between border-b border-[var(--color-border)] -mx-4 -mt-4 px-4', className), ...props, children: [jsx("div", { className: "flex items-center gap-4", children: modes.map((mode) => (jsxs("button", { type: "button", onClick: () => handleModeChange(mode), className: cn('py-3 text-sm font-medium transition-colors relative cursor-pointer', activeMode === mode
26
+ ? 'text-[var(--color-primary)]'
27
+ : 'text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]'), "aria-selected": activeMode === mode, role: "tab", children: [modeLabels[mode], activeMode === mode && (jsx("div", { className: "absolute bottom-0 left-0 right-0 h-0.5 bg-[var(--color-primary)] rounded-full" }))] }, mode))) }), showClose && (jsx("button", { type: "button", onClick: () => setIsOpen(false), className: "p-1 text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] transition-colors rounded-md hover:bg-[var(--color-background-secondary)] cursor-pointer", "aria-label": "Close color picker", children: jsx(XMarkIcon, { className: "size-5" }) }))] }));
28
+ };
29
+ ColorPickerHeader.displayName = 'ColorPickerHeader';
30
+
31
+ export { ColorPickerHeader };
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import type { ColorPickerImageProps } from './ColorPicker.types';
3
+ /**
4
+ * ColorPickerImage - Pick colors from an uploaded image
5
+ */
6
+ export declare const ColorPickerImage: React.FC<ColorPickerImageProps>;
@@ -0,0 +1,154 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { ArrowUpTrayIcon, XMarkIcon } from '@heroicons/react/20/solid';
3
+ import { useContext, useRef, useState, useEffect, useCallback } from 'react';
4
+ import { cn } from '../../../utils/cn.js';
5
+ import { Button } from '../../core/Button/Button.js';
6
+ import { ColorPickerContext } from './ColorPickerContext.js';
7
+ import { rgbToHsl } from './colorUtils.js';
8
+
9
+ /**
10
+ * ColorPickerImage - Pick colors from an uploaded image
11
+ */
12
+ const ColorPickerImage = ({ className, ...props }) => {
13
+ const context = useContext(ColorPickerContext);
14
+ if (!context) {
15
+ throw new Error('ColorPickerImage must be used within a ColorPicker');
16
+ }
17
+ const { setColor } = context;
18
+ const canvasRef = useRef(null);
19
+ const fileInputRef = useRef(null);
20
+ const [imageSrc, setImageSrc] = useState(null);
21
+ const [imageLoaded, setImageLoaded] = useState(false);
22
+ const [isDragOver, setIsDragOver] = useState(false);
23
+ const [pickedColor, setPickedColor] = useState(null);
24
+ const [cursorPos, setCursorPos] = useState(null);
25
+ // Clean up object URL on unmount or change
26
+ useEffect(() => {
27
+ return () => {
28
+ if (imageSrc)
29
+ URL.revokeObjectURL(imageSrc);
30
+ };
31
+ }, [imageSrc]);
32
+ const loadImage = useCallback((file) => {
33
+ if (!file.type.startsWith('image/'))
34
+ return;
35
+ // Clean up previous
36
+ if (imageSrc)
37
+ URL.revokeObjectURL(imageSrc);
38
+ const url = URL.createObjectURL(file);
39
+ setImageSrc(url);
40
+ setImageLoaded(false);
41
+ setPickedColor(null);
42
+ setCursorPos(null);
43
+ const img = new Image();
44
+ img.onload = () => {
45
+ const canvas = canvasRef.current;
46
+ if (!canvas)
47
+ return;
48
+ // Fit image within container while maintaining aspect ratio
49
+ const maxWidth = 300;
50
+ const maxHeight = 200;
51
+ let width = img.width;
52
+ let height = img.height;
53
+ if (width > maxWidth || height > maxHeight) {
54
+ const ratio = Math.min(maxWidth / width, maxHeight / height);
55
+ width = Math.round(width * ratio);
56
+ height = Math.round(height * ratio);
57
+ }
58
+ // Account for device pixel ratio for sharp rendering
59
+ const dpr = window.devicePixelRatio || 1;
60
+ canvas.width = width * dpr;
61
+ canvas.height = height * dpr;
62
+ canvas.style.width = `${width}px`;
63
+ canvas.style.height = `${height}px`;
64
+ const ctx = canvas.getContext('2d');
65
+ if (!ctx)
66
+ return;
67
+ ctx.scale(dpr, dpr);
68
+ ctx.drawImage(img, 0, 0, width, height);
69
+ setImageLoaded(true);
70
+ };
71
+ img.src = url;
72
+ }, [imageSrc]);
73
+ // File input handler
74
+ const handleFileChange = (e) => {
75
+ var _a;
76
+ const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
77
+ if (file)
78
+ loadImage(file);
79
+ };
80
+ // Drag and drop handlers
81
+ const handleDragOver = (e) => {
82
+ e.preventDefault();
83
+ setIsDragOver(true);
84
+ };
85
+ const handleDragLeave = () => {
86
+ setIsDragOver(false);
87
+ };
88
+ const handleDrop = (e) => {
89
+ var _a;
90
+ e.preventDefault();
91
+ setIsDragOver(false);
92
+ const file = (_a = e.dataTransfer.files) === null || _a === void 0 ? void 0 : _a[0];
93
+ if (file)
94
+ loadImage(file);
95
+ };
96
+ // Pick color from canvas
97
+ const pickColorAtPosition = useCallback((clientX, clientY) => {
98
+ const canvas = canvasRef.current;
99
+ if (!canvas)
100
+ return;
101
+ const rect = canvas.getBoundingClientRect();
102
+ const dpr = window.devicePixelRatio || 1;
103
+ // Map click position to canvas pixel coordinates
104
+ const x = Math.round(((clientX - rect.left) / rect.width) * canvas.width / dpr) * dpr;
105
+ const y = Math.round(((clientY - rect.top) / rect.height) * canvas.height / dpr) * dpr;
106
+ const ctx = canvas.getContext('2d');
107
+ if (!ctx)
108
+ return;
109
+ const pixel = ctx.getImageData(x, y, 1, 1).data;
110
+ const r = pixel[0];
111
+ const g = pixel[1];
112
+ const b = pixel[2];
113
+ const a = pixel[3] / 255;
114
+ const hsl = rgbToHsl(r, g, b);
115
+ setColor({ ...hsl, a });
116
+ // Set picked color for display
117
+ const hex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
118
+ setPickedColor(hex);
119
+ // Set cursor position as percentage
120
+ setCursorPos({
121
+ x: ((clientX - rect.left) / rect.width) * 100,
122
+ y: ((clientY - rect.top) / rect.height) * 100,
123
+ });
124
+ }, [setColor]);
125
+ const handleCanvasClick = (e) => {
126
+ pickColorAtPosition(e.clientX, e.clientY);
127
+ };
128
+ // Clear image
129
+ const handleClear = () => {
130
+ if (imageSrc)
131
+ URL.revokeObjectURL(imageSrc);
132
+ setImageSrc(null);
133
+ setImageLoaded(false);
134
+ setPickedColor(null);
135
+ setCursorPos(null);
136
+ if (fileInputRef.current)
137
+ fileInputRef.current.value = '';
138
+ };
139
+ return (jsxs("div", { className: cn('space-y-3', className), ...props, children: [!imageLoaded ? (
140
+ /* Upload dropzone */
141
+ jsxs("div", { onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, className: cn('flex flex-col items-center justify-center gap-2 h-[200px] rounded-xl border-2 border-dashed cursor-pointer transition-colors', isDragOver
142
+ ? 'border-[var(--color-primary)] bg-[var(--color-primary-50)]'
143
+ : 'border-[var(--color-border)] hover:border-[var(--color-primary)] hover:bg-[var(--color-background-secondary)]'), children: [jsx(ArrowUpTrayIcon, { className: "size-8 text-[var(--color-text-muted)]" }), jsx("div", { className: "text-sm text-[var(--color-text-muted)]", children: "Click or drag an image" }), jsx("div", { className: "text-xs text-[var(--color-text-placeholder)]", children: "PNG, JPG, SVG, WebP" }), jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", onChange: handleFileChange, className: "hidden", "aria-label": "Upload image" })] })) : (
144
+ /* Canvas with color picking */
145
+ jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "relative inline-block rounded-xl overflow-hidden ring-1 ring-inset ring-black/5", children: [jsx("canvas", { ref: canvasRef, onClick: handleCanvasClick, className: "cursor-crosshair block" }), cursorPos && (jsx("div", { className: "absolute w-5 h-5 border-2 border-white rounded-full pointer-events-none", style: {
146
+ left: `${cursorPos.x}%`,
147
+ top: `${cursorPos.y}%`,
148
+ transform: 'translate(-50%, -50%)',
149
+ boxShadow: '0 0 0 1.5px rgba(0,0,0,0.15), 0 2px 6px rgba(0,0,0,0.15)',
150
+ }, children: pickedColor && (jsx("div", { className: "absolute inset-1 rounded-full", style: { backgroundColor: pickedColor } })) }))] }), jsxs("div", { className: "flex items-center justify-between", children: [pickedColor && (jsxs("div", { className: "flex items-center gap-2", children: [jsx("div", { className: "size-5 rounded-full border border-[var(--color-border)]", style: { backgroundColor: pickedColor } }), jsx("span", { className: "text-xs font-mono text-[var(--color-text-secondary)]", children: pickedColor })] })), jsxs(Button, { variant: "ghost", size: "sm", color: "neutral", onClick: handleClear, "aria-label": "Remove image", className: "ml-auto", children: [jsx(XMarkIcon, { className: "size-3.5" }), "Change"] })] })] })), !imageLoaded && jsx("canvas", { ref: canvasRef, className: "hidden" })] }));
151
+ };
152
+ ColorPickerImage.displayName = 'ColorPickerImage';
153
+
154
+ export { ColorPickerImage };
@@ -1,7 +1,3 @@
1
1
  import React from "react";
2
2
  import type { ColorPickerInputProps } from "./ColorPicker.types";
3
- /**
4
- * ColorPickerInput - Manual color value input
5
- * Supports hex, rgb, and hsl input based on selected format
6
- */
7
3
  export declare const ColorPickerInput: React.FC<ColorPickerInputProps>;
@@ -1,27 +1,37 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { useContext, useState } from 'react';
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useContext, useState, useEffect } from 'react';
3
+ import { ChevronUpDownIcon, ClipboardDocumentCheckIcon, ClipboardDocumentIcon } from '@heroicons/react/20/solid';
3
4
  import { cn } from '../../../utils/cn.js';
4
- import { Input } from '../Input/Input.js';
5
- import '../Input/Input.theme.js';
6
- import { ColorPickerContext } from './ColorPicker.js';
7
- import { hslToRgb, parseColor } from './colorUtils.js';
5
+ import { Button } from '../../core/Button/Button.js';
6
+ import { ColorPickerContext } from './ColorPickerContext.js';
7
+ import { parseColor, hslToRgb } from './colorUtils.js';
8
8
 
9
- /**
10
- * ColorPickerInput - Manual color value input
11
- * Supports hex, rgb, and hsl input based on selected format
12
- */
13
9
  const ColorPickerInput = ({ className, ...props }) => {
14
10
  const context = useContext(ColorPickerContext);
15
11
  if (!context) {
16
12
  throw new Error("ColorPickerInput must be used within a ColorPicker");
17
13
  }
18
- const { color, setColor, format, theme, getFormattedValue } = context;
14
+ const { color, setColor, format, setFormat, theme, getFormattedValue, showAlpha } = context;
19
15
  const [inputValue, setInputValue] = useState(getFormattedValue());
20
16
  const [isInvalid, setIsInvalid] = useState(false);
17
+ const [copied, setCopied] = useState(false);
18
+ // Sync inputValue when color changes externally (canvas, sliders, presets)
19
+ useEffect(() => {
20
+ if (!isInvalid) {
21
+ setInputValue(getFormattedValue());
22
+ }
23
+ }, [color, format]); // eslint-disable-line react-hooks/exhaustive-deps
24
+ const handleCopy = async () => {
25
+ try {
26
+ await navigator.clipboard.writeText(getFormattedValue());
27
+ setCopied(true);
28
+ setTimeout(() => setCopied(false), 1500);
29
+ }
30
+ catch (_a) { }
31
+ };
21
32
  const handleChange = (e) => {
22
33
  const value = e.target.value;
23
34
  setInputValue(value);
24
- // Try to parse the color
25
35
  const parsed = parseColor(value);
26
36
  if (parsed) {
27
37
  setColor(parsed);
@@ -32,7 +42,6 @@ const ColorPickerInput = ({ className, ...props }) => {
32
42
  }
33
43
  };
34
44
  const handleBlur = () => {
35
- // Reset to current valid color if invalid
36
45
  if (isInvalid) {
37
46
  setInputValue(getFormattedValue());
38
47
  setIsInvalid(false);
@@ -43,54 +52,37 @@ const ColorPickerInput = ({ className, ...props }) => {
43
52
  e.currentTarget.blur();
44
53
  }
45
54
  };
46
- // Render different input types based on format
47
- if (format === "hex") {
48
- return (jsx("div", { className: cn(theme.inputContainerStyle, className), ...props, children: jsx(Input, { type: "text", size: "sm", value: inputValue, onChange: handleChange, onBlur: handleBlur, onKeyDown: handleKeyDown, state: isInvalid ? "invalid" : "default", placeholder: "#000000", className: "flex-1 text-base font-mono", "aria-label": "Hex color value" }) }));
49
- }
50
- if (format === "rgb") {
51
- const rgb = hslToRgb(color.h, color.s, color.l);
52
- return (jsxs("div", { className: cn(theme.inputContainerStyle, "flex gap-2", className), ...props, children: [jsx(Input, { type: "number", size: "sm", min: 0, max: 255, value: rgb.r, onChange: (e) => {
53
- const newR = parseInt(e.target.value, 10);
54
- if (!isNaN(newR) && newR >= 0 && newR <= 255) {
55
- const parsed = parseColor(`rgb(${newR}, ${rgb.g}, ${rgb.b})`);
56
- if (parsed)
57
- setColor(parsed);
58
- }
59
- }, className: "flex-1 text-base", "aria-label": "Red", placeholder: "R" }), jsx(Input, { type: "number", size: "sm", min: 0, max: 255, value: rgb.g, onChange: (e) => {
60
- const newG = parseInt(e.target.value, 10);
61
- if (!isNaN(newG) && newG >= 0 && newG <= 255) {
62
- const parsed = parseColor(`rgb(${rgb.r}, ${newG}, ${rgb.b})`);
63
- if (parsed)
64
- setColor(parsed);
65
- }
66
- }, className: "flex-1 text-base", "aria-label": "Green", placeholder: "G" }), jsx(Input, { type: "number", size: "sm", min: 0, max: 255, value: rgb.b, onChange: (e) => {
67
- const newB = parseInt(e.target.value, 10);
68
- if (!isNaN(newB) && newB >= 0 && newB <= 255) {
69
- const parsed = parseColor(`rgb(${rgb.r}, ${rgb.g}, ${newB})`);
70
- if (parsed)
71
- setColor(parsed);
72
- }
73
- }, className: "flex-1 text-base", "aria-label": "Blue", placeholder: "B" })] }));
74
- }
75
- if (format === "hsl") {
76
- return (jsxs("div", { className: cn(theme.inputContainerStyle, "flex gap-2", className), ...props, children: [jsx(Input, { type: "number", size: "sm", min: 0, max: 360, value: Math.round(color.h), onChange: (e) => {
77
- const newH = parseInt(e.target.value, 10);
78
- if (!isNaN(newH) && newH >= 0 && newH <= 360) {
79
- setColor({ ...color, h: newH });
80
- }
81
- }, className: "flex-1 text-base", "aria-label": "Hue", placeholder: "H" }), jsx(Input, { type: "number", size: "sm", min: 0, max: 100, value: Math.round(color.s), onChange: (e) => {
82
- const newS = parseInt(e.target.value, 10);
83
- if (!isNaN(newS) && newS >= 0 && newS <= 100) {
84
- setColor({ ...color, s: newS });
85
- }
86
- }, className: "flex-1 text-base", "aria-label": "Saturation", placeholder: "S" }), jsx(Input, { type: "number", size: "sm", min: 0, max: 100, value: Math.round(color.l), onChange: (e) => {
87
- const newL = parseInt(e.target.value, 10);
88
- if (!isNaN(newL) && newL >= 0 && newL <= 100) {
89
- setColor({ ...color, l: newL });
90
- }
91
- }, className: "flex-1 text-base", "aria-label": "Lightness", placeholder: "L" })] }));
92
- }
93
- return null;
55
+ // Cycle through formats on click
56
+ const cycleFormat = () => {
57
+ const formats = ['hex', 'rgb', 'hsl'];
58
+ const currentIndex = formats.indexOf(format);
59
+ const nextIndex = (currentIndex + 1) % formats.length;
60
+ setFormat(formats[nextIndex]);
61
+ };
62
+ // Format display label
63
+ const formatLabel = format === 'hex' ? 'Hex' : format === 'rgb' ? 'RGB' : 'HSL';
64
+ // Alpha as percentage
65
+ const alphaPercent = Math.round(color.a * 100);
66
+ // Handle alpha change
67
+ const handleAlphaChange = (e) => {
68
+ const val = parseInt(e.target.value, 10);
69
+ if (!isNaN(val) && val >= 0 && val <= 100) {
70
+ setColor({ ...color, a: val / 100 });
71
+ }
72
+ };
73
+ // For RGB/HSL, show channel values inline
74
+ const getDisplayValue = () => {
75
+ if (format === 'hex')
76
+ return inputValue;
77
+ if (format === 'rgb') {
78
+ const rgb = hslToRgb(color.h, color.s, color.l);
79
+ return `${rgb.r}, ${rgb.g}, ${rgb.b}`;
80
+ }
81
+ return `${Math.round(color.h)}°, ${Math.round(color.s)}%, ${Math.round(color.l)}%`;
82
+ };
83
+ // Get the current color as rgba for the swatch
84
+ const swatchColor = `hsla(${color.h}, ${color.s}%, ${color.l}%, ${color.a})`;
85
+ return (jsxs("div", { className: cn("flex items-stretch border border-[var(--color-border)] rounded-lg overflow-hidden divide-x divide-[var(--color-border)]", className), ...props, children: [jsxs(Button, { variant: "ghost", size: "sm", color: "neutral", onClick: cycleFormat, "aria-label": "Change color format", className: "!rounded-none !border-0 !px-3 !py-2 flex-shrink-0", children: [formatLabel, jsx(ChevronUpDownIcon, { className: "size-4 text-[var(--color-text-muted)]" })] }), jsxs("div", { className: "flex items-center gap-2 px-3 py-2 flex-1 min-w-0", children: [jsx("div", { className: "size-5 rounded-full flex-shrink-0 border border-[var(--color-border)]", style: { backgroundColor: swatchColor } }), jsx("input", { type: "text", value: format === 'hex' ? inputValue : getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onKeyDown: handleKeyDown, readOnly: format !== 'hex', className: cn("flex-1 min-w-0 bg-transparent text-sm font-mono text-[var(--color-text-primary)] outline-none", isInvalid && "text-[var(--color-danger)]"), "aria-label": "Color value" }), jsx(Button, { variant: "ghost", size: "sm", color: "neutral", iconOnly: true, onClick: handleCopy, "aria-label": "Copy color value", title: copied ? "Copied!" : "Copy color value", className: "!p-0 !size-auto flex-shrink-0", children: copied ? (jsx(ClipboardDocumentCheckIcon, { className: "size-4 text-[var(--color-success)]" })) : (jsx(ClipboardDocumentIcon, { className: "size-4" })) })] }), showAlpha && (jsxs("div", { className: "flex items-center px-3 py-2 flex-shrink-0", children: [jsx("input", { type: "number", min: 0, max: 100, value: alphaPercent, onChange: handleAlphaChange, className: "w-10 bg-transparent text-sm text-[var(--color-text-primary)] text-center outline-none font-mono", "aria-label": "Opacity percentage" }), jsx("span", { className: "text-sm text-[var(--color-text-muted)]", children: "%" })] }))] }));
94
86
  };
95
87
  ColorPickerInput.displayName = "ColorPickerInput";
96
88
 
@@ -1,14 +1,15 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { useContext } from 'react';
3
+ import { CheckIcon } from '@heroicons/react/16/solid';
3
4
  import { cn } from '../../../utils/cn.js';
4
- import { ColorPickerContext } from './ColorPicker.js';
5
+ import { ColorPickerContext } from './ColorPickerContext.js';
5
6
  import { parseColor } from './colorUtils.js';
6
7
 
7
8
  /**
8
9
  * ColorPickerPresets - Grid of preset color swatches
9
10
  * Allows quick selection from predefined colors
10
11
  */
11
- const ColorPickerPresets = ({ colors: customColors, className, ...props }) => {
12
+ const ColorPickerPresets = ({ colors: customColors, label = 'Preset colors', className, ...props }) => {
12
13
  const context = useContext(ColorPickerContext);
13
14
  if (!context) {
14
15
  throw new Error('ColorPickerPresets must be used within a ColorPicker');
@@ -41,15 +42,15 @@ const ColorPickerPresets = ({ colors: customColors, className, ...props }) => {
41
42
  Math.abs(parsed.s - color.s) < 1 &&
42
43
  Math.abs(parsed.l - color.l) < 1);
43
44
  };
44
- return (jsx("div", { className: cn(theme.presetGridStyle, className), ...props, children: colors.map((presetColor, index) => {
45
- const selected = isSelected(presetColor);
46
- return (jsxs("button", { type: "button", role: "button", tabIndex: 0, onClick: () => handlePresetClick(presetColor), onKeyDown: (e) => handleKeyDown(e, presetColor), "data-selected": selected ? '' : undefined, className: cn(theme.presetSwatchStyle), "aria-label": `Select color ${presetColor}`, title: presetColor, children: [jsx("div", { className: "absolute inset-0 rounded", style: {
47
- backgroundImage: 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==")',
48
- backgroundSize: '8px 8px',
49
- } }), jsx("div", { className: "absolute inset-0 rounded", style: {
50
- backgroundColor: presetColor,
51
- } })] }, `${presetColor}-${index}`));
52
- }) }));
45
+ return (jsxs("div", { className: cn(className), ...props, children: [label !== false && (jsx("div", { className: theme.sectionLabelStyle, children: label })), jsx("div", { className: cn(theme.presetGridStyle), children: colors.map((presetColor, index) => {
46
+ const selected = isSelected(presetColor);
47
+ return (jsxs("button", { type: "button", role: "button", tabIndex: 0, onClick: () => handlePresetClick(presetColor), onKeyDown: (e) => handleKeyDown(e, presetColor), "data-selected": selected ? '' : undefined, className: cn(theme.presetSwatchStyle), "aria-label": `Select color ${presetColor}`, title: presetColor, children: [jsx("div", { className: "absolute inset-0 rounded", style: {
48
+ backgroundImage: 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==")',
49
+ backgroundSize: '8px 8px',
50
+ } }), jsx("div", { className: "absolute inset-0 rounded", style: {
51
+ backgroundColor: presetColor,
52
+ } }), selected && (jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: jsx(CheckIcon, { className: "size-3.5 text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.5)]" }) }))] }, `${presetColor}-${index}`));
53
+ }) })] }));
53
54
  };
54
55
  ColorPickerPresets.displayName = 'ColorPickerPresets';
55
56
 
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ import type { ColorPickerSavedColorsProps } from './ColorPicker.types';
3
+ export declare const ColorPickerSavedColors: React.FC<ColorPickerSavedColorsProps>;