@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.
- package/{src/input/ArrayInput.module.css → css/_arrayInput.css} +8 -8
- package/css/_colorPicker.css +65 -0
- package/{src/internal/Field.module.css → css/_field.css} +66 -30
- package/{src/input/File.module.css → css/_file.css} +21 -6
- package/css/styles.css +27 -0
- package/dist/cjs/builder/FileField.js +40 -28
- package/dist/cjs/builder/FormField.js +50 -34
- package/dist/cjs/builder/SchemaEditor.js +72 -0
- package/dist/cjs/builder/components/AddSchemaFields.js +63 -0
- package/dist/cjs/builder/components/SchemaArrayForm.js +145 -0
- package/dist/cjs/builder/components/SchemaField.js +414 -0
- package/dist/cjs/form/Controller.js +46 -1
- package/dist/cjs/form/Form.js +71 -13
- package/dist/cjs/hooks/useFileUploader.js +16 -0
- package/dist/cjs/hooks/useFormBuilder.js +67 -18
- package/dist/cjs/i18n/ar.json.js +5 -4
- package/dist/cjs/i18n/en.json.js +5 -5
- package/dist/cjs/index.js +12 -2
- package/dist/cjs/input/ArrayInput.js +128 -38
- package/dist/cjs/input/AvatarInput.js +43 -23
- package/dist/cjs/input/Checkbox.js +18 -8
- package/dist/cjs/input/ColorInput.js +114 -59
- package/dist/cjs/input/ColorPicker.js +60 -76
- package/dist/cjs/input/DateInput.js +197 -0
- package/dist/cjs/input/Field.js +10 -4
- package/dist/cjs/input/File.js +15 -8
- package/dist/cjs/input/Hidden.js +13 -2
- package/dist/cjs/input/Input.js +24 -10
- package/dist/cjs/input/Number.js +22 -10
- package/dist/cjs/input/Password.js +23 -13
- package/dist/cjs/input/Radio.js +14 -5
- package/dist/cjs/input/RadioGroup.js +10 -1
- package/dist/cjs/input/{Range.js → RangeSlider.js} +16 -8
- package/dist/cjs/input/Search.js +32 -22
- package/dist/cjs/input/Slider.js +12 -6
- package/dist/cjs/input/Switch.js +20 -9
- package/dist/cjs/input/Textarea.js +29 -8
- package/dist/cjs/internal/ErrorIcon.js +2 -2
- package/dist/cjs/internal/FieldWrapper.js +11 -4
- package/dist/cjs/internal/InputWrapper.js +29 -22
- package/dist/cjs/select/AutoComplete.js +22 -13
- package/dist/cjs/select/DualList.js +34 -12
- package/dist/cjs/select/List.js +49 -21
- package/dist/cjs/select/Options.js +49 -42
- package/dist/cjs/select/Select.js +21 -10
- package/dist/cjs/select/useSelect.js +22 -11
- package/dist/cjs/types/schema.js +43 -0
- package/dist/cjs/utils/eventHandlers.js +15 -5
- package/dist/esm/builder/FileField.js +42 -30
- package/dist/esm/builder/FormField.js +50 -34
- package/dist/esm/builder/SchemaEditor.js +72 -0
- package/dist/esm/builder/components/AddSchemaFields.js +63 -0
- package/dist/esm/builder/components/SchemaArrayForm.js +145 -0
- package/dist/esm/builder/components/SchemaField.js +414 -0
- package/dist/esm/form/Controller.js +46 -1
- package/dist/esm/form/Form.js +73 -16
- package/dist/esm/hooks/useFileUploader.js +16 -0
- package/dist/esm/hooks/useFormBuilder.js +65 -16
- package/dist/esm/i18n/ar.json.js +5 -4
- package/dist/esm/i18n/en.json.js +5 -5
- package/dist/esm/index.js +6 -2
- package/dist/esm/input/ArrayInput.js +128 -38
- package/dist/esm/input/AvatarInput.js +43 -23
- package/dist/esm/input/Checkbox.js +19 -9
- package/dist/esm/input/ColorInput.js +116 -61
- package/dist/esm/input/ColorPicker.js +60 -76
- package/dist/esm/input/DateInput.js +197 -0
- package/dist/esm/input/Field.js +11 -5
- package/dist/esm/input/File.js +18 -11
- package/dist/esm/input/Hidden.js +13 -2
- package/dist/esm/input/Input.js +26 -12
- package/dist/esm/input/Number.js +24 -12
- package/dist/esm/input/Password.js +26 -16
- package/dist/esm/input/Radio.js +15 -6
- package/dist/esm/input/RadioGroup.js +11 -2
- package/dist/esm/input/{Range.js → RangeSlider.js} +16 -8
- package/dist/esm/input/Search.js +35 -25
- package/dist/esm/input/Slider.js +12 -6
- package/dist/esm/input/Switch.js +20 -9
- package/dist/esm/input/Textarea.js +30 -9
- package/dist/esm/internal/ErrorIcon.js +3 -3
- package/dist/esm/internal/FieldWrapper.js +11 -4
- package/dist/esm/internal/InputWrapper.js +30 -23
- package/dist/esm/select/AutoComplete.js +23 -14
- package/dist/esm/select/DualList.js +35 -13
- package/dist/esm/select/List.js +50 -22
- package/dist/esm/select/Options.js +49 -42
- package/dist/esm/select/Select.js +22 -11
- package/dist/esm/select/useSelect.js +23 -12
- package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
- package/dist/esm/utils/eventHandlers.js +15 -5
- package/dist/types/builder/FileField.d.ts +4 -2
- package/dist/types/builder/FormField.d.ts +3 -1
- package/dist/types/builder/SchemaEditor.d.ts +9 -0
- package/dist/types/builder/components/AddSchemaFields.d.ts +2 -0
- package/dist/types/builder/components/SchemaArrayForm.d.ts +2 -0
- package/dist/types/builder/components/SchemaField.d.ts +10 -0
- package/dist/types/form/Controller.d.ts +47 -2
- package/dist/types/form/Form.d.ts +49 -6
- package/dist/types/hooks/useFileUploader.d.ts +16 -0
- package/dist/types/hooks/useFormBuilder.d.ts +38 -1
- package/dist/types/index.d.ts +6 -3
- package/dist/types/input/ArrayInput.d.ts +69 -12
- package/dist/types/input/AvatarInput.d.ts +23 -3
- package/dist/types/input/Checkbox.d.ts +13 -4
- package/dist/types/input/ColorInput.d.ts +15 -2
- package/dist/types/input/ColorPicker.d.ts +11 -1
- package/dist/types/input/DateInput.d.ts +28 -0
- package/dist/types/input/Field.d.ts +5 -1
- package/dist/types/input/File.d.ts +9 -0
- package/dist/types/input/Hidden.d.ts +13 -1
- package/dist/types/input/Input.d.ts +9 -1
- package/dist/types/input/Number.d.ts +9 -1
- package/dist/types/input/Password.d.ts +7 -1
- package/dist/types/input/Radio.d.ts +9 -1
- package/dist/types/input/RadioGroup.d.ts +8 -1
- package/dist/types/input/RangeSlider.d.ts +67 -0
- package/dist/types/input/Search.d.ts +14 -1
- package/dist/types/input/Slider.d.ts +8 -2
- package/dist/types/input/Switch.d.ts +10 -2
- package/dist/types/input/Textarea.d.ts +15 -1
- package/dist/types/internal/FieldWrapper.d.ts +1 -1
- package/dist/types/internal/InputWrapper.d.ts +1 -1
- package/dist/types/ranger/index.d.ts +2 -2
- package/dist/types/select/AutoComplete.d.ts +8 -1
- package/dist/types/select/DualList.d.ts +21 -7
- package/dist/types/select/List.d.ts +9 -1
- package/dist/types/select/Options.d.ts +4 -3
- package/dist/types/select/Select.d.ts +9 -1
- package/dist/types/select/useSelect.d.ts +10 -6
- package/dist/types/types/index.d.ts +38 -10
- package/dist/types/types/schema.d.ts +78 -33
- package/dist/types/utils/eventHandlers.d.ts +2 -2
- package/package.json +22 -20
- package/src/builder/FileField.tsx +56 -30
- package/src/builder/FormField.tsx +52 -32
- package/src/builder/SchemaEditor.tsx +79 -0
- package/src/builder/components/AddSchemaFields.tsx +75 -0
- package/src/builder/components/SchemaArrayForm.tsx +155 -0
- package/src/builder/components/SchemaField.tsx +435 -0
- package/src/form/Controller.tsx +48 -3
- package/src/form/Form.tsx +84 -13
- package/src/hooks/useFileUploader.ts +16 -0
- package/src/hooks/useFormBuilder.tsx +70 -19
- package/src/i18n/ar.json +5 -4
- package/src/i18n/en.json +5 -5
- package/src/index.ts +12 -3
- package/src/input/ArrayInput.tsx +195 -66
- package/src/input/AvatarInput.tsx +66 -18
- package/src/input/Checkbox.tsx +22 -9
- package/src/input/ColorInput.tsx +128 -72
- package/src/input/ColorPicker.tsx +54 -72
- package/src/input/DateInput.tsx +260 -0
- package/src/input/Field.tsx +15 -11
- package/src/input/File.tsx +18 -16
- package/src/input/Hidden.tsx +17 -2
- package/src/input/Input.tsx +27 -13
- package/src/input/Number.tsx +22 -13
- package/src/input/Password.tsx +28 -18
- package/src/input/Radio.tsx +15 -6
- package/src/input/RadioGroup.tsx +12 -3
- package/src/input/{Range.tsx → RangeSlider.tsx} +17 -9
- package/src/input/Search.tsx +36 -25
- package/src/input/Slider.tsx +20 -11
- package/src/input/Switch.tsx +17 -6
- package/src/input/Textarea.tsx +34 -9
- package/src/internal/ErrorIcon.tsx +3 -3
- package/src/internal/FieldWrapper.tsx +8 -4
- package/src/internal/InputWrapper.tsx +56 -30
- package/src/select/AutoComplete.tsx +16 -6
- package/src/select/DualList.tsx +40 -22
- package/src/select/List.tsx +52 -8
- package/src/select/Options.tsx +50 -46
- package/src/select/Select.tsx +29 -10
- package/src/select/useSelect.tsx +26 -9
- package/src/types/index.ts +42 -10
- package/src/types/schema.ts +96 -41
- package/src/utils/eventHandlers.ts +14 -4
- package/dist/cjs/input/ArrayInput.module.css +0 -1
- package/dist/cjs/input/ColorPicker.module.css +0 -1
- package/dist/cjs/input/File.module.css +0 -1
- package/dist/cjs/internal/Field.module.css +0 -1
- package/dist/esm/input/ArrayInput.module.css +0 -1
- package/dist/esm/input/ColorPicker.module.css +0 -1
- package/dist/esm/input/File.module.css +0 -1
- package/dist/esm/internal/Field.module.css +0 -1
- 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
|
|
42
|
+
* default avatar image
|
|
41
43
|
*/
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
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
|
|
171
|
+
icon="icon-[mdi--stop]"
|
|
125
172
|
bg="danger"
|
|
126
173
|
color="white"
|
|
127
174
|
size="sm"
|
|
128
|
-
className="p-
|
|
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
|
|
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
|
/>
|
package/src/input/Checkbox.tsx
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
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 ??
|
|
114
|
+
() => iconChecked ?? icon ?? "icon-[mdi--checkbox-marked]",
|
|
105
115
|
[icon, iconChecked],
|
|
106
116
|
);
|
|
107
|
-
const iconOff = useMemo(
|
|
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-
|
|
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-
|
|
174
|
+
<span className="flex-initial leading-tight truncate">{label}</span>
|
|
162
175
|
)}
|
|
163
176
|
</label>
|
|
164
177
|
);
|
package/src/input/ColorInput.tsx
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
77
|
+
readOnly,
|
|
50
78
|
invalid,
|
|
51
79
|
error,
|
|
52
80
|
value,
|
|
53
81
|
showPicker,
|
|
54
82
|
defaultColor = "",
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
155
|
+
backgroundColor: "var(--fabric-bg)",
|
|
156
|
+
boxShadow: "0 0 1px 1px var(--color-shadow)",
|
|
164
157
|
},
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
173
|
-
hue: {
|
|
174
|
-
paddingRight: "calc(1rem - 2px)",
|
|
161
|
+
saturation: {
|
|
162
|
+
boxShadow: "0 0 1px 1px var(--color-shadow)",
|
|
175
163
|
},
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
);
|