@react-fabric/form 1.0.0-beta.9 → 1.0.2
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 +95 -0
- package/{src/internal/Field.module.css → css/_field.css} +94 -33
- package/{src/input/File.module.css → css/_file.css} +21 -6
- package/css/styles.css +27 -0
- package/dist/cjs/builder/FileField.js +107 -78
- package/dist/cjs/builder/FormField.js +60 -39
- package/dist/cjs/builder/SchemaEditor.js +72 -0
- package/dist/cjs/builder/components/AddSchemaFields.js +64 -0
- package/dist/cjs/builder/components/SchemaArrayForm.js +146 -0
- package/dist/cjs/builder/components/SchemaField.js +423 -0
- package/dist/cjs/form/Controller.js +66 -19
- package/dist/cjs/form/Form.js +97 -39
- package/dist/cjs/form/useForm.js +32 -0
- package/dist/cjs/hooks/useFileUploader.js +74 -45
- package/dist/cjs/hooks/useFormBuilder.js +85 -31
- package/dist/cjs/i18n/{ar.json.js → ar/form.json.js} +5 -4
- package/dist/cjs/i18n/{en.json.js → en/form.json.js} +5 -5
- package/dist/cjs/i18n/index.js +4 -4
- package/dist/cjs/index.js +41 -2
- package/dist/cjs/input/ArrayInput.js +259 -135
- package/dist/cjs/input/AvatarInput.js +99 -63
- package/dist/cjs/input/Checkbox.js +24 -25
- package/dist/cjs/input/ColorInput.js +138 -81
- package/dist/cjs/input/ColorPicker.js +78 -95
- package/dist/cjs/input/DateInput.js +192 -0
- package/dist/cjs/input/Field.js +21 -20
- package/dist/cjs/input/File.js +85 -80
- package/dist/cjs/input/Hidden.js +27 -13
- package/dist/cjs/input/Input.js +49 -48
- package/dist/cjs/input/Number.js +46 -44
- package/dist/cjs/input/Password.js +51 -49
- package/dist/cjs/input/Radio.js +26 -16
- package/dist/cjs/input/RadioGroup.js +26 -11
- package/dist/cjs/input/{Range.js → RangeSlider.js} +61 -66
- package/dist/cjs/input/Search.js +58 -64
- package/dist/cjs/input/Slider.js +57 -66
- package/dist/cjs/input/Switch.js +29 -27
- package/dist/cjs/input/Textarea.js +65 -53
- package/dist/cjs/internal/ErrorIcon.js +9 -9
- package/dist/cjs/internal/FieldWrapper.js +23 -13
- package/dist/cjs/internal/InputWrapper.js +85 -87
- package/dist/cjs/ranger/index.js +7 -7
- package/dist/cjs/ranger/utils.js +9 -4
- package/dist/cjs/select/AutoComplete.js +93 -76
- package/dist/cjs/select/DualList.js +235 -154
- package/dist/cjs/select/List.js +122 -89
- package/dist/cjs/select/Options.js +58 -54
- package/dist/cjs/select/Select.js +119 -89
- package/dist/cjs/select/helpers.js +5 -5
- package/dist/cjs/select/useSelect.js +83 -58
- package/dist/cjs/types/schema.js +43 -0
- package/dist/cjs/utils/eventHandlers.js +16 -6
- package/dist/cjs/utils/getColor.js +6 -6
- package/dist/cjs/utils/getProps.js +49 -0
- package/dist/esm/builder/FileField.js +109 -80
- package/dist/esm/builder/FormField.js +60 -39
- package/dist/esm/builder/SchemaEditor.js +72 -0
- package/dist/esm/builder/components/AddSchemaFields.js +64 -0
- package/dist/esm/builder/components/SchemaArrayForm.js +146 -0
- package/dist/esm/builder/components/SchemaField.js +423 -0
- package/dist/esm/form/Controller.js +66 -19
- package/dist/esm/form/Form.js +97 -39
- package/dist/esm/form/useForm.js +32 -0
- package/dist/esm/hooks/useFileUploader.js +75 -46
- package/dist/esm/hooks/useFormBuilder.js +84 -30
- package/dist/esm/i18n/{ar.json.js → ar/form.json.js} +5 -4
- package/dist/esm/i18n/{en.json.js → en/form.json.js} +5 -5
- package/dist/esm/i18n/index.js +2 -2
- package/dist/esm/index.js +36 -1
- package/dist/esm/input/ArrayInput.js +260 -136
- package/dist/esm/input/AvatarInput.js +99 -63
- package/dist/esm/input/Checkbox.js +26 -27
- package/dist/esm/input/ColorInput.js +141 -84
- package/dist/esm/input/ColorPicker.js +80 -97
- package/dist/esm/input/DateInput.js +192 -0
- package/dist/esm/input/Field.js +21 -20
- package/dist/esm/input/File.js +89 -84
- package/dist/esm/input/Hidden.js +28 -14
- package/dist/esm/input/Input.js +51 -50
- package/dist/esm/input/Number.js +48 -46
- package/dist/esm/input/Password.js +54 -52
- package/dist/esm/input/Radio.js +27 -17
- package/dist/esm/input/RadioGroup.js +27 -12
- package/dist/esm/input/{Range.js → RangeSlider.js} +62 -67
- package/dist/esm/input/Search.js +61 -67
- package/dist/esm/input/Slider.js +58 -67
- package/dist/esm/input/Switch.js +31 -29
- package/dist/esm/input/Textarea.js +67 -55
- package/dist/esm/internal/ErrorIcon.js +11 -11
- package/dist/esm/internal/FieldWrapper.js +23 -13
- package/dist/esm/internal/InputWrapper.js +88 -90
- package/dist/esm/ranger/index.js +7 -7
- package/dist/esm/ranger/utils.js +9 -4
- package/dist/esm/select/AutoComplete.js +95 -78
- package/dist/esm/select/DualList.js +233 -152
- package/dist/esm/select/List.js +125 -92
- package/dist/esm/select/Options.js +58 -54
- package/dist/esm/select/Select.js +122 -92
- package/dist/esm/select/helpers.js +5 -5
- package/dist/esm/select/useSelect.js +84 -59
- package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
- package/dist/esm/utils/eventHandlers.js +16 -6
- package/dist/esm/utils/getColor.js +6 -6
- package/dist/esm/utils/getProps.js +49 -0
- package/dist/types/builder/FileField.d.ts +7 -4
- package/dist/types/builder/FormField.d.ts +4 -2
- package/dist/types/builder/SchemaEditor.d.ts +6 -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 +12 -0
- package/dist/types/form/Controller.d.ts +45 -3
- package/dist/types/form/Form.d.ts +38 -7
- package/dist/types/form/useForm.d.ts +6 -0
- package/dist/types/hooks/useFileUploader.d.ts +24 -3
- package/dist/types/hooks/useFormBuilder.d.ts +41 -3
- package/dist/types/index.d.ts +32 -2
- package/dist/types/input/ArrayInput.d.ts +68 -13
- package/dist/types/input/AvatarInput.d.ts +23 -6
- package/dist/types/input/Checkbox.d.ts +12 -10
- package/dist/types/input/ColorInput.d.ts +15 -5
- package/dist/types/input/ColorPicker.d.ts +10 -2
- package/dist/types/input/DateInput.d.ts +21 -0
- package/dist/types/input/Field.d.ts +9 -36
- package/dist/types/input/File.d.ts +16 -4
- package/dist/types/input/Hidden.d.ts +7 -6
- package/dist/types/input/Input.d.ts +11 -4
- package/dist/types/input/Number.d.ts +9 -4
- package/dist/types/input/Password.d.ts +9 -4
- package/dist/types/input/Radio.d.ts +9 -4
- package/dist/types/input/RadioGroup.d.ts +14 -6
- package/dist/types/input/{Range.d.ts → RangeSlider.d.ts} +11 -6
- package/dist/types/input/Search.d.ts +14 -4
- package/dist/types/input/Slider.d.ts +9 -6
- package/dist/types/input/Switch.d.ts +13 -8
- package/dist/types/input/Textarea.d.ts +15 -4
- package/dist/types/internal/ErrorIcon.d.ts +4 -1
- package/dist/types/internal/FieldWrapper.d.ts +10 -1
- package/dist/types/internal/InputWrapper.d.ts +19 -1
- package/dist/types/ranger/index.d.ts +7 -3
- package/dist/types/ranger/utils.d.ts +2 -2
- package/dist/types/select/AutoComplete.d.ts +7 -2
- package/dist/types/select/DualList.d.ts +11 -37
- package/dist/types/select/List.d.ts +7 -2
- package/dist/types/select/Options.d.ts +8 -7
- package/dist/types/select/Select.d.ts +7 -2
- package/dist/types/select/helpers.d.ts +2 -2
- package/dist/types/select/useSelect.d.ts +10 -28
- package/dist/types/types/index.d.ts +73 -39
- package/dist/types/types/schema.d.ts +78 -32
- package/dist/types/utils/eventHandlers.d.ts +4 -4
- package/dist/types/utils/getColor.d.ts +4 -4
- package/dist/types/utils/getProps.d.ts +26 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/package.json +25 -25
- package/src/builder/FileField.tsx +73 -56
- package/src/builder/FormField.tsx +66 -58
- package/src/builder/SchemaEditor.tsx +78 -0
- package/src/builder/components/AddSchemaFields.tsx +64 -0
- package/src/builder/components/SchemaArrayForm.tsx +146 -0
- package/src/builder/components/SchemaField.tsx +405 -0
- package/src/form/Controller.tsx +61 -24
- package/src/form/Form.tsx +74 -50
- package/src/form/useForm.ts +53 -0
- package/src/hooks/useFileUploader.ts +115 -84
- package/src/hooks/useFormBuilder.tsx +80 -41
- package/src/i18n/{ar.json → ar/form.json} +5 -4
- package/src/i18n/{en.json → en/form.json} +5 -5
- package/src/i18n/index.ts +2 -2
- package/src/index.ts +34 -20
- package/src/input/ArrayInput.tsx +255 -173
- package/src/input/AvatarInput.tsx +82 -39
- package/src/input/Checkbox.tsx +30 -54
- package/src/input/ColorInput.tsx +146 -105
- package/src/input/ColorPicker.tsx +76 -102
- package/src/input/DateInput.tsx +225 -0
- package/src/input/Field.tsx +23 -65
- package/src/input/File.tsx +76 -98
- package/src/input/Hidden.tsx +36 -24
- package/src/input/Input.tsx +55 -53
- package/src/input/Number.tsx +49 -54
- package/src/input/Password.tsx +61 -66
- package/src/input/Radio.tsx +28 -37
- package/src/input/RadioGroup.tsx +30 -25
- package/src/input/{Range.tsx → RangeSlider.tsx} +81 -109
- package/src/input/Search.tsx +64 -77
- package/src/input/Slider.tsx +80 -109
- package/src/input/Switch.tsx +39 -53
- package/src/input/Textarea.tsx +72 -59
- package/src/internal/ErrorIcon.tsx +22 -21
- package/src/internal/FieldWrapper.tsx +33 -23
- package/src/internal/InputWrapper.tsx +132 -143
- package/src/ranger/index.tsx +37 -117
- package/src/ranger/utils.ts +12 -11
- package/src/select/AutoComplete.tsx +113 -123
- package/src/select/DualList.tsx +218 -204
- package/src/select/List.tsx +130 -97
- package/src/select/Options.tsx +59 -57
- package/src/select/Select.tsx +148 -122
- package/src/select/helpers.ts +13 -8
- package/src/select/useSelect.tsx +110 -127
- package/src/types/index.ts +76 -45
- package/src/types/schema.ts +97 -40
- package/src/utils/eventHandlers.ts +24 -26
- package/src/utils/getColor.ts +9 -8
- package/src/utils/getProps.ts +47 -0
- package/src/utils/index.ts +1 -0
- 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
|
@@ -22,9 +22,12 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { Avatar, Icon, ProgressBar } from "@react-fabric/core";
|
|
25
|
-
import {
|
|
25
|
+
import type { AvatarProps } from "@react-fabric/core/dist/types/components/avatar/Avatar";
|
|
26
|
+
import type { RefProp } from "@react-fabric/core/dist/types/types";
|
|
27
|
+
import { cn } from "@react-fabric/utilities";
|
|
26
28
|
import { useEffect, useState } from "react";
|
|
27
|
-
import {
|
|
29
|
+
import type { UploadHandler } from "../hooks/useFileUploader";
|
|
30
|
+
import { useFileUploader } from "../hooks/useFileUploader";
|
|
28
31
|
import { HiddenInput } from "./Hidden";
|
|
29
32
|
|
|
30
33
|
export interface Props extends RefProp {
|
|
@@ -37,14 +40,25 @@ export interface Props extends RefProp {
|
|
|
37
40
|
*/
|
|
38
41
|
value?: string;
|
|
39
42
|
/**
|
|
40
|
-
* avatar
|
|
43
|
+
* default avatar image
|
|
41
44
|
*/
|
|
42
|
-
|
|
45
|
+
defaultValue?: string;
|
|
46
|
+
/**
|
|
47
|
+
* avatar name
|
|
48
|
+
*/
|
|
49
|
+
avatarName?: string;
|
|
50
|
+
rounded?: boolean;
|
|
51
|
+
variant?: AvatarProps["variant"];
|
|
52
|
+
fallbackIcon?: AvatarProps["fallbackIcon"];
|
|
53
|
+
avatarClassName?: AvatarProps["className"];
|
|
43
54
|
size?: string | number;
|
|
44
55
|
/**
|
|
45
56
|
* upload handler to store file in temp storage
|
|
46
57
|
*/
|
|
47
58
|
uploadHandler: UploadHandler;
|
|
59
|
+
/**
|
|
60
|
+
* file stream path
|
|
61
|
+
*/
|
|
48
62
|
fileUrl?: (path: string) => string;
|
|
49
63
|
/**
|
|
50
64
|
* change handler
|
|
@@ -53,25 +67,40 @@ export interface Props extends RefProp {
|
|
|
53
67
|
* @param base64
|
|
54
68
|
* @returns storage path
|
|
55
69
|
*/
|
|
56
|
-
onChange?: (path: string) => void;
|
|
70
|
+
onChange?: (path: string | null) => void;
|
|
57
71
|
}
|
|
58
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Avatar input component for uploading and displaying user avatars.
|
|
75
|
+
* It allows users to select an image file, upload it, and display the avatar.
|
|
76
|
+
* It also provides a way to remove the uploaded file and shows upload progress.
|
|
77
|
+
*/
|
|
59
78
|
export const AvatarInput = ({
|
|
60
|
-
src,
|
|
61
79
|
size = "6rem",
|
|
80
|
+
avatarName,
|
|
81
|
+
avatarClassName,
|
|
82
|
+
value,
|
|
83
|
+
rounded,
|
|
84
|
+
defaultValue,
|
|
85
|
+
fallbackIcon,
|
|
86
|
+
variant,
|
|
62
87
|
fileUrl,
|
|
63
88
|
uploadHandler,
|
|
89
|
+
onChange,
|
|
64
90
|
...rest
|
|
65
|
-
}: Props) => {
|
|
66
|
-
const { files,
|
|
91
|
+
}: Props): React.ReactElement => {
|
|
92
|
+
const { files, upload, remove } = useFileUploader(
|
|
67
93
|
async (data, config) => await uploadHandler?.(data, config),
|
|
68
|
-
|
|
69
|
-
|
|
94
|
+
value ?? defaultValue,
|
|
95
|
+
{
|
|
96
|
+
multiple: false,
|
|
97
|
+
onChange: (files) => onChange?.(files?.path ?? null),
|
|
98
|
+
},
|
|
70
99
|
);
|
|
71
100
|
|
|
72
101
|
const [base64, setBase64] = useState("");
|
|
73
102
|
useEffect(() => {
|
|
74
|
-
if (
|
|
103
|
+
if (files?.length) {
|
|
75
104
|
const file = files?.[0]?.file;
|
|
76
105
|
void (
|
|
77
106
|
file &&
|
|
@@ -87,19 +116,37 @@ export const AvatarInput = ({
|
|
|
87
116
|
})
|
|
88
117
|
);
|
|
89
118
|
} else {
|
|
90
|
-
|
|
119
|
+
// eslint-disable-next-line @eslint-react/set-state-in-effect
|
|
120
|
+
setBase64(fileUrl?.(defaultValue ?? "") ?? defaultValue ?? "");
|
|
91
121
|
}
|
|
92
|
-
}, [files,
|
|
122
|
+
}, [files, defaultValue, fileUrl]);
|
|
93
123
|
|
|
94
124
|
useEffect(() => {
|
|
95
|
-
|
|
96
|
-
|
|
125
|
+
// eslint-disable-next-line @eslint-react/set-state-in-effect
|
|
126
|
+
if (!value && defaultValue) setBase64(fileUrl?.(defaultValue) ?? defaultValue);
|
|
127
|
+
// eslint-disable-next-line @eslint-react/set-state-in-effect
|
|
128
|
+
if (value) setBase64(fileUrl?.(value) ?? value);
|
|
129
|
+
}, [value, defaultValue, fileUrl]);
|
|
97
130
|
|
|
98
131
|
return (
|
|
99
|
-
<div className="flex flex-nowrap items-end">
|
|
100
|
-
<div
|
|
101
|
-
|
|
102
|
-
|
|
132
|
+
<div className="flex flex-nowrap items-end overflow-hidden p-px">
|
|
133
|
+
<div
|
|
134
|
+
className={cn(
|
|
135
|
+
"inline-block leading-none relative outline overflow-hidden",
|
|
136
|
+
rounded && "rounded-full",
|
|
137
|
+
!rounded && "rounded",
|
|
138
|
+
)}
|
|
139
|
+
>
|
|
140
|
+
<Avatar
|
|
141
|
+
name={avatarName ?? "temp"}
|
|
142
|
+
size={size}
|
|
143
|
+
avatar={base64}
|
|
144
|
+
rounded={rounded}
|
|
145
|
+
variant={variant}
|
|
146
|
+
fallbackIcon={fallbackIcon}
|
|
147
|
+
className={avatarClassName}
|
|
148
|
+
/>
|
|
149
|
+
<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
150
|
<span>Edit</span>
|
|
104
151
|
<input
|
|
105
152
|
type="file"
|
|
@@ -109,40 +156,36 @@ export const AvatarInput = ({
|
|
|
109
156
|
/>
|
|
110
157
|
</div>
|
|
111
158
|
</div>
|
|
112
|
-
<HiddenInput hiddenValue={
|
|
159
|
+
<HiddenInput hiddenValue={files?.[0]?.path} {...rest} />
|
|
113
160
|
{files.map((file, idx) => (
|
|
114
|
-
|
|
161
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
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>
|
|
120
167
|
</div>
|
|
121
168
|
)}
|
|
122
169
|
{file.progress && (
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
color="white"
|
|
127
|
-
size="sm"
|
|
128
|
-
className="p-[2px]"
|
|
129
|
-
aria-label="abort"
|
|
170
|
+
<button
|
|
171
|
+
type="button"
|
|
172
|
+
className={cn("size-fit flex-content leading-0 cursor-pointer")}
|
|
130
173
|
onClick={() => file.abort?.()}
|
|
131
|
-
|
|
174
|
+
>
|
|
175
|
+
<Icon icon="icon-[mdi--stop]" bg="danger" color="white" size="sm" className="p-0.5" aria-label="abort" />
|
|
176
|
+
</button>
|
|
132
177
|
)}
|
|
133
178
|
{!file.progress && (
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
size="sm"
|
|
138
|
-
aria-label="remove"
|
|
179
|
+
<button
|
|
180
|
+
type="button"
|
|
181
|
+
className={cn("size-fit flex-content leading-0 cursor-pointer")}
|
|
139
182
|
onClick={() => remove(file.key)}
|
|
140
|
-
|
|
183
|
+
>
|
|
184
|
+
<Icon icon="icon-[mdi--trash-can-outline]" color="danger" size="sm" aria-label="remove" />
|
|
185
|
+
</button>
|
|
141
186
|
)}
|
|
142
187
|
</div>
|
|
143
|
-
{file.progress &&
|
|
144
|
-
<ProgressBar animate value={file.progress * 100} size="xxs" />
|
|
145
|
-
)}
|
|
188
|
+
{file.progress && <ProgressBar animate value={file.progress * 100} size="xxs" />}
|
|
146
189
|
</div>
|
|
147
190
|
))}
|
|
148
191
|
</div>
|
package/src/input/Checkbox.tsx
CHANGED
|
@@ -21,37 +21,25 @@
|
|
|
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 {
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
import {
|
|
28
|
-
useCallback,
|
|
29
|
-
useDeferredValue,
|
|
30
|
-
useEffect,
|
|
31
|
-
useMemo,
|
|
32
|
-
useState,
|
|
33
|
-
type ChangeEvent,
|
|
34
|
-
} from "react";
|
|
24
|
+
import { Icon, useControlledValue } from "@react-fabric/core";
|
|
25
|
+
import type { RefProp } from "@react-fabric/core/dist/types/types";
|
|
26
|
+
import { cn } from "@react-fabric/utilities";
|
|
27
|
+
import { useCallback, useMemo } from "react";
|
|
35
28
|
import { ErrorIcon } from "../internal/ErrorIcon";
|
|
36
29
|
|
|
37
|
-
|
|
30
|
+
interface CheckboxProps extends RefProp<HTMLInputElement> {
|
|
38
31
|
/**
|
|
39
32
|
* checkbox label
|
|
40
33
|
*/
|
|
41
|
-
label?:
|
|
34
|
+
label?: React.ReactElement | string;
|
|
42
35
|
/**
|
|
43
36
|
* checked
|
|
44
37
|
*/
|
|
45
38
|
checked?: boolean;
|
|
46
|
-
/**
|
|
47
|
-
* value
|
|
48
|
-
*/
|
|
49
|
-
value?: string | number;
|
|
50
39
|
/**
|
|
51
40
|
* on checked change
|
|
52
41
|
*/
|
|
53
|
-
onChange?: (checked: boolean
|
|
54
|
-
|
|
42
|
+
onChange?: (checked: boolean) => void;
|
|
55
43
|
/**
|
|
56
44
|
* disabled input
|
|
57
45
|
*/
|
|
@@ -82,10 +70,15 @@ export interface CheckboxProps extends RefProp<HTMLInputElement> {
|
|
|
82
70
|
name?: string;
|
|
83
71
|
}
|
|
84
72
|
|
|
85
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Checkbox component for rendering a checkbox input with label and error handling.
|
|
75
|
+
* It supports custom icons for checked and unchecked states, and handles disabled and invalid states.
|
|
76
|
+
* The component uses a deferred value to manage the checked state, ensuring smooth updates.
|
|
77
|
+
* It also provides an error icon to indicate validation errors.
|
|
78
|
+
*/
|
|
79
|
+
export function Checkbox({
|
|
86
80
|
ref,
|
|
87
81
|
label,
|
|
88
|
-
value,
|
|
89
82
|
name,
|
|
90
83
|
checked,
|
|
91
84
|
invalid,
|
|
@@ -96,43 +89,31 @@ export const Checkbox = ({
|
|
|
96
89
|
iconChecked,
|
|
97
90
|
onChange,
|
|
98
91
|
...rest
|
|
99
|
-
}: CheckboxProps)
|
|
100
|
-
const
|
|
101
|
-
const deferred = useDeferredValue(!!checked);
|
|
92
|
+
}: CheckboxProps) {
|
|
93
|
+
const { currentValue, updateValue } = useControlledValue(!!checked, false);
|
|
102
94
|
|
|
103
|
-
const iconOn = useMemo(
|
|
104
|
-
|
|
105
|
-
[icon, iconChecked],
|
|
106
|
-
);
|
|
107
|
-
const iconOff = useMemo(() => icon ?? CoreIcons.checkboxOff, [icon]);
|
|
108
|
-
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
setActualValue(deferred);
|
|
111
|
-
}, [deferred]);
|
|
95
|
+
const iconOn = useMemo(() => iconChecked ?? icon ?? "icon-[mdi--checkbox-marked]", [icon, iconChecked]);
|
|
96
|
+
const iconOff = useMemo(() => icon ?? "icon-[mdi--checkbox-blank-outline]", [icon]);
|
|
112
97
|
|
|
113
98
|
const handleChange = useCallback(
|
|
114
|
-
(e?: ChangeEvent<HTMLInputElement>) => {
|
|
115
|
-
|
|
99
|
+
(e?: React.ChangeEvent<HTMLInputElement>) => {
|
|
100
|
+
updateValue(e?.target.checked ?? false);
|
|
116
101
|
onChange?.(e?.target.checked ?? false);
|
|
117
102
|
},
|
|
118
|
-
[onChange,
|
|
103
|
+
[onChange, updateValue],
|
|
119
104
|
);
|
|
120
105
|
|
|
121
106
|
return (
|
|
122
107
|
<label
|
|
123
|
-
className={
|
|
108
|
+
className={cn(
|
|
124
109
|
invalid ? "ring-danger-500" : "ring-primary-500",
|
|
125
|
-
disabled
|
|
126
|
-
|
|
127
|
-
: " 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",
|
|
110
|
+
disabled ? "opacity-50 cursor-not-allowed" : " opacity-85 hover:opacity-100 cursor-pointer",
|
|
111
|
+
"inline-flex gap-1 py-1 items-center relative rounded ring-offset-2 has-focus-visible:ring-1",
|
|
129
112
|
)}
|
|
130
113
|
style={{ width }}
|
|
131
114
|
>
|
|
132
115
|
<input
|
|
133
|
-
className={
|
|
134
|
-
"appearance-none absolute opacity-0 inset-0 bg-transparent border-none outline-none ring-0",
|
|
135
|
-
)}
|
|
116
|
+
className={cn("appearance-none absolute opacity-0 inset-0 bg-transparent border-none outline-none ring-0")}
|
|
136
117
|
aria-invalid={invalid}
|
|
137
118
|
aria-disabled={disabled}
|
|
138
119
|
disabled={disabled}
|
|
@@ -143,23 +124,18 @@ export const Checkbox = ({
|
|
|
143
124
|
tabIndex={0}
|
|
144
125
|
name={name}
|
|
145
126
|
autoComplete="off"
|
|
146
|
-
checked={
|
|
127
|
+
checked={!!currentValue}
|
|
147
128
|
onChange={handleChange}
|
|
148
129
|
{...rest}
|
|
149
130
|
/>
|
|
150
131
|
<Icon
|
|
151
|
-
className={
|
|
152
|
-
"p-0.5",
|
|
153
|
-
actualValue && icon && !iconChecked && "bg-primary-500 text-white",
|
|
154
|
-
)}
|
|
132
|
+
className={cn("p-0.5", currentValue && icon && !iconChecked && "bg-primary-500 text-white!")}
|
|
155
133
|
size="1.25em"
|
|
156
134
|
color={invalid ? "danger" : "primary"}
|
|
157
|
-
icon={
|
|
135
|
+
icon={currentValue ? iconOn : iconOff}
|
|
158
136
|
/>
|
|
159
137
|
<ErrorIcon invalid={invalid} error={error} />
|
|
160
|
-
{label &&
|
|
161
|
-
<span className="flex-initial leading-none truncate">{label}</span>
|
|
162
|
-
)}
|
|
138
|
+
{label && <span className="flex-initial leading-tight truncate">{label}</span>}
|
|
163
139
|
</label>
|
|
164
140
|
);
|
|
165
|
-
}
|
|
141
|
+
}
|
package/src/input/ColorInput.tsx
CHANGED
|
@@ -21,137 +21,178 @@
|
|
|
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";
|
|
25
|
-
import { type RefProp } from "@react-fabric/core/dist/types/types";
|
|
26
|
-
import { isString } from "@react-fabric/utilities";
|
|
27
|
-
import classNames from "classnames";
|
|
28
24
|
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
arrow,
|
|
26
|
+
autoUpdate,
|
|
27
|
+
flip,
|
|
28
|
+
FloatingArrow,
|
|
29
|
+
FloatingFocusManager,
|
|
30
|
+
offset,
|
|
31
|
+
shift,
|
|
32
|
+
useClick,
|
|
33
|
+
useDismiss,
|
|
34
|
+
useFloating,
|
|
35
|
+
useInteractions,
|
|
36
|
+
} from "@floating-ui/react";
|
|
37
|
+
import { Icon, useControlledValue } from "@react-fabric/core";
|
|
38
|
+
import type { RefProp } from "@react-fabric/core/dist/types/types";
|
|
39
|
+
import { mergeRefs } from "@react-fabric/utilities";
|
|
40
|
+
import { startTransition, useCallback, useRef, useState } from "react";
|
|
41
|
+
import { InputWrapper } from "../internal/InputWrapper";
|
|
42
|
+
import type { InputProps, WrapperProps } from "../types";
|
|
43
|
+
import { getWrapperProps } from "../utils";
|
|
44
|
+
import type { ColorProps } from "./ColorPicker";
|
|
45
|
+
import { ColorPicker } from "./ColorPicker";
|
|
37
46
|
|
|
38
|
-
export interface ColorInputProps extends InputProps, ColorProps, RefProp {
|
|
47
|
+
export interface ColorInputProps extends InputProps, WrapperProps, Omit<ColorProps, "onChange">, RefProp<HTMLButtonElement> {
|
|
39
48
|
/**
|
|
40
49
|
* show eydrop picker
|
|
41
50
|
*/
|
|
42
51
|
showPicker?: boolean;
|
|
43
52
|
}
|
|
44
53
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
defaultColor = "",
|
|
55
|
-
decorateEnd,
|
|
56
|
-
decorateStart,
|
|
57
|
-
onChange,
|
|
58
|
-
// @ts-expect-error ignore
|
|
59
|
-
noOutline,
|
|
60
|
-
...rest
|
|
61
|
-
}: ColorInputProps) => {
|
|
62
|
-
const [actualValue, setActualValue] = useState("");
|
|
63
|
-
const deferred = useDeferredValue(value ?? defaultColor);
|
|
54
|
+
interface EyeDropper {
|
|
55
|
+
open(): Promise<{ sRGBHex: string }>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface Window {
|
|
59
|
+
EyeDropper?: {
|
|
60
|
+
new (): EyeDropper;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Color input component that allows users to select a color
|
|
66
|
+
* and optionally use an eye dropper to pick a color from the screen.
|
|
67
|
+
* It uses the Floating UI library for positioning the color picker.
|
|
68
|
+
* It also provides a button to clear the selected color.
|
|
69
|
+
*
|
|
70
|
+
* This component is a wrapper around the `ColorPicker` component
|
|
71
|
+
* and provides additional functionality such as showing an eye dropper
|
|
72
|
+
* and handling the color selection state.
|
|
73
|
+
*/
|
|
74
|
+
export function ColorInput(props: ColorInputProps) {
|
|
75
|
+
const {
|
|
76
|
+
ref,
|
|
77
|
+
disabled,
|
|
78
|
+
readOnly,
|
|
79
|
+
value,
|
|
80
|
+
invalid,
|
|
81
|
+
allowClear,
|
|
82
|
+
showPicker,
|
|
83
|
+
defaultColor = "",
|
|
84
|
+
onBlur,
|
|
85
|
+
onFocus,
|
|
86
|
+
onChange,
|
|
87
|
+
} = props;
|
|
88
|
+
const { currentValue, updateValue } = useControlledValue(value, defaultColor);
|
|
68
89
|
|
|
69
90
|
const handleChange = useCallback(
|
|
70
91
|
(e?: string) => {
|
|
71
|
-
|
|
72
|
-
|
|
92
|
+
const value = e ?? defaultColor;
|
|
93
|
+
updateValue(value);
|
|
94
|
+
if (onChange) startTransition(() => onChange(value ?? null));
|
|
73
95
|
},
|
|
74
|
-
[onChange],
|
|
96
|
+
[defaultColor, updateValue, onChange],
|
|
75
97
|
);
|
|
76
98
|
|
|
99
|
+
const arrowRef = useRef(null);
|
|
100
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
101
|
+
const { refs, floatingStyles, context } = useFloating({
|
|
102
|
+
open: isOpen,
|
|
103
|
+
onOpenChange: (open, _, reason) => {
|
|
104
|
+
if (reason === "reference-press") return;
|
|
105
|
+
setIsOpen(open);
|
|
106
|
+
},
|
|
107
|
+
strategy: "fixed",
|
|
108
|
+
placement: "bottom",
|
|
109
|
+
whileElementsMounted: autoUpdate,
|
|
110
|
+
middleware: [
|
|
111
|
+
shift({ padding: 8 }),
|
|
112
|
+
flip(),
|
|
113
|
+
offset(9),
|
|
114
|
+
arrow({
|
|
115
|
+
element: arrowRef,
|
|
116
|
+
}),
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
const click = useClick(context, {
|
|
120
|
+
enabled: !disabled && !readOnly,
|
|
121
|
+
});
|
|
122
|
+
const dismiss = useDismiss(context, {
|
|
123
|
+
referencePress: true,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, click]);
|
|
127
|
+
|
|
77
128
|
return (
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
!noOutline && "bg-[var(--fabric-input)] outline overflow-hidden",
|
|
83
|
-
)}
|
|
129
|
+
<InputWrapper
|
|
130
|
+
showClear={allowClear && currentValue !== defaultColor && !disabled && !readOnly}
|
|
131
|
+
onClear={handleChange}
|
|
132
|
+
{...getWrapperProps(props)}
|
|
84
133
|
>
|
|
85
|
-
|
|
134
|
+
<button
|
|
135
|
+
data-ref="button"
|
|
136
|
+
type="button"
|
|
137
|
+
className="flex-1 flex items-center"
|
|
138
|
+
ref={mergeRefs(ref, (el: HTMLButtonElement) => refs.setReference(el))}
|
|
139
|
+
{...getReferenceProps({
|
|
140
|
+
onBlur,
|
|
141
|
+
onFocus,
|
|
142
|
+
})}
|
|
143
|
+
data-inner-clickable="true"
|
|
144
|
+
data-dropdown-open={isOpen ? true : undefined}
|
|
145
|
+
>
|
|
86
146
|
<div
|
|
87
|
-
className=
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
147
|
+
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"
|
|
148
|
+
aria-invalid={invalid}
|
|
149
|
+
style={{
|
|
150
|
+
backgroundColor: currentValue ?? "",
|
|
151
|
+
opacity: disabled ? 0.65 : 1,
|
|
152
|
+
pointerEvents: disabled ? "none" : undefined,
|
|
153
|
+
}}
|
|
91
154
|
>
|
|
92
|
-
{
|
|
155
|
+
{!currentValue && <Icon size="md" className="text-muted" icon="icon-[mdi--palette]" />}
|
|
93
156
|
</div>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<
|
|
157
|
+
</button>
|
|
158
|
+
{isOpen && (
|
|
159
|
+
<FloatingFocusManager context={context} closeOnFocusOut>
|
|
97
160
|
<div
|
|
98
|
-
|
|
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"
|
|
161
|
+
ref={(el) => refs.setFloating(el)}
|
|
100
162
|
style={{
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
pointerEvents: disabled ? "none" : undefined,
|
|
163
|
+
zIndex: "var(--z-popover)",
|
|
164
|
+
...floatingStyles,
|
|
104
165
|
}}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
166
|
+
data-ref="dropdownBody"
|
|
167
|
+
{...getFloatingProps()}
|
|
108
168
|
>
|
|
109
|
-
{
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
169
|
+
<ColorPicker {...props} onChange={handleChange} defaultColor={defaultColor} value={currentValue} />
|
|
170
|
+
<FloatingArrow
|
|
171
|
+
ref={arrowRef}
|
|
172
|
+
context={context}
|
|
173
|
+
strokeWidth={0.5}
|
|
174
|
+
className="stroke-muted"
|
|
175
|
+
style={{
|
|
176
|
+
fill: currentValue || "var(--bg-color-default)",
|
|
177
|
+
}}
|
|
178
|
+
/>
|
|
116
179
|
</div>
|
|
117
|
-
</
|
|
118
|
-
<ColorPicker
|
|
119
|
-
{...rest}
|
|
120
|
-
onChange={handleChange}
|
|
121
|
-
defaultColor={defaultColor}
|
|
122
|
-
value={actualValue}
|
|
123
|
-
/>
|
|
124
|
-
</Dropdown>
|
|
125
|
-
{!disabled && showPicker && (
|
|
126
|
-
<Icon
|
|
127
|
-
icon={CoreIcons.colorPicker}
|
|
128
|
-
onClick={() =>
|
|
129
|
-
(window as AnyObject).EyeDropper &&
|
|
130
|
-
new (window as AnyObject).EyeDropper()
|
|
131
|
-
.open()
|
|
132
|
-
.then((resp: AnyObject) => handleChange(resp.sRGBHex))
|
|
133
|
-
}
|
|
134
|
-
/>
|
|
180
|
+
</FloatingFocusManager>
|
|
135
181
|
)}
|
|
136
|
-
{
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
182
|
+
{!disabled && showPicker && (
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
className="size-fit leading-0 cursor-pointer"
|
|
186
|
+
onClick={() => {
|
|
187
|
+
const ED = (window as Window).EyeDropper;
|
|
188
|
+
if (ED) {
|
|
189
|
+
void new ED().open().then((resp) => handleChange(resp.sRGBHex));
|
|
190
|
+
}
|
|
191
|
+
}}
|
|
142
192
|
>
|
|
143
|
-
|
|
144
|
-
</
|
|
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
|
-
/>
|
|
193
|
+
<Icon className="flex-content p-1" icon="icon-[mdi--eyedropper]" />
|
|
194
|
+
</button>
|
|
154
195
|
)}
|
|
155
|
-
</
|
|
196
|
+
</InputWrapper>
|
|
156
197
|
);
|
|
157
|
-
}
|
|
198
|
+
}
|