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

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 (187) hide show
  1. package/{src/input/ArrayInput.module.css → css/_arrayInput.css} +8 -8
  2. package/css/_colorPicker.css +65 -0
  3. package/{src/internal/Field.module.css → css/_field.css} +66 -30
  4. package/{src/input/File.module.css → css/_file.css} +21 -6
  5. package/css/styles.css +27 -0
  6. package/dist/cjs/builder/FileField.js +40 -28
  7. package/dist/cjs/builder/FormField.js +50 -34
  8. package/dist/cjs/builder/SchemaEditor.js +72 -0
  9. package/dist/cjs/builder/components/AddSchemaFields.js +63 -0
  10. package/dist/cjs/builder/components/SchemaArrayForm.js +145 -0
  11. package/dist/cjs/builder/components/SchemaField.js +414 -0
  12. package/dist/cjs/form/Controller.js +46 -1
  13. package/dist/cjs/form/Form.js +71 -13
  14. package/dist/cjs/hooks/useFileUploader.js +16 -0
  15. package/dist/cjs/hooks/useFormBuilder.js +67 -18
  16. package/dist/cjs/i18n/ar.json.js +5 -4
  17. package/dist/cjs/i18n/en.json.js +5 -5
  18. package/dist/cjs/index.js +12 -2
  19. package/dist/cjs/input/ArrayInput.js +128 -38
  20. package/dist/cjs/input/AvatarInput.js +43 -23
  21. package/dist/cjs/input/Checkbox.js +18 -8
  22. package/dist/cjs/input/ColorInput.js +114 -59
  23. package/dist/cjs/input/ColorPicker.js +60 -76
  24. package/dist/cjs/input/DateInput.js +197 -0
  25. package/dist/cjs/input/Field.js +10 -4
  26. package/dist/cjs/input/File.js +15 -8
  27. package/dist/cjs/input/Hidden.js +13 -2
  28. package/dist/cjs/input/Input.js +24 -10
  29. package/dist/cjs/input/Number.js +22 -10
  30. package/dist/cjs/input/Password.js +23 -13
  31. package/dist/cjs/input/Radio.js +14 -5
  32. package/dist/cjs/input/RadioGroup.js +10 -1
  33. package/dist/cjs/input/{Range.js → RangeSlider.js} +16 -8
  34. package/dist/cjs/input/Search.js +32 -22
  35. package/dist/cjs/input/Slider.js +12 -6
  36. package/dist/cjs/input/Switch.js +20 -9
  37. package/dist/cjs/input/Textarea.js +29 -8
  38. package/dist/cjs/internal/ErrorIcon.js +2 -2
  39. package/dist/cjs/internal/FieldWrapper.js +11 -4
  40. package/dist/cjs/internal/InputWrapper.js +29 -22
  41. package/dist/cjs/select/AutoComplete.js +22 -13
  42. package/dist/cjs/select/DualList.js +34 -12
  43. package/dist/cjs/select/List.js +49 -21
  44. package/dist/cjs/select/Options.js +49 -42
  45. package/dist/cjs/select/Select.js +21 -10
  46. package/dist/cjs/select/useSelect.js +22 -11
  47. package/dist/cjs/types/schema.js +43 -0
  48. package/dist/cjs/utils/eventHandlers.js +15 -5
  49. package/dist/esm/builder/FileField.js +42 -30
  50. package/dist/esm/builder/FormField.js +50 -34
  51. package/dist/esm/builder/SchemaEditor.js +72 -0
  52. package/dist/esm/builder/components/AddSchemaFields.js +63 -0
  53. package/dist/esm/builder/components/SchemaArrayForm.js +145 -0
  54. package/dist/esm/builder/components/SchemaField.js +414 -0
  55. package/dist/esm/form/Controller.js +46 -1
  56. package/dist/esm/form/Form.js +73 -16
  57. package/dist/esm/hooks/useFileUploader.js +16 -0
  58. package/dist/esm/hooks/useFormBuilder.js +65 -16
  59. package/dist/esm/i18n/ar.json.js +5 -4
  60. package/dist/esm/i18n/en.json.js +5 -5
  61. package/dist/esm/index.js +6 -2
  62. package/dist/esm/input/ArrayInput.js +128 -38
  63. package/dist/esm/input/AvatarInput.js +43 -23
  64. package/dist/esm/input/Checkbox.js +19 -9
  65. package/dist/esm/input/ColorInput.js +116 -61
  66. package/dist/esm/input/ColorPicker.js +60 -76
  67. package/dist/esm/input/DateInput.js +197 -0
  68. package/dist/esm/input/Field.js +11 -5
  69. package/dist/esm/input/File.js +18 -11
  70. package/dist/esm/input/Hidden.js +13 -2
  71. package/dist/esm/input/Input.js +26 -12
  72. package/dist/esm/input/Number.js +24 -12
  73. package/dist/esm/input/Password.js +26 -16
  74. package/dist/esm/input/Radio.js +15 -6
  75. package/dist/esm/input/RadioGroup.js +11 -2
  76. package/dist/esm/input/{Range.js → RangeSlider.js} +16 -8
  77. package/dist/esm/input/Search.js +35 -25
  78. package/dist/esm/input/Slider.js +12 -6
  79. package/dist/esm/input/Switch.js +20 -9
  80. package/dist/esm/input/Textarea.js +30 -9
  81. package/dist/esm/internal/ErrorIcon.js +3 -3
  82. package/dist/esm/internal/FieldWrapper.js +11 -4
  83. package/dist/esm/internal/InputWrapper.js +30 -23
  84. package/dist/esm/select/AutoComplete.js +23 -14
  85. package/dist/esm/select/DualList.js +35 -13
  86. package/dist/esm/select/List.js +50 -22
  87. package/dist/esm/select/Options.js +49 -42
  88. package/dist/esm/select/Select.js +22 -11
  89. package/dist/esm/select/useSelect.js +23 -12
  90. package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
  91. package/dist/esm/utils/eventHandlers.js +15 -5
  92. package/dist/types/builder/FileField.d.ts +4 -2
  93. package/dist/types/builder/FormField.d.ts +3 -1
  94. package/dist/types/builder/SchemaEditor.d.ts +9 -0
  95. package/dist/types/builder/components/AddSchemaFields.d.ts +2 -0
  96. package/dist/types/builder/components/SchemaArrayForm.d.ts +2 -0
  97. package/dist/types/builder/components/SchemaField.d.ts +10 -0
  98. package/dist/types/form/Controller.d.ts +47 -2
  99. package/dist/types/form/Form.d.ts +49 -6
  100. package/dist/types/hooks/useFileUploader.d.ts +16 -0
  101. package/dist/types/hooks/useFormBuilder.d.ts +38 -1
  102. package/dist/types/index.d.ts +6 -3
  103. package/dist/types/input/ArrayInput.d.ts +69 -12
  104. package/dist/types/input/AvatarInput.d.ts +23 -3
  105. package/dist/types/input/Checkbox.d.ts +13 -4
  106. package/dist/types/input/ColorInput.d.ts +15 -2
  107. package/dist/types/input/ColorPicker.d.ts +11 -1
  108. package/dist/types/input/DateInput.d.ts +28 -0
  109. package/dist/types/input/Field.d.ts +5 -1
  110. package/dist/types/input/File.d.ts +9 -0
  111. package/dist/types/input/Hidden.d.ts +13 -1
  112. package/dist/types/input/Input.d.ts +9 -1
  113. package/dist/types/input/Number.d.ts +9 -1
  114. package/dist/types/input/Password.d.ts +7 -1
  115. package/dist/types/input/Radio.d.ts +9 -1
  116. package/dist/types/input/RadioGroup.d.ts +8 -1
  117. package/dist/types/input/RangeSlider.d.ts +67 -0
  118. package/dist/types/input/Search.d.ts +14 -1
  119. package/dist/types/input/Slider.d.ts +8 -2
  120. package/dist/types/input/Switch.d.ts +10 -2
  121. package/dist/types/input/Textarea.d.ts +15 -1
  122. package/dist/types/internal/FieldWrapper.d.ts +1 -1
  123. package/dist/types/internal/InputWrapper.d.ts +1 -1
  124. package/dist/types/ranger/index.d.ts +2 -2
  125. package/dist/types/select/AutoComplete.d.ts +8 -1
  126. package/dist/types/select/DualList.d.ts +21 -7
  127. package/dist/types/select/List.d.ts +9 -1
  128. package/dist/types/select/Options.d.ts +4 -3
  129. package/dist/types/select/Select.d.ts +9 -1
  130. package/dist/types/select/useSelect.d.ts +10 -6
  131. package/dist/types/types/index.d.ts +38 -10
  132. package/dist/types/types/schema.d.ts +78 -33
  133. package/dist/types/utils/eventHandlers.d.ts +2 -2
  134. package/package.json +22 -20
  135. package/src/builder/FileField.tsx +56 -30
  136. package/src/builder/FormField.tsx +52 -32
  137. package/src/builder/SchemaEditor.tsx +79 -0
  138. package/src/builder/components/AddSchemaFields.tsx +75 -0
  139. package/src/builder/components/SchemaArrayForm.tsx +155 -0
  140. package/src/builder/components/SchemaField.tsx +435 -0
  141. package/src/form/Controller.tsx +48 -3
  142. package/src/form/Form.tsx +84 -13
  143. package/src/hooks/useFileUploader.ts +16 -0
  144. package/src/hooks/useFormBuilder.tsx +70 -19
  145. package/src/i18n/ar.json +5 -4
  146. package/src/i18n/en.json +5 -5
  147. package/src/index.ts +12 -3
  148. package/src/input/ArrayInput.tsx +195 -66
  149. package/src/input/AvatarInput.tsx +66 -18
  150. package/src/input/Checkbox.tsx +22 -9
  151. package/src/input/ColorInput.tsx +128 -72
  152. package/src/input/ColorPicker.tsx +54 -72
  153. package/src/input/DateInput.tsx +260 -0
  154. package/src/input/Field.tsx +15 -11
  155. package/src/input/File.tsx +18 -16
  156. package/src/input/Hidden.tsx +17 -2
  157. package/src/input/Input.tsx +27 -13
  158. package/src/input/Number.tsx +22 -13
  159. package/src/input/Password.tsx +28 -18
  160. package/src/input/Radio.tsx +15 -6
  161. package/src/input/RadioGroup.tsx +12 -3
  162. package/src/input/{Range.tsx → RangeSlider.tsx} +17 -9
  163. package/src/input/Search.tsx +36 -25
  164. package/src/input/Slider.tsx +20 -11
  165. package/src/input/Switch.tsx +17 -6
  166. package/src/input/Textarea.tsx +34 -9
  167. package/src/internal/ErrorIcon.tsx +3 -3
  168. package/src/internal/FieldWrapper.tsx +8 -4
  169. package/src/internal/InputWrapper.tsx +56 -30
  170. package/src/select/AutoComplete.tsx +16 -6
  171. package/src/select/DualList.tsx +40 -22
  172. package/src/select/List.tsx +52 -8
  173. package/src/select/Options.tsx +50 -46
  174. package/src/select/Select.tsx +29 -10
  175. package/src/select/useSelect.tsx +26 -9
  176. package/src/types/index.ts +42 -10
  177. package/src/types/schema.ts +96 -41
  178. package/src/utils/eventHandlers.ts +14 -4
  179. package/dist/cjs/input/ArrayInput.module.css +0 -1
  180. package/dist/cjs/input/ColorPicker.module.css +0 -1
  181. package/dist/cjs/input/File.module.css +0 -1
  182. package/dist/cjs/internal/Field.module.css +0 -1
  183. package/dist/esm/input/ArrayInput.module.css +0 -1
  184. package/dist/esm/input/ColorPicker.module.css +0 -1
  185. package/dist/esm/input/File.module.css +0 -1
  186. package/dist/esm/internal/Field.module.css +0 -1
  187. package/dist/types/input/Range.d.ts +0 -59
