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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/{src/input/ArrayInput.module.css → css/_arrayInput.css} +8 -8
  2. package/css/_colorPicker.css +65 -0
  3. package/{src/internal/Field.module.css → css/_field.css} +66 -30
  4. package/{src/input/File.module.css → css/_file.css} +21 -6
  5. package/css/styles.css +27 -0
  6. package/dist/cjs/builder/FileField.js +40 -28
  7. package/dist/cjs/builder/FormField.js +50 -34
  8. package/dist/cjs/builder/SchemaEditor.js +72 -0
  9. package/dist/cjs/builder/components/AddSchemaFields.js +63 -0
  10. package/dist/cjs/builder/components/SchemaArrayForm.js +145 -0
  11. package/dist/cjs/builder/components/SchemaField.js +414 -0
  12. package/dist/cjs/form/Controller.js +46 -1
  13. package/dist/cjs/form/Form.js +71 -13
  14. package/dist/cjs/hooks/useFileUploader.js +16 -0
  15. package/dist/cjs/hooks/useFormBuilder.js +67 -18
  16. package/dist/cjs/i18n/ar.json.js +5 -4
  17. package/dist/cjs/i18n/en.json.js +5 -5
  18. package/dist/cjs/index.js +12 -2
  19. package/dist/cjs/input/ArrayInput.js +128 -38
  20. package/dist/cjs/input/AvatarInput.js +43 -23
  21. package/dist/cjs/input/Checkbox.js +18 -8
  22. package/dist/cjs/input/ColorInput.js +114 -59
  23. package/dist/cjs/input/ColorPicker.js +60 -76
  24. package/dist/cjs/input/DateInput.js +197 -0
  25. package/dist/cjs/input/Field.js +10 -4
  26. package/dist/cjs/input/File.js +15 -8
  27. package/dist/cjs/input/Hidden.js +13 -2
  28. package/dist/cjs/input/Input.js +24 -10
  29. package/dist/cjs/input/Number.js +22 -10
  30. package/dist/cjs/input/Password.js +23 -13
  31. package/dist/cjs/input/Radio.js +14 -5
  32. package/dist/cjs/input/RadioGroup.js +10 -1
  33. package/dist/cjs/input/{Range.js → RangeSlider.js} +16 -8
  34. package/dist/cjs/input/Search.js +32 -22
  35. package/dist/cjs/input/Slider.js +12 -6
  36. package/dist/cjs/input/Switch.js +20 -9
  37. package/dist/cjs/input/Textarea.js +29 -8
  38. package/dist/cjs/internal/ErrorIcon.js +2 -2
  39. package/dist/cjs/internal/FieldWrapper.js +11 -4
  40. package/dist/cjs/internal/InputWrapper.js +29 -22
  41. package/dist/cjs/select/AutoComplete.js +22 -13
  42. package/dist/cjs/select/DualList.js +34 -12
  43. package/dist/cjs/select/List.js +49 -21
  44. package/dist/cjs/select/Options.js +49 -42
  45. package/dist/cjs/select/Select.js +21 -10
  46. package/dist/cjs/select/useSelect.js +22 -11
  47. package/dist/cjs/types/schema.js +43 -0
  48. package/dist/cjs/utils/eventHandlers.js +15 -5
  49. package/dist/esm/builder/FileField.js +42 -30
  50. package/dist/esm/builder/FormField.js +50 -34
  51. package/dist/esm/builder/SchemaEditor.js +72 -0
  52. package/dist/esm/builder/components/AddSchemaFields.js +63 -0
  53. package/dist/esm/builder/components/SchemaArrayForm.js +145 -0
  54. package/dist/esm/builder/components/SchemaField.js +414 -0
  55. package/dist/esm/form/Controller.js +46 -1
  56. package/dist/esm/form/Form.js +73 -16
  57. package/dist/esm/hooks/useFileUploader.js +16 -0
  58. package/dist/esm/hooks/useFormBuilder.js +65 -16
  59. package/dist/esm/i18n/ar.json.js +5 -4
  60. package/dist/esm/i18n/en.json.js +5 -5
  61. package/dist/esm/index.js +6 -2
  62. package/dist/esm/input/ArrayInput.js +128 -38
  63. package/dist/esm/input/AvatarInput.js +43 -23
  64. package/dist/esm/input/Checkbox.js +19 -9
  65. package/dist/esm/input/ColorInput.js +116 -61
  66. package/dist/esm/input/ColorPicker.js +60 -76
  67. package/dist/esm/input/DateInput.js +197 -0
  68. package/dist/esm/input/Field.js +11 -5
  69. package/dist/esm/input/File.js +18 -11
  70. package/dist/esm/input/Hidden.js +13 -2
  71. package/dist/esm/input/Input.js +26 -12
  72. package/dist/esm/input/Number.js +24 -12
  73. package/dist/esm/input/Password.js +26 -16
  74. package/dist/esm/input/Radio.js +15 -6
  75. package/dist/esm/input/RadioGroup.js +11 -2
  76. package/dist/esm/input/{Range.js → RangeSlider.js} +16 -8
  77. package/dist/esm/input/Search.js +35 -25
  78. package/dist/esm/input/Slider.js +12 -6
  79. package/dist/esm/input/Switch.js +20 -9
  80. package/dist/esm/input/Textarea.js +30 -9
  81. package/dist/esm/internal/ErrorIcon.js +3 -3
  82. package/dist/esm/internal/FieldWrapper.js +11 -4
  83. package/dist/esm/internal/InputWrapper.js +30 -23
  84. package/dist/esm/select/AutoComplete.js +23 -14
  85. package/dist/esm/select/DualList.js +35 -13
  86. package/dist/esm/select/List.js +50 -22
  87. package/dist/esm/select/Options.js +49 -42
  88. package/dist/esm/select/Select.js +22 -11
  89. package/dist/esm/select/useSelect.js +23 -12
  90. package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
  91. package/dist/esm/utils/eventHandlers.js +15 -5
  92. package/dist/types/builder/FileField.d.ts +4 -2
  93. package/dist/types/builder/FormField.d.ts +3 -1
  94. package/dist/types/builder/SchemaEditor.d.ts +9 -0
  95. package/dist/types/builder/components/AddSchemaFields.d.ts +2 -0
  96. package/dist/types/builder/components/SchemaArrayForm.d.ts +2 -0
  97. package/dist/types/builder/components/SchemaField.d.ts +10 -0
  98. package/dist/types/form/Controller.d.ts +47 -2
  99. package/dist/types/form/Form.d.ts +49 -6
  100. package/dist/types/hooks/useFileUploader.d.ts +16 -0
  101. package/dist/types/hooks/useFormBuilder.d.ts +38 -1
  102. package/dist/types/index.d.ts +6 -3
  103. package/dist/types/input/ArrayInput.d.ts +69 -12
  104. package/dist/types/input/AvatarInput.d.ts +23 -3
  105. package/dist/types/input/Checkbox.d.ts +13 -4
  106. package/dist/types/input/ColorInput.d.ts +15 -2
  107. package/dist/types/input/ColorPicker.d.ts +11 -1
  108. package/dist/types/input/DateInput.d.ts +28 -0
  109. package/dist/types/input/Field.d.ts +5 -1
  110. package/dist/types/input/File.d.ts +9 -0
  111. package/dist/types/input/Hidden.d.ts +13 -1
  112. package/dist/types/input/Input.d.ts +9 -1
  113. package/dist/types/input/Number.d.ts +9 -1
  114. package/dist/types/input/Password.d.ts +7 -1
  115. package/dist/types/input/Radio.d.ts +9 -1
  116. package/dist/types/input/RadioGroup.d.ts +8 -1
  117. package/dist/types/input/RangeSlider.d.ts +67 -0
  118. package/dist/types/input/Search.d.ts +14 -1
  119. package/dist/types/input/Slider.d.ts +8 -2
  120. package/dist/types/input/Switch.d.ts +10 -2
  121. package/dist/types/input/Textarea.d.ts +15 -1
  122. package/dist/types/internal/FieldWrapper.d.ts +1 -1
  123. package/dist/types/internal/InputWrapper.d.ts +1 -1
  124. package/dist/types/ranger/index.d.ts +2 -2
  125. package/dist/types/select/AutoComplete.d.ts +8 -1
  126. package/dist/types/select/DualList.d.ts +21 -7
  127. package/dist/types/select/List.d.ts +9 -1
  128. package/dist/types/select/Options.d.ts +4 -3
  129. package/dist/types/select/Select.d.ts +9 -1
  130. package/dist/types/select/useSelect.d.ts +10 -6
  131. package/dist/types/types/index.d.ts +38 -10
  132. package/dist/types/types/schema.d.ts +78 -33
  133. package/dist/types/utils/eventHandlers.d.ts +2 -2
  134. package/package.json +22 -20
  135. package/src/builder/FileField.tsx +56 -30
  136. package/src/builder/FormField.tsx +52 -32
  137. package/src/builder/SchemaEditor.tsx +79 -0
  138. package/src/builder/components/AddSchemaFields.tsx +75 -0
  139. package/src/builder/components/SchemaArrayForm.tsx +155 -0
  140. package/src/builder/components/SchemaField.tsx +435 -0
  141. package/src/form/Controller.tsx +48 -3
  142. package/src/form/Form.tsx +84 -13
  143. package/src/hooks/useFileUploader.ts +16 -0
  144. package/src/hooks/useFormBuilder.tsx +70 -19
  145. package/src/i18n/ar.json +5 -4
  146. package/src/i18n/en.json +5 -5
  147. package/src/index.ts +12 -3
  148. package/src/input/ArrayInput.tsx +195 -66
  149. package/src/input/AvatarInput.tsx +66 -18
  150. package/src/input/Checkbox.tsx +22 -9
  151. package/src/input/ColorInput.tsx +128 -72
  152. package/src/input/ColorPicker.tsx +54 -72
  153. package/src/input/DateInput.tsx +260 -0
  154. package/src/input/Field.tsx +15 -11
  155. package/src/input/File.tsx +18 -16
  156. package/src/input/Hidden.tsx +17 -2
  157. package/src/input/Input.tsx +27 -13
  158. package/src/input/Number.tsx +22 -13
  159. package/src/input/Password.tsx +28 -18
  160. package/src/input/Radio.tsx +15 -6
  161. package/src/input/RadioGroup.tsx +12 -3
  162. package/src/input/{Range.tsx → RangeSlider.tsx} +17 -9
  163. package/src/input/Search.tsx +36 -25
  164. package/src/input/Slider.tsx +20 -11
  165. package/src/input/Switch.tsx +17 -6
  166. package/src/input/Textarea.tsx +34 -9
  167. package/src/internal/ErrorIcon.tsx +3 -3
  168. package/src/internal/FieldWrapper.tsx +8 -4
  169. package/src/internal/InputWrapper.tsx +56 -30
  170. package/src/select/AutoComplete.tsx +16 -6
  171. package/src/select/DualList.tsx +40 -22
  172. package/src/select/List.tsx +52 -8
  173. package/src/select/Options.tsx +50 -46
  174. package/src/select/Select.tsx +29 -10
  175. package/src/select/useSelect.tsx +26 -9
  176. package/src/types/index.ts +42 -10
  177. package/src/types/schema.ts +96 -41
  178. package/src/utils/eventHandlers.ts +14 -4
  179. package/dist/cjs/input/ArrayInput.module.css +0 -1
  180. package/dist/cjs/input/ColorPicker.module.css +0 -1
  181. package/dist/cjs/input/File.module.css +0 -1
  182. package/dist/cjs/internal/Field.module.css +0 -1
  183. package/dist/esm/input/ArrayInput.module.css +0 -1
  184. package/dist/esm/input/ColorPicker.module.css +0 -1
  185. package/dist/esm/input/File.module.css +0 -1
  186. package/dist/esm/internal/Field.module.css +0 -1
  187. package/dist/types/input/Range.d.ts +0 -59
