@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,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> & { height?: string | number }) => {
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 (state.activeIndex === null) {
171
- setActiveIndex(state.selectedIndex);
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="group flex-1 py-1 px-2 truncate text-start flex gap-1 flex-wrap"
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) ?? item[labelProperty] ?? item}
327
+ {(item, label) => renderer?.(item) ?? label}
284
328
  </Options>
285
329
  </FloatingFocusManager>
286
330
  </Wrapper>
@@ -22,8 +22,8 @@
22
22
  */
23
23
 
24
24
  import { EmptyContent } from "@react-fabric/core";
25
+ import { type ReactElement } from "@react-fabric/core/dist/types/types";
25
26
  import classNames from "classnames";
26
- import { type ReactElement, type ReactNode } from "react";
27
27
  import { Trans } from "react-i18next";
28
28
 
29
29
  export const Options = ({
@@ -34,6 +34,7 @@ export const Options = ({
34
34
  className,
35
35
  itemRef,
36
36
  empty,
37
+ info,
37
38
  valueProperty = "id",
38
39
  labelProperty = "label",
39
40
  itemProps,
@@ -41,6 +42,7 @@ export const Options = ({
41
42
  ...rest
42
43
  }: {
43
44
  ref: AnyObject;
45
+ info?: AnyObject;
44
46
  itemRef: AnyObject;
45
47
  style?: AnyObject;
46
48
  items: AnyObject[];
@@ -50,34 +52,53 @@ export const Options = ({
50
52
  valueProperty?: AnyObject;
51
53
  labelProperty?: AnyObject;
52
54
  itemProps: (item: AnyObject) => AnyObject;
53
- children: (item: AnyObject, label: string) => ReactNode;
55
+ children: (item: AnyObject, label: string) => React.ReactNode;
54
56
  } & KeyValue) => {
55
57
  return (
56
58
  <div
57
59
  ref={ref}
58
- className={classNames(
59
- "bg-base select-none overflow-x-hidden overflow-y-auto",
60
- className,
61
- )}
60
+ className={classNames("bg-base select-none flex flex-col", className)}
62
61
  style={style}
63
62
  {...rest}
64
63
  >
65
- {items.length === 0 &&
66
- (empty ?? (
67
- <EmptyContent message={<Trans i18nKey="form:select.emptyList" />} />
68
- ))}
69
- {items.map((item, index) => {
70
- if (item.___group___)
71
- return (
72
- <p
73
- key={index}
74
- className="px-2 py-1 text-muted text-xs sticky -top-px z-2 bg-base border-t"
75
- >
76
- {item.label}
77
- </p>
78
- );
64
+ <div className={classNames("overflow-x-hidden overflow-y-auto flex-1")}>
65
+ {items.length === 0 &&
66
+ (empty ?? (
67
+ <EmptyContent message={<Trans i18nKey="form:select.emptyList" />} />
68
+ ))}
69
+ {items.map((item, index) => {
70
+ if (item.___group___)
71
+ return (
72
+ <p
73
+ key={index}
74
+ className="px-2 py-1 text-muted text-xs sticky -top-px z-2 bg-base border-t"
75
+ >
76
+ {item.label}
77
+ </p>
78
+ );
79
+
80
+ if (item.___create___)
81
+ return (
82
+ <div
83
+ role="none"
84
+ key={index}
85
+ tabIndex={active === index ? 0 : -1}
86
+ ref={(el: AnyObject) => itemRef(index, el)}
87
+ data-active={active === index ? true : undefined}
88
+ className={classNames(
89
+ "block text-start truncate px-2 py-1 leading-normal cursor-pointer select-none",
90
+ "data-active:bg-primary-200 data-selected:bg-primary-600 data-selected:text-white data-active:data-selected:underline",
91
+ )}
92
+ {...itemProps(item)}
93
+ >
94
+ <Trans
95
+ i18nKey="form:select.createOption"
96
+ values={{ query: item.value ?? item }}
97
+ components={[<strong key="t0">query</strong>]}
98
+ />
99
+ </div>
100
+ );
79
101
 
80
- if (item.___create___)
81
102
  return (
82
103
  <div
83
104
  role="none"
@@ -86,36 +107,19 @@ export const Options = ({
86
107
  ref={(el: AnyObject) => itemRef(index, el)}
87
108
  data-active={active === index ? true : undefined}
88
109
  className={classNames(
89
- "block text-start truncate px-2 py-1 leading-normal cursor-pointer select-none",
90
- "data-[active]:bg-primary-200 data-[selected]:bg-primary-600 data-[selected]:text-white data-[active]:data-[selected]:underline",
110
+ "block text-start truncate px-2 py-1 leading-normal cursor-pointer",
111
+ "data-active:bg-primary-200 data-selected:bg-primary-600 data-selected:text-white data-active:data-selected:underline",
91
112
  )}
92
113
  {...itemProps(item)}
93
114
  >
94
- <Trans
95
- i18nKey="form:select.createOption"
96
- values={{ query: item.value }}
97
- components={[<strong key="t0">query</strong>]}
98
- />
115
+ {children(item, item[labelProperty] ?? item)}
99
116
  </div>
100
117
  );
101
-
102
- return (
103
- <div
104
- role="none"
105
- key={index}
106
- tabIndex={active === index ? 0 : -1}
107
- ref={(el: AnyObject) => itemRef(index, el)}
108
- data-active={active === index ? true : undefined}
109
- className={classNames(
110
- "block text-start truncate px-2 py-1 leading-normal cursor-pointer",
111
- "data-[active]:bg-primary-200 data-[selected]:bg-primary-600 data-[selected]:text-white data-[active]:data-[selected]:underline",
112
- )}
113
- {...itemProps(item)}
114
- >
115
- {children(item, item[labelProperty] ?? item)}
116
- </div>
117
- );
118
- })}
118
+ })}
119
+ </div>
120
+ {info && (
121
+ <div className="px-2 py-1 text-sm bg-dimmed text-muted">{info}</div>
122
+ )}
119
123
  </div>
120
124
  );
121
125
  };
@@ -35,7 +35,7 @@ import {
35
35
  useListNavigation,
36
36
  useTypeahead,
37
37
  } from "@floating-ui/react";
38
- import { Chip, CoreIcons, Icon } from "@react-fabric/core";
38
+ import { Chip, Icon } from "@react-fabric/core";
39
39
  import { isArray, isEmpty } from "@react-fabric/utilities";
40
40
  import classNames from "classnames";
41
41
  import { useCallback, useId, useLayoutEffect, useMemo } from "react";
@@ -44,6 +44,14 @@ import { type SelectProps } from "../types";
44
44
  import { Options } from "./Options";
45
45
  import { useSelect } from "./useSelect";
46
46
 
47
+ /**
48
+ * Select component for selecting items from a dropdown list.
49
+ * It supports single and multiple selections, searching, creating new options,
50
+ * and custom rendering of options.
51
+ *
52
+ * @param {SelectProps<T>} props - The properties for the Select component.
53
+ * @returns {JSX.Element} The rendered Select component.
54
+ */
47
55
  export const Select = <T extends AnyObject = string>({
48
56
  ref,
49
57
  name,
@@ -56,12 +64,17 @@ export const Select = <T extends AnyObject = string>({
56
64
  autoFocus,
57
65
  error,
58
66
  options,
67
+ defaultOpen,
68
+ hideHandle,
59
69
  groupProperty,
60
70
  labelProperty = "label" as AnyObject,
61
71
  valueProperty = "id" as AnyObject,
72
+ sortProperty,
73
+ infoProperty,
62
74
  multiple,
63
75
  searchable,
64
76
  allowCreate,
77
+ emptyDisplay,
65
78
  createOption,
66
79
  matcher,
67
80
  renderer,
@@ -86,9 +99,11 @@ export const Select = <T extends AnyObject = string>({
86
99
  value,
87
100
  options,
88
101
  multiple,
102
+ defaultOpen,
89
103
  groupProperty,
90
104
  labelProperty,
91
105
  valueProperty,
106
+ sortProperty,
92
107
  allowCreate,
93
108
  createOption,
94
109
  matcher,
@@ -172,6 +187,9 @@ export const Select = <T extends AnyObject = string>({
172
187
  return false;
173
188
  } else if (state.query) {
174
189
  handleQuery("");
190
+ evt.preventDefault();
191
+ evt.stopPropagation();
192
+ return false;
175
193
  } else {
176
194
  onEnterPressed?.(evt);
177
195
  }
@@ -231,7 +249,7 @@ export const Select = <T extends AnyObject = string>({
231
249
  ));
232
250
  } else if (!isEmpty(state.value) && !state.query) {
233
251
  return (
234
- <span className="self-center pointer-events-none select-none">
252
+ <span className="self-center pointer-events-none select-none truncate leading-tight">
235
253
  {renderer?.(state.value) ?? state.value[labelProperty] ?? state.value}
236
254
  </span>
237
255
  );
@@ -263,7 +281,7 @@ export const Select = <T extends AnyObject = string>({
263
281
  required={required}
264
282
  error={error}
265
283
  wrapperRef={refs.setReference}
266
- className={state.open ? "!z-[99]" : ""}
284
+ className={state.open ? "z-99!" : ""}
267
285
  {...rest}
268
286
  >
269
287
  <div
@@ -290,15 +308,15 @@ export const Select = <T extends AnyObject = string>({
290
308
  ref={ref}
291
309
  autoFocus={autoFocus}
292
310
  className={classNames(
293
- "appearance-none bg-transparent outline-none border-none ring-0 flex-1",
311
+ "appearance-none bg-transparent outline-none border-none ring-0 flex-1 peer",
294
312
  disabled && "cursor-not-allowed pointer-events-none",
295
- multiple && "min-w-24",
313
+ multiple ? "min-w-24" : "min-w-2",
296
314
  )}
297
315
  placeholder={isEmpty(state.value) ? placeholder : ""}
298
316
  onChange={(evt) => handleQuery(evt.target.value)}
299
317
  />
300
318
  </div>
301
- {!state.loading && (
319
+ {!state.loading && !hideHandle && (
302
320
  <Icon
303
321
  className={classNames(
304
322
  "flex-content p-2 z-0 order-9 text-muted cursor-pointer self-stretch flex items-center h-auto",
@@ -306,16 +324,15 @@ export const Select = <T extends AnyObject = string>({
306
324
  disabled && "pointer-events-none",
307
325
  )}
308
326
  onClick={() => !disabled && openDropdown()}
309
- icon={CoreIcons.chevronDown}
327
+ icon="icon-[mdi--chevron-down]"
310
328
  />
311
329
  )}
312
330
  {state.loading && (
313
331
  <Icon
314
- animate="spin"
315
332
  className={classNames(
316
333
  "flex-content p-2 z-0 order-9 text-muted pointer-events-none",
317
334
  )}
318
- icon={CoreIcons.spinner}
335
+ icon="icon-[svg-spinners--eclipse]"
319
336
  />
320
337
  )}
321
338
  {state.open && !state.loading && (
@@ -327,7 +344,7 @@ export const Select = <T extends AnyObject = string>({
327
344
  >
328
345
  <Options
329
346
  ref={refs.setFloating}
330
- className="outline shadow-lg max-h-[40vh] z-[var(--z-popover)]"
347
+ className="outline shadow-lg max-h-[40vh] z-(--z-popover)"
331
348
  style={{
332
349
  width: refs.reference.current?.getBoundingClientRect().width,
333
350
  ...floatingStyles,
@@ -339,6 +356,8 @@ export const Select = <T extends AnyObject = string>({
339
356
  labelProperty={labelProperty}
340
357
  itemRef={setItemRef}
341
358
  itemProps={makeItemProps}
359
+ empty={emptyDisplay}
360
+ info={state.activeItem?.[infoProperty ?? "info"]}
342
361
  >
343
362
  {(item, label) => renderer?.(item) ?? label}
344
363
  </Options>
@@ -23,6 +23,7 @@
23
23
 
24
24
  import { useDebounce } from "@react-fabric/core";
25
25
  import {
26
+ compareValues,
26
27
  dedupe,
27
28
  EMPTY_ARRAY,
28
29
  groupBy,
@@ -64,6 +65,7 @@ interface State {
64
65
  open: boolean;
65
66
  loading: boolean;
66
67
  activeIndex: number | null;
68
+ activeItem: AnyObject;
67
69
  selectedIndex: number | null;
68
70
  value: AnyObject;
69
71
  // filter
@@ -76,11 +78,14 @@ export const useSelect = ({
76
78
  options = EMPTY_ARRAY,
77
79
  value,
78
80
  autoComplete,
81
+ alwaysOpen = false,
82
+ defaultOpen = false,
79
83
  multiple = false,
80
84
  allowCreate = false,
81
85
  groupProperty = "",
82
86
  valueProperty = "id",
83
87
  labelProperty = "label",
88
+ sortProperty,
84
89
  createOption = makeOption,
85
90
  matcher,
86
91
  onQuery,
@@ -90,10 +95,13 @@ export const useSelect = ({
90
95
  value?: AnyObject;
91
96
  options?: AnyObject[];
92
97
  multiple?: boolean;
98
+ alwaysOpen?: boolean;
99
+ defaultOpen?: boolean;
93
100
  allowCreate?: boolean;
94
101
  groupProperty?: AnyObject;
95
102
  valueProperty?: AnyObject;
96
103
  labelProperty?: AnyObject;
104
+ sortProperty?: AnyObject;
97
105
  autoComplete?: boolean;
98
106
  matcher?: (item: AnyObject, query: string) => boolean;
99
107
  createOption?: (query: string) => AnyObject;
@@ -144,12 +152,16 @@ export const useSelect = ({
144
152
  };
145
153
  }
146
154
  if (action.type === "close") {
147
- return { ...state, activeIndex: null, open: false };
155
+ return { ...state, activeIndex: null, activeItem: null, open: false };
148
156
  }
149
157
  if (action.type === "active" && state.open) {
150
158
  if (autoComplete && action.index !== null)
151
159
  state.query = state.items[action.index];
152
- return { ...state, activeIndex: action.index };
160
+ return {
161
+ ...state,
162
+ activeIndex: action.index,
163
+ activeItem: state.items[action.index ?? -1],
164
+ };
153
165
  }
154
166
  if (action.type === "setValue") {
155
167
  return {
@@ -165,6 +177,7 @@ export const useSelect = ({
165
177
  ...state,
166
178
  value: state.emptyValue,
167
179
  activeIndex: null,
180
+ activeItem: null,
168
181
  selectedIndex: autoComplete
169
182
  ? state.multiple
170
183
  ? action.value?.length
@@ -187,7 +200,7 @@ export const useSelect = ({
187
200
  newState.value = action.value;
188
201
  }
189
202
  }
190
- if (!state.multiple) newState.open = false;
203
+ if (!state.multiple) newState.open = alwaysOpen;
191
204
  fireChange(newState.value);
192
205
  return newState;
193
206
  }
@@ -223,6 +236,8 @@ export const useSelect = ({
223
236
  if (autoComplete && action.query !== undefined) {
224
237
  optionList = dedupe([action.query, ...optionList]);
225
238
  }
239
+ if (sortProperty)
240
+ optionList = optionList.sort(compareValues("asc", sortProperty));
226
241
  const grouped = groupBy(optionList, groupProperty, "");
227
242
  if (action.open) newState.open = true;
228
243
  newState.items = [];
@@ -230,7 +245,7 @@ export const useSelect = ({
230
245
  if (allowCreate && action.query)
231
246
  newState.items.push({
232
247
  ___create___: true,
233
- value: createOption(action.query),
248
+ value: action.query,
234
249
  });
235
250
  Object.entries(grouped).forEach(([key, list]) => {
236
251
  if (key) {
@@ -254,9 +269,10 @@ export const useSelect = ({
254
269
  {
255
270
  multiple,
256
271
  emptyValue,
257
- open: false,
272
+ open: alwaysOpen || defaultOpen,
258
273
  loading: false,
259
274
  activeIndex: null,
275
+ activeItem: null,
260
276
  selectedIndex: null,
261
277
  value: emptyValue,
262
278
  // filter
@@ -283,7 +299,7 @@ export const useSelect = ({
283
299
  dispatch({ type: "options", options: optionsRef.current });
284
300
  dispatch({
285
301
  type: "setValue",
286
- value: hasValue ?? newOption ?? "",
302
+ value: hasValue ?? newOption ?? deferred,
287
303
  });
288
304
  }
289
305
  if (multiple) {
@@ -295,12 +311,12 @@ export const useSelect = ({
295
311
  ? createOption(item)
296
312
  : undefined;
297
313
  const hasValue = options.find(
298
- (opt: AnyObject) => (opt[valueProperty] ?? opt) === deferred,
314
+ (opt: AnyObject) => (opt[valueProperty] ?? opt) === item,
299
315
  );
300
316
  if (!hasValue && newOption) {
301
317
  optionsRef.current.push(newOption);
302
318
  }
303
- return hasValue ?? newOption ?? "";
319
+ return hasValue ?? newOption ?? item;
304
320
  },
305
321
  );
306
322
  optionsRef.current = [...optionsRef.current, ...(options ?? [])];
@@ -334,6 +350,7 @@ export const useSelect = ({
334
350
  });
335
351
  },
336
352
  [onQuery],
353
+ 300,
337
354
  );
338
355
 
339
356
  // handlers
@@ -399,7 +416,7 @@ export const useSelect = ({
399
416
  }, []);
400
417
 
401
418
  const setActiveIndex = useCallback((index: number | null) => {
402
- dispatch({ type: "active", index });
419
+ index && index > -1 && dispatch({ type: "active", index });
403
420
  }, []);
404
421
 
405
422
  return {
@@ -22,6 +22,8 @@
22
22
  */
23
23
 
24
24
  import {
25
+ type NestedKeys,
26
+ type ReactElement,
25
27
  type RefProp,
26
28
  type TestProps,
27
29
  } from "@react-fabric/core/dist/types/types";
@@ -30,11 +32,15 @@ export interface InputProps<V = string> extends TestProps {
30
32
  /**
31
33
  * value
32
34
  */
33
- value?: V;
35
+ value?: V | null;
36
+ /**
37
+ * default value
38
+ */
39
+ defaultValue?: V | null;
34
40
  /**
35
41
  * change handler
36
42
  */
37
- onChange?: (value?: V) => void;
43
+ onChange?: (value: V | null) => void;
38
44
  /**
39
45
  * on focus event handler
40
46
  */
@@ -72,11 +78,15 @@ export interface InputProps<V = string> extends TestProps {
72
78
  *
73
79
  */
74
80
  decorateEnd?: JSX.Element | string | number | boolean;
81
+ decorateEndShowWhenEmpty?: boolean;
82
+ decorateEndHideWhenEmpty?: boolean;
75
83
  /**
76
84
  * info label at bottom
77
85
  */
78
86
  info?: string;
79
87
 
88
+ monospace?: boolean;
89
+
80
90
  /**
81
91
  * required
82
92
  */
@@ -120,9 +130,15 @@ export interface InputProps<V = string> extends TestProps {
120
130
  labelWidth?: string;
121
131
  }
122
132
 
123
- export interface BaseSelectProps<T>
124
- extends RefProp<HTMLInputElement>,
125
- Omit<InputProps, "value" | "onChange"> {
133
+ export interface BaseSelectProps<T extends AnyObject = string> {
134
+ /**
135
+ * open dropdown by default
136
+ */
137
+ defaultOpen?: boolean;
138
+ /**
139
+ * hide dropdown handle
140
+ */
141
+ hideHandle?: boolean;
126
142
  /**
127
143
  * list options
128
144
  */
@@ -130,15 +146,23 @@ export interface BaseSelectProps<T>
130
146
  /**
131
147
  * group by property name
132
148
  */
133
- groupProperty?: keyof T;
149
+ groupProperty?: NestedKeys<T>;
134
150
  /**
135
151
  * label property name
136
152
  */
137
- labelProperty?: keyof T;
153
+ labelProperty?: NestedKeys<T>;
154
+ /**
155
+ * value property name
156
+ */
157
+ valueProperty?: NestedKeys<T>;
158
+ /**
159
+ * value property name
160
+ */
161
+ infoProperty?: NestedKeys<T>;
138
162
  /**
139
163
  * value property name
140
164
  */
141
- valueProperty?: keyof T;
165
+ sortProperty?: NestedKeys<T> | true;
142
166
  /**
143
167
  * searchable list
144
168
  */
@@ -147,6 +171,10 @@ export interface BaseSelectProps<T>
147
171
  * allow create item
148
172
  */
149
173
  allowCreate?: boolean;
174
+ /**
175
+ * empty list display
176
+ */
177
+ emptyDisplay?: ReactElement;
150
178
  /**
151
179
  * create new item object
152
180
  */
@@ -158,14 +186,18 @@ export interface BaseSelectProps<T>
158
186
  /**
159
187
  * item renderer
160
188
  */
161
- renderer?: (option: T) => React.ReactNode;
189
+ renderer?: (option: T) => ReactElement;
162
190
  /**
163
191
  * item matcher
164
192
  */
165
193
  matcher?: (option: T, query: string) => boolean;
166
194
  }
167
195
 
168
- export type SelectProps<T> = BaseSelectProps<T> &
196
+ type BaseSelectType<T> = BaseSelectProps<T> &
197
+ RefProp<HTMLInputElement> &
198
+ Omit<InputProps, "value" | "onChange">;
199
+
200
+ export type SelectProps<T> = BaseSelectType<T> &
169
201
  (
170
202
  | {
171
203
  /**