@andreagiugni/tailwind-dashboard-ui 0.5.3 → 0.5.4

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.
package/README.md CHANGED
@@ -205,7 +205,7 @@ Legend: **(+native)** = also extends the element's native HTML attributes.
205
205
  | `DatePicker` | `id`, `mode` (single/multiple/range/time), `defaultDate`, `onChange`, `label`, `placeholder` |
206
206
  | `DateTimePicker` | `id`, `label?`, `placeholder?`, `defaultDate?`, `defaultTime?` (`"HH:MM"`), `onChange?({ date, time })` — flatpickr date calendar + a compact `HH:MM` time input |
207
207
  | `Slider` | `value` / `defaultValue`, `onChange(value)`, `min`, `max`, `step`, `color`, `label?`, `showValue?`, `disabled` |
208
- | `ColorPicker` | `defaultValue?` (hex), `defaultFormat?` / `formats?` (`hex`/`rgb`/`rgba`/`hsl`/`hsla`), `showInput?`, `withEyeDropper?`, `label?`, `onChange?(value, color)` — saturation/hue/alpha picker + value input with a format switcher; built on `react-beautiful-color` (bundled), client-only, **import its CSS** (see ColorPicker note above) |
208
+ | `ColorPicker` | `defaultValue?` (hex), `defaultFormat?` / `formats?` (`hex`/`rgb`/`rgba`/`hsl`/`hsla`), `showInput?`, `withEyeDropper?`, `label?`, `onChange?(value, color)` — compact swatch + formatted value trigger opening a saturation/hue/alpha popup with a value input and format switcher; built on `react-beautiful-color` (bundled), client-only, **import its CSS** (see ColorPicker note above) |
209
209
  | `Editor` (WYSIWYG) | `content`, `onChange(html)`, `placeholder?`, `editable?`, `toolbar?` (ordered tool ids or custom buttons), `editorClassName?` — optional Tiptap peers; render client-only (see Data viz) |
210
210
 
211
211
  ### Data viz
