@react-fabric/form 1.0.0-beta.5 → 1.0.0-beta.51

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 (174) 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} +64 -29
  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 +37 -25
  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 +62 -0
  10. package/dist/cjs/builder/components/SchemaArrayForm.js +143 -0
  11. package/dist/cjs/builder/components/SchemaField.js +414 -0
  12. package/dist/cjs/form/Controller.js +1 -1
  13. package/dist/cjs/form/Form.js +11 -6
  14. package/dist/cjs/hooks/useFormBuilder.js +27 -15
  15. package/dist/cjs/index.js +11 -2
  16. package/dist/cjs/input/ArrayInput.js +61 -33
  17. package/dist/cjs/input/AvatarInput.js +9 -8
  18. package/dist/cjs/input/Checkbox.js +6 -5
  19. package/dist/cjs/input/ColorInput.js +95 -56
  20. package/dist/cjs/input/ColorPicker.js +1 -35
  21. package/dist/cjs/input/DateInput.js +186 -0
  22. package/dist/cjs/input/Field.js +5 -4
  23. package/dist/cjs/input/File.js +4 -7
  24. package/dist/cjs/input/Hidden.js +3 -1
  25. package/dist/cjs/input/Input.js +11 -7
  26. package/dist/cjs/input/Number.js +9 -7
  27. package/dist/cjs/input/Password.js +10 -8
  28. package/dist/cjs/input/Radio.js +2 -2
  29. package/dist/cjs/input/RadioGroup.js +6 -3
  30. package/dist/cjs/input/{Range.js → RangeSlider.js} +5 -5
  31. package/dist/cjs/input/Search.js +16 -16
  32. package/dist/cjs/input/Slider.js +3 -3
  33. package/dist/cjs/input/Switch.js +30 -7
  34. package/dist/cjs/input/Textarea.js +13 -4
  35. package/dist/cjs/internal/ErrorIcon.js +1 -1
  36. package/dist/cjs/internal/FieldWrapper.js +11 -4
  37. package/dist/cjs/internal/InputWrapper.js +19 -16
  38. package/dist/cjs/select/AutoComplete.js +12 -8
  39. package/dist/cjs/select/DualList.js +4 -1
  40. package/dist/cjs/select/List.js +45 -20
  41. package/dist/cjs/select/Options.js +2 -2
  42. package/dist/cjs/select/Select.js +14 -4
  43. package/dist/cjs/select/useSelect.js +10 -6
  44. package/dist/cjs/types/schema.js +43 -0
  45. package/dist/cjs/utils/eventHandlers.js +11 -5
  46. package/dist/cjs/utils/getColor.js +6 -0
  47. package/dist/esm/builder/FileField.js +39 -27
  48. package/dist/esm/builder/FormField.js +50 -34
  49. package/dist/esm/builder/SchemaEditor.js +72 -0
  50. package/dist/esm/builder/components/AddSchemaFields.js +62 -0
  51. package/dist/esm/builder/components/SchemaArrayForm.js +143 -0
  52. package/dist/esm/builder/components/SchemaField.js +414 -0
  53. package/dist/esm/form/Controller.js +1 -1
  54. package/dist/esm/form/Form.js +11 -6
  55. package/dist/esm/hooks/useFormBuilder.js +25 -13
  56. package/dist/esm/index.js +5 -1
  57. package/dist/esm/input/ArrayInput.js +61 -33
  58. package/dist/esm/input/AvatarInput.js +9 -8
  59. package/dist/esm/input/Checkbox.js +6 -5
  60. package/dist/esm/input/ColorInput.js +97 -58
  61. package/dist/esm/input/ColorPicker.js +1 -35
  62. package/dist/esm/input/DateInput.js +186 -0
  63. package/dist/esm/input/Field.js +6 -5
  64. package/dist/esm/input/File.js +6 -9
  65. package/dist/esm/input/Hidden.js +3 -1
  66. package/dist/esm/input/Input.js +13 -9
  67. package/dist/esm/input/Number.js +11 -9
  68. package/dist/esm/input/Password.js +12 -10
  69. package/dist/esm/input/Radio.js +2 -2
  70. package/dist/esm/input/RadioGroup.js +7 -4
  71. package/dist/esm/input/{Range.js → RangeSlider.js} +5 -5
  72. package/dist/esm/input/Search.js +18 -18
  73. package/dist/esm/input/Slider.js +3 -3
  74. package/dist/esm/input/Switch.js +32 -9
  75. package/dist/esm/input/Textarea.js +14 -5
  76. package/dist/esm/internal/ErrorIcon.js +1 -1
  77. package/dist/esm/internal/FieldWrapper.js +11 -4
  78. package/dist/esm/internal/InputWrapper.js +19 -16
  79. package/dist/esm/select/AutoComplete.js +12 -8
  80. package/dist/esm/select/DualList.js +4 -1
  81. package/dist/esm/select/List.js +46 -21
  82. package/dist/esm/select/Options.js +2 -2
  83. package/dist/esm/select/Select.js +14 -4
  84. package/dist/esm/select/useSelect.js +11 -7
  85. package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
  86. package/dist/esm/utils/eventHandlers.js +11 -5
  87. package/dist/esm/utils/getColor.js +6 -1
  88. package/dist/types/builder/FileField.d.ts +4 -2
  89. package/dist/types/builder/FormField.d.ts +3 -1
  90. package/dist/types/builder/SchemaEditor.d.ts +2 -0
  91. package/dist/types/builder/components/AddSchemaFields.d.ts +2 -0
  92. package/dist/types/builder/components/SchemaArrayForm.d.ts +2 -0
  93. package/dist/types/builder/components/SchemaField.d.ts +10 -0
  94. package/dist/types/form/Controller.d.ts +2 -2
  95. package/dist/types/form/Form.d.ts +5 -1
  96. package/dist/types/hooks/useFormBuilder.d.ts +3 -1
  97. package/dist/types/index.d.ts +5 -2
  98. package/dist/types/input/ArrayInput.d.ts +39 -6
  99. package/dist/types/input/AvatarInput.d.ts +6 -3
  100. package/dist/types/input/Checkbox.d.ts +4 -4
  101. package/dist/types/input/ColorInput.d.ts +2 -2
  102. package/dist/types/input/ColorPicker.d.ts +1 -1
  103. package/dist/types/input/DateInput.d.ts +19 -0
  104. package/dist/types/input/Field.d.ts +0 -1
  105. package/dist/types/input/Input.d.ts +1 -1
  106. package/dist/types/input/Number.d.ts +1 -1
  107. package/dist/types/input/Password.d.ts +1 -1
  108. package/dist/types/input/Radio.d.ts +0 -1
  109. package/dist/types/input/RadioGroup.d.ts +18 -3
  110. package/dist/types/input/{Range.d.ts → RangeSlider.d.ts} +1 -1
  111. package/dist/types/input/Search.d.ts +5 -1
  112. package/dist/types/input/Switch.d.ts +7 -4
  113. package/dist/types/input/Textarea.d.ts +5 -1
  114. package/dist/types/internal/FieldWrapper.d.ts +1 -1
  115. package/dist/types/internal/InputWrapper.d.ts +1 -1
  116. package/dist/types/ranger/index.d.ts +2 -2
  117. package/dist/types/select/AutoComplete.d.ts +1 -1
  118. package/dist/types/select/DualList.d.ts +6 -2
  119. package/dist/types/select/List.d.ts +1 -1
  120. package/dist/types/select/Select.d.ts +1 -1
  121. package/dist/types/select/useSelect.d.ts +7 -15
  122. package/dist/types/types/index.d.ts +28 -7
  123. package/dist/types/types/schema.d.ts +78 -33
  124. package/dist/types/utils/eventHandlers.d.ts +1 -1
  125. package/package.json +15 -12
  126. package/src/builder/FileField.tsx +53 -27
  127. package/src/builder/FormField.tsx +52 -32
  128. package/src/builder/SchemaEditor.tsx +72 -0
  129. package/src/builder/components/AddSchemaFields.tsx +74 -0
  130. package/src/builder/components/SchemaArrayForm.tsx +158 -0
  131. package/src/builder/components/SchemaField.tsx +436 -0
  132. package/src/form/Controller.tsx +3 -3
  133. package/src/form/Form.tsx +14 -5
  134. package/src/hooks/useFormBuilder.tsx +29 -15
  135. package/src/index.ts +11 -2
  136. package/src/input/ArrayInput.tsx +130 -54
  137. package/src/input/AvatarInput.tsx +12 -8
  138. package/src/input/Checkbox.tsx +6 -5
  139. package/src/input/ColorInput.tsx +113 -69
  140. package/src/input/ColorPicker.tsx +2 -36
  141. package/src/input/DateInput.tsx +250 -0
  142. package/src/input/Field.tsx +10 -11
  143. package/src/input/File.tsx +7 -14
  144. package/src/input/Hidden.tsx +4 -1
  145. package/src/input/Input.tsx +15 -10
  146. package/src/input/Number.tsx +10 -10
  147. package/src/input/Password.tsx +13 -12
  148. package/src/input/Radio.tsx +2 -2
  149. package/src/input/RadioGroup.tsx +27 -4
  150. package/src/input/{Range.tsx → RangeSlider.tsx} +4 -4
  151. package/src/input/Search.tsx +20 -19
  152. package/src/input/Slider.tsx +3 -3
  153. package/src/input/Switch.tsx +43 -7
  154. package/src/input/Textarea.tsx +19 -5
  155. package/src/internal/ErrorIcon.tsx +1 -1
  156. package/src/internal/FieldWrapper.tsx +8 -4
  157. package/src/internal/InputWrapper.tsx +36 -20
  158. package/src/select/AutoComplete.tsx +5 -1
  159. package/src/select/DualList.tsx +8 -0
  160. package/src/select/List.tsx +41 -8
  161. package/src/select/Options.tsx +1 -1
  162. package/src/select/Select.tsx +13 -3
  163. package/src/select/useSelect.tsx +13 -5
  164. package/src/types/index.ts +34 -8
  165. package/src/types/schema.ts +96 -41
  166. package/src/utils/eventHandlers.ts +9 -3
  167. package/dist/cjs/input/ArrayInput.module.css +0 -1
  168. package/dist/cjs/input/ColorPicker.module.css +0 -1
  169. package/dist/cjs/input/File.module.css +0 -1
  170. package/dist/cjs/internal/Field.module.css +0 -1
  171. package/dist/esm/input/ArrayInput.module.css +0 -1
  172. package/dist/esm/input/ColorPicker.module.css +0 -1
  173. package/dist/esm/input/File.module.css +0 -1
  174. package/dist/esm/internal/Field.module.css +0 -1
