@saas-ui/forms 2.0.0-next.3 → 2.0.0-next.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +53 -6
  3. package/dist/ajv/index.d.ts +358 -11
  4. package/dist/ajv/index.js +7 -9
  5. package/dist/ajv/index.js.map +1 -1
  6. package/dist/ajv/index.mjs +7 -10
  7. package/dist/ajv/index.mjs.map +1 -1
  8. package/dist/index.d.ts +448 -247
  9. package/dist/index.js +707 -682
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +691 -666
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/yup/index.d.ts +580 -21
  14. package/dist/yup/index.js +6 -10
  15. package/dist/yup/index.js.map +1 -1
  16. package/dist/yup/index.mjs +4 -8
  17. package/dist/yup/index.mjs.map +1 -1
  18. package/dist/zod/index.d.ts +580 -11
  19. package/dist/zod/index.js +5 -0
  20. package/dist/zod/index.js.map +1 -1
  21. package/dist/zod/index.mjs +5 -1
  22. package/dist/zod/index.mjs.map +1 -1
  23. package/package.json +19 -10
  24. package/src/array-field.tsx +82 -45
  25. package/src/auto-form.tsx +7 -3
  26. package/src/base-field.tsx +54 -0
  27. package/src/create-field.tsx +144 -0
  28. package/src/create-form.tsx +54 -0
  29. package/src/default-fields.tsx +163 -0
  30. package/src/display-field.tsx +9 -11
  31. package/src/display-if.tsx +20 -13
  32. package/src/field-resolver.ts +10 -8
  33. package/src/field.tsx +18 -445
  34. package/src/fields-context.tsx +23 -0
  35. package/src/fields.tsx +34 -21
  36. package/src/form-context.tsx +84 -0
  37. package/src/form.tsx +69 -52
  38. package/src/index.ts +44 -4
  39. package/src/input-right-button/input-right-button.stories.tsx +1 -1
  40. package/src/input-right-button/input-right-button.tsx +0 -2
  41. package/src/layout.tsx +16 -11
  42. package/src/number-input/number-input.tsx +9 -5
  43. package/src/object-field.tsx +13 -8
  44. package/src/password-input/password-input.stories.tsx +23 -2
  45. package/src/password-input/password-input.tsx +6 -6
  46. package/src/pin-input/pin-input.tsx +1 -5
  47. package/src/radio/radio-input.stories.tsx +1 -1
  48. package/src/radio/radio-input.tsx +12 -10
  49. package/src/select/native-select.tsx +1 -4
  50. package/src/select/select-context.tsx +130 -0
  51. package/src/select/select.stories.tsx +116 -85
  52. package/src/select/select.test.tsx +1 -1
  53. package/src/select/select.tsx +160 -146
  54. package/src/step-form.tsx +29 -11
  55. package/src/submit-button.tsx +5 -1
  56. package/src/types.ts +144 -0
  57. package/src/use-array-field.tsx +9 -3
  58. package/src/utils.ts +23 -1
  59. package/src/watch-field.tsx +2 -6
  60. /package/src/radio/{radio.test.tsx → radio-input.test.tsx} +0 -0
@@ -10,176 +10,190 @@ import {
10
10
  MenuListProps,
11
11
  MenuItemOption,
12
12
  MenuOptionGroup,
13
- MenuOptionGroupProps,
14
13
  Button,
15
14
  ButtonProps,
16
15
  omitThemingProps,
17
16
  useMultiStyleConfig,
18
17
  SystemStyleObject,
19
- useFormControl,
20
- HTMLChakraProps,
18
+ MenuItemOptionProps,
19
+ useFormControlContext,
21
20
  } from '@chakra-ui/react'
22
- import { ChevronDownIcon } from '@chakra-ui/icons'
23
- import { cx, __DEV__ } from '@chakra-ui/utils'
21
+ import { cx, dataAttr } from '@chakra-ui/utils'
22
+ import { ChevronDownIcon } from '@saas-ui/core'
24
23
 
