@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
package/src/input/Textarea.tsx
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
} from "react";
|
|
33
33
|
import { InputWrapper } from "../internal/InputWrapper";
|
|
34
34
|
import { type InputProps } from "../types";
|
|
35
|
-
import {
|
|
35
|
+
import { useHandleEnter } from "../utils";
|
|
36
36
|
|
|
37
37
|
export interface TextareaProps
|
|
38
38
|
extends InputProps,
|
|
@@ -49,8 +49,22 @@ export interface TextareaProps
|
|
|
49
49
|
* increment rows on value change
|
|
50
50
|
*/
|
|
51
51
|
variableRows?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* expandable textarea
|
|
54
|
+
*/
|
|
55
|
+
expandable?: boolean;
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Textarea component that allows users to input multi-line text.
|
|
60
|
+
* It supports features like variable row count, expandable behavior,
|
|
61
|
+
* and handling of focus, blur, and change events.
|
|
62
|
+
* This component is designed to be flexible and user-friendly,
|
|
63
|
+
* providing a clear interface for text input with optional validation and accessibility features.
|
|
64
|
+
*
|
|
65
|
+
* @param {TextareaProps} props - The properties for the Textarea component.
|
|
66
|
+
* @returns {JSX.Element} The rendered Textarea component.
|
|
67
|
+
*/
|
|
54
68
|
export const Textarea = ({
|
|
55
69
|
ref,
|
|
56
70
|
value,
|
|
@@ -60,17 +74,21 @@ export const Textarea = ({
|
|
|
60
74
|
disabled,
|
|
61
75
|
required,
|
|
62
76
|
placeholder,
|
|
77
|
+
defaultValue,
|
|
63
78
|
autoFocus,
|
|
64
79
|
rows = 5,
|
|
65
80
|
maxRows = 12,
|
|
66
81
|
variableRows,
|
|
82
|
+
expandable,
|
|
83
|
+
onBlur,
|
|
84
|
+
onFocus,
|
|
67
85
|
onChange,
|
|
68
86
|
onEnterPressed,
|
|
69
87
|
...rest
|
|
70
88
|
}: TextareaProps) => {
|
|
71
89
|
const [actualValue, setActualValue] = useState("");
|
|
72
90
|
const [fixedRows, setFixedRows] = useState(rows);
|
|
73
|
-
const deferred = useDeferredValue(value);
|
|
91
|
+
const deferred = useDeferredValue(value ?? defaultValue);
|
|
74
92
|
const wrapperRef = useRef(null);
|
|
75
93
|
|
|
76
94
|
useEffect(() => {
|
|
@@ -79,11 +97,11 @@ export const Textarea = ({
|
|
|
79
97
|
|
|
80
98
|
const handleChange = useCallback(
|
|
81
99
|
(e?: ChangeEvent<HTMLTextAreaElement>) => {
|
|
82
|
-
const value = e?.target.value;
|
|
100
|
+
const value = e?.target.value ?? defaultValue;
|
|
83
101
|
setActualValue(value ?? "");
|
|
84
|
-
onChange?.(value ??
|
|
102
|
+
onChange?.(value ?? null);
|
|
85
103
|
},
|
|
86
|
-
[onChange],
|
|
104
|
+
[onChange, defaultValue],
|
|
87
105
|
);
|
|
88
106
|
|
|
89
107
|
useEffect(() => {
|
|
@@ -95,20 +113,25 @@ export const Textarea = ({
|
|
|
95
113
|
}
|
|
96
114
|
}, [actualValue, variableRows, rows, maxRows]);
|
|
97
115
|
|
|
116
|
+
const handleEnterPressed = useHandleEnter(onEnterPressed, {
|
|
117
|
+
preventDefault: !!onEnterPressed,
|
|
118
|
+
});
|
|
119
|
+
|
|
98
120
|
return (
|
|
99
121
|
<InputWrapper
|
|
100
|
-
showClear={!!actualValue}
|
|
122
|
+
showClear={!!actualValue && !disabled && !readOnly}
|
|
101
123
|
onClear={handleChange}
|
|
102
124
|
invalid={invalid}
|
|
103
125
|
readOnly={readOnly}
|
|
104
126
|
disabled={disabled}
|
|
105
127
|
required={required}
|
|
106
128
|
wrapperRef={wrapperRef}
|
|
129
|
+
textareaExpandable={expandable}
|
|
107
130
|
textarea
|
|
108
131
|
{...rest}
|
|
109
132
|
>
|
|
110
133
|
<textarea
|
|
111
|
-
className="appearance-none self-stretch bg-transparent py-1 px-2 flex-1 border-none outline-none ring-0 min-h-
|
|
134
|
+
className="appearance-none self-stretch bg-transparent py-1 px-2 flex-1 border-none outline-none ring-0 min-h-12 resize-none"
|
|
112
135
|
aria-invalid={invalid}
|
|
113
136
|
aria-disabled={disabled}
|
|
114
137
|
aria-readonly={readOnly}
|
|
@@ -120,10 +143,12 @@ export const Textarea = ({
|
|
|
120
143
|
name={name}
|
|
121
144
|
ref={ref}
|
|
122
145
|
autoComplete="off"
|
|
123
|
-
{
|
|
146
|
+
autoFocus={autoFocus}
|
|
124
147
|
value={actualValue}
|
|
148
|
+
onBlur={onBlur}
|
|
149
|
+
onFocus={onFocus}
|
|
125
150
|
onChange={handleChange}
|
|
126
|
-
onKeyDown={
|
|
151
|
+
onKeyDown={handleEnterPressed as any}
|
|
127
152
|
/>
|
|
128
153
|
</InputWrapper>
|
|
129
154
|
);
|
|
@@ -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, Tooltip } from "@react-fabric/core";
|
|
25
25
|
import { isString } from "@react-fabric/utilities";
|
|
26
26
|
import { useTranslation } from "react-i18next";
|
|
27
27
|
|
|
@@ -48,12 +48,12 @@ export const ErrorIcon = ({ error = "", invalid }: KeyValue) => {
|
|
|
48
48
|
) as string)
|
|
49
49
|
}
|
|
50
50
|
placement="bottom"
|
|
51
|
-
color="danger"
|
|
51
|
+
color="danger-200"
|
|
52
52
|
>
|
|
53
53
|
<Icon
|
|
54
54
|
className="flex-content first:ms-2 self-center"
|
|
55
55
|
color="danger"
|
|
56
|
-
icon=
|
|
56
|
+
icon="icon-[mdi--alert-circle-outline]"
|
|
57
57
|
/>
|
|
58
58
|
</Tooltip>
|
|
59
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,8 +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 {
|
|
25
|
-
import { CoreIcons, Icon } from "@react-fabric/core";
|
|
24
|
+
import { Icon } from "@react-fabric/core";
|
|
26
25
|
import { isString } from "@react-fabric/utilities";
|
|
27
26
|
import classNames from "classnames";
|
|
28
27
|
import {
|
|
@@ -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
|
)}
|
|
@@ -135,11 +147,11 @@ export const InputWrapper = ({
|
|
|
135
147
|
{children}
|
|
136
148
|
{allowClear && showClear && (
|
|
137
149
|
<Icon
|
|
138
|
-
icon=
|
|
150
|
+
icon="icon-[mdi--close]"
|
|
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,18 +160,36 @@ 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
|
-
icon={
|
|
188
|
+
icon={
|
|
189
|
+
isExpanded
|
|
190
|
+
? "icon-[mdi--arrow-collapse]"
|
|
191
|
+
: "icon-[mdi--arrow-expand]"
|
|
192
|
+
}
|
|
163
193
|
className="hidden group-hover:block absolute top-0 end-0 bg-base hover:bg-tint-200 p-1 opacity-65 rounded-capped"
|
|
164
194
|
/>
|
|
165
195
|
)}
|
|
@@ -170,17 +200,13 @@ export const InputWrapper = ({
|
|
|
170
200
|
<Wrapper {...wrapperProps}>
|
|
171
201
|
<div
|
|
172
202
|
ref={elRef}
|
|
173
|
-
className=
|
|
203
|
+
className={classNames(
|
|
204
|
+
"relative min-h-8 flex flex-col flex-nowrap",
|
|
205
|
+
width ? "flex-content" : "flex-1",
|
|
206
|
+
)}
|
|
174
207
|
style={{ width, flexBasis: width }}
|
|
175
208
|
>
|
|
176
|
-
{
|
|
177
|
-
{isExpanded && (
|
|
178
|
-
<FloatingPortal
|
|
179
|
-
root={elRef.current?.closest<HTMLElement>(".theme-base")}
|
|
180
|
-
>
|
|
181
|
-
{el}
|
|
182
|
-
</FloatingPortal>
|
|
183
|
-
)}
|
|
209
|
+
{el}
|
|
184
210
|
</div>
|
|
185
211
|
</Wrapper>
|
|
186
212
|
);
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
useListNavigation,
|
|
35
35
|
useTypeahead,
|
|
36
36
|
} from "@floating-ui/react";
|
|
37
|
-
import { Chip,
|
|
37
|
+
import { Chip, Icon } from "@react-fabric/core";
|
|
38
38
|
import { isArray, isEmpty } from "@react-fabric/utilities";
|
|
39
39
|
import classNames from "classnames";
|
|
40
40
|
import { useCallback, useId, useLayoutEffect, useMemo, useRef } from "react";
|
|
@@ -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",
|
|
@@ -312,11 +323,10 @@ export const AutoComplete = ({
|
|
|
312
323
|
</div>
|
|
313
324
|
{state.loading && (
|
|
314
325
|
<Icon
|
|
315
|
-
animate="spin"
|
|
316
326
|
className={classNames(
|
|
317
327
|
"flex-content p-2 z-0 order-9 text-muted pointer-events-none",
|
|
318
328
|
)}
|
|
319
|
-
icon=
|
|
329
|
+
icon="icon-[svg-spinners--eclipse]"
|
|
320
330
|
/>
|
|
321
331
|
)}
|
|
322
332
|
{state.open && !state.loading && (
|
|
@@ -328,7 +338,7 @@ export const AutoComplete = ({
|
|
|
328
338
|
>
|
|
329
339
|
<Options
|
|
330
340
|
ref={refs.setFloating}
|
|
331
|
-
className="outline shadow-lg max-h-[40vh] z-
|
|
341
|
+
className="outline shadow-lg max-h-[40vh] z-(--z-popover)"
|
|
332
342
|
style={{
|
|
333
343
|
width: refs.reference.current?.getBoundingClientRect().width,
|
|
334
344
|
...floatingStyles,
|
package/src/select/DualList.tsx
CHANGED
|
@@ -21,8 +21,20 @@
|
|
|
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
|
-
|
|
24
|
+
import {
|
|
25
|
+
DndContext,
|
|
26
|
+
DragOverlay,
|
|
27
|
+
useDraggable,
|
|
28
|
+
useDroppable,
|
|
29
|
+
} from "@dnd-kit/core";
|
|
30
|
+
import { restrictToHorizontalAxis } from "@dnd-kit/modifiers";
|
|
31
|
+
import { Button, ButtonGroup } from "@react-fabric/core";
|
|
32
|
+
import {
|
|
33
|
+
type NestedKeys,
|
|
34
|
+
type ReactElement,
|
|
35
|
+
type RefProp,
|
|
36
|
+
} from "@react-fabric/core/dist/types/types";
|
|
37
|
+
import { mergeRefs } from "@react-fabric/utilities";
|
|
26
38
|
import {
|
|
27
39
|
useCallback,
|
|
28
40
|
useDeferredValue,
|
|
@@ -32,14 +44,6 @@ import {
|
|
|
32
44
|
} from "react";
|
|
33
45
|
import { Field, type FieldProps } from "../input/Field";
|
|
34
46
|
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
47
|
|
|
44
48
|
export interface DualListProps<T>
|
|
45
49
|
extends Omit<FieldProps, "children">,
|
|
@@ -59,29 +63,45 @@ export interface DualListProps<T>
|
|
|
59
63
|
/**
|
|
60
64
|
* group by property name
|
|
61
65
|
*/
|
|
62
|
-
groupProperty?:
|
|
66
|
+
groupProperty?: NestedKeys<T>;
|
|
63
67
|
/**
|
|
64
68
|
* label property name
|
|
65
69
|
*/
|
|
66
|
-
labelProperty?:
|
|
70
|
+
labelProperty?: NestedKeys<T>;
|
|
67
71
|
/**
|
|
68
72
|
* value property name
|
|
69
73
|
*/
|
|
70
|
-
valueProperty?:
|
|
74
|
+
valueProperty?: NestedKeys<T>;
|
|
75
|
+
/**
|
|
76
|
+
* empty list display
|
|
77
|
+
*/
|
|
78
|
+
emptyDisplay?: ReactElement;
|
|
71
79
|
/**
|
|
72
80
|
* item renderer
|
|
73
81
|
*/
|
|
74
|
-
renderer?: (option: T) =>
|
|
82
|
+
renderer?: (option: T) => ReactElement;
|
|
75
83
|
/**
|
|
76
84
|
* change handler
|
|
77
85
|
*/
|
|
78
86
|
onChange?: (value?: string[]) => void;
|
|
79
87
|
}
|
|
80
88
|
|
|
89
|
+
/**
|
|
90
|
+
* DualList component allows users to select items from two lists.
|
|
91
|
+
* It displays two lists side by side, one for unselected items and one for selected items.
|
|
92
|
+
* Users can move items between the lists using buttons or drag-and-drop functionality.
|
|
93
|
+
* It supports multiple selections and provides options for grouping, labeling, and rendering items.
|
|
94
|
+
* It also includes features for handling empty states and custom rendering of items.
|
|
95
|
+
* This component is useful for scenarios where users need to manage selections from a larger set of options, such as in forms or settings.
|
|
96
|
+
*
|
|
97
|
+
* @param {DualListProps<T>} props - The properties for the DualList component.
|
|
98
|
+
* @returns {JSX.Element} The rendered DualList component.
|
|
99
|
+
*/
|
|
81
100
|
export const DualList = <T extends AnyObject = string>({
|
|
82
101
|
ref,
|
|
83
102
|
value,
|
|
84
103
|
options,
|
|
104
|
+
emptyDisplay,
|
|
85
105
|
groupProperty,
|
|
86
106
|
height = "18rem",
|
|
87
107
|
labelProperty = "label" as AnyObject,
|
|
@@ -210,6 +230,7 @@ export const DualList = <T extends AnyObject = string>({
|
|
|
210
230
|
error={error}
|
|
211
231
|
height={height}
|
|
212
232
|
options={leftOptions}
|
|
233
|
+
emptyDisplay={emptyDisplay}
|
|
213
234
|
groupProperty={groupProperty}
|
|
214
235
|
labelProperty={labelProperty}
|
|
215
236
|
valueProperty={valueProperty}
|
|
@@ -222,39 +243,35 @@ export const DualList = <T extends AnyObject = string>({
|
|
|
222
243
|
<div className="self-center mx-2">
|
|
223
244
|
<ButtonGroup vertical className="outline overflow-hidden bg-base">
|
|
224
245
|
<Button
|
|
225
|
-
rtlFlip
|
|
226
246
|
size="sm"
|
|
227
247
|
variant="link"
|
|
228
248
|
aria-label="scroll to top"
|
|
229
249
|
onClick={moveAllLeft}
|
|
230
|
-
icon={
|
|
250
|
+
icon={{ icon: "icon-[mdi--chevron-double-left]", rtlFlip: true }}
|
|
231
251
|
disabled={!rightOptions.length}
|
|
232
252
|
/>
|
|
233
253
|
<Button
|
|
234
|
-
rtlFlip
|
|
235
254
|
size="sm"
|
|
236
255
|
variant="link"
|
|
237
256
|
aria-label="scroll up"
|
|
238
257
|
onClick={moveRightSelected}
|
|
239
|
-
icon={
|
|
258
|
+
icon={{ icon: "icon-[mdi--chevron-left]", rtlFlip: true }}
|
|
240
259
|
disabled={!rightSelected.length}
|
|
241
260
|
/>
|
|
242
261
|
<Button
|
|
243
|
-
rtlFlip
|
|
244
262
|
size="sm"
|
|
245
263
|
variant="link"
|
|
246
264
|
aria-label="scroll down"
|
|
247
265
|
onClick={moveLeftSelected}
|
|
248
|
-
icon={
|
|
266
|
+
icon={{ icon: "icon-[mdi--chevron-right]", rtlFlip: true }}
|
|
249
267
|
disabled={!leftSelected.length}
|
|
250
268
|
/>
|
|
251
269
|
<Button
|
|
252
|
-
rtlFlip
|
|
253
270
|
size="sm"
|
|
254
271
|
variant="link"
|
|
255
272
|
aria-label="scroll to bottom"
|
|
256
273
|
onClick={moveAllRight}
|
|
257
|
-
icon={
|
|
274
|
+
icon={{ icon: "icon-[mdi--chevron-double-right]", rtlFlip: true }}
|
|
258
275
|
disabled={!leftOptions.length}
|
|
259
276
|
/>
|
|
260
277
|
</ButtonGroup>
|
|
@@ -268,6 +285,7 @@ export const DualList = <T extends AnyObject = string>({
|
|
|
268
285
|
allowClear
|
|
269
286
|
height={height}
|
|
270
287
|
options={rightOptions}
|
|
288
|
+
emptyDisplay={emptyDisplay}
|
|
271
289
|
groupProperty={groupProperty}
|
|
272
290
|
labelProperty={labelProperty}
|
|
273
291
|
valueProperty={valueProperty}
|