@react-fabric/form 1.0.0-beta.8 → 1.0.1

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,17 @@
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, Tooltip } from "@react-fabric/core";
25
- import { type RefProp } from "@react-fabric/core/dist/types/types";
26
- import classNames from "classnames";
27
- import {
28
- useCallback,
29
- useDeferredValue,
30
- useEffect,
31
- useMemo,
32
- useState,
33
- type ChangeEvent,
34
- } from "react";
24
+ import { Button, Tooltip, useControlledValue } from "@react-fabric/core";
25
+ import type { RefProp } from "@react-fabric/core/dist/types/types";
26
+ import { cn } from "@react-fabric/utilities";
27
+ import type { InputHTMLAttributes } from "react";
28
+ import { useCallback, useId, useState } from "react";
35
29
  import { useTranslation } from "react-i18next";
36
30
  import { InputWrapper } from "../internal/InputWrapper";
37
- import { type InputProps } from "../types";
38
- import { handleEnter } from "../utils";
31
+ import type { InputProps, WrapperProps } from "../types";
32
+ import { getWrapperProps, useHandleEnter } from "../utils";
39
33
 
40
- export interface PasswordProps extends InputProps, RefProp<HTMLInputElement> {
34
+ export interface PasswordProps extends InputProps, WrapperProps, RefProp<HTMLInputElement> {
41
35
  /**
42
36
  * show password toggle
43
37
  */
@@ -46,69 +40,68 @@ export interface PasswordProps extends InputProps, RefProp<HTMLInputElement> {
46
40
  * password strength meter (0-1)
47
41
  */
48
42
  strength?: number;
43
+
44
+ autoComplete?: InputHTMLAttributes<HTMLInputElement>["autoComplete"];
49
45
  }
50
46
 
51
- export const Password = ({
52
- ref,
53
- value,
54
- name,
55
- strength,
56
- invalid,
57
- readOnly,
58
- disabled,
59
- required,
60
- placeholder,
61
- autoFocus,
62
- showToggle,
63
- onChange,
64
- onEnterPressed,
65
- ...rest
66
- }: PasswordProps) => {
67
- const { t } = useTranslation("form");
68
- const [show, setShow] = useState(false);
47
+ /**
48
+ * Password input component with optional strength meter and toggle visibility.
49
+ */
50
+ export function Password(props: PasswordProps) {
51
+ const {
52
+ value,
53
+ defaultValue,
54
+ onEnterPressed,
55
+ onChange,
56
+ onBlur,
57
+ onFocus,
58
+ disabled,
59
+ readOnly,
60
+ invalid,
61
+ showToggle,
62
+ strength,
63
+ placeholder,
64
+ name,
65
+ autoFocus,
66
+ allowClear,
67
+ autoComplete = "off",
68
+ ref,
69
+ } = props;
69
70
 
70
- const [actualValue, setActualValue] = useState("");
71
- const deferred = useDeferredValue(value);
71
+ const id = useId();
72
+ const { currentValue, updateValue } = useControlledValue(value, defaultValue);
72
73
 
73
- const handleEnterPressed = useMemo(
74
- () => handleEnter(onEnterPressed),
75
- [onEnterPressed],
76
- );
74
+ const { t } = useTranslation("form");
75
+ const [show, setShow] = useState(false);
77
76
 
78
- useEffect(() => {
79
- setActualValue(deferred ?? "");
80
- }, [deferred]);
77
+ const handleEnterPressed = useHandleEnter(onEnterPressed);
81
78
 
82
79
  const handleChange = useCallback(
83
- (e?: ChangeEvent<HTMLInputElement>) => {
84
- const value = e?.target.value;
85
- setActualValue(value ?? "");
86
- onChange?.(value ?? undefined);
80
+ (e?: React.ChangeEvent<HTMLInputElement>) => {
81
+ const value = e?.target.value ?? defaultValue;
82
+ updateValue(value);
83
+ onChange?.(value);
87
84
  },
88
- [onChange],
85
+ [defaultValue, updateValue, onChange],
89
86
  );
90
87
 
91
88
  return (
92
89
  <InputWrapper
93
- showClear={!!actualValue}
90
+ id={id}
91
+ showClear={allowClear && !!currentValue && !disabled && !readOnly}
94
92
  onClear={handleChange}
95
93
  invalid={invalid}
96
- readOnly={readOnly}
97
- disabled={disabled}
98
- required={required}
99
- {...rest}
100
- decorateEnd={
101
- showToggle && (
102
- <Tooltip
103
- color="warning"
104
- content={t(show ? "password.hide" : "password.show")}
105
- >
94
+ {...getWrapperProps(props)}
95
+ actionButton={
96
+ showToggle &&
97
+ currentValue && (
98
+ <Tooltip color="warning" content={t(show ? "password.hide" : "password.show")}>
106
99
  <Button
107
100
  tabIndex={-1}
108
101
  color="warning"
109
102
  variant="link"
110
103
  aria-label={t(show ? "password.hide" : "password.show")}
111
- icon={show ? CoreIcons.eyeOff : CoreIcons.eye}
104
+ icon={show ? "icon-[mdi--eye-off-outline]" : "icon-[mdi--eye-outline]"}
112
105
  onClick={() => setShow(!show)}
113
106
  />
114
107
  </Tooltip>
@@ -116,7 +109,8 @@ export const Password = ({
116
109
  }
117
110
  >
118
111
  <input
119
- className="appearance-none bg-transparent py-1 px-2 flex-1 border-none outline-none ring-0"
112
+ id={id}
113
+ 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"
120
114
  aria-invalid={invalid}
121
115
  aria-disabled={disabled}
122
116
  aria-readonly={readOnly}
@@ -128,23 +122,24 @@ export const Password = ({
128
122
  size={1}
129
123
  type={show ? "text" : "password"}
130
124
  ref={ref}
131
- autoComplete="off"
125
+ autoComplete={autoComplete}
132
126
  {...{ autoFocus }}
133
- value={actualValue}
127
+ value={currentValue ?? ""}
128
+ onBlur={onBlur}
129
+ onFocus={onFocus}
134
130
  onChange={handleChange}
135
131
  onKeyDown={handleEnterPressed}
136
132
  />
137
133
  {!!strength && (
138
134
  <div
139
- className={classNames(
140
- "absolute bottom-px h-[3px] inset-x-0 after:block after:h-full after:bg-dimmed",
141
- "ltr:bg-gradient-to-r rtl:bg-gradient-to-l from-danger-500 via-marigold-500 to-jade-500",
142
- )}
135
+ className={cn("absolute bottom-px h-0.5 inset-x-0 after:block after:h-full after:bg-dimmed")}
143
136
  style={{
144
137
  paddingInlineStart: `${strength > 1 ? 100 : strength * 100}%`,
138
+ background:
139
+ "linear-gradient(to right,var(--color-scarlet-500) 25%,var(--color-amber-500) 50%,var(--color-jade-500) 75%,var(--color-avocado-500) 100%)",
145
140
  }}
146
141
  />
147
142
  )}
148
143
  </InputWrapper>
149
144
  );
150
- };
145
+ }
@@ -21,17 +21,18 @@
21
21
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  */
23
23
 
24
- import { CoreIcons, Icon } from "@react-fabric/core";
25
- import { type RefProp } from "@react-fabric/core/dist/types/types";
26
- import classNames from "classnames";
27
- import { useCallback, useMemo, type ChangeEvent } from "react";
24
+ import { Icon } from "@react-fabric/core";
25
+ import type { RefProp } from "@react-fabric/core/dist/types/types";
26
+ import { cn } from "@react-fabric/utilities";
27
+ import { useCallback, useMemo } from "react";
28
+ import { ErrorIcon } from "../internal/ErrorIcon";
28
29
 
29
30
  export interface RadioProps extends RefProp<HTMLInputElement> {
30
31
  name?: string;
31
32
  /**
32
33
  * radio label
33
34
  */
34
- label?: JSX.Element | string;
35
+ label?: React.ReactElement | string;
35
36
  /**
36
37
  * value
37
38
  */
@@ -69,49 +70,48 @@ export interface RadioProps extends RefProp<HTMLInputElement> {
69
70
  iconChecked?: string;
70
71
  }
71
72
 
72
- export const Radio = ({
73
+ /**
74
+ * Radio input component with custom icons and label.
75
+ *
76
+ * This component renders a radio button with an optional label and custom icons for checked and unchecked states.
77
+ * It supports disabled and invalid states, and allows for custom width.
78
+ */
79
+ export function Radio({
73
80
  ref,
74
81
  name,
75
82
  label,
76
83
  value,
77
84
  invalid,
78
85
  disabled,
79
- error,
80
86
  width,
81
87
  icon,
88
+ error,
82
89
  iconChecked,
83
90
  onChange,
84
91
  ...rest
85
- }: RadioProps) => {
86
- const iconOn = useMemo(
87
- () => iconChecked ?? icon ?? CoreIcons.radioOn,
88
- [icon, iconChecked],
89
- );
90
- const iconOff = useMemo(() => icon ?? CoreIcons.radioOff, [icon]);
92
+ }: RadioProps) {
93
+ const iconOn = useMemo(() => iconChecked ?? icon ?? "icon-[mdi--radiobox-marked]", [icon, iconChecked]);
94
+ const iconOff = useMemo(() => icon ?? "icon-[mdi--radiobox-blank]", [icon]);
91
95
 
92
96
  const handleChange = useCallback(
93
- (e?: ChangeEvent<HTMLInputElement>) => {
97
+ (e?: React.ChangeEvent<HTMLInputElement>) => {
94
98
  // @ts-expect-error ignore
95
- e?.target.checked && value && onChange?.(value);
99
+ if (e?.target.checked && value) onChange?.(value);
96
100
  },
97
101
  [onChange, value],
98
102
  );
99
103
 
100
104
  return (
101
105
  <label
102
- className={classNames(
106
+ className={cn(
103
107
  invalid ? "ring-danger-500" : "ring-primary-500",
104
- disabled
105
- ? "opacity-50 cursor-not-allowed"
106
- : "hover:opacity-100 cursor-pointer",
107
- "inline-flex gap-1 py-1 items-center relative cursor-pointer opacity-85 rounded ring-offset-2 has-[:focus-visible]:ring-1",
108
+ disabled ? "opacity-50 cursor-not-allowed" : "hover:opacity-100 cursor-pointer",
109
+ "inline-flex gap-1 py-1 items-center relative cursor-pointer opacity-85 rounded ring-offset-2 has-focus-visible:ring-1",
108
110
  )}
109
111
  style={{ width }}
110
112
  >
111
113
  <input
112
- className={classNames(
113
- "appearance-none absolute peer opacity-0 inset-0 bg-transparent border-none outline-none ring-0",
114
- )}
114
+ className={cn("appearance-none absolute peer opacity-0 inset-0 bg-transparent border-none outline-none ring-0")}
115
115
  aria-disabled={disabled}
116
116
  disabled={disabled}
117
117
  type="radio"
@@ -124,23 +124,14 @@ export const Radio = ({
124
124
  {...rest}
125
125
  />
126
126
  <Icon
127
- className={classNames(
128
- "p-0.5 hidden peer-checked:inline-block",
129
- icon && !iconChecked && "bg-primary-500 text-white",
130
- )}
127
+ className={cn("p-0.5 not-peer-checked:hidden", icon && !iconChecked && "bg-primary-500 text-white!")}
131
128
  color={invalid ? "danger" : "primary"}
132
129
  size="1.25em"
133
130
  icon={iconOn}
134
131
  />
135
- <Icon
136
- className={classNames("p-0.5 peer-checked:hidden")}
137
- color={invalid ? "danger" : "primary"}
138
- size="1.25em"
139
- icon={iconOff}
140
- />
141
- {label && (
142
- <span className="flex-initial leading-none truncate">{label}</span>
143
- )}
132
+ <Icon className={cn("p-0.5 peer-checked:hidden")} color={invalid ? "danger" : "primary"} size="1.25em" icon={iconOff} />
133
+ <ErrorIcon invalid={invalid} error={error} />
134
+ {label && <span className="flex-initial leading-tight truncate">{label}</span>}
144
135
  </label>
145
136
  );
146
- };
137
+ }
@@ -21,15 +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 {
25
- type ChildrenProp,
26
- type CssProp,
27
- } from "@react-fabric/core/dist/types/types";
28
- import { Children, cloneElement } from "react";
24
+ import { cloneChildren } from "@react-fabric/core";
25
+ import type { ChildrenProp, CssProp } from "@react-fabric/core/dist/types/types";
26
+ import { cn } from "@react-fabric/utilities";
27
+ import { cloneElement, isValidElement } from "react";
29
28
  import { FieldWrapper } from "../internal/FieldWrapper";
30
- import { type Radio } from "./Radio";
29
+ import type { RadioProps } from "./Radio";
31
30
 
32
- export interface RadioGroupProps extends ChildrenProp<typeof Radio>, CssProp {
31
+ export interface RadioGroupProps extends ChildrenProp<RadioProps>, CssProp {
33
32
  /**
34
33
  * input label
35
34
  */
@@ -37,7 +36,7 @@ export interface RadioGroupProps extends ChildrenProp<typeof Radio>, CssProp {
37
36
  /**
38
37
  * append to label end
39
38
  */
40
- appendLabel?: JSX.Element | string | number | boolean;
39
+ appendLabel?: React.ReactElement | string | number | boolean;
41
40
  /**
42
41
  * field info
43
42
  */
@@ -67,31 +66,37 @@ export interface RadioGroupProps extends ChildrenProp<typeof Radio>, CssProp {
67
66
  * vertical orientation
68
67
  */
69
68
  vertical?: boolean;
69
+ /**
70
+ * value
71
+ */
72
+ value?: string | number;
70
73
  /**
71
74
  * change handler
72
75
  */
73
76
  onChange?: (value: AnyObject) => void;
74
77
  }
75
78
 
76
- export const RadioGroup = ({
77
- children,
78
- name,
79
- className,
80
- optionWidth,
81
- onChange,
82
- ...rest
83
- }: RadioGroupProps) => {
79
+ /**
80
+ * RadioGroup component for rendering a group of radio buttons.
81
+ * It allows for customization of the label, options, and layout.
82
+ * The component supports inline and vertical orientations, and can handle change events.
83
+ */
84
+ export function RadioGroup({ children, name, className, optionWidth, vertical, value, onChange, ...rest }: RadioGroupProps) {
84
85
  return (
85
86
  <FieldWrapper {...rest}>
86
- <div className={className}>
87
- {Children.map(children, (child) =>
88
- cloneElement(child as AnyObject, {
89
- name,
90
- onChange,
91
- width: optionWidth,
92
- }),
93
- )}
87
+ <div className={cn(className, vertical && "flex flex-col gap-1")}>
88
+ {cloneChildren(children)?.map((child) => {
89
+ if (isValidElement<RadioProps & { checked: boolean }>(child)) {
90
+ return cloneElement(child, {
91
+ name,
92
+ checked: child.props?.value === value,
93
+ onChange,
94
+ width: optionWidth,
95
+ });
96
+ }
97
+ return child;
98
+ })}
94
99
  </div>
95
100
  </FieldWrapper>
96
101
  );
97
- };
102
+ }
@@ -21,25 +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 RangeProps
39
- extends Omit<
40
- InputProps<[number, number]>,
41
- "placeholder" | "allowClear" | "children"
42
- > {
32
+ extends Omit<InputProps<[number, number]>, "placeholder" | "allowClear" | "children" | "defaultValue">, WrapperProps {
43
33
  /**
44
34
  * min value
45
35
  */
@@ -75,11 +65,11 @@ export interface RangeProps
75
65
  /**
76
66
  * custom min label
77
67
  */
78
- minLabel?: string;
68
+ minLabel?: string | React.ReactElement;
79
69
  /**
80
70
  * custom max label
81
71
  */
82
- maxLabel?: string;
72
+ maxLabel?: string | React.ReactElement;
83
73
  /**
84
74
  * step ranges
85
75
  */
@@ -95,36 +85,34 @@ export interface RangeProps
95
85
  }
96
86
 
97
87
  /**
98
- * Number Range input
88
+ * RangeSlider component for rendering a range slider input.
89
+ * It supports vertical orientation, custom colors, step increments,
90
+ * min/max values, and display of labels and value tooltips.
91
+ * The component uses the Ranger library for handling the slider functionality.
92
+ * It also provides error handling and supports disabled and read-only states.
93
+ * The component is designed to be flexible and customizable, allowing for various configurations.
99
94
  */
100
- export const Range = ({
101
- error,
102
- name,
103
- value,
104
- step = 1,
105
- min = 0,
106
- max = 100,
107
- height = 192,
108
- color,
109
- vertical,
110
- showLabels,
111
- showValue,
112
- minLabel,
113
- maxLabel,
114
- ranges,
115
- format,
116
- autoFocus,
117
- disabled,
118
- readOnly,
119
- onBlur,
120
- onChange,
121
- onEnterPressed,
122
- onFocus,
123
- onSlide,
124
- invalid,
125
- ...rest
126
- }: RangeProps) => {
127
- const [actualValue, setActualValue] = useState<[number, number]>([0, 0]);
95
+ export function RangeSlider(props: RangeProps) {
96
+ const {
97
+ value,
98
+ showLabels,
99
+ showValue,
100
+ onChange,
101
+ onSlide,
102
+ ranges,
103
+ invalid,
104
+ step = 1,
105
+ min = 0,
106
+ max = 100,
107
+ height = 192,
108
+ vertical,
109
+ minLabel,
110
+ maxLabel,
111
+ format,
112
+ color,
113
+ } = props;
114
+
115
+ const [actualValue, setActualValue] = useState<[number, number]>(() => [0, 0]);
128
116
  const [displayValue, setDisplayValue] = useState(false);
129
117
  const deferred = useDeferredValue(value);
130
118
 
@@ -132,16 +120,18 @@ export const Range = ({
132
120
  const maxValue = useMemo(() => ranges?.slice(-1)[0] ?? max, [max, ranges]);
133
121
 
134
122
  useEffect(() => {
123
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
135
124
  setActualValue(deferred ?? [min, max]);
136
125
  }, [deferred, min, max]);
137
126
  useEffect(() => {
127
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
138
128
  setDisplayValue(!!showValue);
139
129
  }, [showValue]);
140
130
 
141
131
  const handleChange = useCallback(
142
132
  (val: [number, number]) => {
143
133
  setActualValue(val ?? [min, max]);
144
- onChange?.(val ?? undefined);
134
+ onChange?.(val ?? null);
145
135
  },
146
136
  [onChange, min, max],
147
137
  );
@@ -174,23 +164,14 @@ export const Range = ({
174
164
  onDrag: (val) => onSlide?.(val[0]),
175
165
  onDragStart: () => setDisplayValue(true),
176
166
  onDragEnd: () => setDisplayValue(!!showValue),
177
- onChange: (values: readonly number[]) =>
178
- handleChange([values[0], values[1]]),
167
+ onChange: (values: readonly number[]) => handleChange([values[0], values[1]]),
179
168
  });
180
169
 
181
170
  return (
182
- <InputWrapper
183
- {...rest}
184
- noOutline
185
- error={error}
186
- invalid={invalid}
187
- isDisabled={disabled}
188
- isReadOnly={readOnly}
189
- canClear={!!actualValue}
190
- >
171
+ <InputWrapper {...getWrapperProps(props)} borderless focusRing>
191
172
  <div
192
- className={classNames(
193
- "flex-auto order-3 flex items-center select-none z-0",
173
+ className={cn(
174
+ "flex-1 order-3 flex items-center select-none z-0",
194
175
  vertical && "flex-col w-8",
195
176
  invalid ? "text-danger-500" : "text-primary-600",
196
177
  )}
@@ -200,17 +181,14 @@ export const Range = ({
200
181
  {showLabels && (vertical ? maxDisplay : minDisplay)}
201
182
  <div
202
183
  ref={rangerInstance.trackElRef}
203
- className={classNames(
204
- "flex-auto relative flex items-center",
205
- vertical ? "my-2 w-8" : "mx-2 h-8",
206
- )}
184
+ className={cn("flex-1 relative flex items-center", vertical ? "my-2 w-8" : "mx-2 h-8")}
207
185
  style={{ height: vertical ? height : undefined }}
208
186
  >
209
- {rangerInstance.ticks.map(({ value, key, styles }) => (
187
+ {rangerInstance.ticks.map(({ key, styles }) => (
210
188
  <div
211
189
  key={key}
212
- className={classNames(
213
- "absolute inline-block h-1 w-1 rounded-sm bg-invert opacity-30 leading-[0] z-1",
190
+ className={cn(
191
+ "absolute inline-block h-1 w-1 rounded-sm bg-invert opacity-30 leading-0 z-1",
214
192
  vertical && "left-1/2 -ml-0.5",
215
193
  )}
216
194
  style={styles}
@@ -219,58 +197,52 @@ export const Range = ({
219
197
  {rangerInstance.segments.map(({ key, styles, active }) => (
220
198
  <div
221
199
  key={key}
222
- className={classNames(
200
+ className={cn(
223
201
  "absolute",
224
- active
225
- ? "bg-current z-1 opacity-90"
226
- : "bg-invert opacity-5 z-0",
227
- vertical
228
- ? "-bottom-1 left-1/2 -translate-x-1/2"
229
- : "top-1/2 -translate-y-1/2",
202
+ active ? "bg-current z-1 opacity-90" : "bg-invert opacity-5 z-0",
203
+ vertical ? "-bottom-1 left-1/2 -translate-x-1/2" : "top-1/2 -translate-y-1/2",
230
204
  !vertical && (active ? "h-1" : "h-0.5"),
231
205
  vertical && (active ? "w-1" : "w-0.5"),
232
206
  )}
233
207
  style={styles}
234
208
  />
235
209
  ))}
236
- {rangerInstance.handles.map(
237
- ({ key, value, props, styles, valueStyles }) => (
238
- <Fragment key={key}>
239
- <button
240
- type="button"
241
- className={classNames(
242
- "bg-current cursor-pointer appearance-none h-3 w-3 rounded-full z-1 absolute",
243
- "focus:ring-2 focus:ring-offset-2",
244
- vertical && "left-1/2 -ml-1.5",
245
- )}
246
- {...props}
247
- style={styles}
248
- />
249
- {displayValue && (
250
- <div
251
- className={classNames(
252
- "bg-invert text-invert py-px px-2 text-xs absolute rounded z-1 whitespace-nowrap",
253
- "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",
254
- vertical
255
- ? "end-0 ltr:translate-x-full rtl:-translate-x-full translate-y-2"
256
- : "top-1/2 -translate-y-1/2",
257
- !vertical &&
258
- (key === 0
259
- ? "-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"
260
- : "ms-3 ltr:before:-translate-x-[0.125rem] rtl:before:translate-x-[0.125rem] before:start-0"),
261
- )}
262
- data-align={key === 0 ? "start" : "end"}
263
- style={valueStyles}
264
- >
265
- {Format.number(value, format)}
266
- </div>
210
+ {rangerInstance.handles.map(({ key, value, props, styles, valueStyles }) => (
211
+ <Fragment key={key}>
212
+ <button
213
+ type="button"
214
+ className={cn(
215
+ "bg-current cursor-pointer appearance-none h-3 w-3 rounded-full z-1 absolute",
216
+ "focus:ring-2 focus:ring-offset-2",
217
+ vertical && "left-1/2 -ml-1.5",
267
218
  )}
268
- </Fragment>
269
- ),
270
- )}
219
+ {...props}
220
+ style={styles}
221
+ />
222
+ {displayValue && (
223
+ <div
224
+ className={cn(
225
+ "bg-invert text-invert py-px px-2 text-xs absolute rounded z-1 whitespace-nowrap",
226
+ "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",
227
+ vertical
228
+ ? "inset-e-0 ltr:translate-x-full rtl:-translate-x-full translate-y-2"
229
+ : "top-1/2 -translate-y-1/2",
230
+ !vertical &&
231
+ (key === 0
232
+ ? "-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"
233
+ : "ms-3 ltr:before:-translate-x-0.5 rtl:before:translate-x-0.5 before:inset-s-0"),
234
+ )}
235
+ data-align={key === 0 ? "start" : "end"}
236
+ style={valueStyles}
237
+ >
238
+ {Format.number(value, format)}
239
+ </div>
240
+ )}
241
+ </Fragment>
242
+ ))}
271
243
  </div>
272
244
  {showLabels && (vertical ? minDisplay : maxDisplay)}
273
245
  </div>
274
246
  </InputWrapper>
275
247
  );
276
- };
248
+ }