@react-fabric/form 1.0.0-beta.61 → 1.0.0-beta.63

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 (155) hide show
  1. package/css/_colorPicker.css +35 -5
  2. package/css/_file.css +2 -2
  3. package/dist/cjs/builder/FileField.js +14 -11
  4. package/dist/cjs/builder/SchemaEditor.js +1 -1
  5. package/dist/cjs/builder/components/SchemaArrayForm.js +2 -4
  6. package/dist/cjs/builder/components/SchemaField.js +3 -3
  7. package/dist/cjs/form/Controller.js +1 -1
  8. package/dist/cjs/form/Form.js +42 -41
  9. package/dist/cjs/form/useForm.js +32 -0
  10. package/dist/cjs/hooks/useFileUploader.js +44 -32
  11. package/dist/cjs/hooks/useFormBuilder.js +3 -3
  12. package/dist/cjs/i18n/index.js +4 -4
  13. package/dist/cjs/index.js +2 -1
  14. package/dist/cjs/input/ArrayInput.js +66 -19
  15. package/dist/cjs/input/AvatarInput.js +21 -16
  16. package/dist/cjs/input/Checkbox.js +1 -1
  17. package/dist/cjs/input/ColorInput.js +26 -19
  18. package/dist/cjs/input/ColorPicker.js +16 -9
  19. package/dist/cjs/input/DateInput.js +3 -3
  20. package/dist/cjs/input/Field.js +1 -1
  21. package/dist/cjs/input/File.js +1 -1
  22. package/dist/cjs/input/Hidden.js +2 -11
  23. package/dist/cjs/input/Input.js +1 -1
  24. package/dist/cjs/input/Number.js +1 -1
  25. package/dist/cjs/input/Password.js +1 -1
  26. package/dist/cjs/input/Radio.js +1 -1
  27. package/dist/cjs/input/RadioGroup.js +7 -3
  28. package/dist/cjs/input/RangeSlider.js +1 -1
  29. package/dist/cjs/input/Search.js +1 -1
  30. package/dist/cjs/input/Slider.js +1 -1
  31. package/dist/cjs/input/Switch.js +4 -4
  32. package/dist/cjs/input/Textarea.js +1 -1
  33. package/dist/cjs/internal/InputWrapper.js +2 -2
  34. package/dist/cjs/ranger/index.js +5 -5
  35. package/dist/cjs/select/AutoComplete.js +4 -2
  36. package/dist/cjs/select/DualList.js +3 -3
  37. package/dist/cjs/select/List.js +7 -4
  38. package/dist/cjs/select/Options.js +5 -4
  39. package/dist/cjs/select/Select.js +5 -3
  40. package/dist/cjs/select/useSelect.js +15 -13
  41. package/dist/esm/builder/FileField.js +14 -11
  42. package/dist/esm/builder/SchemaEditor.js +1 -1
  43. package/dist/esm/builder/components/SchemaArrayForm.js +2 -4
  44. package/dist/esm/builder/components/SchemaField.js +3 -3
  45. package/dist/esm/form/Controller.js +1 -1
  46. package/dist/esm/form/Form.js +45 -43
  47. package/dist/esm/form/useForm.js +32 -0
  48. package/dist/esm/hooks/useFileUploader.js +45 -33
  49. package/dist/esm/hooks/useFormBuilder.js +3 -3
  50. package/dist/esm/i18n/index.js +2 -2
  51. package/dist/esm/index.js +2 -1
  52. package/dist/esm/input/ArrayInput.js +66 -19
  53. package/dist/esm/input/AvatarInput.js +21 -16
  54. package/dist/esm/input/Checkbox.js +1 -1
  55. package/dist/esm/input/ColorInput.js +27 -20
  56. package/dist/esm/input/ColorPicker.js +17 -10
  57. package/dist/esm/input/DateInput.js +3 -3
  58. package/dist/esm/input/Field.js +1 -1
  59. package/dist/esm/input/File.js +1 -1
  60. package/dist/esm/input/Hidden.js +3 -12
  61. package/dist/esm/input/Input.js +1 -1
  62. package/dist/esm/input/Number.js +1 -1
  63. package/dist/esm/input/Password.js +1 -1
  64. package/dist/esm/input/Radio.js +1 -1
  65. package/dist/esm/input/RadioGroup.js +7 -3
  66. package/dist/esm/input/RangeSlider.js +1 -1
  67. package/dist/esm/input/Search.js +1 -1
  68. package/dist/esm/input/Slider.js +1 -1
  69. package/dist/esm/input/Switch.js +4 -4
  70. package/dist/esm/input/Textarea.js +1 -1
  71. package/dist/esm/internal/InputWrapper.js +2 -2
  72. package/dist/esm/ranger/index.js +5 -5
  73. package/dist/esm/select/AutoComplete.js +4 -2
  74. package/dist/esm/select/DualList.js +3 -3
  75. package/dist/esm/select/List.js +8 -5
  76. package/dist/esm/select/Options.js +5 -4
  77. package/dist/esm/select/Select.js +6 -4
  78. package/dist/esm/select/useSelect.js +15 -13
  79. package/dist/types/builder/FileField.d.ts +2 -1
  80. package/dist/types/builder/SchemaEditor.d.ts +1 -1
  81. package/dist/types/form/Controller.d.ts +1 -1
  82. package/dist/types/form/Form.d.ts +7 -14
  83. package/dist/types/form/useForm.d.ts +11 -0
  84. package/dist/types/hooks/useFileUploader.d.ts +4 -2
  85. package/dist/types/index.d.ts +3 -2
  86. package/dist/types/input/ArrayInput.d.ts +5 -4
  87. package/dist/types/input/AvatarInput.d.ts +2 -2
  88. package/dist/types/input/Checkbox.d.ts +2 -2
  89. package/dist/types/input/ColorInput.d.ts +1 -1
  90. package/dist/types/input/ColorPicker.d.ts +5 -1
  91. package/dist/types/input/DateInput.d.ts +1 -1
  92. package/dist/types/input/Field.d.ts +1 -1
  93. package/dist/types/input/File.d.ts +1 -1
  94. package/dist/types/input/Hidden.d.ts +4 -12
  95. package/dist/types/input/Input.d.ts +1 -1
  96. package/dist/types/input/Number.d.ts +1 -1
  97. package/dist/types/input/Password.d.ts +1 -1
  98. package/dist/types/input/Radio.d.ts +2 -2
  99. package/dist/types/input/RadioGroup.d.ts +7 -3
  100. package/dist/types/input/RangeSlider.d.ts +1 -1
  101. package/dist/types/input/Search.d.ts +1 -1
  102. package/dist/types/input/Slider.d.ts +1 -1
  103. package/dist/types/input/Switch.d.ts +2 -2
  104. package/dist/types/input/Textarea.d.ts +1 -1
  105. package/dist/types/select/AutoComplete.d.ts +1 -1
  106. package/dist/types/select/DualList.d.ts +1 -1
  107. package/dist/types/select/List.d.ts +1 -1
  108. package/dist/types/select/Select.d.ts +1 -1
  109. package/dist/types/types/index.d.ts +3 -3
  110. package/dist/types/types/schema.d.ts +1 -1
  111. package/package.json +14 -14
  112. package/src/builder/FileField.tsx +5 -3
  113. package/src/builder/SchemaEditor.tsx +2 -2
  114. package/src/builder/components/SchemaArrayForm.tsx +1 -3
  115. package/src/builder/components/SchemaField.tsx +3 -3
  116. package/src/form/Controller.tsx +1 -1
  117. package/src/form/Form.tsx +21 -43
  118. package/src/form/useForm.ts +44 -0
  119. package/src/hooks/useFileUploader.ts +41 -29
  120. package/src/i18n/index.ts +2 -2
  121. package/src/index.ts +3 -7
  122. package/src/input/ArrayInput.tsx +20 -10
  123. package/src/input/AvatarInput.tsx +12 -12
  124. package/src/input/Checkbox.tsx +2 -2
  125. package/src/input/ColorInput.tsx +29 -45
  126. package/src/input/ColorPicker.tsx +42 -49
  127. package/src/input/DateInput.tsx +3 -3
  128. package/src/input/Field.tsx +2 -2
  129. package/src/input/File.tsx +1 -1
  130. package/src/input/Hidden.tsx +5 -25
  131. package/src/input/Input.tsx +1 -1
  132. package/src/input/Number.tsx +1 -1
  133. package/src/input/Password.tsx +1 -1
  134. package/src/input/Radio.tsx +2 -2
  135. package/src/input/RadioGroup.tsx +11 -3
  136. package/src/input/RangeSlider.tsx +1 -1
  137. package/src/input/Search.tsx +1 -1
  138. package/src/input/Slider.tsx +1 -1
  139. package/src/input/Switch.tsx +6 -6
  140. package/src/input/Textarea.tsx +1 -1
  141. package/src/internal/InputWrapper.tsx +1 -2
  142. package/src/select/AutoComplete.tsx +13 -52
  143. package/src/select/DualList.tsx +3 -3
  144. package/src/select/List.tsx +31 -69
  145. package/src/select/Options.tsx +4 -3
  146. package/src/select/Select.tsx +19 -41
  147. package/src/select/useSelect.tsx +24 -69
  148. package/src/types/index.ts +3 -3
  149. package/src/types/schema.ts +1 -1
  150. /package/dist/cjs/i18n/{ar.json.js → ar/form.json.js} +0 -0
  151. /package/dist/cjs/i18n/{en.json.js → en/form.json.js} +0 -0
  152. /package/dist/esm/i18n/{ar.json.js → ar/form.json.js} +0 -0
  153. /package/dist/esm/i18n/{en.json.js → en/form.json.js} +0 -0
  154. /package/src/i18n/{ar.json → ar/form.json} +0 -0
  155. /package/src/i18n/{en.json → en/form.json} +0 -0
