@kopexa/color-picker 0.0.37 → 0.0.39

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.
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ // src/messages.ts
4
+ import { defineMessages } from "@kopexa/i18n";
5
+ var messages = defineMessages({
6
+ label: {
7
+ id: "color-picker.label",
8
+ defaultMessage: "Color",
9
+ description: "Generic label for a color field"
10
+ },
11
+ hexLabel: {
12
+ id: "color-picker.hex_label",
13
+ defaultMessage: "Hex",
14
+ description: "Label for the hex input inside the color picker popover"
15
+ },
16
+ pickColor: {
17
+ id: "color-picker.pick_color",
18
+ defaultMessage: "Pick a color",
19
+ description: "Prompt to pick a color"
20
+ },
21
+ clear: {
22
+ id: "color-picker.clear",
23
+ defaultMessage: "Clear",
24
+ description: "Button inside the color picker popover that resets the field to its unset state"
25
+ }
26
+ });
27
+
28
+ export {
29
+ messages
30
+ };
@@ -1,12 +1,12 @@
1
1
  "use client";
2
2
  import {
3
3
  messages
4
- } from "./chunk-JFALBXTO.mjs";
4
+ } from "./chunk-CRQDZQE6.mjs";
5
5
 
6
6
  // src/color-picker-field.tsx
7
7
  import { useSafeIntl } from "@kopexa/i18n";
8
8
  import { cn } from "@kopexa/shared-utils";