25
- interface Option {
26
- value: string
27
- label?: string
28
- }
24
+ import { FieldOption } from '../types'
29
25
 
30
- interface SelectOptions {
31
- /**
32
- * An array of options
33
- * If you leave this empty the children prop will be rendered.
34
- */
35
- options?: Option[]
36
- /**
37
- * Props passed to the MenuList.
38
- */
39
- menuListProps?: MenuListProps
40
- /**
41
- * Customize how the value is rendered.
42
- * @type (value?: string[]) => React.ReactElement
43
- */
44
- renderValue?: (value?: string[]) => React.ReactElement | undefined
45
- /**
46
- * Enable multiple select.
47
- */
48
- multiple?: boolean
49
- }
26
+ import {
27
+ SelectOptions,
28
+ SelectProvider,
29
+ useSelect,
30
+ useSelectContext,
31
+ } from './select-context'
32
+
33
+ export interface SelectOption
34
+ extends Omit<MenuItemOptionProps, 'value'>,
35
+ FieldOption {}
50
36
 
51
37
  export interface SelectProps
52
38
  extends Omit<MenuProps, 'children'>,
53
- Pick<ButtonProps, 'isDisabled' | 'leftIcon' | 'rightIcon'>,
54
- Pick<MenuOptionGroupProps, 'onChange'>,
55
39
  SelectOptions {}
56
40
 
