@react-fabric/form 1.0.0-beta.9 → 1.0.2

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 (215) hide show
  1. package/{src/input/ArrayInput.module.css → css/_arrayInput.css} +8 -8
  2. package/css/_colorPicker.css +95 -0
  3. package/{src/internal/Field.module.css → css/_field.css} +94 -33
  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 +107 -78
  7. package/dist/cjs/builder/FormField.js +60 -39
  8. package/dist/cjs/builder/SchemaEditor.js +72 -0
  9. package/dist/cjs/builder/components/AddSchemaFields.js +64 -0
  10. package/dist/cjs/builder/components/SchemaArrayForm.js +146 -0
  11. package/dist/cjs/builder/components/SchemaField.js +423 -0
  12. package/dist/cjs/form/Controller.js +66 -19
  13. package/dist/cjs/form/Form.js +97 -39
  14. package/dist/cjs/form/useForm.js +32 -0
  15. package/dist/cjs/hooks/useFileUploader.js +74 -45
  16. package/dist/cjs/hooks/useFormBuilder.js +85 -31
  17. package/dist/cjs/i18n/{ar.json.js → ar/form.json.js} +5 -4
  18. package/dist/cjs/i18n/{en.json.js → en/form.json.js} +5 -5
  19. package/dist/cjs/i18n/index.js +4 -4
  20. package/dist/cjs/index.js +41 -2
  21. package/dist/cjs/input/ArrayInput.js +259 -135
  22. package/dist/cjs/input/AvatarInput.js +99 -63
  23. package/dist/cjs/input/Checkbox.js +24 -25
  24. package/dist/cjs/input/ColorInput.js +138 -81
  25. package/dist/cjs/input/ColorPicker.js +78 -95
  26. package/dist/cjs/input/DateInput.js +192 -0
  27. package/dist/cjs/input/Field.js +21 -20
  28. package/dist/cjs/input/File.js +85 -80
  29. package/dist/cjs/input/Hidden.js +27 -13
  30. package/dist/cjs/input/Input.js +49 -48
  31. package/dist/cjs/input/Number.js +46 -44
  32. package/dist/cjs/input/Password.js +51 -49
  33. package/dist/cjs/input/Radio.js +26 -16
  34. package/dist/cjs/input/RadioGroup.js +26 -11
  35. package/dist/cjs/input/{Range.js → RangeSlider.js} +61 -66
  36. package/dist/cjs/input/Search.js +58 -64
  37. package/dist/cjs/input/Slider.js +57 -66
  38. package/dist/cjs/input/Switch.js +29 -27
  39. package/dist/cjs/input/Textarea.js +65 -53
  40. package/dist/cjs/internal/ErrorIcon.js +9 -9
  41. package/dist/cjs/internal/FieldWrapper.js +23 -13
  42. package/dist/cjs/internal/InputWrapper.js +85 -87
  43. package/dist/cjs/ranger/index.js +7 -7
  44. package/dist/cjs/ranger/utils.js +9 -4
  45. package/dist/cjs/select/AutoComplete.js +93 -76
  46. package/dist/cjs/select/DualList.js +235 -154
  47. package/dist/cjs/select/List.js +122 -89
  48. package/dist/cjs/select/Options.js +58 -54
  49. package/dist/cjs/select/Select.js +119 -89
  50. package/dist/cjs/select/helpers.js +5 -5
  51. package/dist/cjs/select/useSelect.js +83 -58
  52. package/dist/cjs/types/schema.js +43 -0
  53. package/dist/cjs/utils/eventHandlers.js +16 -6
  54. package/dist/cjs/utils/getColor.js +6 -6
  55. package/dist/cjs/utils/getProps.js +49 -0
  56. package/dist/esm/builder/FileField.js +109 -80
  57. package/dist/esm/builder/FormField.js +60 -39
  58. package/dist/esm/builder/SchemaEditor.js +72 -0
  59. package/dist/esm/builder/components/AddSchemaFields.js +64 -0
  60. package/dist/esm/builder/components/SchemaArrayForm.js +146 -0
  61. package/dist/esm/builder/components/SchemaField.js +423 -0
  62. package/dist/esm/form/Controller.js +66 -19
  63. package/dist/esm/form/Form.js +97 -39
  64. package/dist/esm/form/useForm.js +32 -0
  65. package/dist/esm/hooks/useFileUploader.js +75 -46
  66. package/dist/esm/hooks/useFormBuilder.js +84 -30
  67. package/dist/esm/i18n/{ar.json.js → ar/form.json.js} +5 -4
  68. package/dist/esm/i18n/{en.json.js → en/form.json.js} +5 -5
  69. package/dist/esm/i18n/index.js +2 -2
  70. package/dist/esm/index.js +36 -1
  71. package/dist/esm/input/ArrayInput.js +260 -136
  72. package/dist/esm/input/AvatarInput.js +99 -63
  73. package/dist/esm/input/Checkbox.js +26 -27
  74. package/dist/esm/input/ColorInput.js +141 -84
  75. package/dist/esm/input/ColorPicker.js +80 -97
  76. package/dist/esm/input/DateInput.js +192 -0
  77. package/dist/esm/input/Field.js +21 -20
  78. package/dist/esm/input/File.js +89 -84
  79. package/dist/esm/input/Hidden.js +28 -14
  80. package/dist/esm/input/Input.js +51 -50
  81. package/dist/esm/input/Number.js +48 -46
  82. package/dist/esm/input/Password.js +54 -52
  83. package/dist/esm/input/Radio.js +27 -17
  84. package/dist/esm/input/RadioGroup.js +27 -12
  85. package/dist/esm/input/{Range.js → RangeSlider.js} +62 -67
  86. package/dist/esm/input/Search.js +61 -67
  87. package/dist/esm/input/Slider.js +58 -67
  88. package/dist/esm/input/Switch.js +31 -29
  89. package/dist/esm/input/Textarea.js +67 -55
  90. package/dist/esm/internal/ErrorIcon.js +11 -11
  91. package/dist/esm/internal/FieldWrapper.js +23 -13
  92. package/dist/esm/internal/InputWrapper.js +88 -90
  93. package/dist/esm/ranger/index.js +7 -7
  94. package/dist/esm/ranger/utils.js +9 -4
  95. package/dist/esm/select/AutoComplete.js +95 -78
  96. package/dist/esm/select/DualList.js +233 -152
  97. package/dist/esm/select/List.js +125 -92
  98. package/dist/esm/select/Options.js +58 -54
  99. package/dist/esm/select/Select.js +122 -92
  100. package/dist/esm/select/helpers.js +5 -5
  101. package/dist/esm/select/useSelect.js +84 -59
  102. package/{src/input/ColorPicker.module.css → dist/esm/types/schema.js} +18 -27
  103. package/dist/esm/utils/eventHandlers.js +16 -6
  104. package/dist/esm/utils/getColor.js +6 -6
  105. package/dist/esm/utils/getProps.js +49 -0
  106. package/dist/types/builder/FileField.d.ts +7 -4
  107. package/dist/types/builder/FormField.d.ts +4 -2
  108. package/dist/types/builder/SchemaEditor.d.ts +6 -0
  109. package/dist/types/builder/components/AddSchemaFields.d.ts +2 -0
  110. package/dist/types/builder/components/SchemaArrayForm.d.ts +2 -0
  111. package/dist/types/builder/components/SchemaField.d.ts +12 -0
  112. package/dist/types/form/Controller.d.ts +45 -3
  113. package/dist/types/form/Form.d.ts +38 -7
  114. package/dist/types/form/useForm.d.ts +6 -0
  115. package/dist/types/hooks/useFileUploader.d.ts +24 -3
  116. package/dist/types/hooks/useFormBuilder.d.ts +41 -3
  117. package/dist/types/index.d.ts +32 -2
  118. package/dist/types/input/ArrayInput.d.ts +68 -13
  119. package/dist/types/input/AvatarInput.d.ts +23 -6
  120. package/dist/types/input/Checkbox.d.ts +12 -10
  121. package/dist/types/input/ColorInput.d.ts +15 -5
  122. package/dist/types/input/ColorPicker.d.ts +10 -2
  123. package/dist/types/input/DateInput.d.ts +21 -0
  124. package/dist/types/input/Field.d.ts +9 -36
  125. package/dist/types/input/File.d.ts +16 -4
  126. package/dist/types/input/Hidden.d.ts +7 -6
  127. package/dist/types/input/Input.d.ts +11 -4
  128. package/dist/types/input/Number.d.ts +9 -4
  129. package/dist/types/input/Password.d.ts +9 -4
  130. package/dist/types/input/Radio.d.ts +9 -4
  131. package/dist/types/input/RadioGroup.d.ts +14 -6
  132. package/dist/types/input/{Range.d.ts → RangeSlider.d.ts} +11 -6
  133. package/dist/types/input/Search.d.ts +14 -4
  134. package/dist/types/input/Slider.d.ts +9 -6
  135. package/dist/types/input/Switch.d.ts +13 -8
  136. package/dist/types/input/Textarea.d.ts +15 -4
  137. package/dist/types/internal/ErrorIcon.d.ts +4 -1
  138. package/dist/types/internal/FieldWrapper.d.ts +10 -1
  139. package/dist/types/internal/InputWrapper.d.ts +19 -1
  140. package/dist/types/ranger/index.d.ts +7 -3
  141. package/dist/types/ranger/utils.d.ts +2 -2
  142. package/dist/types/select/AutoComplete.d.ts +7 -2
  143. package/dist/types/select/DualList.d.ts +11 -37
  144. package/dist/types/select/List.d.ts +7 -2
  145. package/dist/types/select/Options.d.ts +8 -7
  146. package/dist/types/select/Select.d.ts +7 -2
  147. package/dist/types/select/helpers.d.ts +2 -2
  148. package/dist/types/select/useSelect.d.ts +10 -28
  149. package/dist/types/types/index.d.ts +73 -39
  150. package/dist/types/types/schema.d.ts +78 -32
  151. package/dist/types/utils/eventHandlers.d.ts +4 -4
  152. package/dist/types/utils/getColor.d.ts +4 -4
  153. package/dist/types/utils/getProps.d.ts +26 -0
  154. package/dist/types/utils/index.d.ts +1 -0
  155. package/package.json +25 -25
  156. package/src/builder/FileField.tsx +73 -56
  157. package/src/builder/FormField.tsx +66 -58
  158. package/src/builder/SchemaEditor.tsx +78 -0
  159. package/src/builder/components/AddSchemaFields.tsx +64 -0
  160. package/src/builder/components/SchemaArrayForm.tsx +146 -0
  161. package/src/builder/components/SchemaField.tsx +405 -0
  162. package/src/form/Controller.tsx +61 -24
  163. package/src/form/Form.tsx +74 -50
  164. package/src/form/useForm.ts +53 -0
  165. package/src/hooks/useFileUploader.ts +115 -84
  166. package/src/hooks/useFormBuilder.tsx +80 -41
  167. package/src/i18n/{ar.json → ar/form.json} +5 -4
  168. package/src/i18n/{en.json → en/form.json} +5 -5
  169. package/src/i18n/index.ts +2 -2
  170. package/src/index.ts +34 -20
  171. package/src/input/ArrayInput.tsx +255 -173
  172. package/src/input/AvatarInput.tsx +82 -39
  173. package/src/input/Checkbox.tsx +30 -54
  174. package/src/input/ColorInput.tsx +146 -105
  175. package/src/input/ColorPicker.tsx +76 -102
  176. package/src/input/DateInput.tsx +225 -0
  177. package/src/input/Field.tsx +23 -65
  178. package/src/input/File.tsx +76 -98
  179. package/src/input/Hidden.tsx +36 -24
  180. package/src/input/Input.tsx +55 -53
  181. package/src/input/Number.tsx +49 -54
  182. package/src/input/Password.tsx +61 -66
  183. package/src/input/Radio.tsx +28 -37
  184. package/src/input/RadioGroup.tsx +30 -25
  185. package/src/input/{Range.tsx → RangeSlider.tsx} +81 -109
  186. package/src/input/Search.tsx +64 -77
  187. package/src/input/Slider.tsx +80 -109
  188. package/src/input/Switch.tsx +39 -53
  189. package/src/input/Textarea.tsx +72 -59
  190. package/src/internal/ErrorIcon.tsx +22 -21
  191. package/src/internal/FieldWrapper.tsx +33 -23
  192. package/src/internal/InputWrapper.tsx +132 -143
  193. package/src/ranger/index.tsx +37 -117
  194. package/src/ranger/utils.ts +12 -11
  195. package/src/select/AutoComplete.tsx +113 -123
  196. package/src/select/DualList.tsx +218 -204
  197. package/src/select/List.tsx +130 -97
  198. package/src/select/Options.tsx +59 -57
  199. package/src/select/Select.tsx +148 -122
  200. package/src/select/helpers.ts +13 -8
  201. package/src/select/useSelect.tsx +110 -127
  202. package/src/types/index.ts +76 -45
  203. package/src/types/schema.ts +97 -40
  204. package/src/utils/eventHandlers.ts +24 -26
  205. package/src/utils/getColor.ts +9 -8
  206. package/src/utils/getProps.ts +47 -0
  207. package/src/utils/index.ts +1 -0
  208. package/dist/cjs/input/ArrayInput.module.css +0 -1
  209. package/dist/cjs/input/ColorPicker.module.css +0 -1
  210. package/dist/cjs/input/File.module.css +0 -1
  211. package/dist/cjs/internal/Field.module.css +0 -1
  212. package/dist/esm/input/ArrayInput.module.css +0 -1
  213. package/dist/esm/input/ColorPicker.module.css +0 -1
  214. package/dist/esm/input/File.module.css +0 -1
  215. package/dist/esm/internal/Field.module.css +0 -1