9
- import { useState } from "react";
9
+ import { useEffect, useState } from "react";
10
10
  import {
11
11
  Button as AriaButton,
12
12
  ColorArea,
@@ -38,35 +38,70 @@ var DEFAULT_PRESETS = [
38
38
  "#6366f1",
39
39
  "#a855f7"
40
40
  ];
41
+ var UNSET_FALLBACK = "#ffffff";
42
+ var safeParseColor = (hex) => {
43
+ try {
44
+ return parseColor(hex);
45
+ } catch {
46
+ return parseColor(UNSET_FALLBACK);
47
+ }
48
+ };
41
49
  function ColorPickerField({
42
50
  value,
43
- defaultValue = "#3b82f6",
51
+ defaultValue,
44
52
  onChange,
45
53
  presets = DEFAULT_PRESETS,
46
54
  hidePresets = false,
47
55
  hideHexInput = false,
48
56
  triggerClassName,
49
57
  triggerContent,
50
- isDisabled = false
58
+ isDisabled = false,
59
+ allowClear = true
51
60
  }) {
52
61
  const intl = useSafeIntl();
53
62
  const t = {
54
- hexLabel: intl.formatMessage(messages.hexLabel)
55
- };
56
- const safeParseColor = (hex) => {
57
- try {
58
- return parseColor(hex);
59
- } catch {
60
- return parseColor("#3b82f6");
61
- }
63
+ hexLabel: intl.formatMessage(messages.hexLabel),
64
+ clear: intl.formatMessage(messages.clear)
62
65
  };
66
+ const initialHex = value != null ? value : defaultValue;
63
67
  const [color, setColor] = useState(
64
- () => safeParseColor(value != null ? value : defaultValue)
68
+ () => safeParseColor(initialHex != null ? initialHex : UNSET_FALLBACK)
65
69
  );
70
+ const [hasColor, setHasColor] = useState(!!initialHex);
71
+ const [hexDraft, setHexDraft] = useState("");
72
+ useEffect(() => {
73
+ if (value === void 0) return;
74
+ if (value) {
75
+ setColor(safeParseColor(value));
76
+ setHasColor(true);
77
+ } else {
78
+ setColor(parseColor(UNSET_FALLBACK));
79
+ setHasColor(false);
80
+ }
81
+ }, [value]);
66
82
  const handleChange = (newColor) => {
67
83
  setColor(newColor);
84
+ setHasColor(true);
68
85
  onChange == null ? void 0 : onChange(newColor.toString("hex"));
69
86
  };
87
+ const handleClear = () => {
88
+ setColor(parseColor(UNSET_FALLBACK));
89
+ setHasColor(false);
90
+ setHexDraft("");
91
+ onChange == null ? void 0 : onChange("");
92
+ };
93
+ const commitHexDraft = () => {
94
+ const trimmed = hexDraft.trim();
95
+ if (!trimmed) return;
96
+ try {
97
+ const parsed = parseColor(
98
+ trimmed.startsWith("#") ? trimmed : `#${trimmed}`
99
+ );
100
+ handleChange(parsed);
101
+ setHexDraft("");
102
+ } catch {
103
+ }
104
+ };
70
105
  const hexValue = color.toString("hex");
71
106
  const triggerClasses = cn(
72
107
  "flex h-10 items-center gap-2 rounded-md border border-input bg-background px-3 text-sm",
@@ -76,9 +111,12 @@ function ColorPickerField({
76
111
  triggerClassName
77
112
  );
78
113
  return /* @__PURE__ */ jsx(ColorPicker, { value: color, onChange: handleChange, children: /* @__PURE__ */ jsxs(DialogTrigger, { children: [
79
- /* @__PURE__ */ jsx(AriaButton, { className: triggerClasses, isDisabled, children: triggerContent != null ? triggerContent : /* @__PURE__ */ jsxs(Fragment, { children: [
114
+ /* @__PURE__ */ jsx(AriaButton, { className: triggerClasses, isDisabled, children: triggerContent != null ? triggerContent : hasColor ? /* @__PURE__ */ jsxs(Fragment, { children: [
80
115
  /* @__PURE__ */ jsx(ColorSwatch, { className: "size-5 rounded border" }),
81
116
  /* @__PURE__ */ jsx("span", { className: "font-mono", children: hexValue })
117
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
118
+ /* @__PURE__ */ jsx("div", { className: "size-5 rounded border border-dashed border-muted-foreground/40 bg-transparent" }),
119
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground", children: "\u2014" })
82
120
  ] }) }),
83
121
  /* @__PURE__ */ jsx(
84
122
  Popover,
@@ -99,11 +137,38 @@ function ColorPickerField({
99
137
  ),
100
138
  /* @__PURE__ */ jsx(ColorSlider, { colorSpace: "hsb", channel: "hue", children: /* @__PURE__ */ jsx(SliderTrack, { className: "h-7 w-[192px] rounded-t-none rounded-b-md border border-t-0", children: /* @__PURE__ */ jsx(ColorThumb, { className: "top-1/2 size-5 rounded-full border-2 border-white shadow-md" }) }) })
101
139
  ] }),
102
- !hideHexInput && /* @__PURE__ */ jsxs(ColorField, { colorSpace: "hsb", className: "flex flex-col gap-1", children: [
140
+ !hideHexInput && (hasColor ? /* @__PURE__ */ jsxs(ColorField, { colorSpace: "hsb", className: "flex flex-col gap-1", children: [
103
141
  /* @__PURE__ */ jsx(Label, { className: "text-sm", children: t.hexLabel }),
104
142
  /* @__PURE__ */ jsx(Input, { className: "h-9 w-[192px] rounded-md border border-input bg-background px-3 font-mono text-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" })
105
- ] }),
106
- !hidePresets && presets.length > 0 && /* @__PURE__ */ jsx(ColorSwatchPicker, { className: "grid w-[192px] grid-cols-5 gap-1", children: presets.map((preset) => /* @__PURE__ */ jsx(ColorSwatchPickerItem, { color: preset, children: /* @__PURE__ */ jsx(ColorSwatch, { className: "size-8 rounded border" }) }, preset)) })
143
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
144
+ /* @__PURE__ */ jsx(Label, { className: "text-sm text-muted-foreground/60", children: t.hexLabel }),
145
+ /* @__PURE__ */ jsx(
146
+ "input",
147
+ {
148
+ type: "text",
149
+ value: hexDraft,
150
+ onChange: (e) => setHexDraft(e.target.value),
151
+ onBlur: commitHexDraft,
152
+ onKeyDown: (e) => {
153
+ if (e.key === "Enter") {
154
+ e.preventDefault();
155
+ commitHexDraft();
156
+ }
157
+ },
158
+ placeholder: "#FFFFFF",
159
+ className: "h-9 w-[192px] rounded-md border border-input bg-background px-3 font-mono text-sm placeholder:text-muted-foreground/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
160
+ }
161
+ )
162
+ ] })),
163
+ !hidePresets && presets.length > 0 && /* @__PURE__ */ jsx(ColorSwatchPicker, { className: "grid w-[192px] grid-cols-5 gap-1", children: presets.map((preset) => /* @__PURE__ */ jsx(ColorSwatchPickerItem, { color: preset, children: /* @__PURE__ */ jsx(ColorSwatch, { className: "size-8 rounded border" }) }, preset)) }),
164
+ allowClear && hasColor && /* @__PURE__ */ jsx(
165
+ AriaButton,
166
+ {
167
+ onPress: handleClear,
168
+ className: "w-[192px] rounded-md px-3 py-1.5 text-xs text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
169
+ children: t.clear
170
+ }
171
+ )
107
172
  ] })
108
173
  }
109
174
  )
@@ -3,11 +3,13 @@ import { ReactNode } from 'react';
3
3
 
4
4
  interface ColorPickerFieldProps {
5
5
  /**
6
- * The current color value (hex string like "#ff0000")
6
+ * The current color value (hex string like "#ff0000").
7
+ * If falsy, the trigger shows an "unset" state instead of a color.
7
8
  */
8
9
  value?: string;
9
10
  /**
10
- * The default color value for uncontrolled usage
11
+ * The default color value for uncontrolled usage.
12
+ * If not provided, the field starts in an "unset" state.
11
13
  */
12
14
  defaultValue?: string;
13
15
  /**
@@ -44,11 +46,18 @@ interface ColorPickerFieldProps {
44
46
  * Whether the field is disabled
45
47
  */
46
48
  isDisabled?: boolean;
49
+ /**
50
+ * Whether to show a "Clear" button in the popover that resets the field
51
+ * to its unset state. The button is only rendered when a color is actually
52
+ * set. Click emits `onChange("")`.
53
+ * @default true
54
+ */
55
+ allowClear?: boolean;
47
56
  }
48
57
  /**
49
58
  * A high-level color picker field.
50
59
  * Uses react-aria-components directly to ensure proper context handling.
51
60
  */
52
- declare function ColorPickerField({ value, defaultValue, onChange, presets, hidePresets, hideHexInput, triggerClassName, triggerContent, isDisabled, }: ColorPickerFieldProps): react_jsx_runtime.JSX.Element;
61
+ declare function ColorPickerField({ value, defaultValue, onChange, presets, hidePresets, hideHexInput, triggerClassName, triggerContent, isDisabled, allowClear, }: ColorPickerFieldProps): react_jsx_runtime.JSX.Element;
53
62
 
54
63
  export { ColorPickerField, type ColorPickerFieldProps };
@@ -3,11 +3,13 @@ import { ReactNode } from 'react';
3
3
 
4
4
  interface ColorPickerFieldProps {
5
5
  /**
6
- * The current color value (hex string like "#ff0000")
6
+ * The current color value (hex string like "#ff0000").
7
+ * If falsy, the trigger shows an "unset" state instead of a color.
7
8
  */
8
9
  value?: string;
9
10
  /**
10
- * The default color value for uncontrolled usage
11
+ * The default color value for uncontrolled usage.
12
+ * If not provided, the field starts in an "unset" state.
11
13
  */
12
14
  defaultValue?: string;
13
15
  /**
@@ -44,11 +46,18 @@ interface ColorPickerFieldProps {
44
46
  * Whether the field is disabled
45
47
  */
46
48
  isDisabled?: boolean;
49
+ /**
50
+ * Whether to show a "Clear" button in the popover that resets the field
51
+ * to its unset state. The button is only rendered when a color is actually
52
+ * set. Click emits `onChange("")`.
53
+ * @default true
54
+ */
55
+ allowClear?: boolean;
47
56
  }
48
57
  /**
49
58
  * A high-level color picker field.
50
59
  * Uses react-aria-components directly to ensure proper context handling.
51
60
  */
52
- declare function ColorPickerField({ value, defaultValue, onChange, presets, hidePresets, hideHexInput, triggerClassName, triggerContent, isDisabled, }: ColorPickerFieldProps): react_jsx_runtime.JSX.Element;
61
+ declare function ColorPickerField({ value, defaultValue, onChange, presets, hidePresets, hideHexInput, triggerClassName, triggerContent, isDisabled, allowClear, }: ColorPickerFieldProps): react_jsx_runtime.JSX.Element;
53
62
 
54
63
  export { ColorPickerField, type ColorPickerFieldProps };
@@ -35,15 +35,23 @@ var import_i18n = require("@kopexa/i18n");
35
35
  var messages = (0, import_i18n.defineMessages)({
36
36
  label: {
37
37
  id: "color-picker.label",
38
- defaultMessage: "Color"
38
+ defaultMessage: "Color",
39
+ description: "Generic label for a color field"
39
40
  },
40
41
  hexLabel: {
41
42
  id: "color-picker.hex_label",
42
- defaultMessage: "Hex"
43
+ defaultMessage: "Hex",
44
+ description: "Label for the hex input inside the color picker popover"
43
45
  },
44
46
  pickColor: {
45
47
  id: "color-picker.pick_color",
46
- defaultMessage: "Pick a color"
48
+ defaultMessage: "Pick a color",
49
+ description: "Prompt to pick a color"
50
+ },
51
+ clear: {
52
+ id: "color-picker.clear",
53
+ defaultMessage: "Clear",
54
+ description: "Button inside the color picker popover that resets the field to its unset state"
47
55
  }
48
56
  });
49
57
 
@@ -61,35 +69,70 @@ var DEFAULT_PRESETS = [
61
69
  "#6366f1",
62
70
  "#a855f7"
63
71
  ];
72
+ var UNSET_FALLBACK = "#ffffff";
73
+ var safeParseColor = (hex) => {
74
+ try {
75
+ return (0, import_react_aria_components.parseColor)(hex);
76
+ } catch {
77
+ return (0, import_react_aria_components.parseColor)(UNSET_FALLBACK);
78
+ }
79
+ };
64
80
  function ColorPickerField({
65
81
  value,
66
- defaultValue = "#3b82f6",
82
+ defaultValue,
67
83
  onChange,
68
84
  presets = DEFAULT_PRESETS,
69
85
  hidePresets = false,
70
86
  hideHexInput = false,
71
87
  triggerClassName,
72
88
  triggerContent,
73
- isDisabled = false
89
+ isDisabled = false,
90
+ allowClear = true
74
91
  }) {
75
92
  const intl = (0, import_i18n2.useSafeIntl)();
76
93
  const t = {
77
- hexLabel: intl.formatMessage(messages.hexLabel)
78
- };
79
- const safeParseColor = (hex) => {
80
- try {
81
- return (0, import_react_aria_components.parseColor)(hex);
82
- } catch {
83
- return (0, import_react_aria_components.parseColor)("#3b82f6");
84
- }
94
+ hexLabel: intl.formatMessage(messages.hexLabel),
95
+ clear: intl.formatMessage(messages.clear)
85
96
  };
97
+ const initialHex = value != null ? value : defaultValue;
86
98
  const [color, setColor] = (0, import_react.useState)(
87
- () => safeParseColor(value != null ? value : defaultValue)
99
+ () => safeParseColor(initialHex != null ? initialHex : UNSET_FALLBACK)
88
100
  );
101
+ const [hasColor, setHasColor] = (0, import_react.useState)(!!initialHex);
102
+ const [hexDraft, setHexDraft] = (0, import_react.useState)("");
103
+ (0, import_react.useEffect)(() => {
104
+ if (value === void 0) return;
105
+ if (value) {
106
+ setColor(safeParseColor(value));
107
+ setHasColor(true);
108
+ } else {
109
+ setColor((0, import_react_aria_components.parseColor)(UNSET_FALLBACK));
110
+ setHasColor(false);
111
+ }
112
+ }, [value]);
89
113
  const handleChange = (newColor) => {
90
114
  setColor(newColor);
115
+ setHasColor(true);
91
116
  onChange == null ? void 0 : onChange(newColor.toString("hex"));
92
117
  };
118
+ const handleClear = () => {
119
+ setColor((0, import_react_aria_components.parseColor)(UNSET_FALLBACK));
120
+ setHasColor(false);
121
+ setHexDraft("");
122
+ onChange == null ? void 0 : onChange("");
123
+ };
124
+ const commitHexDraft = () => {
125
+ const trimmed = hexDraft.trim();
126
+ if (!trimmed) return;
127
+ try {
128
+ const parsed = (0, import_react_aria_components.parseColor)(
129
+ trimmed.startsWith("#") ? trimmed : `#${trimmed}`
130
+ );
131
+ handleChange(parsed);
132
+ setHexDraft("");
133
+ } catch {
134
+ }
135
+ };
93
136
  const hexValue = color.toString("hex");
94
137
  const triggerClasses = (0, import_shared_utils.cn)(
95
138
  "flex h-10 items-center gap-2 rounded-md border border-input bg-background px-3 text-sm",
@@ -99,9 +142,12 @@ function ColorPickerField({
99
142
  triggerClassName
100
143
  );
101
144
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.ColorPicker, { value: color, onChange: handleChange, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_aria_components.DialogTrigger, { children: [
102
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.Button, { className: triggerClasses, isDisabled, children: triggerContent != null ? triggerContent : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
145
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.Button, { className: triggerClasses, isDisabled, children: triggerContent != null ? triggerContent : hasColor ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
103
146
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.ColorSwatch, { className: "size-5 rounded border" }),
104
147
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-mono", children: hexValue })
148
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "size-5 rounded border border-dashed border-muted-foreground/40 bg-transparent" }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-mono text-muted-foreground", children: "\u2014" })
105
151
  ] }) }),
106
152
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
107
153
  import_react_aria_components.Popover,
@@ -122,11 +168,38 @@ function ColorPickerField({
122
168
  ),
123
169
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.ColorSlider, { colorSpace: "hsb", channel: "hue", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.SliderTrack, { className: "h-7 w-[192px] rounded-t-none rounded-b-md border border-t-0", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.ColorThumb, { className: "top-1/2 size-5 rounded-full border-2 border-white shadow-md" }) }) })
124
170
  ] }),
125
- !hideHexInput && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_aria_components.ColorField, { colorSpace: "hsb", className: "flex flex-col gap-1", children: [
171
+ !hideHexInput && (hasColor ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_aria_components.ColorField, { colorSpace: "hsb", className: "flex flex-col gap-1", children: [
126
172
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.Label, { className: "text-sm", children: t.hexLabel }),
127
173
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.Input, { className: "h-9 w-[192px] rounded-md border border-input bg-background px-3 font-mono text-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" })
128
- ] }),
129
- !hidePresets && presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.ColorSwatchPicker, { className: "grid w-[192px] grid-cols-5 gap-1", children: presets.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.ColorSwatchPickerItem, { color: preset, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.ColorSwatch, { className: "size-8 rounded border" }) }, preset)) })
174
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1", children: [
175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.Label, { className: "text-sm text-muted-foreground/60", children: t.hexLabel }),
176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
177
+ "input",
178
+ {
179
+ type: "text",
180
+ value: hexDraft,
181
+ onChange: (e) => setHexDraft(e.target.value),
182
+ onBlur: commitHexDraft,
183
+ onKeyDown: (e) => {
184
+ if (e.key === "Enter") {
185
+ e.preventDefault();
186
+ commitHexDraft();
187
+ }
188
+ },
189
+ placeholder: "#FFFFFF",
190
+ className: "h-9 w-[192px] rounded-md border border-input bg-background px-3 font-mono text-sm placeholder:text-muted-foreground/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
191
+ }
192
+ )
193
+ ] })),
194
+ !hidePresets && presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.ColorSwatchPicker, { className: "grid w-[192px] grid-cols-5 gap-1", children: presets.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.ColorSwatchPickerItem, { color: preset, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_aria_components.ColorSwatch, { className: "size-8 rounded border" }) }, preset)) }),
195
+ allowClear && hasColor && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
196
+ import_react_aria_components.Button,
197
+ {
198
+ onPress: handleClear,
199
+ className: "w-[192px] rounded-md px-3 py-1.5 text-xs text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
200
+ children: t.clear
201
+ }
202
+ )
130
203
  ] })