@@ -0,0 +1,44 @@
1
+ /*
2
+ * React Fabric
3
+ * @version: 1.0.0
4
+ *
5
+ *
6
+ * The MIT License (MIT)
7
+ * Copyright (c) 2026 Adarsh Pastakia
8
+ *
9
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
10
+ * and associated documentation files (the "Software"), to deal in the Software without restriction,
11
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
12
+ * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
13
+ * subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in all copies or substantial
16
+ * portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19
+ * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ */
23
+
24
+ import { useFormContext, useWatch } from "react-hook-form";
25
+
26
+ export const useForm = <K extends KeyValue = KeyValue>() => {
27
+ const { formState, getValues, setValue, watch, reset, resetField, setError, setFocus } = useFormContext<K>();
28
+ const values = useWatch<K>();
29
+
30
+ return {
31
+ values: {
32
+ ...values,
33
+ ...getValues(),
34
+ },
35
+ formState,
36
+ getValues,
37
+ setValue,
38
+ watch,
39
+ reset,
40
+ resetField,
41
+ setError,
42
+ setFocus,
43
+ };
44
+ };
@@ -23,7 +23,7 @@
23
23
 
24
24
  import { isArray, isObject, isString, uuid } from "@react-fabric/utilities";
25
25
  import { type AxiosRequestConfig } from "axios";