@@ -21,23 +21,14 @@
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, CoreIcons, Icon, useDebounce } from "@react-fabric/core";
25
- import { type RefProp } from "@react-fabric/core/dist/types/types";
26
- import {
27
- Fragment,
28
- useCallback,
29
- useDeferredValue,
30
- useEffect,
31
- useMemo,
32
- useRef,
33
- useState,
34
- type ChangeEvent,
35
- } from "react";
24
+ import { Button, Icon, useControlledValue, useDebounce } from "@react-fabric/core";
25
+ import type { RefProp } from "@react-fabric/core/dist/types/types";
26
+ import { Fragment, useCallback, useId, useRef } from "react";
36
27
  import { InputWrapper } from "../internal/InputWrapper";
37
- import { type InputProps } from "../types";
38
- import { handleEnter } from "../utils";
28
+ import type { InputProps, WrapperProps } from "../types";
29
+ import { getWrapperProps, useHandleEnter } from "../utils";
39
30
 
40
- export interface SearchProps extends InputProps, RefProp<HTMLInputElement> {
31
+ export interface SearchProps extends InputProps, WrapperProps, RefProp<HTMLInputElement> {
41
32
  /**
42
33
  * is searching
43
34
  */
@@ -46,98 +37,92 @@ export interface SearchProps extends InputProps, RefProp<HTMLInputElement> {
46
37
  * search on value change
47
38
  */
48
39
  searchOnChange?: boolean;
40
+ /**
41
+ * hide search action handle
42
+ */
43
+ hideHandle?: boolean;
49
44
  /**
50
45
  * search handler
51
46
  */
52
47
  onSearch?: (query?: string) => void;
53
48
  }
54
49
 
55
- export const Search = ({
56
- ref,
57
- value,
58
- name,
59
- invalid,
60
- readOnly,
61
- disabled,
62
- required,
63
- searching,
64
- placeholder,
65
- autoFocus,
66
- allowClear = true,
67
- searchOnChange = true,
68
- decorateEnd,
69
- decorateStart,
70
- onSearch,
71
- onChange,
72
- onEnterPressed,
73
- ...rest
74
- }: SearchProps) => {
75
- const [actualValue, setActualValue] = useState("");
76
- const deferred = useDeferredValue(value);
50
+ /**
51
+ * Search input component with optional searching state and search handler.
52
+ * This component allows users to input search queries and trigger a search action.
53
+ * It supports features like searching on value change, showing a search handle,
54
+ * and handling search actions with a debounce mechanism.
55
+ */
56
+ export function Search(props: SearchProps) {
57
+ const {
58
+ value,
59
+ defaultValue,
60
+ onSearch,
61
+ onChange,
62
+ onBlur,
63
+ onFocus,
64
+ hideHandle,
65
+ disabled,
66
+ readOnly,
67
+ invalid,
68
+ searching,
69
+ searchOnChange,
70
+ placeholder,
71
+ name,
72
+ autoFocus,
73
+ allowClear,
74
+ ref,
75
+ } = props;
77
76
 
78
- const refValue = useRef("");
79
- const handleSearch = useDebounce(
80
- () => onSearch?.(refValue.current),
81
- [onSearch],
82
- 250,
83
- );
77
+ const id = useId();
78
+ const { currentValue, updateValue } = useControlledValue(value, defaultValue);
84
79
 
85
- const handleEnterPressed = useMemo(
86
- () => handleEnter(handleSearch),
87
- [handleSearch],
88
- );
80
+ const valueRef = useRef("");
81
+ const handleSearch = useDebounce(() => onSearch?.(valueRef.current), [onSearch], 250);
89
82
 
90
- useEffect(() => {
91
- setActualValue(deferred ?? "");
92
- }, [deferred]);
83
+ const handleEnterPressed = useHandleEnter(handleSearch);
93
84
 
94
85
  const handleChange = useCallback(
95
- (e?: ChangeEvent<HTMLInputElement>) => {
96
- const value = e?.target.value;
97
- refValue.current = value ?? "";
98
- setActualValue(value ?? "");
99
- onChange?.(value ?? undefined);
86
+ (e?: React.ChangeEvent<HTMLInputElement>) => {
87
+ const value = e?.target.value ?? defaultValue;
88
+ valueRef.current = value ?? "";
89
+ updateValue(value);
90
+ onChange?.(value);
100
91
 
101
- // value was cleared so force fire onSearch
102
- if (searchOnChange) {
92
+ // when e is undefined value was cleared so force fire onSearch
93
+ if (!e || searchOnChange) {
103
94
  handleSearch();
104
95
  }
105
96
  },
106
- [onChange, searchOnChange, handleSearch],
97
+ [defaultValue, updateValue, onChange, searchOnChange, handleSearch],
107
98
  );
108
99
 
109
100
  return (
110
101
  <InputWrapper
111
- showClear={!!actualValue}
102
+ id={id}
103
+ showClear={allowClear && (currentValue ?? "") !== (defaultValue ?? "") && !disabled && !readOnly}
112
104
  onClear={handleChange}
113
- allowClear={allowClear}
114
- invalid={invalid}
115
- readOnly={readOnly}
116
- disabled={disabled}
117
- required={required}
118
- decorateStart={decorateStart}
119
- decorateEnd={
105
+ hasStepper
106
+ {...getWrapperProps(props)}
107
+ actionButton={
120
108
  <Fragment>
121
- {decorateEnd}
122
- {searching && (
123
- <Icon className="p-2" icon={CoreIcons.spinner} animate="spin" />
124
- )}
125
- {!searching && (
109
+ {searching && <Icon className="p-2" icon="icon-[svg-spinners--eclipse]" />}
110
+ {!searching && !hideHandle && (
126
111
  <Button
127
112
  tabIndex={-1}
128
113
  variant="link"
129
114
  aria-label="search"
130
- disabled={!actualValue}
131
- icon={CoreIcons.search}
115
+ disabled={!currentValue}
116
+ icon="icon-[mdi--magnify]"
132
117
  onClick={handleSearch}
133
118
  />
134
119
  )}
135
120
  </Fragment>
136
121
  }
137
- {...rest}
138
122
  >
139
123
  <input
140
- className="appearance-none bg-transparent py-1 px-2 flex-1 border-none outline-none ring-0"
124
+ id={id}
125
+ className="appearance-none bg-transparent py-1 px-4 not-first:ps-2 not-last:pe-2 flex-1 border-none outline-none ring-0 peer"
141
126
  aria-invalid={invalid}
142
127
  aria-disabled={disabled}
143
128
  aria-readonly={readOnly}
@@ -151,10 +136,12 @@ export const Search = ({
151
136
  ref={ref}
152
137
  autoComplete="off"
153
138
  {...{ autoFocus }}
154
- value={actualValue}
139
+ value={currentValue ?? ""}
140
+ onBlur={onBlur}
141
+ onFocus={onFocus}
155
142
  onChange={handleChange}
156
143
  onKeyDown={handleEnterPressed}
157
144
  />
158
145
  </InputWrapper>
159
146
  );
160
- };
147
+ }
@@ -21,22 +21,15 @@
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 { Format } from "@react-fabric/utilities";
25
- import classNames from "classnames";
26
- import {
27
- Fragment,
28
- useCallback,
29
- useDeferredValue,
30
- useEffect,
31
- useMemo,
32
- useState,
33
- } from "react";
24
+ import { Format, cn } from "@react-fabric/utilities";
25
+ import { Fragment, useCallback, useDeferredValue, useEffect, useMemo, useState } from "react";
34
26
  import { InputWrapper } from "../internal/InputWrapper";
35
27
  import { useRanger } from "../ranger";
36
- import { type InputProps } from "../types";
28
+ import type { InputProps, WrapperProps } from "../types";
29
+ import { getWrapperProps } from "../utils";
37
30
 
38
31
  export interface SliderProps
39
- extends Omit<InputProps<number>, "placeholder" | "allowClear" | "children"> {
32
+ extends Omit<InputProps<number>, "placeholder" | "allowClear" | "children" | "defaultValue">, WrapperProps {
40
33
  /**
41
34
  * min value
42
35
  */
@@ -72,11 +65,11 @@ export interface SliderProps
72
65
  /**
73
66
  * custom min label
74
67
  */
75
- minLabel?: string;
68
+ minLabel?: string | React.ReactElement;
76
69
  /**
77
70
  * custom max label
78
71
  */
79
- maxLabel?: string;
72
+ maxLabel?: string | React.ReactElement;
80
73
  /**
81
74
  * step ranges
82
75
  */
@@ -96,36 +89,32 @@ export interface SliderProps
96
89
  }
97
90
 
98
91
  /**
99
- * Number slider input
92
+ * Slider component for rendering a slider input with labels and value display.
93
+ * It supports vertical orientation, custom min/max labels, and step ranges.
94
+ * The component handles value changes, displays tooltips, and integrates with ranger for slider functionality.
95
+ * It also supports deferred value updates for smoother interactions.
100
96
  */
101
- export const Slider = ({
102
- error,
103
- name,
104
- value,
105
- step = 1,
106
- min = 0,
107
- max = 100,
108
- height = 192,
109
- color,
110
- vertical,
111
- showLabels,
112
- showValue,
113
- minLabel,
114
- maxLabel,
115
- ranges,
116
- useInfinity,
117
- format,
118
- invalid,
119
- autoFocus,
120
- disabled,
121
- readOnly,
122
- onBlur,
123
- onChange,
124
- onEnterPressed,
125
- onFocus,
126
- onSlide,
127
- ...rest
128
- }: SliderProps) => {
97
+ export function Slider(props: SliderProps) {
98
+ const {
99
+ value,
100
+ showLabels,
101
+ showValue,
102
+ onChange,
103
+ onSlide,
104
+ ranges,
105
+ useInfinity,
106
+ invalid,
107
+ step = 1,
108
+ min = 0,
109
+ max = 100,
110
+ height = 192,
111
+ vertical,
112
+ minLabel,
113
+ maxLabel,
114
+ format,
115
+ color,
116
+ } = props;
117
+
129
118
  const [actualValue, setActualValue] = useState<number>(0);
130
119
  const [displayValue, setDisplayValue] = useState(false);
131
120
  const deferred = useDeferredValue(value);
@@ -134,11 +123,12 @@ export const Slider = ({
134
123
  const maxValue = useMemo(() => ranges?.slice(-1)[0] ?? max, [max, ranges]);
135
124
 
136
125
  useEffect(() => {
137
- setActualValue(
138
- useInfinity && deferred === Infinity ? max : deferred ?? min,
139
- );
126
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
127
+ setActualValue(useInfinity && deferred === Infinity ? max : (deferred ?? min));
140
128
  }, [deferred, min, max, useInfinity]);
129
+
141
130
  useEffect(() => {
131
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
142
132
  setDisplayValue(!!showValue);
143
133
  }, [showValue]);
144
134
 
@@ -146,7 +136,7 @@ export const Slider = ({
146
136
  (value: number) => {
147
137
  const val = useInfinity && value === max ? Infinity : value;
148
138
  setActualValue(val ?? 0);
149
- onChange?.(val ?? undefined);
139
+ onChange?.(val ?? null);
150
140
  },
151
141
  [onChange, max, useInfinity],
152
142
  );
@@ -183,18 +173,10 @@ export const Slider = ({
183
173
  });
184
174
 
185
175
  return (
186
- <InputWrapper
187
- {...rest}
188
- noOutline
189
- error={error}
190
- invalid={invalid}
191
- isDisabled={disabled}
192
- isReadOnly={readOnly}
193
- canClear={!!actualValue}
194
- >
176
+ <InputWrapper {...getWrapperProps(props)} borderless focusRing>
195
177
  <div
196
- className={classNames(
197
- "flex-auto order-3 flex items-center select-none relative z-0",
178
+ className={cn(
179
+ "flex-1 order-3 flex items-center select-none relative z-0",
198
180
  vertical && "flex-col w-8",
199
181
  invalid ? "text-danger-500" : "text-primary-600",
200
182
  )}
@@ -204,17 +186,14 @@ export const Slider = ({
204
186
  {showLabels && (vertical ? maxDisplay : minDisplay)}
205
187
  <div
206
188
  ref={rangerInstance.trackElRef}
207
- className={classNames(
208
- "flex-auto relative flex items-center",
209
- vertical ? "my-2 w-8" : "mx-2 h-8",
210
- )}
189
+ className={cn("relative flex items-center", vertical ? "my-2 w-8" : "flex-1 mx-2 h-8")}
211
190
  style={{ height: vertical ? height : undefined }}
212
191
  >
213
- {rangerInstance.ticks.map(({ value, key, styles }) => (
192
+ {rangerInstance.ticks.map(({ key, styles }) => (
214
193
  <div
215
194
  key={key}
216
- className={classNames(
217
- "absolute inline-block h-1 w-1 rounded-sm bg-invert opacity-30 leading-[0] z-1",
195
+ className={cn(
196
+ "absolute inline-block h-1 w-1 rounded-sm bg-invert opacity-30 leading-0 z-1",
218
197
  vertical && "left-1/2 -ml-0.5",
219
198
  )}
220
199
  style={styles}
@@ -223,60 +202,52 @@ export const Slider = ({
223
202
  {rangerInstance.segments.map(({ key, styles, active }) => (
224
203
  <div
225
204
  key={key}
226
- className={classNames(
205
+ className={cn(
227
206
  "absolute",
228
- active
229
- ? "bg-current z-1 opacity-90"
230
- : "bg-invert opacity-5 z-0",
231
- vertical
232
- ? "-bottom-1 left-1/2 -translate-x-1/2"
233
- : "top-1/2 -translate-y-1/2",
207
+ active ? "bg-current z-1 opacity-90" : "bg-invert opacity-5 z-0",
208
+ vertical ? "-bottom-1 left-1/2 -translate-x-1/2" : "top-1/2 -translate-y-1/2",
234
209
  !vertical && (active ? "h-1" : "h-0.5"),
235
210
  vertical && (active ? "w-1" : "w-0.5"),
236
211
  )}
237
212
  style={styles}
238
213
  />
239
214
  ))}
240
- {rangerInstance.handles.map(
241
- ({ key, value, props, styles, percentage, valueStyles }) => (
242
- <Fragment key={key}>
243
- <button
244
- type="button"
245
- className={classNames(
246
- "bg-current cursor-pointer appearance-none h-3 w-3 rounded-full z-3 absolute",
247
- "focus:ring-2 focus:ring-offset-2",
248
- vertical && "left-1/2 -ml-1.5",
249
- )}
250
- {...props}
251
- style={styles}
252
- />
253
- {displayValue && (
254
- <div
255
- className={classNames(
256
- "bg-invert text-invert py-px px-2 text-xs absolute rounded z-[5] whitespace-nowrap",
257
- "before:absolute before:h-1.5 before:w-1.5 before:z-[-1] before:bg-invert before:top-1/2 before:-translate-y-1/2 before:rotate-45",
258
- vertical
259
- ? "end-0 ltr:translate-x-full rtl:-translate-x-full translate-y-2"
260
- : "top-1/2 -translate-y-1/2",
261
- !vertical &&
262
- (percentage > 50
263
- ? "-ms-3 ltr:-translate-x-full rtl:translate-x-full ltr:before:translate-x-[0.125rem] rtl:before:-translate-x-[0.125rem] before:end-0"
264
- : "ms-3 ltr:before:-translate-x-[0.125rem] rtl:before:translate-x-[0.125rem] before:start-0"),
265
- )}
266
- data-align={percentage > 50 ? "start" : "end"}
267
- style={valueStyles}
268
- >
269
- {value === Infinity || (useInfinity && value === max)
270
- ? maxLabel ?? "∞"
271
- : Format.number(value, format)}
272
- </div>
215
+ {rangerInstance.handles.map(({ key, value, props, styles, percentage, valueStyles }) => (
216
+ <Fragment key={key}>
217
+ <button
218
+ type="button"
219
+ className={cn(
220
+ "bg-current cursor-pointer appearance-none h-3 w-3 rounded-full z-3 absolute",
221
+ "focus:ring-2 focus:ring-offset-2",
222
+ vertical && "left-1/2 -ml-1.5",
273
223
  )}
274
- </Fragment>
275
- ),
276
- )}
224
+ {...props}
225
+ style={styles}
226
+ />
227
+ {displayValue && (
228
+ <div
229
+ className={cn(
230
+ "bg-invert text-invert py-px px-2 text-xs absolute rounded z-5 whitespace-nowrap pointer-events-none",
231
+ "before:absolute before:h-1.5 before:w-1.5 before:z-[-1] before:bg-invert before:top-1/2 before:-translate-y-1/2 before:rotate-45",
232
+ vertical
233
+ ? "inset-e-0 ltr:translate-x-full rtl:-translate-x-full translate-y-2 ltr:before:-translate-x-0.5 rtl:before:translate-x-0.5 before:inset-s-0"
234
+ : "top-1/2 -translate-y-1/2",
235
+ !vertical &&
236
+ (percentage > 50
237
+ ? "-ms-3 ltr:-translate-x-full rtl:translate-x-full ltr:before:translate-x-0.5 rtl:before:-translate-x-0.5 before:inset-e-0"
238
+ : "ms-3 ltr:before:-translate-x-0.5 rtl:before:translate-x-0.5 before:inset-s-0"),
239
+ )}
240
+ data-align={percentage > 50 ? "start" : "end"}
241
+ style={valueStyles}
242
+ >
243
+ {value === Infinity || (useInfinity && value === max) ? (maxLabel ?? "∞") : Format.number(value, format)}
244
+ </div>
245
+ )}
246
+ </Fragment>
247
+ ))}
277
248
  </div>
278
249
  {showLabels && (vertical ? minDisplay : maxDisplay)}
279
250
  </div>
280
251
  </InputWrapper>
281
252
  );
282
- };
253
+ }
@@ -21,19 +21,10 @@
21
21
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  */
23
23
 
24
- import {
25
- type ColorType,
26
- type RefProp,
27
- } from "@react-fabric/core/dist/types/types";
28
- import classNames from "classnames";
29
- import {
30
- Fragment,
31
- useCallback,
32
- useDeferredValue,
33
- useEffect,
34
- useState,
35
- type ChangeEvent,
36
- } from "react";
24
+ import { useControlledValue } from "@react-fabric/core";
25
+ import type { ColorUnion, RefProp } from "@react-fabric/core/dist/types/types";
26
+ import { cn } from "@react-fabric/utilities";
27
+ import { Fragment, useCallback } from "react";
37
28
  import { ErrorIcon } from "../internal/ErrorIcon";
38
29
  import { getBgClass, getColor, getColorClass } from "../utils";
39
30
 
@@ -41,7 +32,7 @@ export interface SwitchProps extends RefProp<HTMLInputElement> {
41
32
  /**
42
33
  * Switch label
43
34
  */
44
- label?: JSX.Element | string;
35
+ label?: React.ReactElement | string;
45
36
  /**
46
37
  * checked
47
38
  */
@@ -72,22 +63,28 @@ export interface SwitchProps extends RefProp<HTMLInputElement> {
72
63
  /**
73
64
  * cutom Switch icon
74
65
  */
75
- color?: string | ColorType;
66
+ color?: ColorUnion;
76
67
  /**
77
68
  * cutom Switch icon
78
69
  */
79
- defaultColor?: string | ColorType;
70
+ defaultColor?: ColorUnion;
80
71
 
81
72
  name?: string;
82
73
 
83
74
  onLabel?: string;
84
- onLabelColor?: string | ColorType;
75
+ onLabelColor?: ColorUnion;
85
76
 
86
77
  offLabel?: string;
87
- offLabelColor?: string | ColorType;
78
+ offLabelColor?: ColorUnion;
88
79
  }
89
80
 
90
- export const Switch = ({
81
+ /**
82
+ * Switch input component with custom labels and colors.
83
+ * This component renders a switch input with optional labels for the "on" and "off" states,
84
+ * and allows for custom colors for the switch background and labels.
85
+ * It supports disabled and invalid states, and handles change events.
86
+ */
87
+ export function Switch({
91
88
  ref,
92
89
  label,
93
90
  checked,
@@ -104,72 +101,63 @@ export const Switch = ({
104
101
  defaultColor,
105
102
  onChange,
106
103
  ...rest
107
- }: SwitchProps) => {
108
- const [actualValue, setActualValue] = useState(false);
109
- const deferred = useDeferredValue(!!checked);
110
-
111
- useEffect(() => {
112
- setActualValue(deferred);
113
- }, [deferred]);
104
+ }: SwitchProps) {
105
+ const { currentValue, updateValue } = useControlledValue(!!checked, false);
114
106
 
115
107
  const handleChange = useCallback(
116
- (e?: ChangeEvent<HTMLInputElement>) => {
117
- setActualValue(e?.target.checked ?? false);
108
+ (e?: React.ChangeEvent<HTMLInputElement>) => {
109
+ updateValue(e?.target.checked ?? false);
118
110
  onChange?.(e?.target.checked ?? false);
119
111
  },
120
- [onChange],
112
+ [onChange, updateValue],
121
113
  );
122
114
 
123
115
  return (
124
116
  <label
125
- className={classNames(
117
+ className={cn(
126
118
  invalid ? "ring-danger-500" : "ring-primary-500",
127
- disabled
128
- ? "opacity-50 cursor-not-allowed"
129
- : "hover:opacity-100 cursor-pointer",
130
- "inline-flex gap-1 py-1 items-center relative cursor-pointer opacity-85 rounded ring-offset-2 has-[:focus-visible]:ring-1",
119
+ disabled ? "opacity-50 cursor-not-allowed" : "opacity-85 hover:opacity-100 cursor-pointer",
120
+ "inline-flex gap-1 py-1 items-center relative rounded ring-offset-2 has-focus-visible:ring-1",
131
121
  )}
132
122
  style={{ width }}
133
123
  >
134
124
  <div
135
- className={classNames(
136
- actualValue && getBgClass(color ? `${color}-500` : "primary-500"),
137
- !actualValue &&
138
- (defaultColor ? getBgClass(`${defaultColor}-500`) : "bg-tint-200"),
139
- "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",
125
+ className={cn(
126
+ currentValue && getBgClass(color ? `${color}-500` : "primary-500"),
127
+ !currentValue && (defaultColor ? getBgClass(`${defaultColor}-500`) : "bg-tint-200"),
128
+ "relative grid grid-cols-2 items-center h-[1.5em] min-w-[2.5em] rounded-full p-[0.25em] transition-colors duration-200 ease-in-out",
140
129
  )}
141
130
  style={{
142
- backgroundColor: actualValue
143
- ? getColor(color)
144
- : getColor(defaultColor),
131
+ backgroundColor: currentValue ? getColor(color) : getColor(defaultColor),
145
132
  }}
146
133
  >
147
134
  <input
148
- className={classNames(
149
- "appearance-none absolute opacity-0 inset-0 bg-transparent border-none outline-none ring-0",
135
+ className={cn(
136
+ "appearance-none absolute opacity-0 inset-0 bg-transparent border-none cursor-[inherit] outline-none ring-0",
150
137
  )}
151
138
  aria-invalid={invalid}
152
139
  aria-disabled={disabled}
153
140
  disabled={disabled}
154
141
  aria-errormessage={error}
155
142
  type="checkbox"
143
+ name={name}
156
144
  ref={ref}
157
145
  size={1}
158
146
  tabIndex={0}
159
147
  autoComplete="off"
160
- checked={actualValue}
148
+ checked={!!currentValue}
161
149
  onChange={handleChange}
162
150
  {...rest}
163
151
  />
164
152
  <span
165
153
  aria-hidden="true"
166
- className={classNames(
154
+ className={cn(
167
155
  !disabled && "shadow-sm",
168
- actualValue ? "translate-x-[100%]" : "translate-x-0",
169
- "pointer-events-none inline-block h-4 start-1 absolute rounded-full bg-white ring-0 transition duration-200 ease-in-out",
156
+ currentValue ? "translate-x-full" : "translate-x-0",
157
+ "pointer-events-none inline-block h-[1em] inset-s-[0.25em] absolute rounded-full bg-white ring-0 transition duration-200 ease-in-out",
170
158
  )}
171
159
  style={{
172
- width: "calc(50% - 0.25rem)",
160
+ width: "calc(50% - 0.25em)",
173
161
  }}
174
162
  />
175
163
  {(!!onLabel || !!offLabel) && (
@@ -194,9 +182,7 @@ export const Switch = ({
194
182
  )}
195
183
  </div>
196
184
  <ErrorIcon invalid={invalid} error={error} />
197
- {label && (
198
- <span className="flex-initial leading-5 truncate">{label}</span>
199
- )}
185
+ {label && <span className="flex-initial leading-tight truncate">{label}</span>}
200
186
  </label>
201
187
  );
202
- };
188
+ }