131
204
  }
132
205
  )
@@ -2,8 +2,8 @@
2
2
  "use client";
3
3
  import {
4
4
  ColorPickerField
5
- } from "./chunk-GSZMYL3E.mjs";
6
- import "./chunk-JFALBXTO.mjs";
5
+ } from "./chunk-QFIB6TFB.mjs";
6
+ import "./chunk-CRQDZQE6.mjs";
7
7
  export {
8
8
  ColorPickerField
9
9
  };
package/dist/index.js CHANGED
@@ -169,15 +169,23 @@ var import_i18n = require("@kopexa/i18n");
169
169
  var messages = (0, import_i18n.defineMessages)({
170
170
  label: {
171
171
  id: "color-picker.label",
172
- defaultMessage: "Color"
172
+ defaultMessage: "Color",
173
+ description: "Generic label for a color field"
173
174
  },
174
175
  hexLabel: {
175
176
  id: "color-picker.hex_label",
176
- defaultMessage: "Hex"
177
+ defaultMessage: "Hex",
178
+ description: "Label for the hex input inside the color picker popover"
177
179
  },
178
180
  pickColor: {
179
181
  id: "color-picker.pick_color",
180
- defaultMessage: "Pick a color"
182
+ defaultMessage: "Pick a color",
183
+ description: "Prompt to pick a color"
184
+ },
185
+ clear: {
186
+ id: "color-picker.clear",
187
+ defaultMessage: "Clear",
188
+ description: "Button inside the color picker popover that resets the field to its unset state"
181
189
  }
182
190
  });