@@ -22,7 +22,9 @@
22
22
  */
23
23
 
24
24
  import { Avatar, Icon, ProgressBar } from "@react-fabric/core";
25
+ import { type AvatarProps } from "@react-fabric/core/dist/types/components/avatar/Avatar";
25
26
  import { type RefProp } from "@react-fabric/core/dist/types/types";
27
+ import classNames from "classnames";
26
28
  import { useEffect, useState } from "react";
27
29
  import { useFileUploader, type UploadHandler } from "../hooks/useFileUploader";
28
30
  import { HiddenInput } from "./Hidden";
@@ -37,14 +39,25 @@ export interface Props extends RefProp {
37
39
  */
38
40
  value?: string;
39
41
  /**
40
- * avatar stream src
42
+ * default avatar image
41
43
  */
42
- src?: string;
44
+ defaultValue?: string;
45
+ /**
46
+ * avatar name
47
+ */
48
+ avatarName?: string;
49
+ rounded?: boolean;
50
+ variant?: AvatarProps["variant"];
51
+ fallbackIcon?: AvatarProps["fallbackIcon"];
52
+ avatarClassName?: AvatarProps["className"];
43
53
  size?: string | number;
44
54
  /**
45
55
  * upload handler to store file in temp storage
46
56
  */
47
57
  uploadHandler: UploadHandler;
58
+ /**
59
+ * file stream path
60
+ */
48
61
  fileUrl?: (path: string) => string;
49
62
  /**
50
63
  * change handler
@@ -56,16 +69,30 @@ export interface Props extends RefProp {
56
69
  onChange?: (path: string) => void;
57
70
  }
58
71
 
72
+ /**
73
+ * Avatar input component for uploading and displaying user avatars.
74
+ * It allows users to select an image file, upload it, and display the avatar.
75
+ * It also provides a way to remove the uploaded file and shows upload progress.
76
+ *
77
+ * @param {Props} props - The properties for the AvatarInput component.
78
+ * @returns {JSX.Element} - The rendered AvatarInput component.
79
+ */
59
80
  export const AvatarInput = ({
60
- src,
61
81
  size = "6rem",
82
+ avatarName,
83
+ avatarClassName,
84
+ value,
85
+ rounded,
86
+ defaultValue,
87
+ fallbackIcon,
88
+ variant,
62
89
  fileUrl,
63
90
  uploadHandler,
64
91
  ...rest
65
92
  }: Props) => {
66
93
  const { files, list, upload, remove } = useFileUploader(
67
94
  async (data, config) => await uploadHandler?.(data, config),
68
- rest.value,
95
+ value,
69
96
  false,
70
97
  );
71
98
 
@@ -87,19 +114,36 @@ export const AvatarInput = ({
87
114
  })
88
115
  );
89
116
  } else {
90
- setBase64("");
117
+ setBase64(fileUrl?.(defaultValue ?? "") ?? defaultValue ?? "");
91
118
  }