57
- const SelectButton = forwardRef((props, ref) => {
58
- const styles = useMultiStyleConfig('Input', props)
59
-
60
- /* @ts-ignore */
61
- const focusStyles = styles.field._focusVisible
62
-
63
- const height = styles.field.h || styles.field.height
64
-
65
- const buttonStyles: SystemStyleObject = {
66
- fontWeight: 'normal',
67
- textAlign: 'left',
68
- color: 'inherit',
69
- _active: {
70
- bg: 'transparent',
71
- },
72
- minH: height,
73
- _focus: focusStyles,
74
- _expanded: focusStyles,
75
- ...styles.field,
76
- h: 'auto',
41
+ export interface SelectButtonProps extends ButtonProps {}
42
+
43
+ /**
44
+ * Button that opens the select menu and displays the selected value.
45
+ *
46
+ * @see https://saas-ui.dev/docs/components/forms/select
47
+ */
48
+ export const SelectButton = forwardRef<SelectButtonProps, 'button'>(
49
+ (props, ref) => {
50
+ const styles = useMultiStyleConfig('SuiSelect', props)
51
+
52
+ const {
53
+ displayValue,
54
+ renderValue,
55
+ placeholder,
56
+ isDisabled: isSelectDisabled,
57
+ } = useSelectContext()
58
+
59
+ const {
60
+ isInvalid,
61
+ isReadOnly,
62
+ isDisabled,
63
+ isFocused,
64
+ isRequired,
65
+ id,
66
+ onBlur,
67
+ onFocus,
68
+ } = useFormControlContext()
69
+
70
+ const { rightIcon = <ChevronDownIcon />, ...rest } = props
71
+
72
+ /* @ts-ignore */
73
+ const focusStyles = styles.field?._focusVisible
74
+ /* @ts-ignore */
75
+ const readOnlyStyles = styles.field?._readOnly
76
+ /* @ts-ignore */
77
+ const invalid = styles.field?._invalid
78
+
79
+ const height = styles.field?.h || styles.field?.height
80
+
81
+ const buttonStyles: SystemStyleObject = {
82
+ fontWeight: 'normal',
83
+ textAlign: 'left',
84
+ color: 'inherit',
85
+ _active: {
86
+ bg: 'transparent',
87
+ },
88
+ minH: height,
89
+ _focus: focusStyles,
90
+ _expanded: focusStyles,
91
+ _readOnly: readOnlyStyles,
92
+ _invalid: invalid,
93
+ ...styles.field,
94
+ h: 'auto',
95
+ }
96
+
97
+ // Using a Button, so we can simply use leftIcon and rightIcon
98
+ return (
99
+ <MenuButton
100
+ as={Button}
101
+ id={id}
102
+ {...rest}
103
+ onFocus={onFocus}
104
+ onBlur={onBlur}
105
+ isDisabled={isDisabled || isSelectDisabled}
106
+ data-invalid={dataAttr(isInvalid)}
107
+ data-read-only={dataAttr(isReadOnly)}
108
+ data-focus={dataAttr(isFocused)}
109
+ data-required={dataAttr(isRequired)}
110
+ rightIcon={rightIcon}
111
+ ref={ref}
112
+ sx={buttonStyles}
113
+ >
114
+ {renderValue(displayValue) || placeholder}
115
+ </MenuButton>
116
+ )
77
117
  }
118
+ )
78
119
 
79
- // Using a Button, so we can simply use leftIcon and rightIcon
80
- return <MenuButton as={Button} {...props} ref={ref} sx={buttonStyles} />
81
- })
82
-
83
- if (__DEV__) {
84
- SelectButton.displayName = 'SelectButton'
85
- }
120
+ SelectButton.displayName = 'SelectButton'
86
121
 
122
+ /**
123
+ * Allow users to select a value from a list of options.
124
+ *
125
+ * @see https://saas-ui.dev/docs/components/forms/select
126
+ */
87
127
  export const Select = forwardRef<SelectProps, 'select'>((props, ref) => {
88
- const {
89
- name,
90
- options,
91
- children,
92
- onChange,
93
- defaultValue,
94
- value,
95
- placeholder,
96
- isDisabled,
97
- leftIcon,
98
- rightIcon = <ChevronDownIcon />,
99
- multiple,
100
- size,
101
- variant,
102
- menuListProps,
103
- renderValue = (value) => value?.join(', '),
104
- ...rest
105
- } = props
106
- const menuProps = omitThemingProps(rest)
128
+ const { name, children, isDisabled, multiple, ...rest } = props
107
129
 
108
- const [currentValue, setCurrentValue] = React.useState(value || defaultValue)
109
-
110
- const controlProps = useFormControl({ name } as HTMLChakraProps<'input'>)
130
+ const menuProps = omitThemingProps(rest)
111
131
 
112
- const handleChange = (value: string | string[]) => {
113
- setCurrentValue(value)
114
- onChange?.(value)
115
- }
132
+ const context = useSelect(props)
116
133
 
117
- const buttonProps = {
118
- isDisabled,
119
- leftIcon,
120
- rightIcon,
121
- size,
122
- variant,
123
- }
134
+ const { value, controlProps } = context
124
135
 
125
- const getDisplayValue = React.useCallback(
126
- (value: string) => {
127
- if (!options) {
128
- return value
129
- }
130
-
131
- for (const option of options) {
132
- if (option.label && option.value === value) {
133
- return option.label
134
- }
135
- }
136
-
137
- return value
138
- },
139
- [options]
136
+ return (
137
+ <SelectProvider value={context}>
138
+ <Menu {...menuProps} closeOnSelect={!multiple}>
139
+ <chakra.div className={cx('sui-select')}>
140
+ {children}
141
+ <chakra.input
142
+ {...controlProps}
143
+ ref={ref}
144
+ name={name}
145
+ type="hidden"
146
+ value={value || ''}
147
+ className="saas-select__input"
148
+ />
149
+ </chakra.div>
150
+ </Menu>
151
+ </SelectProvider>
140
152
  )
153
+ })
154
+
155
+ export interface SelectListProps extends MenuListProps {}
141
156
 
142
- const displayValue = currentValue
143
- ? (Array.isArray(currentValue) ? currentValue : [currentValue]).map(
144
- getDisplayValue
145
- )
146
- : []
157
+ /**
158
+ * The list of options to choose from.
159
+ *
160
+ * @see https://saas-ui.dev/docs/components/forms/select
161
+ */
162
+ export const SelectList: React.FC<SelectListProps> = (props) => {
163
+ const { defaultValue, value, options, multiple, onChange } =
164
+ useSelectContext()
147
165
 
148
166
  return (
149
- <Menu {...menuProps} closeOnSelect={!multiple}>
150
- <chakra.div className={cx('sui-select')}>
151
- <SelectButton ref={ref} {...buttonProps}>
152
- {renderValue(displayValue) || placeholder}
153
- </SelectButton>
154
- <MenuList maxH="60vh" overflowY="auto" {...menuListProps}>
155
- <MenuOptionGroup
156
- defaultValue={
157
- (defaultValue || value) as string | string[] | undefined
158
- }
159
- onChange={handleChange}
160
- type={multiple ? 'checkbox' : 'radio'}
161
- >
162
- {options
163
- ? options.map(({ value, label, ...rest }, i) => (
164
- <MenuItemOption key={i} value={value} {...rest}>
165
- {label || value}
166
- </MenuItemOption>
167
- ))
168
- : children}
169
- </MenuOptionGroup>
170
- </MenuList>
171
- <chakra.input
172
- {...controlProps}
173
- name={name}
174
- type="hidden"
175
- value={currentValue}
176
- className="saas-select__input"
177
- />
178
- </chakra.div>
179
- </Menu>
167
+ <MenuList maxH="100vh" overflowY="auto" {...props}>
168
+ <MenuOptionGroup
169
+ defaultValue={(defaultValue || value) as string | string[] | undefined}
170
+ value={value}
171
+ onChange={onChange}
172
+ type={multiple ? 'checkbox' : 'radio'}
173
+ >
174
+ {options
175
+ ? options.map(({ value, label, ...rest }, i) => (
176
+ <SelectOption key={i} value={value} {...rest}>
177
+ {label || value}
178
+ </SelectOption>
179
+ ))
180
+ : props.children}
181
+ </MenuOptionGroup>
182
+ </MenuList>
180
183
  )
181
- })
182
-
183
- if (__DEV__) {
184
- Select.displayName = 'Select'
185
184
  }
185
+
186
+ Select.displayName = 'Select'
187
+
188
+ /**
189
+ * An option in a select list
190
+ *
191
+ * @see https://saas-ui.dev/docs/components/forms/select
192
+ */
193
+ export const SelectOption = forwardRef<MenuItemOptionProps, 'button'>(
194
+ (props, ref) => {
195
+ return <MenuItemOption ref={ref} {...props} />
196
+ }
197
+ )
198
+ SelectOption.id = 'MenuItemOption'
199
+ SelectOption.displayName = 'SelectOption'
package/src/step-form.tsx CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  ThemingProps,
11
11
  } from '@chakra-ui/react'