183
191
 
@@ -195,35 +203,70 @@ var DEFAULT_PRESETS = [
195
203
  "#6366f1",
196
204
  "#a855f7"
197
205
  ];
206
+ var UNSET_FALLBACK = "#ffffff";
207
+ var safeParseColor = (hex) => {
208
+ try {
209
+ return (0, import_react_aria_components2.parseColor)(hex);
210
+ } catch {
211
+ return (0, import_react_aria_components2.parseColor)(UNSET_FALLBACK);
212
+ }
213
+ };
198
214
  function ColorPickerField({
199
215
  value,
200
- defaultValue = "#3b82f6",
216
+ defaultValue,
201
217
  onChange,
202
218
  presets = DEFAULT_PRESETS,
203
219
  hidePresets = false,
204
220
  hideHexInput = false,
205
221
  triggerClassName,
206
222
  triggerContent,
207
- isDisabled = false
223
+ isDisabled = false,
224
+ allowClear = true
208
225
  }) {
209
226
  const intl = (0, import_i18n2.useSafeIntl)();
210
227
  const t = {
211
- hexLabel: intl.formatMessage(messages.hexLabel)
212
- };
213
- const safeParseColor = (hex) => {
214
- try {
215
- return (0, import_react_aria_components2.parseColor)(hex);
216
- } catch {
217
- return (0, import_react_aria_components2.parseColor)("#3b82f6");
218
- }
228
+ hexLabel: intl.formatMessage(messages.hexLabel),
229
+ clear: intl.formatMessage(messages.clear)
219
230
  };
231
+ const initialHex = value != null ? value : defaultValue;
220
232
  const [color, setColor] = (0, import_react.useState)(
221
- () => safeParseColor(value != null ? value : defaultValue)
233
+ () => safeParseColor(initialHex != null ? initialHex : UNSET_FALLBACK)
222
234
  );
235
+ const [hasColor, setHasColor] = (0, import_react.useState)(!!initialHex);
236
+ const [hexDraft, setHexDraft] = (0, import_react.useState)("");
237
+ (0, import_react.useEffect)(() => {
238
+ if (value === void 0) return;
239
+ if (value) {
240
+ setColor(safeParseColor(value));
241
+ setHasColor(true);
242
+ } else {
243
+ setColor((0, import_react_aria_components2.parseColor)(UNSET_FALLBACK));
244
+ setHasColor(false);
245
+ }
246
+ }, [value]);
223
247
  const handleChange = (newColor) => {
224
248
  setColor(newColor);
249
+ setHasColor(true);
225
250
  onChange == null ? void 0 : onChange(newColor.toString("hex"));
226
251
  };
252
+ const handleClear = () => {
253
+ setColor((0, import_react_aria_components2.parseColor)(UNSET_FALLBACK));
254
+ setHasColor(false);
255
+ setHexDraft("");
256
+ onChange == null ? void 0 : onChange("");
257
+ };
258
+ const commitHexDraft = () => {
259
+ const trimmed = hexDraft.trim();
260
+ if (!trimmed) return;
261
+ try {
262
+ const parsed = (0, import_react_aria_components2.parseColor)(
263
+ trimmed.startsWith("#") ? trimmed : `#${trimmed}`
264
+ );
265
+ handleChange(parsed);
266
+ setHexDraft("");
267
+ } catch {
268
+ }
269
+ };
227
270
  const hexValue = color.toString("hex");
228
271
  const triggerClasses = (0, import_shared_utils2.cn)(
229
272
  "flex h-10 items-center gap-2 rounded-md border border-input bg-background px-3 text-sm",
@@ -233,9 +276,12 @@ function ColorPickerField({
233
276
  triggerClassName
234
277
  );
235
278
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.ColorPicker, { value: color, onChange: handleChange, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_aria_components2.DialogTrigger, { children: [
236
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.Button, { className: triggerClasses, isDisabled, children: triggerContent != null ? triggerContent : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
279
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.Button, { className: triggerClasses, isDisabled, children: triggerContent != null ? triggerContent : hasColor ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
237
280
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.ColorSwatch, { className: "size-5 rounded border" }),
238
281
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-mono", children: hexValue })
282
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
283
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "size-5 rounded border border-dashed border-muted-foreground/40 bg-transparent" }),
284
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-mono text-muted-foreground", children: "\u2014" })
239
285
  ] }) }),