@@ -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 { handleEnter } from "../utils";
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 ?? undefined);
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-20 resize-none"
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
- {...{ autoFocus }}
146
+ autoFocus={autoFocus}
124
147
  value={actualValue}
148
+ onBlur={onBlur}
149
+ onFocus={onFocus}
125
150
  onChange={handleChange}
126
- onKeyDown={handleEnter(onEnterPressed)}
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 { CoreIcons, Icon, Tooltip } from "@react-fabric/core";
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={CoreIcons.alert}
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 classes from "./Field.module.css";
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
- classes.fieldWrapper,
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 { FloatingPortal } from "@floating-ui/react";
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
- ?.querySelector<HTMLElement>("input,textarea,button")
77
- ?.focus();
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
- ?.querySelector<HTMLElement>("input,textarea,button")
87
- ?.focus();
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
- classes.inputWrapper,
106
- invalid && classes.invalidWrapper,
113
+ "fabric-inputWrapper",
114
+ invalid && "fabric-invalidWrapper",
107
115
  !noOutline &&
108
116
  !noBorder &&
109
- "bg-[var(--fabric-input)] outline overflow-hidden",
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-[99]"
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) => e.key === "Escape" && setExpanded(false)}
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
- classes.decorate,
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={CoreIcons.close}
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
- classes.inputClear,
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
- classes.decorate,
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
- {textarea && (
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={isExpanded ? CoreIcons.arrowCollapse : CoreIcons.arrowExpand}
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="flex-1 relative min-h-8"
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
- {!isExpanded && el}
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, CoreIcons, Icon } from "@react-fabric/core";
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
- submitRef.current?.click();
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 ? "!z-[99]" : ""}
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={CoreIcons.spinner}
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-[var(--z-popover)]"
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,
@@ -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 { Button, ButtonGroup, CoreIcons } from "@react-fabric/core";
25
- import { type RefProp } from "@react-fabric/core/dist/types/types";
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?: keyof T;
66
+ groupProperty?: NestedKeys<T>;
63
67
  /**
64
68
  * label property name
65
69
  */
66
- labelProperty?: keyof T;
70
+ labelProperty?: NestedKeys<T>;
67
71
  /**
68
72
  * value property name
69
73
  */
70
- valueProperty?: keyof T;
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) => React.ReactNode;
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={CoreIcons.chevronsLeft}
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={CoreIcons.chevronLeft}
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={CoreIcons.chevronRight}
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={CoreIcons.chevronsRight}
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}