@react-fabric/form 1.0.0-beta.6 → 1.0.0-beta.60
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 +62 -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 +70 -9
- package/dist/cjs/hooks/useFileUploader.js +16 -0
- package/dist/cjs/hooks/useFormBuilder.js +57 -15
- 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 +126 -36
- package/dist/cjs/input/AvatarInput.js +43 -23
- package/dist/cjs/input/Checkbox.js +16 -6
- package/dist/cjs/input/ColorInput.js +113 -58
- 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 +14 -7
- 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 +22 -12
- package/dist/cjs/input/Radio.js +12 -3
- package/dist/cjs/input/RadioGroup.js +10 -1
- package/dist/cjs/input/{Range.js → RangeSlider.js} +16 -8
- package/dist/cjs/input/Search.js +30 -19
- 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 +1 -1
- package/dist/cjs/internal/FieldWrapper.js +11 -4
- package/dist/cjs/internal/InputWrapper.js +27 -20
- package/dist/cjs/select/AutoComplete.js +21 -11
- package/dist/cjs/select/DualList.js +34 -12
- package/dist/cjs/select/List.js +68 -22
- package/dist/cjs/select/Options.js +49 -42
- package/dist/cjs/select/Select.js +40 -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 +62 -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 +72 -12
- package/dist/esm/hooks/useFileUploader.js +16 -0
- package/dist/esm/hooks/useFormBuilder.js +55 -13
- 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 +126 -36
- package/dist/esm/input/AvatarInput.js +44 -24
- package/dist/esm/input/Checkbox.js +16 -6
- package/dist/esm/input/ColorInput.js +115 -60
- 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 +16 -9
- 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 +24 -14
- package/dist/esm/input/Radio.js +12 -3
- package/dist/esm/input/RadioGroup.js +11 -2
- package/dist/esm/input/{Range.js → RangeSlider.js} +16 -8
- package/dist/esm/input/Search.js +32 -21
- 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 +1 -1
- package/dist/esm/internal/FieldWrapper.js +11 -4
- package/dist/esm/internal/InputWrapper.js +27 -20
- package/dist/esm/select/AutoComplete.js +21 -11
- package/dist/esm/select/DualList.js +34 -12
- package/dist/esm/select/List.js +69 -23
- package/dist/esm/select/Options.js +49 -42
- package/dist/esm/select/Select.js +40 -10
- 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 +48 -2
- package/dist/types/hooks/useFileUploader.d.ts +16 -0
- package/dist/types/hooks/useFormBuilder.d.ts +32 -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 +17 -2
- 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 +34 -5
- 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 +62 -30
- package/src/builder/FormField.tsx +52 -32
- package/src/builder/SchemaEditor.tsx +79 -0
- package/src/builder/components/AddSchemaFields.tsx +74 -0
- package/src/builder/components/SchemaArrayForm.tsx +161 -0
- package/src/builder/components/SchemaField.tsx +436 -0
- package/src/form/Controller.tsx +48 -3
- package/src/form/Form.tsx +80 -8
- package/src/hooks/useFileUploader.ts +16 -0
- package/src/hooks/useFormBuilder.tsx +58 -15
- 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 +199 -64
- package/src/input/AvatarInput.tsx +67 -19
- package/src/input/Checkbox.tsx +16 -6
- package/src/input/ColorInput.tsx +131 -71
- 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 +16 -14
- 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 +24 -16
- package/src/input/Radio.tsx +12 -3
- package/src/input/RadioGroup.tsx +12 -3
- package/src/input/{Range.tsx → RangeSlider.tsx} +17 -9
- package/src/input/Search.tsx +33 -22
- 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 +1 -1
- package/src/internal/FieldWrapper.tsx +8 -4
- package/src/internal/InputWrapper.tsx +49 -27
- package/src/select/AutoComplete.tsx +14 -3
- package/src/select/DualList.tsx +31 -16
- package/src/select/List.tsx +52 -8
- package/src/select/Options.tsx +50 -46
- package/src/select/Select.tsx +25 -5
- package/src/select/useSelect.tsx +26 -9
- package/src/types/index.ts +37 -6
- 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,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import classNames from "classnames";
|
|
25
|
-
import
|
|
25
|
+
import { ErrorIcon } from "./ErrorIcon";
|
|
26
26
|
|
|
27
27
|
export const FieldWrapper = ({
|
|
28
28
|
label,
|
|
@@ -36,21 +36,23 @@ export const FieldWrapper = ({
|
|
|
36
36
|
children,
|
|
37
37
|
listing,
|
|
38
38
|
className,
|
|
39
|
+
invalid,
|
|
40
|
+
error,
|
|
39
41
|
"data-inner": isInner,
|
|
40
42
|
}: KeyValue) => {
|
|
41
43
|
return (
|
|
42
44
|
<div
|
|
43
45
|
className={classNames(
|
|
44
|
-
|
|
46
|
+
"fabric-fieldWrapper",
|
|
45
47
|
className,
|
|
46
48
|
inline ? "flex flex-nowrap gap-1" : "block",
|
|
47
49
|
)}
|
|
48
50
|
data-ref="fieldWrapper"
|
|
49
51
|
style={{ width, flexBasis: width }}
|
|
50
52
|
>
|
|
51
|
-
{!isInner && (
|
|
53
|
+
{!isInner && label && (
|
|
52
54
|
<div
|
|
53
|
-
className="flex flex-nowrap flex-content gap-1 py-0.5 px-2"
|
|
55
|
+
className="flex flex-nowrap flex-content items-end gap-1 py-0.5 px-2"
|
|
54
56
|
style={{ width: inline ? labelWidth : undefined }}
|
|
55
57
|
>
|
|
56
58
|
<label
|
|
@@ -73,6 +75,8 @@ export const FieldWrapper = ({
|
|
|
73
75
|
✽
|
|
74
76
|
</span>
|
|
75
77
|
)}
|
|
78
|
+
<ErrorIcon invalid={invalid} error={error} />
|
|
79
|
+
<div className="flex-1" />
|
|
76
80
|
{appendLabel && <span className="flex-content">{appendLabel}</span>}
|
|
77
81
|
</div>
|
|
78
82
|
)}
|
|
@@ -21,7 +21,6 @@
|
|
|
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 { FloatingPortal } from "@floating-ui/react";
|
|
25
24
|
import { CoreIcons, Icon } from "@react-fabric/core";
|
|
26
25
|
import { isString } from "@react-fabric/utilities";
|
|
27
26
|
import classNames from "classnames";
|
|
@@ -35,7 +34,6 @@ import {
|
|
|
35
34
|
} from "react";
|
|
36
35
|
import { Fragment } from "react/jsx-runtime";
|
|
37
36
|
import { ErrorIcon } from "./ErrorIcon";
|
|
38
|
-
import classes from "./Field.module.css";
|
|
39
37
|
import { FieldWrapper } from "./FieldWrapper";
|
|
40
38
|
|
|
41
39
|
export const InputWrapper = ({
|
|
@@ -45,11 +43,15 @@ export const InputWrapper = ({
|
|
|
45
43
|
showClear,
|
|
46
44
|
decorateStart,
|
|
47
45
|
decorateEnd,
|
|
46
|
+
decorateEndShowWhenEmpty,
|
|
47
|
+
decorateEndHideWhenEmpty,
|
|
48
48
|
children,
|
|
49
49
|
onClear,
|
|
50
50
|
noOutline,
|
|
51
51
|
noBorder,
|
|
52
|
+
actionButton,
|
|
52
53
|
textarea = false,
|
|
54
|
+
textareaExpandable = false,
|
|
53
55
|
"data-inner": isInner,
|
|
54
56
|
"data-open": isOpen,
|
|
55
57
|
// expand autocomplete
|
|
@@ -72,9 +74,12 @@ export const InputWrapper = ({
|
|
|
72
74
|
|
|
73
75
|
const handleClear = useCallback(
|
|
74
76
|
(e: MouseEvent<HTMLDivElement>) => {
|
|
75
|
-
e.currentTarget.parentElement
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
const parent = e.currentTarget.parentElement?.querySelector(
|
|
78
|
+
"& > :not(.fabric-decorate)",
|
|
79
|
+
);
|
|
80
|
+
const el = (parent?.querySelector<HTMLElement>("input,textarea,button") ??
|
|
81
|
+
parent) as HTMLElement;
|
|
82
|
+
el?.focus();
|
|
78
83
|
onClear?.();
|
|
79
84
|
},
|
|
80
85
|
[onClear],
|
|
@@ -82,9 +87,12 @@ export const InputWrapper = ({
|
|
|
82
87
|
|
|
83
88
|
const doFocus = useCallback(
|
|
84
89
|
(e: MouseEvent<HTMLDivElement>) => {
|
|
85
|
-
e.currentTarget.parentElement
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
const parent = e.currentTarget.parentElement?.querySelector(
|
|
91
|
+
"& > :not(.fabric-decorate)",
|
|
92
|
+
);
|
|
93
|
+
const el = (parent?.querySelector<HTMLElement>("input,textarea,button") ??
|
|
94
|
+
parent) as HTMLElement;
|
|
95
|
+
el?.focus();
|
|
88
96
|
},
|
|
89
97
|
[onClear],
|
|
90
98
|
);
|
|
@@ -102,28 +110,32 @@ export const InputWrapper = ({
|
|
|
102
110
|
role="none"
|
|
103
111
|
ref={wrapperRef}
|
|
104
112
|
className={classNames(
|
|
105
|
-
|
|
106
|
-
invalid &&
|
|
113
|
+
"fabric-inputWrapper",
|
|
114
|
+
invalid && "fabric-invalidWrapper",
|
|
107
115
|
!noOutline &&
|
|
108
116
|
!noBorder &&
|
|
109
|
-
"bg-
|
|
110
|
-
"flex flex-nowrap items-center",
|
|
117
|
+
"bg-(--fabric-input) outline overflow-hidden",
|
|
118
|
+
"flex flex-nowrap items-center flex-1",
|
|
111
119
|
textarea ? "rounded-capped group" : "rounded",
|
|
112
120
|
isExpanded
|
|
113
|
-
? "fixed inset-4 z-
|
|
121
|
+
? "fixed inset-4 z-99"
|
|
114
122
|
: floatingExpand
|
|
115
123
|
? "absolute inset-x-0"
|
|
116
124
|
: "relative",
|
|
117
125
|
!noOutline &&
|
|
118
126
|
"after:absolute after:bottom-0 after:h-px after:bg-primary-500 after:mx-auto",
|
|
119
127
|
)}
|
|
120
|
-
onKeyDown={(e) =>
|
|
128
|
+
onKeyDown={(e) =>
|
|
129
|
+
e.key === "Escape" &&
|
|
130
|
+
isExpanded &&
|
|
131
|
+
(setExpanded(false), e.stopPropagation())
|
|
132
|
+
}
|
|
121
133
|
>
|
|
122
134
|
{decorateStart && (
|
|
123
135
|
<div
|
|
124
136
|
onClickCapture={doFocus}
|
|
125
137
|
className={classNames(
|
|
126
|
-
|
|
138
|
+
"fabric-decorate",
|
|
127
139
|
"text-muted leading-none flex-content",
|
|
128
140
|
isString(decorateStart) && "px-1",
|
|
129
141
|
)}
|
|
@@ -139,7 +151,7 @@ export const InputWrapper = ({
|
|
|
139
151
|
onClick={handleClear}
|
|
140
152
|
className={classNames(
|
|
141
153
|
"text-tint-500 hover:text-tint-700 p-1 z-1 flex-content",
|
|
142
|
-
|
|
154
|
+
"fabric-inputClear",
|
|
143
155
|
textarea && "self-start mt-1 me-1",
|
|
144
156
|
)}
|
|
145
157
|
/>
|
|
@@ -148,15 +160,29 @@ export const InputWrapper = ({
|
|
|
148
160
|
<div
|
|
149
161
|
onClickCapture={doFocus}
|
|
150
162
|
className={classNames(
|
|
151
|
-
|
|
163
|
+
"fabric-decorate",
|
|
152
164
|
"text-muted leading-none flex-content",
|
|
153
165
|
isString(decorateEnd) && "px-1",
|
|
166
|
+
decorateEndHideWhenEmpty && "peer-placeholder-shown:hidden",
|
|
167
|
+
decorateEndShowWhenEmpty &&
|
|
168
|
+
"hidden peer-placeholder-shown:inline-block",
|
|
154
169
|
)}
|
|
155
170
|
>
|
|
156
171
|
{decorateEnd}
|
|
157
172
|
</div>
|
|
158
173
|
)}
|
|
159
|
-
{
|
|
174
|
+
{actionButton && (
|
|
175
|
+
<div
|
|
176
|
+
onClickCapture={doFocus}
|
|
177
|
+
className={classNames(
|
|
178
|
+
"fabric-decorate",
|
|
179
|
+
"text-muted leading-none flex-content",
|
|
180
|
+
)}
|
|
181
|
+
>
|
|
182
|
+
{actionButton}
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
{textareaExpandable && (
|
|
160
186
|
<Icon
|
|
161
187
|
onClick={() => setExpanded(!isExpanded)}
|
|
162
188
|
icon={isExpanded ? CoreIcons.arrowCollapse : CoreIcons.arrowExpand}
|
|
@@ -170,17 +196,13 @@ export const InputWrapper = ({
|
|
|
170
196
|
<Wrapper {...wrapperProps}>
|
|
171
197
|
<div
|
|
172
198
|
ref={elRef}
|
|
173
|
-
className=
|
|
199
|
+
className={classNames(
|
|
200
|
+
"relative min-h-8 flex flex-col flex-nowrap",
|
|
201
|
+
width ? "flex-auto" : "flex-1",
|
|
202
|
+
)}
|
|
174
203
|
style={{ width, flexBasis: width }}
|
|
175
204
|
>
|
|
176
|
-
{
|
|
177
|
-
{isExpanded && (
|
|
178
|
-
<FloatingPortal
|
|
179
|
-
root={elRef.current?.closest<HTMLElement>(".theme-base")}
|
|
180
|
-
>
|
|
181
|
-
{el}
|
|
182
|
-
</FloatingPortal>
|
|
183
|
-
)}
|
|
205
|
+
{el}
|
|
184
206
|
</div>
|
|
185
207
|
</Wrapper>
|
|
186
208
|
);
|
|
@@ -61,6 +61,13 @@ type AutoCompleteProps = Omit<
|
|
|
61
61
|
onInput?: (query: string) => void;
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* AutoComplete component for selecting or entering values with history suggestions.
|
|
66
|
+
* It supports multiple selections, history suggestions, and custom styling.
|
|
67
|
+
*
|
|
68
|
+
* @param {AutoCompleteProps} props - The properties for the AutoComplete component.
|
|
69
|
+
* @returns {JSX.Element} The rendered AutoComplete component.
|
|
70
|
+
*/
|
|
64
71
|
export const AutoComplete = ({
|
|
65
72
|
ref,
|
|
66
73
|
name,
|
|
@@ -80,6 +87,7 @@ export const AutoComplete = ({
|
|
|
80
87
|
onInput,
|
|
81
88
|
onFocus,
|
|
82
89
|
onBlur,
|
|
90
|
+
onQuery,
|
|
83
91
|
onEnterPressed,
|
|
84
92
|
...rest
|
|
85
93
|
}: AutoCompleteProps) => {
|
|
@@ -104,6 +112,7 @@ export const AutoComplete = ({
|
|
|
104
112
|
multiple,
|
|
105
113
|
onChange,
|
|
106
114
|
onSelect,
|
|
115
|
+
onQuery,
|
|
107
116
|
});
|
|
108
117
|
|
|
109
118
|
const dropdownKey = useId();
|
|
@@ -181,7 +190,7 @@ export const AutoComplete = ({
|
|
|
181
190
|
return false;
|
|
182
191
|
} else {
|
|
183
192
|
onEnterPressed?.(evt);
|
|
184
|
-
|
|
193
|
+
setOpen(false);
|
|
185
194
|
}
|
|
186
195
|
} else if (evt.key === "Backspace") {
|
|
187
196
|
!state.query && multiple && handleRemove();
|
|
@@ -267,7 +276,7 @@ export const AutoComplete = ({
|
|
|
267
276
|
required={required}
|
|
268
277
|
error={error}
|
|
269
278
|
wrapperRef={refs.setReference}
|
|
270
|
-
className={!multiple && expandOnEdit && state.open ? "
|
|
279
|
+
className={!multiple && expandOnEdit && state.open ? "z-99!" : ""}
|
|
271
280
|
floatingExpand={!multiple && expandOnEdit && state.open}
|
|
272
281
|
{...rest}
|
|
273
282
|
>
|
|
@@ -291,7 +300,9 @@ export const AutoComplete = ({
|
|
|
291
300
|
data-testid={name}
|
|
292
301
|
name={name}
|
|
293
302
|
size={1}
|
|
303
|
+
ref={ref}
|
|
294
304
|
onInput={onInput}
|
|
305
|
+
autoFocus={autoFocus}
|
|
295
306
|
rows={!multiple && expandOnEdit && state.open ? 5 : 1}
|
|
296
307
|
className={classNames(
|
|
297
308
|
"appearance-none bg-transparent outline-none border-none ring-0 flex-1 resize-none",
|
|
@@ -328,7 +339,7 @@ export const AutoComplete = ({
|
|
|
328
339
|
>
|
|
329
340
|
<Options
|
|
330
341
|
ref={refs.setFloating}
|
|
331
|
-
className="outline shadow-lg max-h-[40vh] z-
|
|
342
|
+
className="outline shadow-lg max-h-[40vh] z-(--z-popover)"
|
|
332
343
|
style={{
|
|
333
344
|
width: refs.reference.current?.getBoundingClientRect().width,
|
|
334
345
|
...floatingStyles,
|
package/src/select/DualList.tsx
CHANGED
|
@@ -21,9 +21,18 @@
|
|
|
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
|
+
DndContext,
|
|
26
|
+
DragOverlay,
|
|
27
|
+
useDraggable,
|
|
28
|
+
useDroppable,
|
|
29
|
+
} from "@dnd-kit/core";
|
|
30
|
+
import { restrictToHorizontalAxis } from "@dnd-kit/modifiers";
|
|
24
31
|
import { Button, ButtonGroup, CoreIcons } from "@react-fabric/core";
|
|
25
32
|
import { type RefProp } from "@react-fabric/core/dist/types/types";
|
|
33
|
+
import { mergeRefs } from "@react-fabric/utilities";
|
|
26
34
|
import {
|
|
35
|
+
type ReactElement,
|
|
27
36
|
useCallback,
|
|
28
37
|
useDeferredValue,
|
|
29
38
|
useEffect,
|
|
@@ -32,14 +41,6 @@ import {
|
|
|
32
41
|
} from "react";
|
|
33
42
|
import { Field, type FieldProps } from "../input/Field";
|
|
34
43
|
import { List } from "./List";
|
|
35
|
-
import { restrictToHorizontalAxis } from "@dnd-kit/modifiers";
|
|
36
|
-
import {
|
|
37
|
-
DndContext,
|
|
38
|
-
DragOverlay,
|
|
39
|
-
useDraggable,
|
|
40
|
-
useDroppable,
|
|
41
|
-
} from "@dnd-kit/core";
|
|
42
|
-
import { mergeRefs } from "@react-fabric/utilities";
|
|
43
44
|
|
|
44
45
|
export interface DualListProps<T>
|
|
45
46
|
extends Omit<FieldProps, "children">,
|
|
@@ -68,6 +69,10 @@ export interface DualListProps<T>
|
|
|
68
69
|
* value property name
|
|
69
70
|
*/
|
|
70
71
|
valueProperty?: keyof T;
|
|
72
|
+
/**
|
|
73
|
+
* empty list display
|
|
74
|
+
*/
|
|
75
|
+
emptyDisplay?: ReactElement;
|
|
71
76
|
/**
|
|
72
77
|
* item renderer
|
|
73
78
|
*/
|
|
@@ -78,10 +83,22 @@ export interface DualListProps<T>
|
|
|
78
83
|
onChange?: (value?: string[]) => void;
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
/**
|
|
87
|
+
* DualList component allows users to select items from two lists.
|
|
88
|
+
* It displays two lists side by side, one for unselected items and one for selected items.
|
|
89
|
+
* Users can move items between the lists using buttons or drag-and-drop functionality.
|
|
90
|
+
* It supports multiple selections and provides options for grouping, labeling, and rendering items.
|
|
91
|
+
* It also includes features for handling empty states and custom rendering of items.
|
|
92
|
+
* This component is useful for scenarios where users need to manage selections from a larger set of options, such as in forms or settings.
|
|
93
|
+
*
|
|
94
|
+
* @param {DualListProps<T>} props - The properties for the DualList component.
|
|
95
|
+
* @returns {JSX.Element} The rendered DualList component.
|
|
96
|
+
*/
|
|
81
97
|
export const DualList = <T extends AnyObject = string>({
|
|
82
98
|
ref,
|
|
83
99
|
value,
|
|
84
100
|
options,
|
|
101
|
+
emptyDisplay,
|
|
85
102
|
groupProperty,
|
|
86
103
|
height = "18rem",
|
|
87
104
|
labelProperty = "label" as AnyObject,
|
|
@@ -210,6 +227,7 @@ export const DualList = <T extends AnyObject = string>({
|
|
|
210
227
|
error={error}
|
|
211
228
|
height={height}
|
|
212
229
|
options={leftOptions}
|
|
230
|
+
emptyDisplay={emptyDisplay}
|
|
213
231
|
groupProperty={groupProperty}
|
|
214
232
|
labelProperty={labelProperty}
|
|
215
233
|
valueProperty={valueProperty}
|
|
@@ -222,39 +240,35 @@ export const DualList = <T extends AnyObject = string>({
|
|
|
222
240
|
<div className="self-center mx-2">
|
|
223
241
|
<ButtonGroup vertical className="outline overflow-hidden bg-base">
|
|
224
242
|
<Button
|
|
225
|
-
rtlFlip
|
|
226
243
|
size="sm"
|
|
227
244
|
variant="link"
|
|
228
245
|
aria-label="scroll to top"
|
|
229
246
|
onClick={moveAllLeft}
|
|
230
|
-
icon={CoreIcons.chevronsLeft}
|
|
247
|
+
icon={{ icon: CoreIcons.chevronsLeft, rtlFlip: true }}
|
|
231
248
|
disabled={!rightOptions.length}
|
|
232
249
|
/>
|
|
233
250
|
<Button
|
|
234
|
-
rtlFlip
|
|
235
251
|
size="sm"
|
|
236
252
|
variant="link"
|
|
237
253
|
aria-label="scroll up"
|
|
238
254
|
onClick={moveRightSelected}
|
|
239
|
-
icon={CoreIcons.chevronLeft}
|
|
255
|
+
icon={{ icon: CoreIcons.chevronLeft, rtlFlip: true }}
|
|
240
256
|
disabled={!rightSelected.length}
|
|
241
257
|
/>
|
|
242
258
|
<Button
|
|
243
|
-
rtlFlip
|
|
244
259
|
size="sm"
|
|
245
260
|
variant="link"
|
|
246
261
|
aria-label="scroll down"
|
|
247
262
|
onClick={moveLeftSelected}
|
|
248
|
-
icon={CoreIcons.chevronRight}
|
|
263
|
+
icon={{ icon: CoreIcons.chevronRight, rtlFlip: true }}
|
|
249
264
|
disabled={!leftSelected.length}
|
|
250
265
|
/>
|
|
251
266
|
<Button
|
|
252
|
-
rtlFlip
|
|
253
267
|
size="sm"
|
|
254
268
|
variant="link"
|
|
255
269
|
aria-label="scroll to bottom"
|
|
256
270
|
onClick={moveAllRight}
|
|
257
|
-
icon={CoreIcons.chevronsRight}
|
|
271
|
+
icon={{ icon: CoreIcons.chevronsRight, rtlFlip: true }}
|
|
258
272
|
disabled={!leftOptions.length}
|
|
259
273
|
/>
|
|
260
274
|
</ButtonGroup>
|
|
@@ -268,6 +282,7 @@ export const DualList = <T extends AnyObject = string>({
|
|
|
268
282
|
allowClear
|
|
269
283
|
height={height}
|
|
270
284
|
options={rightOptions}
|
|
285
|
+
emptyDisplay={emptyDisplay}
|
|
271
286
|
groupProperty={groupProperty}
|
|
272
287
|
labelProperty={labelProperty}
|
|
273
288
|
valueProperty={valueProperty}
|
package/src/select/List.tsx
CHANGED
|
@@ -32,13 +32,21 @@ import {
|
|
|
32
32
|
import { Chip } from "@react-fabric/core";
|
|
33
33
|
import { isArray, isEmpty } from "@react-fabric/utilities";
|
|
34
34
|
import classNames from "classnames";
|
|
35
|
-
import { Fragment, useCallback, useId, useMemo } from "react";
|
|
35
|
+
import { Fragment, useCallback, useId, useLayoutEffect, useMemo } from "react";
|
|
36
36
|
import { FieldWrapper } from "../internal/FieldWrapper";
|
|
37
37
|
import { InputWrapper } from "../internal/InputWrapper";
|
|
38
38
|
import { type SelectProps } from "../types";
|
|
39
39
|
import { Options } from "./Options";
|
|
40
40
|
import { useSelect } from "./useSelect";
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* List component for selecting items from a list.
|
|
44
|
+
* It supports single and multiple selections, searching, creating new options,
|
|
45
|
+
* and custom rendering of options.
|
|
46
|
+
*
|
|
47
|
+
* @param {SelectProps<T>} props - The properties for the List component.
|
|
48
|
+
* @returns {JSX.Element} The rendered List component.
|
|
49
|
+
*/
|
|
42
50
|
export const List = <T extends AnyObject = string>({
|
|
43
51
|
ref,
|
|
44
52
|
name,
|
|
@@ -53,8 +61,11 @@ export const List = <T extends AnyObject = string>({
|
|
|
53
61
|
groupProperty,
|
|
54
62
|
labelProperty,
|
|
55
63
|
valueProperty,
|
|
64
|
+
infoProperty,
|
|
65
|
+
sortProperty,
|
|
56
66
|
multiple,
|
|
57
67
|
allowCreate,
|
|
68
|
+
searchable,
|
|
58
69
|
createOption,
|
|
59
70
|
matcher,
|
|
60
71
|
renderer,
|
|
@@ -62,6 +73,7 @@ export const List = <T extends AnyObject = string>({
|
|
|
62
73
|
onChange,
|
|
63
74
|
onSelect,
|
|
64
75
|
onEnterPressed,
|
|
76
|
+
emptyDisplay,
|
|
65
77
|
invalid,
|
|
66
78
|
error,
|
|
67
79
|
allowClear,
|
|
@@ -72,7 +84,9 @@ export const List = <T extends AnyObject = string>({
|
|
|
72
84
|
// @ts-expect-error ignore
|
|
73
85
|
"data-hide-selected": hideSelected,
|
|
74
86
|
...rest
|
|
75
|
-
}: SelectProps<T
|
|
87
|
+
}: Omit<SelectProps<T>, "defaultOpen" | "hideHandle"> & {
|
|
88
|
+
height?: string | number;
|
|
89
|
+
}) => {
|
|
76
90
|
const Wrapper = useMemo(() => (isInner ? Fragment : FieldWrapper), [isInner]);
|
|
77
91
|
const wrapperProps = useMemo(
|
|
78
92
|
() => (isInner ? {} : { ...rest }),
|
|
@@ -93,9 +107,11 @@ export const List = <T extends AnyObject = string>({
|
|
|
93
107
|
value,
|
|
94
108
|
options,
|
|
95
109
|
multiple,
|
|
110
|
+
alwaysOpen: true,
|
|
96
111
|
groupProperty,
|
|
97
112
|
labelProperty,
|
|
98
113
|
valueProperty,
|
|
114
|
+
sortProperty,
|
|
99
115
|
allowCreate,
|
|
100
116
|
createOption,
|
|
101
117
|
matcher,
|
|
@@ -166,9 +182,12 @@ export const List = <T extends AnyObject = string>({
|
|
|
166
182
|
}
|
|
167
183
|
} else if (evt.key === "Backspace") {
|
|
168
184
|
!state.query && handleRemove();
|
|
169
|
-
} else if (["ArrowUp", "ArrowDown"].includes(evt.key)) {
|
|
170
|
-
if (
|
|
171
|
-
|
|
185
|
+
} else if (!searchable || ["ArrowUp", "ArrowDown"].includes(evt.key)) {
|
|
186
|
+
if (
|
|
187
|
+
["ArrowUp", "ArrowDown"].includes(evt.key) &&
|
|
188
|
+
state.activeIndex === null
|
|
189
|
+
) {
|
|
190
|
+
setActiveIndex(state.selectedIndex ?? 0);
|
|
172
191
|
}
|
|
173
192
|
setTimeout(() => {
|
|
174
193
|
props.onKeyDown?.(evt);
|
|
@@ -225,6 +244,15 @@ export const List = <T extends AnyObject = string>({
|
|
|
225
244
|
}
|
|
226
245
|
}, [state.value, state.query, placeholder, renderer]);
|
|
227
246
|
|
|
247
|
+
useLayoutEffect(() => {
|
|
248
|
+
setTimeout(() => {
|
|
249
|
+
state.selectedIndex &&
|
|
250
|
+
listRef.current[state.selectedIndex]?.scrollIntoView({
|
|
251
|
+
block: "center",
|
|
252
|
+
});
|
|
253
|
+
}, 100);
|
|
254
|
+
}, [state.selectedIndex]);
|
|
255
|
+
|
|
228
256
|
return (
|
|
229
257
|
<Wrapper {...wrapperProps}>
|
|
230
258
|
<InputWrapper
|
|
@@ -240,22 +268,34 @@ export const List = <T extends AnyObject = string>({
|
|
|
240
268
|
wrapperRef={refs.setReference}
|
|
241
269
|
>
|
|
242
270
|
<div
|
|
271
|
+
role="none"
|
|
243
272
|
data-select-display="true"
|
|
244
|
-
className=
|
|
273
|
+
className={classNames(
|
|
274
|
+
"group flex-1 py-1 px-2 truncate text-start flex gap-1 relative min-h-5 justify-start",
|
|
275
|
+
multiple ? "flex-wrap" : "flex-nowrap overflow-hidden",
|
|
276
|
+
)}
|
|
277
|
+
{...referenceProps}
|
|
278
|
+
onMouseUp={(e) =>
|
|
279
|
+
e.currentTarget.querySelector<HTMLElement>("input")?.focus()
|
|
280
|
+
}
|
|
245
281
|
>
|
|
246
282
|
{!hideSelected && displayValue}
|
|
247
283
|
<input
|
|
284
|
+
readOnly={!searchable || readOnly}
|
|
248
285
|
disabled={disabled}
|
|
249
286
|
value={state.query ?? ""}
|
|
250
287
|
aria-invalid={invalid}
|
|
251
288
|
data-testid={name}
|
|
252
289
|
name={name}
|
|
290
|
+
size={1}
|
|
291
|
+
ref={ref}
|
|
292
|
+
autoFocus={autoFocus}
|
|
253
293
|
className={classNames(
|
|
254
294
|
"appearance-none bg-transparent outline-none border-none ring-0 flex-1 min-w-24",
|
|
255
295
|
disabled && "cursor-not-allowed pointer-events-none",
|
|
296
|
+
multiple && "min-w-24",
|
|
256
297
|
)}
|
|
257
298
|
placeholder={isEmpty(state.value) ? placeholder : ""}
|
|
258
|
-
{...referenceProps}
|
|
259
299
|
onChange={(evt) => handleQuery(evt.target.value)}
|
|
260
300
|
/>
|
|
261
301
|
</div>
|
|
@@ -277,10 +317,14 @@ export const List = <T extends AnyObject = string>({
|
|
|
277
317
|
{...floatingProps}
|
|
278
318
|
items={state.items}
|
|
279
319
|
active={state.activeIndex}
|
|
320
|
+
valueProperty={valueProperty}
|
|
321
|
+
labelProperty={labelProperty}
|
|
280
322
|
itemRef={setItemRef}
|
|
281
323
|
itemProps={makeItemProps}
|
|
324
|
+
empty={emptyDisplay}
|
|
325
|
+
info={state.activeItem?.[infoProperty ?? "info"]}
|
|
282
326
|
>
|
|
283
|
-
{(item) => renderer?.(item) ??
|
|
327
|
+
{async (item, label) => await (renderer?.(item) ?? label)}
|
|
284
328
|
</Options>
|
|
285
329
|
</FloatingFocusManager>
|
|
286
330
|
</Wrapper>
|