240
286
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
241
287
  import_react_aria_components2.Popover,
@@ -256,11 +302,38 @@ function ColorPickerField({
256
302
  ),
257
303
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.ColorSlider, { colorSpace: "hsb", channel: "hue", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.SliderTrack, { className: "h-7 w-[192px] rounded-t-none rounded-b-md border border-t-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.ColorThumb, { className: "top-1/2 size-5 rounded-full border-2 border-white shadow-md" }) }) })
258
304
  ] }),
259
- !hideHexInput && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_aria_components2.ColorField, { colorSpace: "hsb", className: "flex flex-col gap-1", children: [
305
+ !hideHexInput && (hasColor ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_aria_components2.ColorField, { colorSpace: "hsb", className: "flex flex-col gap-1", children: [
260
306
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.Label, { className: "text-sm", children: t.hexLabel }),
261
307
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.Input, { className: "h-9 w-[192px] rounded-md border border-input bg-background px-3 font-mono text-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" })
262
- ] }),
263
- !hidePresets && presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.ColorSwatchPicker, { className: "grid w-[192px] grid-cols-5 gap-1", children: presets.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.ColorSwatchPickerItem, { color: preset, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.ColorSwatch, { className: "size-8 rounded border" }) }, preset)) })
308
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-1", children: [
309
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.Label, { className: "text-sm text-muted-foreground/60", children: t.hexLabel }),
310
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
311
+ "input",
312
+ {
313
+ type: "text",
314
+ value: hexDraft,
315
+ onChange: (e) => setHexDraft(e.target.value),
316
+ onBlur: commitHexDraft,
317
+ onKeyDown: (e) => {
318
+ if (e.key === "Enter") {
319
+ e.preventDefault();
320
+ commitHexDraft();
321
+ }
322
+ },
323
+ placeholder: "#FFFFFF",
324
+ className: "h-9 w-[192px] rounded-md border border-input bg-background px-3 font-mono text-sm placeholder:text-muted-foreground/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
325
+ }
326
+ )
327
+ ] })),
328
+ !hidePresets && presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.ColorSwatchPicker, { className: "grid w-[192px] grid-cols-5 gap-1", children: presets.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.ColorSwatchPickerItem, { color: preset, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components2.ColorSwatch, { className: "size-8 rounded border" }) }, preset)) }),
329
+ allowClear && hasColor && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
330
+ import_react_aria_components2.Button,
331
+ {
332
+ onPress: handleClear,
333
+ className: "w-[192px] rounded-md px-3 py-1.5 text-xs text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
334
+ children: t.clear
335
+ }
336
+ )
264
337
  ] })