@@ -0,0 +1,291 @@
1
+ "use client";
2
+ import { cn } from './chunk-ZLIYUUA4.js';
3
+ import { useState, useMemo, useRef, useEffect, useCallback, useLayoutEffect } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { Color, ColorPicker as ColorPicker$1 } from 'react-beautiful-color';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ var ALL_FORMATS = ["hex", "rgb", "rgba", "hsl", "hsla"];
9
+ function parseValue(text, format) {
10
+ const t = text.trim();
11
+ if (format === "hex") {
12
+ return /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(t) ? { type: "hex", value: t } : null;
13
+ }
14
+ const n = t.match(/-?\d*\.?\d+/g)?.map(Number) ?? [];
15
+ if (format === "rgb" && n.length >= 3) return { type: "rgb", r: n[0], g: n[1], b: n[2] };
16
+ if (format === "rgba" && n.length >= 4)
17
+ return { type: "rgba", r: n[0], g: n[1], b: n[2], a: n[3] };
18
+ if (format === "hsl" && n.length >= 3) return { type: "hsl", h: n[0], s: n[1], l: n[2] };
19
+ if (format === "hsla" && n.length >= 4)
20
+ return { type: "hsla", h: n[0], s: n[1], l: n[2], a: n[3] };
21
+ return null;
22
+ }
23
+ function colorToString(color, format) {
24
+ switch (format) {
25
+ case "hex": {
26
+ const { r, g, b } = color.getRgb();
27
+ const a = color.getRgba().a;
28
+ const hex = `#${[r, g, b].map((v) => Math.round(v).toString(16).padStart(2, "0")).join("")}`;
29
+ return a < 1 ? `${hex}${Math.round(a * 255).toString(16).padStart(2, "0")}` : hex;
30
+ }
31
+ case "rgb": {
32
+ const { r, g, b } = color.getRgb();
33
+ return `rgb(${r}, ${g}, ${b})`;
34
+ }
35
+ case "rgba": {
36
+ const { r, g, b, a } = color.getRgba();
37
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
38
+ }
39
+ case "hsl": {
40
+ const { h, s, l } = color.getHsl();
41
+ return `hsl(${h}, ${s}%, ${l}%)`;
42
+ }
43
+ case "hsla": {
44
+ const { h, s, l, a } = color.getHsla();
45
+ return `hsla(${h}, ${s}%, ${l}%, ${a})`;
46
+ }
47
+ default:
48
+ return color.getHex();
49
+ }
50
+ }
51
+ var pipetteIcon = /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
52
+ "path",
53
+ {
54
+ d: "M19.5 3.5a2.12 2.12 0 0 0-3 0l-2.3 2.3-1-1-1.4 1.4 1 1L4 14.9V19h4.1l7.7-7.7 1 1 1.4-1.4-1-1 2.3-2.3a2.12 2.12 0 0 0 0-3ZM7.3 17.6H6v-1.3l6.6-6.6 1.3 1.3-6.6 6.6Z",
55
+ fill: "currentColor"
56
+ }
57
+ ) });
58
+ var ColorPicker = ({
59
+ defaultValue = "#3641f5",
60
+ defaultFormat = "hex",
61
+ formats = ALL_FORMATS,
62
+ showInput = true,
63
+ withEyeDropper = false,
64
+ label,
65
+ onChange,
66
+ className,
67
+ ...rest
68
+ }) => {
69
+ const [hsva, setHsva] = useState(
70
+ () => new Color({ type: "hex", value: defaultValue }).getHsva()
71
+ );
72
+ const [format, setFormat] = useState(defaultFormat);
73
+ const [isOpen, setIsOpen] = useState(false);
74
+ const [popupPosition, setPopupPosition] = useState({
75
+ left: 0,
76
+ top: 0,
77
+ width: 320
78
+ });
79
+ const [draft, setDraft] = useState(null);
80
+ const [exact, setExact] = useState(
81
+ () => ({ format: "hex", value: defaultValue })
82
+ );
83
+ const colorInput = useMemo(() => ({ type: "hsva", ...hsva }), [hsva]);
84
+ const currentColor = useMemo(() => new Color(colorInput), [colorInput]);
85
+ const display = exact && exact.format === format ? exact.value : colorToString(currentColor, format);
86
+ const inputValue = draft ?? display;
87
+ const hsvaRef = useRef(hsva);
88
+ hsvaRef.current = hsva;
89
+ const formatRef = useRef(format);
90
+ formatRef.current = format;
91
+ const onChangeRef = useRef(onChange);
92
+ onChangeRef.current = onChange;
93
+ const rootRef = useRef(null);
94
+ const triggerRef = useRef(null);
95
+ const popupRef = useRef(null);
96
+ useEffect(() => {
97
+ if (!isOpen) return;
98
+ const handlePointerDown = (event) => {
99
+ const target = event.target;
100
+ if (rootRef.current && !rootRef.current.contains(target) && !popupRef.current?.contains(target)) {
101
+ setIsOpen(false);
102
+ }
103
+ };
104
+ const handleEscape = (event) => {
105
+ if (event.key === "Escape") setIsOpen(false);
106
+ };
107
+ document.addEventListener("mousedown", handlePointerDown);
108
+ document.addEventListener("keydown", handleEscape);
109
+ return () => {
110
+ document.removeEventListener("mousedown", handlePointerDown);
111
+ document.removeEventListener("keydown", handleEscape);
112
+ };
113
+ }, [isOpen]);
114
+ const updatePopupPosition = useCallback(() => {
115
+ const trigger = triggerRef.current;
116
+ if (!trigger) return;
117
+ const rect = trigger.getBoundingClientRect();
118
+ const viewportPadding = 16;
119
+ const gap = 8;
120
+ const width = Math.min(320, window.innerWidth - viewportPadding * 2);
121
+ const popupHeight = popupRef.current?.offsetHeight ?? 306;
122
+ const fitsBelow = window.innerHeight - rect.bottom - gap >= popupHeight;
123
+ const top = fitsBelow || rect.top < popupHeight + gap ? rect.bottom + gap : rect.top - popupHeight - gap;
124
+ const left = Math.min(
125
+ Math.max(rect.left, viewportPadding),
126
+ window.innerWidth - width - viewportPadding
127
+ );
128
+ setPopupPosition({ left, top: Math.max(viewportPadding, top), width });
129
+ }, []);
130
+ useLayoutEffect(() => {
131
+ if (!isOpen) return;
132
+ updatePopupPosition();
133
+ window.addEventListener("resize", updatePopupPosition);
134
+ window.addEventListener("scroll", updatePopupPosition, true);
135
+ return () => {
136
+ window.removeEventListener("resize", updatePopupPosition);
137
+ window.removeEventListener("scroll", updatePopupPosition, true);
138
+ };
139
+ }, [isOpen, updatePopupPosition]);
140
+ const handlePickerChange = useCallback((next) => {
141
+ const nv = next.getHsva();
142
+ const cur = hsvaRef.current;
143
+ if (nv.h === cur.h && nv.s === cur.s && nv.v === cur.v && nv.a === cur.a) return;
144
+ setHsva(nv);
145
+ setExact(null);
146
+ setDraft(null);
147
+ onChangeRef.current?.(colorToString(next, formatRef.current), next);
148
+ }, []);
149
+ const handleFormatChange = (f) => {
150
+ setFormat(f);
151
+ setDraft(null);
152
+ onChange?.(
153
+ exact && exact.format === f ? exact.value : colorToString(currentColor, f),
154
+ currentColor
155
+ );
156
+ };
157
+ const handleInputChange = (text) => {
158
+ setDraft(text);
159
+ const parsed = parseValue(text, format);
160
+ if (parsed) {
161
+ const c = new Color(parsed);
162
+ setHsva(c.getHsva());
163
+ setExact({ format, value: text.trim() });
164
+ onChange?.(text.trim(), c);
165
+ }
166
+ };
167
+ return /* @__PURE__ */ jsxs(
168
+ "div",
169
+ {
170
+ ref: rootRef,
171
+ className: cn("relative w-full max-w-xs", className),
172
+ ...rest,
173
+ children: [
174
+ label && /* @__PURE__ */ jsx("span", { className: "mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400", children: label }),
175
+ /* @__PURE__ */ jsxs(
176
+ "button",
177
+ {
178
+ ref: triggerRef,
179
+ type: "button",
180
+ "aria-haspopup": "dialog",
181
+ "aria-expanded": isOpen,
182
+ "aria-label": label ? `Open ${label} color picker` : "Open color picker",
183
+ onClick: () => setIsOpen((open) => !open),
184
+ className: "flex h-11 w-full items-center gap-3 rounded-lg border border-gray-300 bg-white px-3 text-left shadow-theme-xs transition hover:bg-gray-50 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:hover:bg-white/[0.03] dark:focus:border-brand-800",
185
+ children: [
186
+ /* @__PURE__ */ jsx(
187
+ "span",
188
+ {
189
+ "aria-hidden": "true",
190
+ className: "h-7 w-7 shrink-0 rounded-md border border-gray-200 dark:border-gray-700",
191
+ style: { backgroundColor: currentColor.format("rgba") }
192
+ }
193
+ ),
194
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-mono text-sm text-gray-800 dark:text-white/90", children: display }),
195
+ /* @__PURE__ */ jsx(
196
+ "svg",
197
+ {
198
+ "aria-hidden": "true",
199
+ className: cn(
200
+ "h-4 w-4 shrink-0 text-gray-500 transition-transform dark:text-gray-400",
201
+ isOpen && "rotate-180"
202
+ ),
203
+ viewBox: "0 0 24 24",
204
+ fill: "none",
205
+ children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
206
+ }
207
+ )
208
+ ]
209
+ }
210
+ ),
211
+ isOpen && typeof document !== "undefined" && createPortal(
212
+ /* @__PURE__ */ jsxs(
213
+ "div",
214
+ {
215
+ ref: popupRef,
216
+ role: "dialog",
217
+ "aria-label": label ? `${label} color picker` : "Color picker",
218
+ className: "fixed z-999 rounded-2xl border border-gray-200 bg-white p-3 shadow-theme-lg dark:border-gray-700 dark:bg-gray-900",
219
+ style: {
220
+ left: popupPosition.left,
221
+ top: popupPosition.top,
222
+ width: popupPosition.width
223
+ },
224
+ children: [
225
+ /* @__PURE__ */ jsxs(
226
+ ColorPicker$1,
227
+ {
228
+ color: colorInput,
229
+ onChange: handlePickerChange,
230
+ className: "h-auto w-full",
231
+ children: [
232
+ /* @__PURE__ */ jsx(ColorPicker$1.Saturation, { className: "mb-4 h-40 rounded-lg" }),
233
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
234
+ withEyeDropper && /* @__PURE__ */ jsx(ColorPicker$1.EyeDropper, { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-gray-300 text-gray-600 hover:bg-gray-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-white/[0.03]", children: pipetteIcon }),
235
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-3", children: [
236
+ /* @__PURE__ */ jsx(ColorPicker$1.Hue, { className: "h-4" }),
237
+ /* @__PURE__ */ jsx(ColorPicker$1.Alpha, { className: "h-4" })
238
+ ] })
239
+ ] })
240
+ ]
241
+ }
242
+ ),
243
+ showInput && /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-stretch gap-2", children: [
244
+ formats.length > 1 && /* @__PURE__ */ jsxs("span", { className: "relative shrink-0", children: [
245
+ /* @__PURE__ */ jsx(
246
+ "select",
247
+ {
248
+ "aria-label": "Color format",
249
+ value: format,
250
+ onChange: (e) => handleFormatChange(e.target.value),
251
+ className: "h-11 appearance-none rounded-lg border border-gray-300 bg-transparent pl-3 pr-8 text-sm text-gray-700 focus:outline-hidden dark:border-gray-700 dark:text-gray-300",
252
+ children: formats.map((f) => /* @__PURE__ */ jsx("option", { value: f, children: f.toUpperCase() }, f))
253
+ }
254
+ ),
255
+ /* @__PURE__ */ jsx(
256
+ "svg",
257
+ {
258
+ "aria-hidden": "true",
259
+ className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400",
260
+ width: "14",
261
+ height: "14",
262
+ viewBox: "0 0 24 24",
263
+ fill: "none",
264
+ children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
265
+ }
266
+ )
267
+ ] }),
268
+ /* @__PURE__ */ jsx(
269
+ "input",
270
+ {
271
+ "aria-label": "Color value",
272
+ value: inputValue,
273
+ onChange: (e) => handleInputChange(e.target.value),
274
+ onBlur: () => setDraft(null),
275
+ className: "h-11 min-w-0 flex-1 rounded-lg border border-gray-300 bg-transparent px-3 font-mono text-sm text-gray-800 focus:outline-hidden dark:border-gray-700 dark:text-white/90"
276
+ }
277
+ )
278
+ ] })
279
+ ]
280
+ }
281
+ ),
282
+ document.body
283
+ )
284
+ ]
285
+ }
286
+ );
287
+ };
288
+
289
+ export { ColorPicker };
290
+ //# sourceMappingURL=chunk-LXA5UOBC.js.map
291
+ //# sourceMappingURL=chunk-LXA5UOBC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/ColorPicker/ColorPicker.tsx"],"names":["RBColorPicker"],"mappings":"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","file":"chunk-LXA5UOBC.js","sourcesContent":["\"use client\";\nimport React, {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { ColorPicker as RBColorPicker, Color } from \"react-beautiful-color\";\nimport { cn } from \"../../lib/cn\";\n\n// `react-beautiful-color` doesn't re-export these types from its entry, so\n// derive them from the values its API does expose.\ntype Hsva = ReturnType<Color[\"getHsva\"]>;\ntype ColorInput = ConstructorParameters<typeof Color>[0] & { type: string };\n\nexport type ColorPickerFormat = \"hex\" | \"rgb\" | \"rgba\" | \"hsl\" | \"hsla\";\n\nexport interface ColorPickerProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Initial color (uncontrolled). Any hex string, e.g. `\"#3641f5\"`. */\n defaultValue?: string;\n /** Format selected on first render. Default: `\"hex\"`. */\n defaultFormat?: ColorPickerFormat;\n /** Formats offered in the switcher. Default: all five. */\n formats?: ColorPickerFormat[];\n /** Render the value input + format switcher. Default: `true`. */\n showInput?: boolean;\n /** Show the native eye-dropper button (browser support varies). */\n withEyeDropper?: boolean;\n /** Optional label rendered above the picker. */\n label?: string;\n /** Fires on every change with the formatted string and the `Color` instance. */\n onChange?: (value: string, color: Color) => void;\n}\n\nconst ALL_FORMATS: ColorPickerFormat[] = [\"hex\", \"rgb\", \"rgba\", \"hsl\", \"hsla\"];\n\n/** Parse the text in the input back into a `ColorInput`, or `null` if invalid. */\nfunction parseValue(text: string, format: ColorPickerFormat): ColorInput | null {\n const t = text.trim();\n if (format === \"hex\") {\n return /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(t)\n ? { type: \"hex\", value: t }\n : null;\n }\n const n = t.match(/-?\\d*\\.?\\d+/g)?.map(Number) ?? [];\n if (format === \"rgb\" && n.length >= 3) return { type: \"rgb\", r: n[0], g: n[1], b: n[2] };\n if (format === \"rgba\" && n.length >= 4)\n return { type: \"rgba\", r: n[0], g: n[1], b: n[2], a: n[3] };\n if (format === \"hsl\" && n.length >= 3) return { type: \"hsl\", h: n[0], s: n[1], l: n[2] };\n if (format === \"hsla\" && n.length >= 4)\n return { type: \"hsla\", h: n[0], s: n[1], l: n[2], a: n[3] };\n return null;\n}\n\n/** Format a `Color` into the requested CSS string. */\nfunction colorToString(color: Color, format: ColorPickerFormat): string {\n switch (format) {\n case \"hex\": {\n const { r, g, b } = color.getRgb();\n const a = color.getRgba().a;\n const hex = `#${[r, g, b].map((v) => Math.round(v).toString(16).padStart(2, \"0\")).join(\"\")}`;\n return a < 1\n ? `${hex}${Math.round(a * 255).toString(16).padStart(2, \"0\")}`\n : hex;\n }\n case \"rgb\": {\n const { r, g, b } = color.getRgb();\n return `rgb(${r}, ${g}, ${b})`;\n }\n case \"rgba\": {\n const { r, g, b, a } = color.getRgba();\n return `rgba(${r}, ${g}, ${b}, ${a})`;\n }\n case \"hsl\": {\n const { h, s, l } = color.getHsl();\n return `hsl(${h}, ${s}%, ${l}%)`;\n }\n case \"hsla\": {\n const { h, s, l, a } = color.getHsla();\n return `hsla(${h}, ${s}%, ${l}%, ${a})`;\n }\n default:\n return color.getHex();\n }\n}\n\nconst pipetteIcon = (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M19.5 3.5a2.12 2.12 0 0 0-3 0l-2.3 2.3-1-1-1.4 1.4 1 1L4 14.9V19h4.1l7.7-7.7 1 1 1.4-1.4-1-1 2.3-2.3a2.12 2.12 0 0 0 0-3ZM7.3 17.6H6v-1.3l6.6-6.6 1.3 1.3-6.6 6.6Z\"\n fill=\"currentColor\"\n />\n </svg>\n);\n\nexport const ColorPicker: React.FC<ColorPickerProps> = ({\n defaultValue = \"#3641f5\",\n defaultFormat = \"hex\",\n formats = ALL_FORMATS,\n showInput = true,\n withEyeDropper = false,\n label,\n onChange,\n className,\n ...rest\n}) => {\n // Internal state lives in full-fidelity HSVA — the picker's native space.\n // Feeding anything lossier (e.g. hex) back into the controlled picker makes\n // the knobs fight the cursor (hue/alpha collapse on round-trip), which is\n // why drag would freeze or snap.\n const [hsva, setHsva] = useState<Hsva>(() =>\n new Color({ type: \"hex\", value: defaultValue }).getHsva()\n );\n const [format, setFormat] = useState<ColorPickerFormat>(defaultFormat);\n const [isOpen, setIsOpen] = useState(false);\n const [popupPosition, setPopupPosition] = useState({\n left: 0,\n top: 0,\n width: 320,\n });\n // While the input is focused we keep a raw draft so partial/invalid typing\n // isn't clobbered by the normalized value.\n const [draft, setDraft] = useState<string | null>(null);\n // HSVA is integer-quantized, so we keep the user's exact text (defaultValue\n // or typed) and show it verbatim until the picker actually changes color.\n const [exact, setExact] = useState<{ format: ColorPickerFormat; value: string } | null>(\n () => ({ format: \"hex\", value: defaultValue })\n );\n\n const colorInput = useMemo(() => ({ type: \"hsva\" as const, ...hsva }), [hsva]);\n const currentColor = useMemo(() => new Color(colorInput), [colorInput]);\n const display =\n exact && exact.format === format ? exact.value : colorToString(currentColor, format);\n const inputValue = draft ?? display;\n\n // The picker's internal drag layer unbinds its document listeners whenever\n // the `onChange` identity changes (its cleanup effect depends on it), which\n // would kill an in-flight drag after the first tick. Keep the handler\n // identity-stable and read mutable values through refs.\n const hsvaRef = useRef(hsva);\n hsvaRef.current = hsva;\n const formatRef = useRef(format);\n formatRef.current = format;\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n const rootRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const popupRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!isOpen) return;\n\n const handlePointerDown = (event: MouseEvent) => {\n const target = event.target as Node;\n if (\n rootRef.current &&\n !rootRef.current.contains(target) &&\n !popupRef.current?.contains(target)\n ) {\n setIsOpen(false);\n }\n };\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") setIsOpen(false);\n };\n\n document.addEventListener(\"mousedown\", handlePointerDown);\n document.addEventListener(\"keydown\", handleEscape);\n return () => {\n document.removeEventListener(\"mousedown\", handlePointerDown);\n document.removeEventListener(\"keydown\", handleEscape);\n };\n }, [isOpen]);\n\n const updatePopupPosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n\n const rect = trigger.getBoundingClientRect();\n const viewportPadding = 16;\n const gap = 8;\n const width = Math.min(320, window.innerWidth - viewportPadding * 2);\n const popupHeight = popupRef.current?.offsetHeight ?? 306;\n const fitsBelow = window.innerHeight - rect.bottom - gap >= popupHeight;\n const top = fitsBelow || rect.top < popupHeight + gap\n ? rect.bottom + gap\n : rect.top - popupHeight - gap;\n const left = Math.min(\n Math.max(rect.left, viewportPadding),\n window.innerWidth - width - viewportPadding\n );\n\n setPopupPosition({ left, top: Math.max(viewportPadding, top), width });\n }, []);\n\n useLayoutEffect(() => {\n if (!isOpen) return;\n\n updatePopupPosition();\n window.addEventListener(\"resize\", updatePopupPosition);\n window.addEventListener(\"scroll\", updatePopupPosition, true);\n return () => {\n window.removeEventListener(\"resize\", updatePopupPosition);\n window.removeEventListener(\"scroll\", updatePopupPosition, true);\n };\n }, [isOpen, updatePopupPosition]);\n\n const handlePickerChange = useCallback((next: Color) => {\n const nv = next.getHsva();\n const cur = hsvaRef.current;\n // The picker re-emits the current color on mount; ignore no-op changes so\n // the exact defaultValue isn't clobbered.\n if (nv.h === cur.h && nv.s === cur.s && nv.v === cur.v && nv.a === cur.a) return;\n setHsva(nv);\n setExact(null);\n setDraft(null);\n onChangeRef.current?.(colorToString(next, formatRef.current), next);\n }, []);\n\n const handleFormatChange = (f: ColorPickerFormat) => {\n setFormat(f);\n setDraft(null);\n onChange?.(\n exact && exact.format === f ? exact.value : colorToString(currentColor, f),\n currentColor\n );\n };\n\n const handleInputChange = (text: string) => {\n setDraft(text);\n const parsed = parseValue(text, format);\n if (parsed) {\n const c = new Color(parsed);\n setHsva(c.getHsva());\n setExact({ format, value: text.trim() });\n onChange?.(text.trim(), c);\n }\n };\n\n return (\n <div\n ref={rootRef}\n className={cn(\"relative w-full max-w-xs\", className)}\n {...rest}\n >\n {label && (\n <span className=\"mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400\">\n {label}\n </span>\n )}\n <button\n ref={triggerRef}\n type=\"button\"\n aria-haspopup=\"dialog\"\n aria-expanded={isOpen}\n aria-label={label ? `Open ${label} color picker` : \"Open color picker\"}\n onClick={() => setIsOpen((open) => !open)}\n className=\"flex h-11 w-full items-center gap-3 rounded-lg border border-gray-300 bg-white px-3 text-left shadow-theme-xs transition hover:bg-gray-50 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:hover:bg-white/[0.03] dark:focus:border-brand-800\"\n >\n <span\n aria-hidden=\"true\"\n className=\"h-7 w-7 shrink-0 rounded-md border border-gray-200 dark:border-gray-700\"\n style={{ backgroundColor: currentColor.format(\"rgba\") }}\n />\n <span className=\"min-w-0 flex-1 truncate font-mono text-sm text-gray-800 dark:text-white/90\">\n {display}\n </span>\n <svg\n aria-hidden=\"true\"\n className={cn(\n \"h-4 w-4 shrink-0 text-gray-500 transition-transform dark:text-gray-400\",\n isOpen && \"rotate-180\"\n )}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n >\n <path d=\"m6 9 6 6 6-6\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </button>\n\n {isOpen && typeof document !== \"undefined\" && createPortal(\n <div\n ref={popupRef}\n role=\"dialog\"\n aria-label={label ? `${label} color picker` : \"Color picker\"}\n className=\"fixed z-999 rounded-2xl border border-gray-200 bg-white p-3 shadow-theme-lg dark:border-gray-700 dark:bg-gray-900\"\n style={{\n left: popupPosition.left,\n top: popupPosition.top,\n width: popupPosition.width,\n }}\n >\n <RBColorPicker\n color={colorInput}\n onChange={handlePickerChange}\n className=\"h-auto w-full\"\n >\n {/* Overflow would clip the drag knobs beyond the track bounds. */}\n <RBColorPicker.Saturation className=\"mb-4 h-40 rounded-lg\" />\n <div className=\"flex items-center gap-3\">\n {withEyeDropper && (\n <RBColorPicker.EyeDropper className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-gray-300 text-gray-600 hover:bg-gray-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-white/[0.03]\">\n {pipetteIcon}\n </RBColorPicker.EyeDropper>\n )}\n <div className=\"flex flex-1 flex-col gap-3\">\n <RBColorPicker.Hue className=\"h-4\" />\n <RBColorPicker.Alpha className=\"h-4\" />\n </div>\n </div>\n </RBColorPicker>\n\n {showInput && (\n <div className=\"mt-4 flex items-stretch gap-2\">\n {formats.length > 1 && (\n <span className=\"relative shrink-0\">\n <select\n aria-label=\"Color format\"\n value={format}\n onChange={(e) => handleFormatChange(e.target.value as ColorPickerFormat)}\n className=\"h-11 appearance-none rounded-lg border border-gray-300 bg-transparent pl-3 pr-8 text-sm text-gray-700 focus:outline-hidden dark:border-gray-700 dark:text-gray-300\"\n >\n {formats.map((f) => (\n <option key={f} value={f}>\n {f.toUpperCase()}\n </option>\n ))}\n </select>\n <svg\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400\"\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n >\n <path d=\"m6 9 6 6 6-6\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </span>\n )}\n <input\n aria-label=\"Color value\"\n value={inputValue}\n onChange={(e) => handleInputChange(e.target.value)}\n onBlur={() => setDraft(null)}\n className=\"h-11 min-w-0 flex-1 rounded-lg border border-gray-300 bg-transparent px-3 font-mono text-sm text-gray-800 focus:outline-hidden dark:border-gray-700 dark:text-white/90\"\n />\n </div>\n )}\n </div>,\n document.body\n )}\n </div>\n );\n};\n"]}
@@ -0,0 +1,293 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var chunkYERNSNT4_cjs = require('./chunk-YERNSNT4.cjs');
5
+ var react = require('react');
6
+ var reactDom = require('react-dom');
7
+ var reactBeautifulColor = require('react-beautiful-color');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+
10
+ var ALL_FORMATS = ["hex", "rgb", "rgba", "hsl", "hsla"];
11
+ function parseValue(text, format) {
12
+ const t = text.trim();
13
+ if (format === "hex") {
14
+ return /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(t) ? { type: "hex", value: t } : null;
15
+ }
16
+ const n = t.match(/-?\d*\.?\d+/g)?.map(Number) ?? [];
17
+ if (format === "rgb" && n.length >= 3) return { type: "rgb", r: n[0], g: n[1], b: n[2] };
18
+ if (format === "rgba" && n.length >= 4)
19
+ return { type: "rgba", r: n[0], g: n[1], b: n[2], a: n[3] };
20
+ if (format === "hsl" && n.length >= 3) return { type: "hsl", h: n[0], s: n[1], l: n[2] };
21
+ if (format === "hsla" && n.length >= 4)
22
+ return { type: "hsla", h: n[0], s: n[1], l: n[2], a: n[3] };
23
+ return null;
24
+ }
25
+ function colorToString(color, format) {
26
+ switch (format) {
27
+ case "hex": {
28
+ const { r, g, b } = color.getRgb();
29
+ const a = color.getRgba().a;
30
+ const hex = `#${[r, g, b].map((v) => Math.round(v).toString(16).padStart(2, "0")).join("")}`;
31
+ return a < 1 ? `${hex}${Math.round(a * 255).toString(16).padStart(2, "0")}` : hex;
32
+ }
33
+ case "rgb": {
34
+ const { r, g, b } = color.getRgb();
35
+ return `rgb(${r}, ${g}, ${b})`;
36
+ }
37
+ case "rgba": {
38
+ const { r, g, b, a } = color.getRgba();
39
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
40
+ }
41
+ case "hsl": {
42
+ const { h, s, l } = color.getHsl();
43
+ return `hsl(${h}, ${s}%, ${l}%)`;
44
+ }
45
+ case "hsla": {
46
+ const { h, s, l, a } = color.getHsla();
47
+ return `hsla(${h}, ${s}%, ${l}%, ${a})`;
48
+ }
49
+ default:
50
+ return color.getHex();
51
+ }
52
+ }
53
+ var pipetteIcon = /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
54
+ "path",
55
+ {
56
+ d: "M19.5 3.5a2.12 2.12 0 0 0-3 0l-2.3 2.3-1-1-1.4 1.4 1 1L4 14.9V19h4.1l7.7-7.7 1 1 1.4-1.4-1-1 2.3-2.3a2.12 2.12 0 0 0 0-3ZM7.3 17.6H6v-1.3l6.6-6.6 1.3 1.3-6.6 6.6Z",
57
+ fill: "currentColor"
58
+ }
59
+ ) });
60
+ var ColorPicker = ({
61
+ defaultValue = "#3641f5",
62
+ defaultFormat = "hex",
63
+ formats = ALL_FORMATS,
64
+ showInput = true,
65
+ withEyeDropper = false,
66
+ label,
67
+ onChange,
68
+ className,
69
+ ...rest
70
+ }) => {
71
+ const [hsva, setHsva] = react.useState(
72
+ () => new reactBeautifulColor.Color({ type: "hex", value: defaultValue }).getHsva()
73
+ );
74
+ const [format, setFormat] = react.useState(defaultFormat);
75
+ const [isOpen, setIsOpen] = react.useState(false);
76
+ const [popupPosition, setPopupPosition] = react.useState({
77
+ left: 0,
78
+ top: 0,
79
+ width: 320
80
+ });
81
+ const [draft, setDraft] = react.useState(null);
82
+ const [exact, setExact] = react.useState(
83
+ () => ({ format: "hex", value: defaultValue })
84
+ );
85
+ const colorInput = react.useMemo(() => ({ type: "hsva", ...hsva }), [hsva]);
86
+ const currentColor = react.useMemo(() => new reactBeautifulColor.Color(colorInput), [colorInput]);
87
+ const display = exact && exact.format === format ? exact.value : colorToString(currentColor, format);
88
+ const inputValue = draft ?? display;
89
+ const hsvaRef = react.useRef(hsva);
90
+ hsvaRef.current = hsva;
91
+ const formatRef = react.useRef(format);
92
+ formatRef.current = format;
93
+ const onChangeRef = react.useRef(onChange);
94
+ onChangeRef.current = onChange;
95
+ const rootRef = react.useRef(null);
96
+ const triggerRef = react.useRef(null);
97
+ const popupRef = react.useRef(null);
98
+ react.useEffect(() => {
99
+ if (!isOpen) return;
100
+ const handlePointerDown = (event) => {
101
+ const target = event.target;
102
+ if (rootRef.current && !rootRef.current.contains(target) && !popupRef.current?.contains(target)) {
103
+ setIsOpen(false);
104
+ }
105
+ };
106
+ const handleEscape = (event) => {
107
+ if (event.key === "Escape") setIsOpen(false);
108
+ };
109
+ document.addEventListener("mousedown", handlePointerDown);
110
+ document.addEventListener("keydown", handleEscape);
111
+ return () => {
112
+ document.removeEventListener("mousedown", handlePointerDown);
113
+ document.removeEventListener("keydown", handleEscape);
114
+ };
115
+ }, [isOpen]);
116
+ const updatePopupPosition = react.useCallback(() => {
117
+ const trigger = triggerRef.current;
118
+ if (!trigger) return;
119
+ const rect = trigger.getBoundingClientRect();
120
+ const viewportPadding = 16;
121
+ const gap = 8;
122
+ const width = Math.min(320, window.innerWidth - viewportPadding * 2);
123
+ const popupHeight = popupRef.current?.offsetHeight ?? 306;
124
+ const fitsBelow = window.innerHeight - rect.bottom - gap >= popupHeight;
125
+ const top = fitsBelow || rect.top < popupHeight + gap ? rect.bottom + gap : rect.top - popupHeight - gap;
126
+ const left = Math.min(
127
+ Math.max(rect.left, viewportPadding),
128
+ window.innerWidth - width - viewportPadding
129
+ );
130
+ setPopupPosition({ left, top: Math.max(viewportPadding, top), width });
131
+ }, []);
132
+ react.useLayoutEffect(() => {
133
+ if (!isOpen) return;
134
+ updatePopupPosition();
135
+ window.addEventListener("resize", updatePopupPosition);
136
+ window.addEventListener("scroll", updatePopupPosition, true);
137
+ return () => {
138
+ window.removeEventListener("resize", updatePopupPosition);
139
+ window.removeEventListener("scroll", updatePopupPosition, true);
140
+ };
141
+ }, [isOpen, updatePopupPosition]);
142
+ const handlePickerChange = react.useCallback((next) => {
143
+ const nv = next.getHsva();
144
+ const cur = hsvaRef.current;
145
+ if (nv.h === cur.h && nv.s === cur.s && nv.v === cur.v && nv.a === cur.a) return;
146
+ setHsva(nv);
147
+ setExact(null);
148
+ setDraft(null);
149
+ onChangeRef.current?.(colorToString(next, formatRef.current), next);
150
+ }, []);
151
+ const handleFormatChange = (f) => {
152
+ setFormat(f);
153
+ setDraft(null);
154
+ onChange?.(
155
+ exact && exact.format === f ? exact.value : colorToString(currentColor, f),
156
+ currentColor
157
+ );
158
+ };
159
+ const handleInputChange = (text) => {
160
+ setDraft(text);
161
+ const parsed = parseValue(text, format);
162
+ if (parsed) {
163
+ const c = new reactBeautifulColor.Color(parsed);
164
+ setHsva(c.getHsva());
165
+ setExact({ format, value: text.trim() });
166
+ onChange?.(text.trim(), c);
167
+ }
168
+ };
169
+ return /* @__PURE__ */ jsxRuntime.jsxs(
170
+ "div",
171
+ {
172
+ ref: rootRef,
173
+ className: chunkYERNSNT4_cjs.cn("relative w-full max-w-xs", className),
174
+ ...rest,
175
+ children: [
176
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400", children: label }),
177
+ /* @__PURE__ */ jsxRuntime.jsxs(
178
+ "button",
179
+ {
180
+ ref: triggerRef,
181
+ type: "button",
182
+ "aria-haspopup": "dialog",
183
+ "aria-expanded": isOpen,
184
+ "aria-label": label ? `Open ${label} color picker` : "Open color picker",
185
+ onClick: () => setIsOpen((open) => !open),
186
+ className: "flex h-11 w-full items-center gap-3 rounded-lg border border-gray-300 bg-white px-3 text-left shadow-theme-xs transition hover:bg-gray-50 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:hover:bg-white/[0.03] dark:focus:border-brand-800",
187
+ children: [
188
+ /* @__PURE__ */ jsxRuntime.jsx(
189
+ "span",
190
+ {
191
+ "aria-hidden": "true",
192
+ className: "h-7 w-7 shrink-0 rounded-md border border-gray-200 dark:border-gray-700",
193
+ style: { backgroundColor: currentColor.format("rgba") }
194
+ }
195
+ ),
196
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate font-mono text-sm text-gray-800 dark:text-white/90", children: display }),
197
+ /* @__PURE__ */ jsxRuntime.jsx(
198
+ "svg",
199
+ {
200
+ "aria-hidden": "true",
201
+ className: chunkYERNSNT4_cjs.cn(
202
+ "h-4 w-4 shrink-0 text-gray-500 transition-transform dark:text-gray-400",
203
+ isOpen && "rotate-180"
204
+ ),
205
+ viewBox: "0 0 24 24",
206
+ fill: "none",
207
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
208
+ }
209
+ )
210
+ ]
211
+ }
212
+ ),
213
+ isOpen && typeof document !== "undefined" && reactDom.createPortal(
214
+ /* @__PURE__ */ jsxRuntime.jsxs(
215
+ "div",
216
+ {
217
+ ref: popupRef,
218
+ role: "dialog",
219
+ "aria-label": label ? `${label} color picker` : "Color picker",
220
+ className: "fixed z-999 rounded-2xl border border-gray-200 bg-white p-3 shadow-theme-lg dark:border-gray-700 dark:bg-gray-900",
221
+ style: {
222
+ left: popupPosition.left,
223
+ top: popupPosition.top,
224
+ width: popupPosition.width
225
+ },
226
+ children: [
227
+ /* @__PURE__ */ jsxRuntime.jsxs(
228
+ reactBeautifulColor.ColorPicker,
229
+ {
230
+ color: colorInput,
231
+ onChange: handlePickerChange,
232
+ className: "h-auto w-full",
233
+ children: [
234
+ /* @__PURE__ */ jsxRuntime.jsx(reactBeautifulColor.ColorPicker.Saturation, { className: "mb-4 h-40 rounded-lg" }),
235
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
236
+ withEyeDropper && /* @__PURE__ */ jsxRuntime.jsx(reactBeautifulColor.ColorPicker.EyeDropper, { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-gray-300 text-gray-600 hover:bg-gray-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-white/[0.03]", children: pipetteIcon }),
237
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-3", children: [
238
+ /* @__PURE__ */ jsxRuntime.jsx(reactBeautifulColor.ColorPicker.Hue, { className: "h-4" }),
239
+ /* @__PURE__ */ jsxRuntime.jsx(reactBeautifulColor.ColorPicker.Alpha, { className: "h-4" })
240
+ ] })
241
+ ] })
242
+ ]
243
+ }
244
+ ),
245
+ showInput && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex items-stretch gap-2", children: [
246
+ formats.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative shrink-0", children: [
247
+ /* @__PURE__ */ jsxRuntime.jsx(
248
+ "select",
249
+ {
250
+ "aria-label": "Color format",
251
+ value: format,
252
+ onChange: (e) => handleFormatChange(e.target.value),
253
+ className: "h-11 appearance-none rounded-lg border border-gray-300 bg-transparent pl-3 pr-8 text-sm text-gray-700 focus:outline-hidden dark:border-gray-700 dark:text-gray-300",
254
+ children: formats.map((f) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: f, children: f.toUpperCase() }, f))
255
+ }
256
+ ),
257
+ /* @__PURE__ */ jsxRuntime.jsx(
258
+ "svg",
259
+ {
260
+ "aria-hidden": "true",
261
+ className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400",
262
+ width: "14",
263
+ height: "14",
264
+ viewBox: "0 0 24 24",
265
+ fill: "none",
266
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
267
+ }
268
+ )
269
+ ] }),
270
+ /* @__PURE__ */ jsxRuntime.jsx(
271
+ "input",
272
+ {
273
+ "aria-label": "Color value",
274
+ value: inputValue,
275
+ onChange: (e) => handleInputChange(e.target.value),
276
+ onBlur: () => setDraft(null),
277
+ className: "h-11 min-w-0 flex-1 rounded-lg border border-gray-300 bg-transparent px-3 font-mono text-sm text-gray-800 focus:outline-hidden dark:border-gray-700 dark:text-white/90"
278
+ }
279
+ )
280
+ ] })
281
+ ]
282
+ }
283
+ ),
284
+ document.body
285
+ )
286
+ ]
287
+ }
288
+ );
289
+ };
290
+
291
+ exports.ColorPicker = ColorPicker;
292
+ //# sourceMappingURL=chunk-T55WXZNL.cjs.map
293
+ //# sourceMappingURL=chunk-T55WXZNL.cjs.map