12
12
 
13
- import { callAllHandlers, runIfFn, cx, __DEV__ } from '@chakra-ui/utils'
13
+ import { callAllHandlers, runIfFn, cx } from '@chakra-ui/utils'
14
14
 
15
15
  import {
16
16
  StepperProvider,
@@ -38,6 +38,11 @@ export interface StepFormProps<
38
38
  TContext extends object = object
39
39
  > extends UseStepFormProps<TFieldValues> {}
40
40
 
41
+ /**
42
+ * The wrapper component provides context, state, and focus management.
43
+ *
44
+ * @see Docs https://saas-ui.dev/docs/components/forms/step-form
45
+ */
41
46
  export const StepForm = React.forwardRef(
42
47
  <
43
48
  TFieldValues extends FieldValues = FieldValues,
@@ -89,6 +94,11 @@ export interface FormStepperProps
89
94
  extends StepperStepsProps,
90
95
  ThemingProps<'Stepper'> {}
91
96
 
97
+ /**
98
+ * Renders a stepper that displays progress above the form.
99
+ *
100
+ * @see Docs https://saas-ui.dev/docs/components/forms/step-form
101
+ */
92
102
  export const FormStepper: React.FC<FormStepperProps> = (props) => {
93
103
  const { activeIndex, setIndex } = useStepperContext()
94
104
 
@@ -139,7 +149,11 @@ export interface FormStepProps
139
149
  Omit<HTMLChakraProps<'div'>, 'onSubmit'> {
140
150
  onSubmit?: FormStepSubmitHandler
141
151
  }
142
-
152
+ /**
153
+ * The form step containing fields for a specific step.
154
+ *
155
+ * @see Docs https://saas-ui.dev/docs/components/forms/step-form
156
+ */
143
157
  export const FormStep: React.FC<FormStepProps> = (props) => {
144
158
  const { name, schema, resolver, children, className, onSubmit, ...rest } =
145
159
  props
@@ -154,10 +168,13 @@ export const FormStep: React.FC<FormStepProps> = (props) => {
154
168
  ) : null
155
169
  }
156
170
 
157
- if (__DEV__) {
158
- FormStep.displayName = 'FormStep'
159
- }
171
+ FormStep.displayName = 'FormStep'
160
172
 
173
+ /**
174
+ * A button that this opens the previous step when clicked. Disabled on the first step.
175
+ *
176
+ * @see Docs https://saas-ui.dev/docs/components/forms/step-form
177
+ */
161
178
  export const PrevButton: React.FC<ButtonProps> = (props) => {
162
179
  const { isFirstStep, isCompleted, prevStep } = useStepperContext()
163
180
 
@@ -172,15 +189,18 @@ export const PrevButton: React.FC<ButtonProps> = (props) => {
172
189
  )
173
190
  }
174
191
 
175
- if (__DEV__) {
176
- PrevButton.displayName = 'PrevButton'
177
- }
192
+ PrevButton.displayName = 'PrevButton'
178
193
 
179
194
  export interface NextButtonProps extends Omit<ButtonProps, 'children'> {
180
195
  submitLabel?: string
181
196
  label?: string
182
197
  }
183
198
 
199
+ /**
200
+ * A button that submits the active step.
201
+ *
202
+ * @see Docs https://saas-ui.dev/docs/components/forms/step-form
203
+ */
184
204
  export const NextButton: React.FC<NextButtonProps> = (props) => {
185
205
  const { label = 'Next', submitLabel = 'Complete', ...rest } = props
186
206
  const { isLastStep, isCompleted } = useStepperContext()
@@ -196,6 +216,4 @@ export const NextButton: React.FC<NextButtonProps> = (props) => {
196
216
  )
197
217
  }
198
218
 
199
- if (__DEV__) {
200
- NextButton.displayName = 'NextButton'
201
- }
219
+ NextButton.displayName = 'NextButton'
@@ -20,7 +20,11 @@ export interface SubmitButtonProps extends ButtonProps {
20
20
  */
21
21
  disableIfInvalid?: boolean
22
22
  }
23
-
23
+ /**
24
+ * A button with type submit and default color scheme primary and isLoading state when the form is submitting.
25
+ *
26
+ * @see Docs https://saas-ui.dev/docs/components/forms/form
27
+ */
24
28
  export const SubmitButton = forwardRef<SubmitButtonProps, 'button'>(
25
29
  (props, ref) => {
26
30
  const {
package/src/types.ts ADDED
@@ -0,0 +1,144 @@
1
+ import { FormControlProps } from '@chakra-ui/react'
2
+ import { MaybeRenderProp } from '@chakra-ui/react-utils'
3
+ import { FieldPath, FieldValues, RegisterOptions } from 'react-hook-form'
4
+ import { DefaultFields } from './default-fields'
5
+ import { FormProps, FormRenderContext } from './form'
6
+ import { SubmitButtonProps } from './submit-button'
7
+
8
+ export type FieldOption = { label?: string; value: string }
9
+ export type FieldOptions<TOption extends FieldOption = FieldOption> =
10
+ | Array<string>
11
+ | Array<TOption>
12
+
13
+ export type ValueOf<T> = T[keyof T]
14
+ export type ShallowMerge<A, B> = Omit<A, keyof B> & B
15
+
16
+ type Split<S extends string, D extends string> = string extends S
17
+ ? string[]
18
+ : S extends ''
19
+ ? []
20
+ : S extends `${infer T}${D}${infer U}`
21
+ ? [T, ...Split<U, D>]
22
+ : [S]
23
+
24
+ type MapPath<T extends string[]> = T extends [infer U, ...infer R]
25
+ ? U extends string
26
+ ? `${U extends `${number}` ? '$' : U}${R[0] extends string
27
+ ? '.'
28
+ : ''}${R extends string[] ? MapPath<R> : ''}`
29
+ : ''
30
+ : ''
31
+
32
+ type TransformPath<T extends string> = MapPath<Split<T, '.'>>
33
+
34
+ export type ArrayFieldPath<Name extends string> = Name extends string
35
+ ? TransformPath<Name>
36
+ : never
37
+
38
+ export interface BaseFieldProps<
39
+ TFieldValues extends FieldValues = FieldValues,
40
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
41
+ > extends Omit<FormControlProps, 'label' | 'type'> {
42
+ /**
43
+ * The field name
44
+ */
45
+ name: TName | ArrayFieldPath<TName>
46
+ /**
47
+ * The field label
48
+ */
49
+ label?: string
50
+ /**
51
+ * Hide the field label
52
+ */
53
+ hideLabel?: boolean
54
+ /**
55
+ * Field help text
56
+ */
57
+ help?: string
58
+ /**
59
+ * React hook form rules
60
+ */
61
+ rules?: Omit<
62
+ RegisterOptions<TFieldValues, TName>,
63
+ 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'
64
+ >
65
+ /**
66
+ * Build-in types:
67
+ * text, number, password, textarea, select, native-select, checkbox, radio, switch, pin
68
+ *
69
+ * Will default to a text field if there is no matching type.
70
+ */
71
+ type?: string
72
+ /**
73
+ * The input placeholder
74
+ */
75
+ placeholder?: string
76
+ }
77
+
78
+ type FieldPathWithArray<
79
+ TFieldValues extends FieldValues,
80
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
81
+ > = TName | ArrayFieldPath<TName>
82
+
83
+ type MergeFieldProps<
84
+ FieldDefs,
85
+ TFieldValues extends FieldValues = FieldValues,
86
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
87
+ > = ValueOf<{
88
+ [K in keyof FieldDefs]: FieldDefs[K] extends React.FC<infer Props>
89
+ ? { type?: K } & ShallowMerge<Props, BaseFieldProps<TFieldValues, TName>>
90
+ : never
91
+ }>
92
+
93
+ export type FieldProps<TFieldValues extends FieldValues = FieldValues> =
94
+ MergeFieldProps<DefaultFields, TFieldValues>
95
+
96
+ export type FormChildren<
97
+ FieldDefs,
98
+ TFieldValues extends FieldValues = FieldValues,
99
+ TContext extends object = object
100
+ > = MaybeRenderProp<
101
+ FormRenderContext<
102
+ TFieldValues,
103
+ TContext,
104
+ MergeFieldProps<
105
+ FieldDefs extends never
106
+ ? DefaultFields
107
+ : ShallowMerge<DefaultFields, FieldDefs>,
108
+ TFieldValues
109
+ >
110
+ >
111
+ >
112
+
113
+ export type DefaultFieldOverrides = {
114
+ submit?: SubmitButtonProps
115
+ [key: string]: any
116
+ }
117
+
118
+ export type FieldOverrides<
119
+ FieldDefs,
120
+ TFieldValues extends FieldValues = FieldValues,
121
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
122
+ > = {
123
+ [K in FieldPathWithArray<TFieldValues, TName>]?: Omit<
124
+ MergeFieldProps<
125
+ FieldDefs extends never
126
+ ? DefaultFields
127
+ : ShallowMerge<DefaultFields, FieldDefs>,
128
+ TFieldValues
129
+ >,
130
+ 'name'
131
+ >
132
+ }
133
+
134
+ export type WithFields<
135
+ TFormProps extends FormProps<any, any, any, any>,
136
+ FieldDefs
137
+ > = TFormProps extends FormProps<infer TFieldValues, infer TContext>
138
+ ? Omit<TFormProps, 'children' | 'fields'> & {
139
+ children?: FormChildren<FieldDefs, TFieldValues, TContext>
140
+ fields?: FieldOverrides<FieldDefs, TFieldValues> & {
141
+ submit?: SubmitButtonProps
142
+ }
143
+ }
144
+ : never
@@ -1,10 +1,13 @@
1
1
  import * as React from 'react'
2
2
  import {
3
3
  useFieldArray,
4
- useFormContext,
5
4
  UseFieldArrayReturn,
5
+ FieldValues,
6
+ FieldPath,
6
7
  } from 'react-hook-form'
7
8
 
9
+ import { useFormContext } from './form-context'
10
+
8
11
  import { createContext } from '@chakra-ui/react-utils'
9
12
 
10
13
  export interface UseArrayFieldReturn extends UseFieldArrayReturn {
@@ -59,11 +62,14 @@ export const [ArrayFieldRowProvider, useArrayFieldRowContext] =
59
62
  name: 'ArrayFieldRowContext',
60
63
  })
61
64
 
62
- export interface ArrayFieldOptions {
65
+ export interface ArrayFieldOptions<
66
+ TFieldValues extends FieldValues = FieldValues,
67
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
68
+ > {
63
69
  /**
64
70
  * The field name
65
71
  */
66
- name: string
72
+ name: TName
67
73
  /**
68
74
  * Default value for new values in the array
69
75
  */
package/src/utils.ts CHANGED
@@ -1,13 +1,35 @@
1
1
  import * as React from 'react'
2
+ import { FieldOption, FieldOptions } from './types'
2
3
 
3
4
  export const mapNestedFields = (name: string, children: React.ReactNode) => {
4
5
  return React.Children.map(children, (child) => {
5
6
  if (React.isValidElement(child) && child.props.name) {
7
+ let childName = child.props.name
8
+ if (childName.includes('.')) {
9
+ childName = childName.replace(/^.*\.(.*)/, '$1')
10
+ } else if (childName.includes('.$')) {
11
+ childName = childName.replace(/^.*\.\$(.*)/, '$1')
12
+ }
13
+
6
14
  return React.cloneElement(child, {
7
15
  ...child.props,
8
- name: `${name}.${child.props.name}`,
16
+ name: `${name}.${childName}`,
9
17
  })
10
18
  }
11
19
  return child
12
20
  })
13
21
  }
22
+
23
+ export const mapOptions = <TOption extends FieldOption = FieldOption>(
24
+ options: FieldOptions<TOption>
25
+ ) => {
26
+ return options.map((option) => {
27
+ if (typeof option === 'string') {
28
+ return {
29
+ label: option,
30
+ value: option,
31
+ }
32
+ }
33
+ return option
34
+ })
35
+ }
@@ -1,9 +1,5 @@
1
- import {
2
- FieldValues,
3
- useFormContext,
4
- UseFormReturn,
5
- useWatch,
6
- } from 'react-hook-form'
1
+ import { FieldValues, useWatch } from 'react-hook-form'
2
+ import { useFormContext, UseFormReturn } from './form-context'
7
3
 
8
4
  export interface WatchFieldProps<
9
5
  Value = unknown,