@@ -21,11 +21,12 @@
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 { cloneChildren } from "@react-fabric/core";
24
25
  import {
25
- type CssProp,
26
26
  type ChildrenProp,
27
+ type CssProp,
27
28
  } from "@react-fabric/core/dist/types/types";
28
- import { Children, cloneElement } from "react";
29
+ import { cloneElement } from "react";
29
30
  import { FieldWrapper } from "../internal/FieldWrapper";
30
31
  import { type Radio } from "./Radio";
31
32
 
@@ -42,6 +43,23 @@ export interface RadioGroupProps extends ChildrenProp<typeof Radio>, CssProp {
42
43
  * field info
43
44
  */
44
45
  info?: string;
46
+
47
+ /**
48
+ * field width
49
+ */
50
+ width?: number | string;
51
+ /**
52
+ * inline label and input
53
+ */
54
+ inline?: boolean;
55
+ /**
56
+ * label width for inline
57
+ */
58
+ labelWidth?: string;
59
+ /**
60
+ * label width for inline
61
+ */
62
+ optionWidth?: string;
45
63
  /**
46
64
  * field name
47
65
  */
@@ -60,14 +78,19 @@ export const RadioGroup = ({
60
78
  children,
61
79
  name,
62
80
  className,
81
+ optionWidth,
63
82
  onChange,
64
83
  ...rest
65
84
  }: RadioGroupProps) => {
66
85
  return (
67
86
  <FieldWrapper {...rest}>
68
87
  <div className={className}>
69
- {Children.map(children, (child) =>
70
- cloneElement(child as AnyObject, { name, onChange }),
88
+ {cloneChildren(children, (child: AnyObject) =>
89
+ cloneElement(child, {
90
+ name,
91
+ onChange,
92
+ width: optionWidth,
93
+ }),
71
94
  )}
72
95
  </div>
73
96
  </FieldWrapper>
@@ -97,7 +97,7 @@ export interface RangeProps
97
97
  /**
98
98
  * Number Range input
99
99
  */
100
- export const Range = ({
100
+ export const RangeSlider = ({
101
101
  error,
102
102
  name,
103
103
  value,
@@ -141,7 +141,7 @@ export const Range = ({
141
141
  const handleChange = useCallback(
142
142
  (val: [number, number]) => {
143
143
  setActualValue(val ?? [min, max]);
144
- onChange?.(val ?? undefined);
144
+ onChange?.(val ?? null);
145
145
  },
146
146
  [onChange, min, max],
147
147
  );
@@ -190,7 +190,7 @@ export const Range = ({
190
190
  >
191
191
  <div
192
192
  className={classNames(
193
- "flex-auto order-3 flex items-center select-none z-0",
193
+ "flex-1 order-3 flex items-center select-none z-0",
194
194
  vertical && "flex-col w-8",
195
195
  invalid ? "text-danger-500" : "text-primary-600",
196
196
  )}
@@ -201,7 +201,7 @@ export const Range = ({
201
201
  <div
202
202
  ref={rangerInstance.trackElRef}
203
203
  className={classNames(
204
- "flex-auto relative flex items-center",
204
+ "flex-1 relative flex items-center",
205
205
  vertical ? "my-2 w-8" : "mx-2 h-8",
206
206
  )}
207
207
  style={{ height: vertical ? height : undefined }}
@@ -28,14 +28,13 @@ import {
28
28
  useCallback,
29
29
  useDeferredValue,
30
30
  useEffect,
31
- useMemo,
32
31
  useRef,
33
32
  useState,
34
33
  type ChangeEvent,
35
34
  } from "react";
36
35
  import { InputWrapper } from "../internal/InputWrapper";
37
36
  import { type InputProps } from "../types";
38
- import { handleEnter } from "../utils";
37
+ import { useHandleEnter } from "../utils";
39
38
 
40
39
  export interface SearchProps extends InputProps, RefProp<HTMLInputElement> {
41
40
  /**
@@ -46,6 +45,10 @@ export interface SearchProps extends InputProps, RefProp<HTMLInputElement> {
46
45
  * search on value change
47
46
  */
48
47
  searchOnChange?: boolean;
48
+ /**
49
+ * hide search action handle
50
+ */
51
+ hideHandle?: boolean;
49
52
  /**
50
53
  * search handler
51
54
  */
@@ -63,10 +66,11 @@ export const Search = ({
63
66
  searching,
64
67
  placeholder,
65
68
  autoFocus,
69
+ hideHandle,
66
70
  allowClear = true,
67
- searchOnChange = true,
68
- decorateEnd,
69
- decorateStart,
71
+ searchOnChange = false,
72
+ onBlur,
73
+ onFocus,
70
74
  onSearch,
71
75
  onChange,
72
76
  onEnterPressed,
@@ -82,10 +86,7 @@ export const Search = ({
82
86
  250,
83
87
  );
84
88
 
85
- const handleEnterPressed = useMemo(
86
- () => handleEnter(handleSearch),
87
- [handleSearch],
88
- );
89
+ const handleEnterPressed = useHandleEnter(handleSearch);
89
90
 
90
91
  useEffect(() => {
91
92
  setActualValue(deferred ?? "");
@@ -96,10 +97,10 @@ export const Search = ({
96
97
  const value = e?.target.value;
97
98
  refValue.current = value ?? "";
98
99
  setActualValue(value ?? "");
99
- onChange?.(value ?? undefined);
100
+ onChange?.(value ?? null);
100
101
 
101
- // value was cleared so force fire onSearch
102
- if (searchOnChange) {
102
+ // when e is undefined value was cleared so force fire onSearch
103
+ if (!e || searchOnChange) {
103
104
  handleSearch();
104
105
  }
105
106
  },
@@ -108,21 +109,19 @@ export const Search = ({
108
109
 
109
110
  return (
110
111
  <InputWrapper
111
- showClear={!!actualValue}
112
+ showClear={!!actualValue && !disabled && !readOnly}
112
113
  onClear={handleChange}
113
114
  allowClear={allowClear}
114
115
  invalid={invalid}
115
116
  readOnly={readOnly}
116
117
  disabled={disabled}
117
118
  required={required}
118
- decorateStart={decorateStart}
119
- decorateEnd={
119
+ actionButton={
120
120
  <Fragment>
121
- {decorateEnd}
122
121
  {searching && (
123
122
  <Icon className="p-2" icon={CoreIcons.spinner} animate="spin" />
124
123
  )}
125
- {!searching && (
124
+ {!searching && !hideHandle && (
126
125
  <Button
127
126
  tabIndex={-1}
128
127
  variant="link"
@@ -137,7 +136,7 @@ export const Search = ({
137
136
  {...rest}
138
137
  >
139
138
  <input
140
- className="appearance-none bg-transparent py-1 px-2 flex-1 border-none outline-none ring-0"
139
+ className="appearance-none bg-transparent py-1 px-2 flex-1 border-none outline-none ring-0 peer"
141
140
  aria-invalid={invalid}
142
141
  aria-disabled={disabled}
143
142
  aria-readonly={readOnly}
@@ -150,8 +149,10 @@ export const Search = ({
150
149
  type="search"
151
150
  ref={ref}
152
151
  autoComplete="off"
153
- {...{ autoFocus }}
152
+ autoFocus={autoFocus}
154
153
  value={actualValue}
154
+ onBlur={onBlur}
155
+ onFocus={onFocus}
155
156
  onChange={handleChange}
156
157
  onKeyDown={handleEnterPressed}
157
158
  />
@@ -146,7 +146,7 @@ export const Slider = ({
146
146
  (value: number) => {
147
147
  const val = useInfinity && value === max ? Infinity : value;
148
148
  setActualValue(val ?? 0);
149
- onChange?.(val ?? undefined);
149
+ onChange?.(val ?? null);
150
150
  },
151
151
  [onChange, max, useInfinity],
152
152
  );
@@ -194,7 +194,7 @@ export const Slider = ({
194
194
  >
195
195
  <div
196
196
  className={classNames(
197
- "flex-auto order-3 flex items-center select-none relative z-0",
197
+ "flex-1 order-3 flex items-center select-none relative z-0",
198
198
  vertical && "flex-col w-8",
199
199
  invalid ? "text-danger-500" : "text-primary-600",
200
200
  )}
@@ -205,7 +205,7 @@ export const Slider = ({
205
205
  <div
206
206
  ref={rangerInstance.trackElRef}
207
207
  className={classNames(
208
- "flex-auto relative flex items-center",
208
+ "flex-1 relative flex items-center",
209
209
  vertical ? "my-2 w-8" : "mx-2 h-8",
210
210
  )}
211
211
  style={{ height: vertical ? height : undefined }}
@@ -27,6 +27,7 @@ import {
27
27
  } from "@react-fabric/core/dist/types/types";
28
28
  import classNames from "classnames";
29
29
  import {
30
+ Fragment,
30
31
  useCallback,
31
32
  useDeferredValue,
32
33
  useEffect,
@@ -34,7 +35,7 @@ import {
34
35
  type ChangeEvent,
35
36
  } from "react";
36
37
  import { ErrorIcon } from "../internal/ErrorIcon";
37
- import { getBgClass, getColor } from "../utils";
38
+ import { getBgClass, getColor, getColorClass } from "../utils";
38
39
 
39
40
  export interface SwitchProps extends RefProp<HTMLInputElement> {
40
41
  /**
@@ -71,13 +72,19 @@ export interface SwitchProps extends RefProp<HTMLInputElement> {
71
72
  /**
72
73
  * cutom Switch icon
73
74
  */
74
- color?: ColorType;
75
+ color?: string | ColorType;
75
76
  /**
76
77
  * cutom Switch icon
77
78
  */
78
- defaultColor?: ColorType;
79
+ defaultColor?: string | ColorType;
79
80
 
80
81
  name?: string;
82
+
83
+ onLabel?: string;
84
+ onLabelColor?: string | ColorType;
85
+
86
+ offLabel?: string;
87
+ offLabelColor?: string | ColorType;
81
88
  }
82
89
 
83
90
  export const Switch = ({
@@ -90,7 +97,13 @@ export const Switch = ({
90
97
  width,
91
98
  color,
92
99
  name,
100
+ onLabel,
101
+ offLabel,
102
+ onLabelColor,
103
+ offLabelColor,
93
104
  defaultColor,
105
+ // @ts-expect-error ignore
106
+ required,
94
107
  onChange,
95
108
  ...rest
96
109
  }: SwitchProps) => {
@@ -125,7 +138,7 @@ export const Switch = ({
125
138
  actualValue && getBgClass(color ? `${color}-500` : "primary-500"),
126
139
  !actualValue &&
127
140
  (defaultColor ? getBgClass(`${defaultColor}-500`) : "bg-tint-200"),
128
- "relative flex h-6 w-10 cursor-pointer rounded-full p-1 transition-colors duration-200 ease-in-out",
141
+ "relative grid grid-cols-2 items-center h-6 min-w-10 cursor-pointer rounded-full p-1 transition-colors duration-200 ease-in-out",
129
142
  )}
130
143
  style={{
131
144
  backgroundColor: actualValue
@@ -154,14 +167,37 @@ export const Switch = ({
154
167
  aria-hidden="true"
155
168
  className={classNames(
156
169
  !disabled && "shadow-sm",
157
- actualValue ? "translate-x-4" : "translate-x-0",
158
- "pointer-events-none inline-block size-4 rounded-full bg-white ring-0 transition duration-200 ease-in-out",
170
+ actualValue ? "translate-x-[100%]" : "translate-x-0",
171
+ "pointer-events-none inline-block h-4 start-1 absolute rounded-full bg-white ring-0 transition duration-200 ease-in-out",
159
172
  )}
173
+ style={{
174
+ width: "calc(50% - 0.25rem)",
175
+ }}
160
176
  />
177
+ {(!!onLabel || !!offLabel) && (
178
+ <Fragment>
179
+ <span
180
+ className={`whitespace-nowrap text-xs px-1 ${getColorClass(onLabelColor ?? "white")}`}
181
+ style={{
182
+ color: getColor(onLabelColor),
183
+ }}
184
+ >
185
+ {onLabel ?? " "}
186
+ </span>
187
+ <span
188
+ className={`whitespace-nowrap text-xs px-1 ${getColorClass(offLabelColor ?? "base")}`}
189
+ style={{
190
+ color: getColor(offLabelColor),
191
+ }}
192
+ >
193
+ {offLabel ?? " "}
194
+ </span>
195
+ </Fragment>
196
+ )}
161
197
  </div>
162
198
  <ErrorIcon invalid={invalid} error={error} />
163
199
  {label && (
164
- <span className="flex-initial leading-5 truncate">{label}</span>
200
+ <span className="flex-initial leading-tight truncate">{label}</span>
165
201
  )}
166
202
  </label>
167
203
  );
@@ -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,6 +49,10 @@ 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
 
54
58
  export const Textarea = ({
@@ -64,6 +68,9 @@ export const Textarea = ({
64
68
  rows = 5,
65
69
  maxRows = 12,
66
70
  variableRows,
71
+ expandable,
72
+ onBlur,
73
+ onFocus,
67
74
  onChange,
68
75
  onEnterPressed,
69
76
  ...rest
@@ -81,7 +88,7 @@ export const Textarea = ({
81
88
  (e?: ChangeEvent<HTMLTextAreaElement>) => {
82
89
  const value = e?.target.value;
83
90
  setActualValue(value ?? "");
84
- onChange?.(value ?? undefined);
91
+ onChange?.(value ?? null);
85
92
  },
86
93
  [onChange],
87
94
  );
@@ -95,15 +102,20 @@ export const Textarea = ({
95
102
  }
96
103
  }, [actualValue, variableRows, rows, maxRows]);
97
104
 
105
+ const handleEnterPressed = useHandleEnter(onEnterPressed, {
106
+ preventDefault: !!onEnterPressed,
107
+ });
108
+
98
109
  return (
99
110
  <InputWrapper
100
- showClear={!!actualValue}
111
+ showClear={!!actualValue && !disabled && !readOnly}
101
112
  onClear={handleChange}
102
113
  invalid={invalid}
103
114
  readOnly={readOnly}
104
115
  disabled={disabled}
105
116
  required={required}
106
117
  wrapperRef={wrapperRef}
118
+ textareaExpandable={expandable}
107
119
  textarea
108
120
  {...rest}
109
121
  >
@@ -120,10 +132,12 @@ export const Textarea = ({
120
132
  name={name}
121
133
  ref={ref}
122
134
  autoComplete="off"
123
- {...{ autoFocus }}
135
+ autoFocus={autoFocus}
124
136
  value={actualValue}
137
+ onBlur={onBlur}
138
+ onFocus={onFocus}
125
139
  onChange={handleChange}
126
- onKeyDown={handleEnter(onEnterPressed)}
140
+ onKeyDown={handleEnterPressed}
127
141
  />
128
142
  </InputWrapper>
129
143
  );
@@ -48,7 +48,7 @@ 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"
@@ -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,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
@@ -102,12 +104,12 @@ export const InputWrapper = ({
102
104
  role="none"
103
105
  ref={wrapperRef}
104
106
  className={classNames(
105
- classes.inputWrapper,
106
- invalid && classes.invalidWrapper,
107
+ "fabric-inputWrapper",
108
+ invalid && "fabric-invalidWrapper",
107
109
  !noOutline &&
108
110
  !noBorder &&
109
- "bg-[var(--fabric-input)] outline overflow-hidden",
110
- "flex flex-nowrap items-center",
111
+ "bg-(--fabric-input) outline overflow-hidden",
112
+ "flex flex-nowrap items-center flex-1",
111
113
  textarea ? "rounded-capped group" : "rounded",
112
114
  isExpanded
113
115
  ? "fixed inset-4 z-[99]"
@@ -117,13 +119,17 @@ export const InputWrapper = ({
117
119
  !noOutline &&
118
120
  "after:absolute after:bottom-0 after:h-px after:bg-primary-500 after:mx-auto",
119
121
  )}
120
- onKeyDown={(e) => e.key === "Escape" && setExpanded(false)}
122
+ onKeyDown={(e) =>
123
+ e.key === "Escape" &&
124
+ isExpanded &&
125
+ (setExpanded(false), e.stopPropagation())
126
+ }
121
127
  >
122
128
  {decorateStart && (
123
129
  <div
124
130
  onClickCapture={doFocus}
125
131
  className={classNames(
126
- classes.decorate,
132
+ "fabric-decorate",
127
133
  "text-muted leading-none flex-content",
128
134
  isString(decorateStart) && "px-1",
129
135
  )}
@@ -139,7 +145,7 @@ export const InputWrapper = ({
139
145
  onClick={handleClear}
140
146
  className={classNames(
141
147
  "text-tint-500 hover:text-tint-700 p-1 z-1 flex-content",
142
- classes.inputClear,
148
+ "fabric-inputClear",
143
149
  textarea && "self-start mt-1 me-1",
144
150
  )}
145
151
  />
@@ -148,15 +154,29 @@ export const InputWrapper = ({
148
154
  <div
149
155
  onClickCapture={doFocus}
150
156
  className={classNames(
151
- classes.decorate,
157
+ "fabric-decorate",
152
158
  "text-muted leading-none flex-content",
153
159
  isString(decorateEnd) && "px-1",
160
+ decorateEndHideWhenEmpty && "peer-placeholder-shown:hidden",
161
+ decorateEndShowWhenEmpty &&
162
+ "hidden peer-placeholder-shown:inline-block",
154
163
  )}
155
164
  >
156
165
  {decorateEnd}
157
166
  </div>
158
167
  )}
159
- {textarea && (
168
+ {actionButton && (
169
+ <div
170
+ onClickCapture={doFocus}
171
+ className={classNames(
172
+ "fabric-decorate",
173
+ "text-muted leading-none flex-content",
174
+ )}
175
+ >
176
+ {actionButton}
177
+ </div>
178
+ )}
179
+ {textareaExpandable && (
160
180
  <Icon
161
181
  onClick={() => setExpanded(!isExpanded)}
162
182
  icon={isExpanded ? CoreIcons.arrowCollapse : CoreIcons.arrowExpand}
@@ -170,17 +190,13 @@ export const InputWrapper = ({
170
190
  <Wrapper {...wrapperProps}>
171
191
  <div
172
192
  ref={elRef}
173
- className="flex-1 relative min-h-8"
193
+ className={classNames(
194
+ "relative min-h-8 flex flex-col flex-nowrap",
195
+ width ? "flex-auto" : "flex-1",
196
+ )}
174
197
  style={{ width, flexBasis: width }}
175
198
  >
176
- {!isExpanded && el}
177
- {isExpanded && (
178
- <FloatingPortal
179
- root={elRef.current?.closest<HTMLElement>(".theme-base")}
180
- >
181
- {el}
182
- </FloatingPortal>
183
- )}
199
+ {el}
184
200
  </div>
185
201
  </Wrapper>
186
202
  );
@@ -80,6 +80,7 @@ export const AutoComplete = ({
80
80
  onInput,
81
81
  onFocus,
82
82
  onBlur,
83
+ onQuery,
83
84
  onEnterPressed,
84
85
  ...rest
85
86
  }: AutoCompleteProps) => {
@@ -104,6 +105,7 @@ export const AutoComplete = ({
104
105
  multiple,
105
106
  onChange,
106
107
  onSelect,
108
+ onQuery,
107
109
  });
108
110
 
109
111
  const dropdownKey = useId();
@@ -291,7 +293,9 @@ export const AutoComplete = ({
291
293
  data-testid={name}
292
294
  name={name}
293
295
  size={1}
296
+ ref={ref}
294
297
  onInput={onInput}
298
+ autoFocus={autoFocus}
295
299
  rows={!multiple && expandOnEdit && state.open ? 5 : 1}
296
300
  className={classNames(
297
301
  "appearance-none bg-transparent outline-none border-none ring-0 flex-1 resize-none",
@@ -328,7 +332,7 @@ export const AutoComplete = ({
328
332
  >
329
333
  <Options
330
334
  ref={refs.setFloating}
331
- className="outline shadow-lg max-h-[40vh] z-[var(--z-popover)]"
335
+ className="outline shadow-lg max-h-[40vh] z-(--z-popover)"
332
336
  style={{
333
337
  width: refs.reference.current?.getBoundingClientRect().width,
334
338
  ...floatingStyles,
@@ -24,6 +24,7 @@
24
24
  import { Button, ButtonGroup, CoreIcons } from "@react-fabric/core";
25
25
  import { type RefProp } from "@react-fabric/core/dist/types/types";
26
26
  import {
27
+ type ReactElement,
27
28
  useCallback,
28
29
  useDeferredValue,
29
30
  useEffect,
@@ -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
  */
@@ -82,6 +87,7 @@ export const DualList = <T extends AnyObject = string>({
82
87
  ref,
83
88
  value,
84
89
  options,
90
+ emptyDisplay,
85
91
  groupProperty,
86
92
  height = "18rem",
87
93
  labelProperty = "label" as AnyObject,
@@ -210,6 +216,7 @@ export const DualList = <T extends AnyObject = string>({
210
216
  error={error}
211
217
  height={height}
212
218
  options={leftOptions}
219
+ emptyDisplay={emptyDisplay}
213
220
  groupProperty={groupProperty}
214
221
  labelProperty={labelProperty}
215
222
  valueProperty={valueProperty}
@@ -268,6 +275,7 @@ export const DualList = <T extends AnyObject = string>({
268
275
  allowClear
269
276
  height={height}
270
277
  options={rightOptions}
278
+ emptyDisplay={emptyDisplay}
271
279
  groupProperty={groupProperty}
272
280
  labelProperty={labelProperty}
273
281
  valueProperty={valueProperty}