265
338
  }
266
339
  )
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  ColorPickerField
4
- } from "./chunk-GSZMYL3E.mjs";
4
+ } from "./chunk-QFIB6TFB.mjs";
5
5
  import {
6
6
  ColorArea,
7
7
  ColorField,
@@ -16,7 +16,7 @@ import {
16
16
  SliderOutput,
17
17
  SliderTrack
18
18
  } from "./chunk-EA7FR3KG.mjs";
19
- import "./chunk-JFALBXTO.mjs";
19
+ import "./chunk-CRQDZQE6.mjs";
20
20
  export {
21
21
  ColorArea,
22
22
  ColorField,
@@ -2,14 +2,22 @@ declare const messages: {
2
2
  label: {
3
3
  id: string;
4
4
  defaultMessage: string;
5
+ description: string;
5
6
  };
6
7
  hexLabel: {
7
8
  id: string;
8
9
  defaultMessage: string;
10
+ description: string;
9
11
  };
10
12
  pickColor: {
11
13
  id: string;
12
14
  defaultMessage: string;
15
+ description: string;
16
+ };
17
+ clear: {
18
+ id: string;
19
+ defaultMessage: string;
20
+ description: string;
13
21
  };
14
22
  };
15
23
 
@@ -2,14 +2,22 @@ declare const messages: {
2
2
  label: {
3
3
  id: string;
4
4
  defaultMessage: string;
5
+ description: string;
5
6
  };
6
7
  hexLabel: {
7
8
  id: string;
8
9
  defaultMessage: string;
10
+ description: string;
9
11
  };
10
12
  pickColor: {
11
13
  id: string;
12
14
  defaultMessage: string;
15
+ description: string;
16
+ };
17
+ clear: {
18
+ id: string;
19
+ defaultMessage: string;
20
+ description: string;
13
21
  };
14
22
  };
15
23
 
package/dist/messages.js CHANGED
@@ -28,15 +28,23 @@ var import_i18n = require("@kopexa/i18n");
28
28
  var messages = (0, import_i18n.defineMessages)({
29
29
  label: {
30
30
  id: "color-picker.label",
31
- defaultMessage: "Color"
31
+ defaultMessage: "Color",
32
+ description: "Generic label for a color field"
32
33
  },
33
34
  hexLabel: {
34
35
  id: "color-picker.hex_label",
35
- defaultMessage: "Hex"
36
+ defaultMessage: "Hex",
37
+ description: "Label for the hex input inside the color picker popover"
36
38
  },
37
39
  pickColor: {
38
40
  id: "color-picker.pick_color",
39
- defaultMessage: "Pick a color"
41
+ defaultMessage: "Pick a color",
42
+ description: "Prompt to pick a color"
43
+ },
44
+ clear: {
45
+ id: "color-picker.clear",
46
+ defaultMessage: "Clear",
47
+ description: "Button inside the color picker popover that resets the field to its unset state"
40
48
  }
41
49
  });
42
50
  // Annotate the CommonJS export names for ESM import in node:
package/dist/messages.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  messages
4
- } from "./chunk-JFALBXTO.mjs";
4
+ } from "./chunk-CRQDZQE6.mjs";
5
5
  export {
6
6
  messages
7
7
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kopexa/color-picker",
3
- "version": "0.0.37",
3
+ "version": "0.0.39",
4
4
  "description": "Color picker components for selecting and managing colors.",
5
5
  "keywords": [
6
6
  "color-picker",
@@ -29,12 +29,12 @@
29
29
  "peerDependencies": {
30
30
  "react": ">=19.0.0-rc.0",
31
31
  "react-dom": ">=19.0.0-rc.0",
32
- "@kopexa/theme": "17.28.0"
32
+ "@kopexa/theme": "17.29.1"
33
33
  },
34
34
  "dependencies": {
35
35
  "react-aria-components": "^1.7.1",
36
- "@kopexa/i18n": "17.13.8",
37
- "@kopexa/shared-utils": "17.0.78"
36
+ "@kopexa/i18n": "17.14.0",
37
+ "@kopexa/shared-utils": "17.0.80"
38
38
  },
39
39
  "clean-package": "../../../clean-package.config.json",
40
40
  "module": "dist/index.mjs",
@@ -1,22 +0,0 @@
1
- "use client";
2
-
3
- // src/messages.ts
4
- import { defineMessages } from "@kopexa/i18n";
5
- var messages = defineMessages({
6
- label: {
7
- id: "color-picker.label",
8
- defaultMessage: "Color"
9
- },
10
- hexLabel: {
11
- id: "color-picker.hex_label",
12
- defaultMessage: "Hex"
13
- },
14
- pickColor: {
15
- id: "color-picker.pick_color",
16
- defaultMessage: "Pick a color"
17
- }
18
- });
19
-
20
- export {
21
- messages
22
- };