92
- }, [files, list]);
119
+ }, [files, list, defaultValue]);
93
120
 
94
121
  useEffect(() => {
95
- src && setBase64(fileUrl?.(src) ?? src);
96
- }, [src]);
122
+ !value &&
123
+ defaultValue &&
124
+ setBase64(fileUrl?.(defaultValue) ?? defaultValue);
125
+ value && setBase64(fileUrl?.(value) ?? value);
126
+ }, [value, defaultValue]);
97
127
 
98
128
  return (
99
- <div className="flex flex-nowrap items-end">
100
- <div className="inline-block leading-none rounded-full relative outline overflow-hidden">
101
- <Avatar name="temp" size={size} avatar={base64} />
102
- <div className="absolute inset-x-0 bottom-0 cursor-pointer bg-opacity-20 hover:bg-opacity-50 bg-black text-white text-xs text-center py-1">
129
+ <div className="flex flex-nowrap items-end overflow-hidden">
130
+ <div
131
+ className={classNames(
132
+ "inline-block leading-none relative outline overflow-hidden",
133
+ rounded && "rounded-full",
134
+ !rounded && "rounded",
135
+ )}
136
+ >
137
+ <Avatar
138
+ name={avatarName ?? "temp"}
139
+ size={size}
140
+ avatar={base64}
141
+ rounded={rounded}
142
+ variant={variant}
143
+ fallbackIcon={fallbackIcon}
144
+ className={avatarClassName}
145
+ />
146
+ <div className="absolute inset-x-0 bottom-0 cursor-pointer opacity-50 bg-black/50 hover:opacity-90 text-white text-xs text-center py-1">
103
147
  <span>Edit</span>
104
148
  <input
105
149
  type="file"
@@ -109,11 +153,14 @@ export const AvatarInput = ({
109
153
  />
110
154
  </div>
111
155
  </div>
112
- <HiddenInput hiddenValue={list?.[0]?.path} {...rest} />
156
+ <HiddenInput
157
+ hiddenValue={list?.[0]?.path}
158
+ defaultValue={defaultValue}
159
+ {...rest}
160
+ />
113
161
  {files.map((file, idx) => (
114
- <div key={idx} className="px-2 py-1 flex-1">
162
+ <div key={idx} className="px-2 py-1 flex-1 overflow-hidden">
115
163
  <div className="flex flex-nowrap gap-1 items-center">
116
- <div className="flex-initial truncate pe-2">{file.filename}</div>
117
164
  {file.error && (
118
165
  <div className="flex-1 truncate px-2">
119
166
  <span className="text-danger-600">{file.error}</span>
@@ -121,20 +168,21 @@ export const AvatarInput = ({
121
168
  )}
122
169
  {file.progress && (
123
170
  <Icon
124
- icon="mdi mdi-stop-circle"
171
+ icon="icon-[mdi--stop]"
125
172
  bg="danger"
126
173
  color="white"
127
174
  size="sm"
128
- className="p-[2px]"
175
+ className="p-0.5 flex-content"
129
176
  aria-label="abort"
130
177
  onClick={() => file.abort?.()}
131
178
  />
132
179
  )}
133
180
  {!file.progress && (
134
181
  <Icon
135
- icon="mdi mdi-trash-can"
182
+ icon="icon-[mdi--trash-can-outline]"
136
183
  color="danger"
137
184
  size="sm"
185
+ className="flex-content"
138
186
  aria-label="remove"
139
187
  onClick={() => remove(file.key)}
140
188
  />
@@ -21,7 +21,7 @@
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 { CoreIcons, Icon } from "@react-fabric/core";
24
+ import { Icon } from "@react-fabric/core";
25
25
  import { type RefProp } from "@react-fabric/core/dist/types/types";
26
26
  import classNames from "classnames";
27
27
  import {
@@ -34,7 +34,7 @@ import {
34
34
  } from "react";
35
35
  import { ErrorIcon } from "../internal/ErrorIcon";
36
36
 
37
- export interface CheckboxProps extends RefProp<HTMLInputElement> {
37
+ interface CheckboxProps extends RefProp<HTMLInputElement> {
38
38
  /**
39
39
  * checkbox label
40
40
  */
@@ -50,8 +50,7 @@ export interface CheckboxProps extends RefProp<HTMLInputElement> {
50
50
  /**
51
51
  * on checked change
52
52
  */
53
- onChange?: (checked: boolean | string | number) => void;
54
-
53
+ onChange?: (checked: boolean) => void;
55
54
  /**
56
55
  * disabled input
57
56
  */
@@ -82,6 +81,15 @@ export interface CheckboxProps extends RefProp<HTMLInputElement> {
82
81
  name?: string;
83
82
  }
84
83
 
84
+ /**
85
+ * Checkbox component for rendering a checkbox input with label and error handling.
86
+ * It supports custom icons for checked and unchecked states, and handles disabled and invalid states.
87
+ * The component uses a deferred value to manage the checked state, ensuring smooth updates.
88
+ * It also provides an error icon to indicate validation errors.
89
+ *
90
+ * @param {CheckboxProps} props - The properties for the Checkbox component.
91
+ * @returns {JSX.Element} The rendered Checkbox component.
92
+ */
85
93
  export const Checkbox = ({
86
94
  ref,
87
95
  label,
@@ -94,6 +102,8 @@ export const Checkbox = ({
94
102
  width,
95
103
  icon,
96
104
  iconChecked,
105
+ // @ts-expect-error ignore
106
+ required,
97
107
  onChange,
98
108
  ...rest
99
109
  }: CheckboxProps) => {
@@ -101,10 +111,13 @@ export const Checkbox = ({
101
111
  const deferred = useDeferredValue(!!checked);
102
112
 
103
113
  const iconOn = useMemo(
104
- () => iconChecked ?? icon ?? CoreIcons.checkboxOn,
114
+ () => iconChecked ?? icon ?? "icon-[mdi--checkbox-marked]",
105
115
  [icon, iconChecked],
106
116
  );
107
- const iconOff = useMemo(() => icon ?? CoreIcons.checkboxOff, [icon]);
117
+ const iconOff = useMemo(
118
+ () => icon ?? "icon-[mdi--checkbox-blank-outline]",
119
+ [icon],
120
+ );
108
121
 
109
122
  useEffect(() => {
110
123
  setActualValue(deferred);
@@ -125,7 +138,7 @@ export const Checkbox = ({
125
138
  disabled
126
139
  ? "opacity-50 cursor-not-allowed"
127
140
  : " opacity-85 hover:opacity-100 cursor-pointer",
128
- "inline-flex gap-1 py-1 items-center relative rounded ring-offset-2 has-[:focus-visible]:ring-1",
141
+ "inline-flex gap-1 py-1 items-center relative rounded ring-offset-2 has-focus-visible:ring-1",
129
142
  )}
130
143
  style={{ width }}
131
144
  >
@@ -150,7 +163,7 @@ export const Checkbox = ({
150
163
  <Icon
151
164
  className={classNames(
152
165
  "p-0.5",
153
- actualValue && icon && !iconChecked && "bg-primary-500 text-white",
166
+ actualValue && icon && !iconChecked && "bg-primary-500 text-white!",
154
167
  )}
155
168
  size="1.25em"
156
169
  color={invalid ? "danger" : "primary"}
@@ -158,7 +171,7 @@ export const Checkbox = ({
158
171
  />
159
172
  <ErrorIcon invalid={invalid} error={error} />
160
173
  {label && (
161
- <span className="flex-initial leading-none truncate">{label}</span>
174
+ <span className="flex-initial leading-tight truncate">{label}</span>
162
175
  )}
163
176
  </label>
164
177
  );
@@ -21,39 +21,67 @@
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 { CoreIcons, Dropdown, Icon } from "@react-fabric/core";
24
+ import {
25
+ arrow,
26
+ autoUpdate,
27
+ flip,
28
+ FloatingArrow,
29
+ offset,
30
+ shift,
31
+ useClick,
32
+ useDismiss,
33
+ useFloating,
34
+ useInteractions,
35
+ } from "@floating-ui/react";
36
+ import { Icon } from "@react-fabric/core";
25
37
  import { type RefProp } from "@react-fabric/core/dist/types/types";
26
- import { isString } from "@react-fabric/utilities";
27
- import classNames from "classnames";
28
38
  import {
29
39
  startTransition,
30
40
  useCallback,
31
41
  useDeferredValue,
32
42
  useEffect,
43
+ useRef,
33
44
  useState,
34
45
  } from "react";
46
+ import { InputWrapper } from "../internal/InputWrapper";
35
47
  import { type InputProps } from "../types";
36
48
  import { ColorPicker, type ColorProps } from "./ColorPicker";
37
49
 
38
- export interface ColorInputProps extends InputProps, ColorProps, RefProp {
50
+ export interface ColorInputProps
51
+ extends InputProps,
52
+ Omit<ColorProps, "onChange">,
53
+ RefProp {
39
54
  /**
40
55
  * show eydrop picker
41
56
  */
42
57
  showPicker?: boolean;
43
58
  }
44
59
 
60
+ /**
61
+ * Color input component that allows users to select a color
62
+ * and optionally use an eye dropper to pick a color from the screen.
63
+ * It uses the Floating UI library for positioning the color picker.
64
+ * It also provides a button to clear the selected color.
65
+ *
66
+ * This component is a wrapper around the `ColorPicker` component
67
+ * and provides additional functionality such as showing an eye dropper
68
+ * and handling the color selection state.
69
+ *
70
+ * @param {ColorInputProps} props - The properties for the ColorInput component.
71
+ * @returns {JSX.Element} - The rendered ColorInput component.
72
+ */
45
73
  export const ColorInput = ({
46
74
  ref,
47
75
  required,
48
76
  disabled,
49
- allowClear,
77
+ readOnly,
50
78
  invalid,
51
79
  error,
52
80
  value,
53
81
  showPicker,
54
82
  defaultColor = "",
55
- decorateEnd,
56
- decorateStart,
83
+ onBlur,
84
+ onFocus,
57
85
  onChange,
58
86
  // @ts-expect-error ignore
59
87
  noOutline,
@@ -68,63 +96,110 @@ export const ColorInput = ({
68
96
 
69
97
  const handleChange = useCallback(
70
98
  (e?: string) => {
71
- setActualValue(e ?? defaultColor);
72
- onChange != null && startTransition(() => onChange(e));
99
+ const value = e ?? defaultColor;
100
+ setActualValue(value);
101
+ onChange != null && startTransition(() => onChange(value ?? null));
73
102
  },
74
- [onChange],
103
+ [onChange, defaultColor],
75
104
  );
76
105
 
106
+ const arrowRef = useRef(null);
107
+ const [isOpen, setIsOpen] = useState(false);
108
+ const { refs, floatingStyles, context } = useFloating({
109
+ open: isOpen,
110
+ onOpenChange: (open, _, reason) => {
111
+ if (reason === "reference-press") return;
112
+ setIsOpen(open);
113
+ },
114
+ strategy: "fixed",
115
+ placement: "bottom",
116
+ whileElementsMounted: autoUpdate,
117
+ middleware: [
118
+ shift({ padding: 8 }),
119
+ flip(),
120
+ offset(9),
121
+ arrow({
122
+ element: arrowRef,
123
+ }),
124
+ ],
125
+ });
126
+ const click = useClick(context, {
127
+ enabled: !disabled && !readOnly,
128
+ });
129
+ const dismiss = useDismiss(context, {
130
+ referencePress: true,
131
+ });
132
+
133
+ const { getReferenceProps, getFloatingProps } = useInteractions([
134
+ dismiss,
135
+ click,
136
+ ]);
137
+
77
138
  return (
78
- <div
79
- className={classNames(
80
- "flex-content min-h-[1.5em] min-w-[3.5em] self-stretch flex items-center outline-tint-200 rounded",
81
- !disabled && "bg-base",
82
- !noOutline && "bg-[var(--fabric-input)] outline overflow-hidden",
83
- )}
139
+ <InputWrapper
140
+ showClear={actualValue !== defaultColor && !disabled && !readOnly}
141
+ onClear={handleChange}
142
+ invalid={invalid}
143
+ readOnly={readOnly}
144
+ disabled={disabled}
145
+ required={required}
146
+ error={error}
147
+ {...rest}
84
148
  >
85
- {decorateStart && (
149
+ <button
150
+ data-ref="button"
151
+ type="button"
152
+ className="flex-1 flex items-center"
153
+ ref={refs.setReference}
154
+ {...getReferenceProps({
155
+ onBlur,
156
+ onFocus,
157
+ })}
158
+ data-inner-clickable="true"
159
+ data-dropdown-open={isOpen ? true : undefined}
160
+ >
86
161
  <div
87
- className={classNames(
88
- "text-muted leading-none flex-content",
89
- isString(decorateStart) && "px-2",
162
+ role="none"
163
+ className="h-[1.5em] min-w-[1.5em] flex-1 flex items-center justify-center self-stretch cursor-pointer m-[0.25em] rounded outline outline-tint-100"
164
+ style={{
165
+ backgroundColor: actualValue,
166
+ opacity: disabled ? 0.65 : 1,
167
+ pointerEvents: disabled ? "none" : undefined,
168
+ }}
169
+ >
170
+ {!actualValue && (
171
+ <Icon size="md" className="text-muted" icon="icon-[mdi--palette]" />
90
172
  )}
173
+ </div>
174
+ </button>
175
+ {isOpen && (
176
+ <div
177
+ ref={refs.setFloating}
178
+ style={{
179
+ zIndex: "var(--z-popover)",
180
+ ...floatingStyles,
181
+ }}
182
+ data-ref="dropdownBody"
183
+ {...getFloatingProps()}
91
184
  >
92
- {decorateStart}
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
+ />
93
197
  </div>
94
198
  )}
95
- <Dropdown showArrow disabled={disabled}>
96
- <button data-ref="button" className="flex-1 flex items-center">
97
- <div
98
- role="none"
99
- className="h-[1.5em] min-w-[1.5em] flex-1 flex items-center justify-center self-stretch cursor-pointer m-[0.25em] rounded outline outline-tint-100"
100
- style={{
101
- backgroundColor: actualValue,
102
- opacity: disabled ? 0.65 : 1,
103
- pointerEvents: disabled ? "none" : undefined,
104
- }}
105
- onClick={(e) =>
106
- (e.currentTarget.nextElementSibling as HTMLElement)?.focus()
107
- }
108
- >
109
- {!actualValue && (
110
- <Icon
111
- size="md"
112
- className="text-muted"
113
- icon={CoreIcons.colorSwatch}
114
- />
115
- )}
116
- </div>
117
- </button>
118
- <ColorPicker
119
- {...rest}
120
- onChange={handleChange}
121
- defaultColor={defaultColor}
122
- value={actualValue}
123
- />
124
- </Dropdown>
125
199
  {!disabled && showPicker && (
126
200
  <Icon
127
- icon={CoreIcons.colorPicker}
201
+ className="flex-content p-1"
202
+ icon="icon-[mdi--eyedropper]"
128
203
  onClick={() =>
129
204
  (window as AnyObject).EyeDropper &&
130
205
  new (window as AnyObject).EyeDropper()
@@ -133,25 +208,6 @@ export const ColorInput = ({
133
208
  }
134
209
  />
135
210
  )}
136
- {decorateEnd && (
137
- <div
138
- className={classNames(
139
- "text-muted leading-none flex-content",
140
- isString(decorateEnd) && "px-2",
141
- )}
142
- >
143
- {decorateEnd}
144
- </div>
145
- )}
146
- {!disabled && allowClear && actualValue !== defaultColor && (
147
- <Icon
148
- icon={CoreIcons.close}
149
- onClick={() => handleChange()}
150
- className={classNames(
151
- "text-tint-500 hover:text-tint-700 p-[0.25em] flex-content",
152
- )}
153
- />
154
- )}
155
- </div>
211
+ </InputWrapper>
156
212
  );
157
213
  };
@@ -32,7 +32,6 @@ import {
32
32
  type FC,
33
33
  } from "react";
34
34
  import { SketchPicker } from "react-color";
35
- import classes from "./ColorPicker.module.css";
36
35
 
37
36
  export interface ColorProps {
38
37
  /**
@@ -50,7 +49,7 @@ export interface ColorProps {
50
49
  /**
51
50
  * color value
52
51
  */
53
- value?: string;
52
+ value?: string | null;
54
53
  /**
55
54
  * change handler
56
55
  */
@@ -103,6 +102,16 @@ const DEFAULT_SWATCHES = [
103
102
  "#f1f3f4",
104
103
  ];
105
104
 
105
+ /**
106
+ * ColorPicker component allows users to select a color using a color picker UI.
107
+ * It supports swatches, alpha transparency, and custom styles.
108
+ * The component uses the `react-color` library for the color picker functionality.
109
+ *
110
+ * @param {ColorProps} props - The properties for the ColorPicker component.
111
+ * @returns {JSX.Element} The rendered ColorPicker component.
112
+ *
113
+ * @see {@link https://react-color.github.io/react-color/} for more details on the color picker library.
114
+ */
106
115
  // eslint-disable-next-line react/display-name
107
116
  export const ColorPicker: FC<ColorProps> = memo(
108
117
  ({
@@ -128,83 +137,56 @@ export const ColorPicker: FC<ColorProps> = memo(
128
137
  );
129
138
 
130
139
  return (
131
- <SketchPicker
132
- width="16rem"
133
- className={classes.colorPicker}
134
- styles={
135
- {
136
- default: {
137
- hue: {
138
- borderRadius: "99px",
139
- boxShadow: "0 0 1px 1px var(--color-shadow)",
140
- },
141
- alpha: {
142
- height: "1rem",
143
- borderRadius: "99px",
144
- backgroundColor: "var(--fabric-bg)",
145
- boxShadow: "0 0 1px 1px var(--color-shadow)",
146
- },
147
- color: {
148
- boxShadow: "0 0 1px 1px var(--color-shadow)",
149
- },
150
- saturation: {
151
- boxShadow: "0 0 1px 1px var(--color-shadow)",
152
- },
153
- activeColor: {
154
- boxShadow: "0 0 1px 1px var(--color-shadow)",
155
- },
156
- picker: {
157
- backgroundColor: "var(--fabric-bg)",
158
- boxShadow:
159
- "0 0 0 1px var(--color-shadow), 0 0 8px var(--fabric-shadow)",
160
- },
161
- Alpha: {
140
+ <div
141
+ role="none"
142
+ // prevent default on mouse down to avoid selection issues
143
+ onMouseDown={(e) => e.preventDefault()}
144
+ >
145
+ <SketchPicker
146
+ width="16rem"
147
+ className={"fabric-colorPicker"}
148
+ styles={
149
+ {
150
+ default: {
151
+ hue: {
152
+ boxShadow: "0 0 1px 1px var(--color-shadow)",
153
+ },
162
154
  alpha: {
163
- paddingRight: "calc(1rem - 2px)",
155
+ backgroundColor: "var(--fabric-bg)",
156
+ boxShadow: "0 0 1px 1px var(--color-shadow)",
164
157
  },
165
- slider: {
166
- width: "calc(1rem - 2px)",
167
- borderRadius: "99px",
168
- height: "calc(1rem - 2px)",
169
- transform: "none",
158
+ color: {
159
+ boxShadow: "0 0 1px 1px var(--color-shadow)",
170
160
  },
171
- },
172
- Hue: {
173
- hue: {
174
- paddingRight: "calc(1rem - 2px)",
161
+ saturation: {
162
+ boxShadow: "0 0 1px 1px var(--color-shadow)",
175
163
  },
176
- slider: {
177
- width: "calc(1rem - 2px)",
178
- borderRadius: "99px",
179
- height: "calc(1rem - 2px)",
180
- transform: "none",
164
+ activeColor: {
165
+ boxShadow: "0 0 1px 1px var(--color-shadow)",
181
166
  },
182
- },
183
- Saturation: {
184
- circle: {
185
- width: "0.5rem",
186
- borderRadius: "99px",
187
- height: "0.5rem",
188
- transform: "translate(-0.25rem, -0.25rem)",
167
+ picker: {
168
+ backgroundColor: "var(--fabric-bg)",
169
+ boxShadow:
170
+ "0 0 0 1px var(--color-shadow), 0 0 8px var(--fabric-shadow)",
171
+ },
172
+ checkboard: {
173
+ white: "transparent",
174
+ black: "red",
189
175
  },
190
176
  },
191
- checkboard: {
192
- white: "transparent",
193
- black: "red",
194
- },
195
- },
196
- } as AnyObject
197
- }
198
- color={actualValue}
199
- presetColors={swatches}
200
- disableAlpha={hideAlpha}
201
- onChange={({ rgb: { a = 1, ...rgb } }) =>
202
- setActualValue(Color(rgb).alpha(a)[a < 1 ? "hexa" : "hex"]())
203
- }
204
- onChangeComplete={({ rgb: { a = 1, ...rgb } }) =>
205
- handleChange(Color(rgb).alpha(a)[a < 1 ? "hexa" : "hex"]())
206
- }
207
- />
177
+ } as AnyObject
178
+ }
179
+ color={actualValue}
180
+ presetColors={swatches}
181
+ 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
+ }
188
+ />
189
+ </div>
208
190
  );
209
191
  },
210
192
  );