26
- import { useCallback, useReducer } from "react";
26
+ import { useCallback, useEffect, useReducer } from "react";
27
27
 
28
28
  export type UploadHandler = (
29
29
  data: FormData,
@@ -54,6 +54,7 @@ type UploaderActions =
54
54
  | { type: "stop"; key: string; path: string }
55
55
  | { type: "error"; key: string; error: string }
56
56
  | { type: "progress"; key: string; progress: number }
57
+ | { type: "init"; initialList?: string | FileInfo | FileInfo[] }
57
58
  | { type: "remove"; key: string }
58
59
  | { type: "abort"; key: string };
59
60
 
@@ -76,7 +77,13 @@ type UploaderActions =
76
77
  export const useFileUploader = (
77
78
  uploader: UploadHandler,
78
79
  initialList?: string | FileInfo | FileInfo[],
79
- multiple = true,
80
+ {
81
+ multiple = true,
82
+ onChange,
83
+ }: {
84
+ multiple?: boolean;
85
+ onChange?: (files: FileInfo[] | FileInfo | null) => void;
86
+ } = {},
80
87
  ) => {
81
88
  const [state, dispatch] = useReducer(
82
89
  (state: UploaderState, action: UploaderActions) => {
@@ -119,6 +126,29 @@ export const useFileUploader = (
119
126
  if (action.type === "remove") {
120
127
  state.files.delete(action.key);
121
128
  }
129
+ if (action.type === "init") {
130
+ state.files = new Map();
131
+ if (isString(action.initialList)) {
132
+ const key = uuid();
133
+ state.files.set(key, {
134
+ key,
135
+ path: action.initialList,
136
+ filename: "",
137
+ size: 0,
138
+ mime: "",
139
+ });
140
+ }
141
+ if (isObject(action.initialList)) {
142
+ const key = uuid();
143
+ state.files.set(key, { ...(action.initialList as AnyObject), key });
144
+ }
145
+ if (isArray(action.initialList)) {
146
+ action.initialList.forEach((file) => {
147
+ const key = uuid();
148
+ state.files.set(key, { ...file, key });
149
+ });
150
+ }
151
+ }
122
152
  state.list = Array.from(state.files.values())
123
153
  .map((file) => {
124
154
  return (
@@ -134,40 +164,23 @@ export const useFileUploader = (
134
164
  );
135
165
  })
136
166
  .filter(Boolean) as FileInfo[];
167
+ if (action.type === "remove" || action.type === "stop") {
168
+ setTimeout(() => {
169
+ onChange?.(multiple ? state.list : state.list[0] ?? null);
170
+ }, 100);
171
+ }
137
172
  return { ...state };
138
173
  },
139
174
  {
140
175
  files: new Map(),
141
176
  count: 0,
142
177
  } as UploaderState,
143
- (state: UploaderState) => {
144
- if (initialList) {
145
- if (isString(initialList)) {
146
- const key = uuid();
147
- state.files.set(key, {
148
- key,
149
- path: initialList,
150
- filename: "",
151
- size: 0,
152
- mime: "",
153
- });
154
- }
155
- if (isObject(initialList)) {
156
- const key = uuid();
157
- state.files.set(key, { ...(initialList as AnyObject), key });
158
- }
159
- if (isArray(initialList)) {
160
- initialList.forEach((file) => {
161
- const key = uuid();
162
- state.files.set(key, { ...file, key });
163
- });
164
- }
165
- }
166
- state.list = Array.from(state.files.values());
167
- return state;
168
- },
169
178
  );
170
179
 
180
+ useEffect(() => {
181
+ dispatch({ type: "init", initialList });
182
+ }, [initialList]);
183
+
171
184
  const doUpload = useCallback(async (key: string, file: File) => {
172
185
  const data = new FormData();
173
186
  data.append("file", file);
@@ -244,7 +257,6 @@ export const useFileUploader = (
244
257
  remove,
245
258
  retry,
246
259
  pending: state.count,
247
- list: state.list,
248
260
  files: Array.from(state.files.values()),
249
261
  };
250
262
  };
package/src/i18n/index.ts CHANGED
@@ -22,7 +22,7 @@
22
22
  */
23
23
 
24
24
  import { addTranslationBundle } from "@react-fabric/core";
25
- import ar from "./ar.json";
26
- import en from "./en.json";
25
+ import ar from "./ar/form.json";
26
+ import en from "./en/form.json";
27
27
 
28
28
  addTranslationBundle("form", { en, ar });
package/src/index.ts CHANGED
@@ -24,19 +24,15 @@
24
24
  import "./i18n";
25
25
 
26
26
  export { Controller } from "./form/Controller";
27
- export { Form, useForm, type FormRef } from "./form/Form";
27
+ export { Form, type FormRef } from "./form/Form";
28
+ export { useForm } from "./form/useForm";
28
29
 
29
30
  export { SchemaEditor } from "./builder/SchemaEditor";
30
31
 
31
32
  export { useFileUploader } from "./hooks/useFileUploader";
32
33
  export { useFormBuilder } from "./hooks/useFormBuilder";
33
34
 
34
- export {
35
- DATA_TYPES,
36
- SCHEMA_CUSTOM_LIST,
37
- type FormSchema,
38
- type SchemaDef,
39
- } from "./types/schema";
35
+ export { DATA_TYPES, SCHEMA_CUSTOM_LIST, type FormSchema, type SchemaDef } from "./types/schema";
40
36
 
41
37
  export { ArrayInput } from "./input/ArrayInput";
42
38
  export { AvatarInput } from "./input/AvatarInput";
@@ -51,7 +51,7 @@ export interface ArrayInputProps<T extends AnyObject = string> {
51
51
  /**
52
52
  * append to label end
53
53
  */
54
- appendLabel?: JSX.Element | string | number | boolean;
54
+ appendLabel?: React.ReactElement | string | number | boolean;
55
55
  /**
56
56
  * info label at bottom
57
57
  */
@@ -91,20 +91,27 @@ export interface ArrayInputProps<T extends AnyObject = string> {
91
91
  enableSorting?: boolean;
92
92
  children:
93
93
  | React.ReactNode
94
- | ((props: { index: number; name: string; item: T }) => React.ReactNode);
94
+ | ((props: {
95
+ index: number;
96
+ name: string;
97
+ item: T;
98
+ onChange: (val: false | undefined | T) => void;
99
+ }) => React.ReactNode);
95
100
  /**
96
101
  * add item button label
97
102
  */
98
103
  addLabel?: string;
99
104
  buttonPosition?: "top" | "bottom" | "both";
100
105
  arrayRef?: Ref<{
101
- addItem: (item: T) => void;
106
+ addItem: (
107
+ item: false | undefined | T | Promise<false | undefined | T>,
108
+ ) => void;
102
109
  removeItem: (idx: number) => void;
103
110
  }>;
104
111
  /**
105
112
  * add new item
106
113
  */
107
- onAdd?: () => T;
114
+ onAdd?: () => false | undefined | T | Promise<false | undefined | T>;
108
115
  /**
109
116
  * on item remove
110
117
  */
@@ -147,7 +154,7 @@ export interface ArrayInputProps<T extends AnyObject = string> {
147
154
  * It also supports drag-and-drop sorting using `@dnd-kit/core` and `@dnd-kit/sortable` for a better user experience when reordering items.
148
155
  *
149
156
  * @param {ArrayInputProps<T>} props - The properties for the ArrayInput component.
150
- * @returns {JSX.Element} The rendered ArrayInput component.
157
+ * @returns {React.ReactElement} The rendered ArrayInput component.
151
158
  *
152
159
  * @example
153
160
  * ```jsx
@@ -235,9 +242,9 @@ export const ArrayInput = <T extends AnyObject = string>({
235
242
  );
236
243
 
237
244
  const handleAdd = useCallback(
238
- (item?: T) => {
245
+ async (item?: Promise<false | undefined | T>) => {
239
246
  if (!item) return;
240
- append(item);
247
+ append((await item) as T);
241
248
  setTimeout(() => {
242
249
  form.setFocus(
243
250
  `${name}.${fields.length}${focusName ? "." + focusName : ""}`,
@@ -261,12 +268,11 @@ export const ArrayInput = <T extends AnyObject = string>({
261
268
  );
262
269
 
263
270
  useImperativeHandle(arrayRef, () => ({
264
- addItem: handleAdd,
271
+ addItem: (t) => handleAdd(Promise.resolve(t)),
265
272
  removeItem: remove,
266
273
  }));
267
274
 
268
275
  const Wrapper = useCallback(
269
- // eslint-disable-next-line @typescript-eslint/promise-function-async
270
276
  ({ children }: { children: React.ReactNode }) => {
271
277
  if (enableSorting) {
272
278
  // pass id list to dnd context
@@ -364,7 +370,7 @@ export const ArrayInput = <T extends AnyObject = string>({
364
370
  size="sm"
365
371
  aria-label="Add item"
366
372
  icon="icon-[mdi--plus-circle-outline]"
367
- onClick={() => handleAdd(onAdd?.())}
373
+ onClick={() => handleAdd(Promise.resolve(onAdd?.()))}
368
374
  data-invalid={!!error}
369
375
  disabled={!!disabled || readOnly || fields.length > maxItems}
370
376
  className={classNames(
@@ -411,6 +417,10 @@ export const ArrayInput = <T extends AnyObject = string>({
411
417
  index,
412
418
  item: item as any,
413
419
  name: `${name}.${index}`,
420
+ onChange: async (val) => {
421
+ const result = await Promise.resolve(val);
422
+ result && form.setValue(`${name}.${index}`, result);
423
+ },
414
424
  })
415
425
  : cloneChildren(children, (child: AnyObject) =>
416
426
  cloneElement(
@@ -75,7 +75,7 @@ export interface Props extends RefProp {
75
75
  * It also provides a way to remove the uploaded file and shows upload progress.
76
76
  *
77
77
  * @param {Props} props - The properties for the AvatarInput component.
78
- * @returns {JSX.Element} - The rendered AvatarInput component.
78
+ * @returns {React.ReactElement} - The rendered AvatarInput component.
79
79
  */
80
80
  export const AvatarInput = ({
81
81
  size = "6rem",
@@ -88,17 +88,21 @@ export const AvatarInput = ({
88
88
  variant,
89
89
  fileUrl,
90
90
  uploadHandler,
91
+ onChange,
91
92
  ...rest
92
93
  }: Props) => {
93
- const { files, list, upload, remove } = useFileUploader(
94
+ const { files, upload, remove } = useFileUploader(
94
95
  async (data, config) => await uploadHandler?.(data, config),
95
- value,
96
- false,
96
+ value ?? defaultValue,
97
+ {
98
+ multiple: false,
99
+ onChange: (files: AnyObject) => onChange?.(files?.path ?? null),
100
+ },
97
101
  );
98
102
 
99
103
  const [base64, setBase64] = useState("");
100
104
  useEffect(() => {
101
- if (list?.length) {
105
+ if (files?.length) {
102
106
  const file = files?.[0]?.file;
103
107
  void (
104
108
  file &&
@@ -116,7 +120,7 @@ export const AvatarInput = ({
116
120
  } else {
117
121
  setBase64(fileUrl?.(defaultValue ?? "") ?? defaultValue ?? "");
118
122
  }
119
- }, [files, list, defaultValue]);
123
+ }, [files, defaultValue]);
120
124
 
121
125
  useEffect(() => {
122
126
  !value &&
@@ -126,7 +130,7 @@ export const AvatarInput = ({
126
130
  }, [value, defaultValue]);
127
131
 
128
132
  return (
129
- <div className="flex flex-nowrap items-end overflow-hidden">
133
+ <div className="flex flex-nowrap items-end overflow-hidden p-px">
130
134
  <div
131
135
  className={classNames(
132
136
  "inline-block leading-none relative outline overflow-hidden",
@@ -153,11 +157,7 @@ export const AvatarInput = ({
153
157
  />
154
158
  </div>
155
159
  </div>
156
- <HiddenInput
157
- hiddenValue={list?.[0]?.path}
158
- defaultValue={defaultValue}
159
- {...rest}
160
- />
160
+ <HiddenInput value={files?.[0]?.path} {...rest} />
161
161
  {files.map((file, idx) => (
162
162
  <div key={idx} className="px-2 py-1 flex-1 overflow-hidden">
163
163
  <div className="flex flex-nowrap gap-1 items-center">
@@ -38,7 +38,7 @@ interface CheckboxProps extends RefProp<HTMLInputElement> {
38
38
  /**
39
39
  * checkbox label
40
40
  */
41
- label?: JSX.Element | string;
41
+ label?: React.ReactElement | string;
42
42
  /**
43
43
  * checked
44
44
  */
@@ -88,7 +88,7 @@ interface CheckboxProps extends RefProp<HTMLInputElement> {
88
88
  * It also provides an error icon to indicate validation errors.
89
89
  *
90
90
  * @param {CheckboxProps} props - The properties for the Checkbox component.
91
- * @returns {JSX.Element} The rendered Checkbox component.
91
+ * @returns {React.ReactElement} The rendered Checkbox component.
92
92
  */
93
93
  export const Checkbox = ({
94
94
  ref,
@@ -26,6 +26,7 @@ import {
26
26
  autoUpdate,
27
27
  flip,
28
28
  FloatingArrow,
29
+ FloatingFocusManager,
29
30
  offset,
30
31
  shift,
31
32
  useClick,
@@ -35,22 +36,12 @@ import {
35
36
  } from "@floating-ui/react";
36
37
  import { Icon } from "@react-fabric/core";
37
38
  import { type RefProp } from "@react-fabric/core/dist/types/types";
38
- import {
39
- startTransition,
40
- useCallback,
41
- useDeferredValue,
42
- useEffect,
43
- useRef,
44
- useState,
45
- } from "react";
39
+ import { startTransition, useCallback, useDeferredValue, useEffect, useRef, useState } from "react";
46
40
  import { InputWrapper } from "../internal/InputWrapper";
47
41
  import { type InputProps } from "../types";
48
42
  import { ColorPicker, type ColorProps } from "./ColorPicker";
49
43
 
50
- export interface ColorInputProps
51
- extends InputProps,
52
- Omit<ColorProps, "onChange">,
53
- RefProp {
44
+ export interface ColorInputProps extends InputProps, Omit<ColorProps, "onChange">, RefProp {
54
45
  /**
55
46
  * show eydrop picker
56
47
  */
@@ -68,7 +59,7 @@ export interface ColorInputProps
68
59
  * and handling the color selection state.
69
60
  *
70
61
  * @param {ColorInputProps} props - The properties for the ColorInput component.
71
- * @returns {JSX.Element} - The rendered ColorInput component.
62
+ * @returns {React.ReactElement} - The rendered ColorInput component.
72
63
  */
73
64
  export const ColorInput = ({
74
65
  ref,
@@ -130,10 +121,7 @@ export const ColorInput = ({
130
121
  referencePress: true,
131
122
  });
132
123
 
133
- const { getReferenceProps, getFloatingProps } = useInteractions([
134
- dismiss,
135
- click,
136
- ]);
124
+ const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, click]);
137
125
 
138
126
  return (
139
127
  <InputWrapper
@@ -167,34 +155,32 @@ export const ColorInput = ({
167
155
  pointerEvents: disabled ? "none" : undefined,
168
156
  }}
169
157
  >
170
- {!actualValue && (
171
- <Icon size="md" className="text-muted" icon="icon-[mdi--palette]" />
172
- )}
158
+ {!actualValue && <Icon size="md" className="text-muted" icon="icon-[mdi--palette]" />}
173
159
  </div>
174
160
  </button>
175
161
  {isOpen && (
176
- <div
177
- ref={refs.setFloating}
178
- style={{
179
- zIndex: "var(--z-popover)",
180
- ...floatingStyles,
181
- }}
182
- data-ref="dropdownBody"
183
- {...getFloatingProps()}
184
- >
185
- <ColorPicker
186
- {...rest}
187
- onChange={handleChange}
188
- defaultColor={defaultColor}
189
- value={actualValue}
190
- />
191
- <FloatingArrow
192
- ref={arrowRef}
193
- context={context}
194
- strokeWidth={0.5}
195
- className="fill-base stroke-muted"
196
- />
197
- </div>
162
+ <FloatingFocusManager context={context} closeOnFocusOut>
163
+ <div
164
+ ref={refs.setFloating}
165
+ style={{
166
+ zIndex: "var(--z-popover)",
167
+ ...floatingStyles,
168
+ }}
169
+ data-ref="dropdownBody"
170
+ {...getFloatingProps()}
171
+ >
172
+ <ColorPicker {...rest} onChange={handleChange} defaultColor={defaultColor} value={actualValue} />
173
+ <FloatingArrow
174
+ ref={arrowRef}
175
+ context={context}
176
+ strokeWidth={0.5}
177
+ className="stroke-muted"
178
+ style={{
179
+ fill: actualValue || "var(--bg-color-base)",
180
+ }}
181
+ />
182
+ </div>
183
+ </FloatingFocusManager>
198
184
  )}
199
185
  {!disabled && showPicker && (
200
186
  <Icon
@@ -202,9 +188,7 @@ export const ColorInput = ({
202
188
  icon="icon-[mdi--eyedropper]"
203
189
  onClick={() =>
204
190
  (window as AnyObject).EyeDropper &&
205
- new (window as AnyObject).EyeDropper()
206
- .open()
207
- .then((resp: AnyObject) => handleChange(resp.sRGBHex))
191
+ new (window as AnyObject).EyeDropper().open().then((resp: AnyObject) => handleChange(resp.sRGBHex))
208
192
  }
209
193
  />
210
194
  )}
@@ -21,17 +21,9 @@
21
21
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  */
23
23
 
24
- import Color from "color";
25
- import {
26
- memo,
27
- startTransition,
28
- useCallback,
29
- useDeferredValue,
30
- useEffect,
31
- useState,
32
- type FC,
33
- } from "react";
34
- import { SketchPicker } from "react-color";
24
+ import chroma from "chroma-js";
25
+ import { memo, startTransition, useCallback, useDeferredValue, useEffect, useState, type FC } from "react";
26
+ import { BlockPicker, SketchPicker } from "react-color";
35
27
 
36
28
  export interface ColorProps {
37
29
  /**
@@ -46,6 +38,10 @@ export interface ColorProps {
46
38
  * hide alpha value
47
39
  */
48
40
  hideAlpha?: boolean;
41
+ /**
42
+ * show only swatches, hide the color picker
43
+ */
44
+ picker?: "default" | "swatches";
49
45
  /**
50
46
  * color value
51
47
  */
@@ -57,16 +53,16 @@ export interface ColorProps {
57
53
  }
58
54
 
59
55
  const DEFAULT_SWATCHES = [
60
- Color("#d7323a").mix(Color("#171a1c"), 0.5).hex(),
61
- Color("#ff781c").mix(Color("#171a1c"), 0.5).hex(),
62
- Color("#f7b731").mix(Color("#171a1c"), 0.5).hex(),
63
- Color("#88ab1b").mix(Color("#171a1c"), 0.5).hex(),
64
- Color("#22aa85").mix(Color("#171a1c"), 0.5).hex(),
65
- Color("#399bec").mix(Color("#171a1c"), 0.5).hex(),
66
- Color("#4862c8").mix(Color("#171a1c"), 0.5).hex(),
67
- Color("#aa66cf").mix(Color("#171a1c"), 0.5).hex(),
68
- Color("#f43f7b").mix(Color("#171a1c"), 0.5).hex(),
69
- Color("#a37f4f").mix(Color("#171a1c"), 0.5).hex(),
56
+ chroma.mix("#d7323a", "#171a1c", 0.5, "lab").hex(),
57
+ chroma.mix("#ff781c", "#171a1c", 0.5, "lab").hex(),
58
+ chroma.mix("#f7b731", "#171a1c", 0.5, "lab").hex(),
59
+ chroma.mix("#88ab1b", "#171a1c", 0.5, "lab").hex(),
60
+ chroma.mix("#22aa85", "#171a1c", 0.5, "lab").hex(),
61
+ chroma.mix("#399bec", "#171a1c", 0.5, "lab").hex(),
62
+ chroma.mix("#4862c8", "#171a1c", 0.5, "lab").hex(),
63
+ chroma.mix("#aa66cf", "#171a1c", 0.5, "lab").hex(),
64
+ chroma.mix("#f43f7b", "#171a1c", 0.5, "lab").hex(),
65
+ chroma.mix("#a37f4f", "#171a1c", 0.5, "lab").hex(),
70
66
 
71
67
  "#d7323a",
72
68
  "#ff781c",
@@ -79,16 +75,16 @@ const DEFAULT_SWATCHES = [
79
75
  "#f43f7b",
80
76
  "#a37f4f",
81
77
 
82
- Color("#d7323a").mix(Color("#f1f3f4"), 0.5).hex(),
83
- Color("#ff781c").mix(Color("#f1f3f4"), 0.5).hex(),
84
- Color("#f7b731").mix(Color("#f1f3f4"), 0.5).hex(),
85
- Color("#88ab1b").mix(Color("#f1f3f4"), 0.5).hex(),
86
- Color("#22aa85").mix(Color("#f1f3f4"), 0.5).hex(),
87
- Color("#399bec").mix(Color("#f1f3f4"), 0.5).hex(),
88
- Color("#4862c8").mix(Color("#f1f3f4"), 0.5).hex(),
89
- Color("#aa66cf").mix(Color("#f1f3f4"), 0.5).hex(),
90
- Color("#f43f7b").mix(Color("#f1f3f4"), 0.5).hex(),
91
- Color("#a37f4f").mix(Color("#f1f3f4"), 0.5).hex(),
78
+ chroma.mix("#d7323a", "#f1f3f4", 0.5, "lab").hex(),
79
+ chroma.mix("#ff781c", "#f1f3f4", 0.5, "lab").hex(),
80
+ chroma.mix("#f7b731", "#f1f3f4", 0.5, "lab").hex(),
81
+ chroma.mix("#88ab1b", "#f1f3f4", 0.5, "lab").hex(),
82
+ chroma.mix("#22aa85", "#f1f3f4", 0.5, "lab").hex(),
83
+ chroma.mix("#399bec", "#f1f3f4", 0.5, "lab").hex(),
84
+ chroma.mix("#4862c8", "#f1f3f4", 0.5, "lab").hex(),
85
+ chroma.mix("#aa66cf", "#f1f3f4", 0.5, "lab").hex(),
86
+ chroma.mix("#f43f7b", "#f1f3f4", 0.5, "lab").hex(),
87
+ chroma.mix("#a37f4f", "#f1f3f4", 0.5, "lab").hex(),
92
88
 
93
89
  "#171a1c",
94
90
  "#2d3439",
@@ -108,19 +104,12 @@ const DEFAULT_SWATCHES = [
108
104
  * The component uses the `react-color` library for the color picker functionality.
109
105
  *
110
106
  * @param {ColorProps} props - The properties for the ColorPicker component.
111
- * @returns {JSX.Element} The rendered ColorPicker component.
107
+ * @returns {React.ReactElement} The rendered ColorPicker component.
112
108
  *
113
109
  * @see {@link https://react-color.github.io/react-color/} for more details on the color picker library.
114
110
  */
115
- // eslint-disable-next-line react/display-name
116
111
  export const ColorPicker: FC<ColorProps> = memo(
117
- ({
118
- value,
119
- onChange,
120
- hideAlpha,
121
- defaultColor = "#fff",
122
- swatches = DEFAULT_SWATCHES,
123
- }: ColorProps) => {
112
+ ({ value, onChange, hideAlpha, defaultColor = "#fff", swatches = DEFAULT_SWATCHES, picker = "default" }: ColorProps) => {
124
113
  const [actualValue, setActualValue] = useState("");
125
114
  const deferred = useDeferredValue(value ?? defaultColor);
126
115
 
@@ -136,13 +125,21 @@ export const ColorPicker: FC<ColorProps> = memo(
136
125
  [onChange],
137
126
  );
138
127
 
128
+ const E = (picker === "swatches" ? BlockPicker : SketchPicker) as unknown as FC<any>;
129
+
139
130
  return (
140
131
  <div
141
132
  role="none"
133
+ className="fabric-colorPicker-container"
142
134
  // prevent default on mouse down to avoid selection issues
143
135
  onMouseDown={(e) => e.preventDefault()}
136
+ style={
137
+ {
138
+ "--color-selected": value,
139
+ } as any
140
+ }
144
141
  >
145
- <SketchPicker
142
+ <E
146
143
  width="16rem"
147
144
  className={"fabric-colorPicker"}
148
145
  styles={
@@ -166,8 +163,7 @@ export const ColorPicker: FC<ColorProps> = memo(
166
163
  },
167
164
  picker: {
168
165
  backgroundColor: "var(--fabric-bg)",
169
- boxShadow:
170
- "0 0 0 1px var(--color-shadow), 0 0 8px var(--fabric-shadow)",
166
+ boxShadow: "0 0 0 1px var(--color-shadow), 0 0 8px var(--fabric-shadow)",
171
167
  },
172
168
  checkboard: {
173
169
  white: "transparent",
@@ -177,14 +173,11 @@ export const ColorPicker: FC<ColorProps> = memo(
177
173
  } as AnyObject
178
174
  }
179
175
  color={actualValue}
176
+ colors={swatches}
180
177
  presetColors={swatches}
181
178
  disableAlpha={hideAlpha}
182
- onChange={({ rgb: { a = 1, ...rgb } }) =>
183
- setActualValue(Color(rgb).alpha(a)[a < 1 ? "hexa" : "hex"]())
184
- }
185
- onChangeComplete={({ rgb: { a = 1, ...rgb } }) =>
186
- handleChange(Color(rgb).alpha(a)[a < 1 ? "hexa" : "hex"]())
187
- }
179
+ onChange={({ rgb: { a = 1, ...rgb } }) => setActualValue(chroma(rgb).alpha(a).hex())}
180
+ onChangeComplete={({ rgb: { a = 1, ...rgb } }) => handleChange(chroma(rgb).alpha(a).hex())}
188
181
  />
189
182
  </div>
190
183
  );