@saas-ui/forms 2.0.0-next.2 → 2.0.0-next.5

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 (49) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +53 -6
  3. package/dist/ajv/index.d.ts +1 -1
  4. package/dist/ajv/index.js.map +1 -1
  5. package/dist/ajv/index.mjs.map +1 -1
  6. package/dist/index.d.ts +265 -166
  7. package/dist/index.js +2821 -556
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +2814 -555
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/yup/index.d.ts +98 -6
  12. package/dist/yup/index.js.map +1 -1
  13. package/dist/yup/index.mjs.map +1 -1
  14. package/dist/zod/index.d.ts +97 -4
  15. package/dist/zod/index.js.map +1 -1
  16. package/dist/zod/index.mjs.map +1 -1
  17. package/package.json +5 -3
  18. package/src/array-field.tsx +50 -30
  19. package/src/auto-form.tsx +7 -3
  20. package/src/base-field.tsx +59 -0
  21. package/src/create-field.tsx +143 -0
  22. package/src/create-form.tsx +31 -0
  23. package/src/default-fields.tsx +146 -0
  24. package/src/display-field.tsx +8 -9
  25. package/src/display-if.tsx +6 -5
  26. package/src/field-resolver.ts +1 -1
  27. package/src/field.tsx +14 -444
  28. package/src/fields-context.tsx +23 -0
  29. package/src/fields.tsx +18 -8
  30. package/src/form.tsx +27 -37
  31. package/src/index.ts +38 -0
  32. package/src/input-right-button/input-right-button.stories.tsx +1 -1
  33. package/src/input-right-button/input-right-button.tsx +0 -2
  34. package/src/layout.tsx +16 -11
  35. package/src/number-input/number-input.tsx +9 -5
  36. package/src/object-field.tsx +8 -7
  37. package/src/password-input/password-input.stories.tsx +23 -2
  38. package/src/password-input/password-input.tsx +5 -5
  39. package/src/pin-input/pin-input.tsx +1 -5
  40. package/src/radio/radio-input.stories.tsx +1 -1
  41. package/src/radio/radio-input.tsx +12 -10
  42. package/src/select/native-select.tsx +1 -4
  43. package/src/select/select.test.tsx +1 -1
  44. package/src/select/select.tsx +18 -14
  45. package/src/step-form.tsx +29 -11
  46. package/src/submit-button.tsx +5 -1
  47. package/src/types.ts +91 -0
  48. package/src/utils.ts +15 -0
  49. /package/src/radio/{radio.test.tsx → radio-input.test.tsx} +0 -0
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './display-field'
2
2
  export * from './field'
3
3
  export * from './fields'
4
+ export * from './fields-context'
4
5
  export * from './form'
5
6
  export * from './auto-form'
6
7
  export * from './layout'
@@ -16,6 +17,43 @@ export * from './watch-field'
16
17
  export * from './input-right-button'
17
18
  export * from './select'
18
19
  export * from './password-input'
20
+ export * from './radio'
21
+
22
+ export * from './base-field'
23
+
24
+ export {
25
+ CheckboxField,
26
+ InputField,
27
+ NativeSelectField,
28
+ NumberInputField,
29
+ PasswordInputField,
30
+ PinField,
31
+ RadioField,
32
+ SelectField,
33
+ SwitchField,
34
+ TextareaField,
35
+ defaultFieldTypes,
36
+ } from './default-fields'
37
+
38
+ export type {
39
+ DefaultFields,
40
+ InputFieldProps,
41
+ NumberInputFieldProps,
42
+ PinFieldProps,
43
+ } from './default-fields'
44
+
45
+ export type {
46
+ FieldProps,
47
+ WithFields,
48
+ BaseFieldProps,
49
+ FieldOptions,
50
+ } from './types'
51
+
52
+ export { createForm } from './create-form'
53
+ export type { CreateFormProps } from './create-form'
54
+
55
+ export { createField } from './create-field'
56
+ export type { CreateFieldOptions } from './create-field'
19
57
 
20
58
  export type {
21
59
  BatchFieldArrayUpdate,
@@ -37,7 +37,7 @@ const Template: ComponentStory<typeof InputRightButton> = (args) => (
37
37
 
38
38
  export const Basic = Template.bind({})
39
39
  Basic.args = {
40
- label: 'Save',
40
+ children: 'Save',
41
41
  }
42
42
 
43
43
  export const WithIcon = Template.bind({})
@@ -7,8 +7,6 @@ import {
7
7
  InputRightElement,
8
8
  } from '@chakra-ui/react'
9
9
 
10
- import { __DEV__ } from '@chakra-ui/utils'
11
-
12
10
  export type InputRightButtonProps = ButtonProps
13
11
 
14
12
  export const InputRightButton = forwardRef<InputRightButtonProps, 'div'>(
package/src/layout.tsx CHANGED
@@ -1,9 +1,15 @@
1
1
  import * as React from 'react'
2
2
 
3
- import { chakra, SimpleGrid, SimpleGridProps, useTheme } from '@chakra-ui/react'
4
- import { cx, __DEV__ } from '@chakra-ui/utils'
3
+ import {
4
+ chakra,
5
+ ResponsiveValue,
6
+ SimpleGrid,
7
+ SimpleGridProps,
8
+ useTheme,
9
+ } from '@chakra-ui/react'
10
+ import { cx } from '@chakra-ui/utils'
5
11
 
6
- export type FormLayoutProps = SimpleGridProps
12
+ export interface FormLayoutProps extends SimpleGridProps {}
7
13
 
8
14
  interface FormLayoutItemProps {
9
15
  children: React.ReactNode
@@ -13,20 +19,21 @@ const FormLayoutItem: React.FC<FormLayoutItemProps> = ({ children }) => {
13
19
  return <chakra.div>{children}</chakra.div>
14
20
  }
15
21
 
16
- if (__DEV__) {
17
- FormLayoutItem.displayName = 'FormLayoutItem'
18
- }
22
+ FormLayoutItem.displayName = 'FormLayoutItem'
19
23
 
20
24
  /**
21
- * FormLayout
25
+ * Create consistent field spacing and positioning.
26
+ *
22
27
  *
23
28
  * Renders form items in a `SimpleGrid`
24
29
  * @see https://chakra-ui.com/docs/layout/simple-grid
30
+ *
31
+ * @see https://saas-ui.dev/docs/components/forms/form
25
32
  */
26
33
  export const FormLayout = ({ children, ...props }: FormLayoutProps) => {
27
34
  const theme = useTheme()
28
35
 
29
- const defaultProps = theme.components?.FormLayout?.defaultProps ?? {
36
+ const defaultProps = theme.components?.SuiFormLayout?.defaultProps ?? {
30
37
  spacing: 4,
31
38
  }
32
39
 
@@ -50,6 +57,4 @@ export const FormLayout = ({ children, ...props }: FormLayoutProps) => {
50
57
  )
51
58
  }
52
59
 
53
- if (__DEV__) {
54
- FormLayout.displayName = 'FormLayout'
55
- }
60
+ FormLayout.displayName = 'FormLayout'
@@ -9,7 +9,8 @@ import {
9
9
  NumberDecrementStepper,
10
10
  NumberInputProps as ChakraNumberInputProps,
11
11
  } from '@chakra-ui/react'
12
- import { __DEV__ } from '@chakra-ui/utils'
12
+
13
+ import { ChevronDownIcon, ChevronUpIcon } from '@saas-ui/core'
13
14
 
14
15
  interface NumberInputOptions {
15
16
  /**
@@ -31,7 +32,12 @@ export interface NumberInputProps
31
32
  NumberInputOptions {}
32
33
 
33
34
  export const NumberInput = forwardRef<NumberInputProps, 'div'>((props, ref) => {
34
- const { hideStepper, incrementIcon, decrementIcon, ...rest } = props
35
+ const {
36
+ hideStepper,
37
+ incrementIcon = <ChevronUpIcon />,
38
+ decrementIcon = <ChevronDownIcon />,
39
+ ...rest
40
+ } = props
35
41
 
36
42
  return (
37
43
  <ChakraNumberInput {...rest} ref={ref}>
@@ -51,6 +57,4 @@ NumberInput.defaultProps = {
51
57
  hideStepper: false,
52
58
  }
53
59
 
54
- if (__DEV__) {
55
- NumberInput.displayName = 'NumberInput'
56
- }
60
+ NumberInput.displayName = 'NumberInput'
@@ -6,14 +6,13 @@ import {
6
6
  ResponsiveValue,
7
7
  useStyleConfig,
8
8
  } from '@chakra-ui/react'
9
- import { __DEV__ } from '@chakra-ui/utils'
10
9
 
11
10
  import { FormLayout } from './layout'
12
- import { FieldProps } from './field'
11
+ import { BaseFieldProps } from './types'
13
12
 
14
13
  import { mapNestedFields } from './utils'
15
14
 
16
- export interface ObjectFieldProps extends FieldProps {
15
+ export interface ObjectFieldProps extends BaseFieldProps {
17
16
  name: string
18
17
  children: React.ReactNode
19
18
  columns?: ResponsiveValue<number>
@@ -24,7 +23,11 @@ export const FormLegend = (props: FormLabelProps) => {
24
23
  const styles = useStyleConfig('SuiFormLegend')
25
24
  return <FormLabel as="legend" sx={styles} {...props} />
26
25
  }
27
-
26
+ /**
27
+ * The object field component.
28
+ *
29
+ * @see Docs https://saas-ui.dev/docs/components/forms/object-field
30
+ */
28
31
  export const ObjectField: React.FC<ObjectFieldProps> = (props) => {
29
32
  const { name, label, hideLabel, children, columns, spacing, ...fieldProps } =
30
33
  props
@@ -39,6 +42,4 @@ export const ObjectField: React.FC<ObjectFieldProps> = (props) => {
39
42
  )
40
43
  }
41
44
 
42
- if (__DEV__) {
43
- ObjectField.displayName = 'ObjectField'
44
- }
45
+ ObjectField.displayName = 'ObjectField'
@@ -1,8 +1,15 @@
1
- import { Container, FormControl, FormLabel, Icon } from '@chakra-ui/react'
1
+ import {
2
+ Container,
3
+ FormControl,
4
+ FormLabel,
5
+ Icon,
6
+ InputLeftAddon,
7
+ InputLeftElement,
8
+ } from '@chakra-ui/react'
2
9
  import { Story } from '@storybook/react'
3
10
  import * as React from 'react'
4
11
 
5
- import { FiEye, FiEyeOff } from 'react-icons/fi'
12
+ import { FiEye, FiEyeOff, FiLock } from 'react-icons/fi'
6
13
 
7
14
  import { PasswordInput } from './password-input'
8
15
 
@@ -48,3 +55,17 @@ export const CustomWidth: Story = () => (
48
55
  <PasswordInput name="password" width="200px" />
49
56
  </FormControl>
50
57
  )
58
+
59
+ export const LeftAddon: Story = () => (
60
+ <FormControl>
61
+ <FormLabel>Password</FormLabel>
62
+ <PasswordInput
63
+ name="password"
64
+ leftAddon={
65
+ <InputLeftElement>
66
+ <FiLock />
67
+ </InputLeftElement>
68
+ }
69
+ />
70
+ </FormControl>
71
+ )
@@ -1,7 +1,7 @@
1
1
  import React, { useState } from 'react'
2
2
 
3
3
  import { forwardRef, InputGroup, Input, InputProps } from '@chakra-ui/react'
4
- import { __DEV__ } from '@chakra-ui/utils'
4
+
5
5
  import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'
6
6
 
7
7
  import { InputRightButton } from '../input-right-button'
@@ -9,6 +9,7 @@ import { InputRightButton } from '../input-right-button'
9
9
  interface PasswordOptions {
10
10
  viewIcon?: React.ReactNode
11
11
  viewOffIcon?: React.ReactNode
12
+ leftAddon?: React.ReactNode
12
13
  }
13
14
 
14
15
  export interface PasswordInputProps extends InputProps, PasswordOptions {}
@@ -23,6 +24,7 @@ export const PasswordInput = forwardRef<PasswordInputProps, 'div'>(
23
24
  width,
24
25
  size,
25
26
  variant,
27
+ leftAddon,
26
28
  ...inputProps
27
29
  } = props
28
30
  const [show, setShow] = useState(false)
@@ -45,13 +47,13 @@ export const PasswordInput = forwardRef<PasswordInputProps, 'div'>(
45
47
 
46
48
  return (
47
49
  <InputGroup {...groupProps}>
50
+ {leftAddon}
48
51
  <Input
49
52
  {...inputProps}
50
53
  ref={ref}
51
54
  type={show ? 'text' : 'password'}
52
55
  autoComplete={show ? 'off' : autoComplete}
53
56
  />
54
-
55
57
  <InputRightButton
56
58
  onClick={handleClick}
57
59
  aria-label={label}
@@ -64,6 +66,4 @@ export const PasswordInput = forwardRef<PasswordInputProps, 'div'>(
64
66
  }
65
67
  )
66
68
 
67
- if (__DEV__) {
68
- PasswordInput.displayName = 'PasswordInput'
69
- }
69
+ PasswordInput.displayName = 'PasswordInput'
@@ -8,8 +8,6 @@ import {
8
8
  SystemProps,
9
9
  } from '@chakra-ui/react'
10
10
 
11
- import { __DEV__ } from '@chakra-ui/utils'
12
-
13
11
  interface PinInputOptions {
14
12
  /**
15
13
  * The pin length.
@@ -45,6 +43,4 @@ PinInput.defaultProps = {
45
43
  pinLength: 4,
46
44
  }
47
45
 
48
- if (__DEV__) {
49
- PinInput.displayName = 'PinInput'
50
- }
46
+ PinInput.displayName = 'PinInput'
@@ -6,7 +6,7 @@ import { ComponentStory } from '@storybook/react'
6
6
  import { RadioInput } from './'
7
7
 
8
8
  export default {
9
- title: 'Components/Forms/Radio',
9
+ title: 'Components/Forms/RadioInput',
10
10
  decorators: [
11
11
  (Story: any) => (
12
12
  <Container mt="40px">
@@ -10,15 +10,17 @@ import {
10
10
  SystemProps,
11
11
  StackDirection,
12
12
  } from '@chakra-ui/react'
13
- import { __DEV__ } from '@chakra-ui/utils'
13
+ import { FieldOptions, FieldOption } from '../types'
14
+ import { mapOptions } from '../utils'
14
15
 
15
- interface Option extends RadioProps {
16
- value: string
17
- label?: string
18
- }
16
+ export interface RadioOption
17
+ extends Omit<RadioProps, 'value' | 'label'>,
18
+ FieldOption {}
19
+
20
+ export type RadioOptions = FieldOptions<RadioOption>
19
21
 
20
22
  interface RadioInputOptions {
21
- options: Option[]
23
+ options: RadioOptions
22
24
  spacing?: SystemProps['margin']
23
25
  direction?: StackDirection
24
26
  }
@@ -28,9 +30,11 @@ export interface RadioInputProps
28
30
  RadioInputOptions {}
29
31
 
30
32
  export const RadioInput = forwardRef<RadioInputProps, 'div'>(
31
- ({ options, spacing, direction, ...props }, ref) => {
33
+ ({ options: optionsProp, spacing, direction, ...props }, ref) => {
32
34
  const { onBlur, onChange, ...groupProps } = props
33
35
 
36
+ const options = mapOptions(optionsProp)
37
+
34
38
  return (
35
39
  <RadioGroup onChange={onChange} {...groupProps}>
36
40
  <Stack spacing={spacing} direction={direction}>
@@ -53,6 +57,4 @@ export const RadioInput = forwardRef<RadioInputProps, 'div'>(
53
57
  }
54
58
  )
55
59
 
56
- if (__DEV__) {
57
- RadioInput.displayName = 'RadioInput'
58
- }
60
+ RadioInput.displayName = 'RadioInput'
@@ -5,7 +5,6 @@ import {
5
5
  Select as ChakraSelect,
6
6
  SelectProps as ChakraSelectProps,
7
7
  } from '@chakra-ui/react'
8
- import { __DEV__ } from '@chakra-ui/utils'
9
8
 
10
9
  interface Option {
11
10
  value: string
@@ -37,6 +36,4 @@ export const NativeSelect = forwardRef<NativeSelectProps, 'select'>(
37
36
  }
38
37
  )
39
38
 
40
- if (__DEV__) {
41
- NativeSelect.displayName = 'NativeSelect'
42
- }
39
+ NativeSelect.displayName = 'NativeSelect'
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react'
2
2
 
3
3
  import { render, testStories } from '@saas-ui/test-utils'
4
- import * as stories from '../stories/select.stories'
4
+ import * as stories from './select.stories'
5
5
 
6
6
  const { MaxHeight, ...rest } = stories
7
7
 
@@ -18,21 +18,24 @@ import {
18
18
  SystemStyleObject,
19
19
  useFormControl,
20
20
  HTMLChakraProps,
21
+ MenuItemOptionProps,
21
22
  } from '@chakra-ui/react'
22
- import { ChevronDownIcon } from '@chakra-ui/icons'
23
- import { cx, __DEV__ } from '@chakra-ui/utils'
23
+ import { cx } from '@chakra-ui/utils'
24
+ import { ChevronDownIcon } from '@saas-ui/core'
24
25
 
25
- interface Option {
26
- value: string
27
- label?: string
28
- }
26
+ import { FieldOptions, FieldOption } from '../types'
27
+ import { mapOptions } from '../utils'
28
+
29
+ export interface SelectOption
30
+ extends Omit<MenuItemOptionProps, 'value'>,
31
+ FieldOption {}
29
32
 
30
33
  interface SelectOptions {
31
34
  /**
32
35
  * An array of options
33
36
  * If you leave this empty the children prop will be rendered.
34
37
  */
35
- options?: Option[]
38
+ options?: FieldOptions<SelectOption>
36
39
  /**
37
40
  * Props passed to the MenuList.
38
41
  */
@@ -80,14 +83,12 @@ const SelectButton = forwardRef((props, ref) => {
80
83
  return <MenuButton as={Button} {...props} ref={ref} sx={buttonStyles} />
81
84
  })
82
85
 
83
- if (__DEV__) {
84
- SelectButton.displayName = 'SelectButton'
85
- }
86
+ SelectButton.displayName = 'SelectButton'
86
87
 
87
88
  export const Select = forwardRef<SelectProps, 'select'>((props, ref) => {
88
89
  const {
89
90
  name,
90
- options,
91
+ options: optionsProp,
91
92
  children,
92
93
  onChange,
93
94
  defaultValue,
@@ -109,6 +110,11 @@ export const Select = forwardRef<SelectProps, 'select'>((props, ref) => {
109
110
 
110
111
  const controlProps = useFormControl({ name } as HTMLChakraProps<'input'>)
111
112
 
113
+ const options = React.useMemo(
114
+ () => optionsProp && mapOptions(optionsProp),
115
+ [optionsProp]
116
+ )
117
+
112
118
  const handleChange = (value: string | string[]) => {
113
119
  setCurrentValue(value)
114
120
  onChange?.(value)
@@ -180,6 +186,4 @@ export const Select = forwardRef<SelectProps, 'select'>((props, ref) => {
180
186
  )
181
187
  })
182
188
 
183
- if (__DEV__) {
184
- Select.displayName = 'Select'
185
- }
189
+ Select.displayName = 'Select'
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,91 @@
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
+
7
+ export type FieldOption = { label: string; value: string }
8
+ export type FieldOptions<TOption extends FieldOption = FieldOption> =
9
+ | Array<string>
10
+ | Array<TOption>
11
+
12
+ export type ValueOf<T> = T[keyof T]
13
+ export type ShallowMerge<A, B> = Omit<A, keyof B> & B
14
+
15
+ export interface BaseFieldProps<
16
+ TFieldValues extends FieldValues = FieldValues,
17
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
18
+ > extends Omit<FormControlProps, 'label' | 'type'> {
19
+ /**
20
+ * The field name
21
+ */
22
+ name: TName
23
+ /**
24
+ * The field label
25
+ */
26
+ label?: string
27
+ /**
28
+ * Hide the field label
29
+ */
30
+ hideLabel?: boolean
31
+ /**
32
+ * Field help text
33
+ */
34
+ help?: string
35
+ /**
36
+ * React hook form rules
37
+ */
38
+ rules?: Omit<
39
+ RegisterOptions<TFieldValues, TName>,
40
+ 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'
41
+ >
42
+ /**
43
+ * Build-in types:
44
+ * text, number, password, textarea, select, native-select, checkbox, radio, switch, pin
45
+ *
46
+ * Will default to a text field if there is no matching type.
47
+ */
48
+ type?: string
49
+ /**
50
+ * The input placeholder
51
+ */
52
+ placeholder?: string
53
+ }
54
+
55
+ type MergeFieldProps<
56
+ FieldDefs,
57
+ TFieldValues extends FieldValues = FieldValues
58
+ > = ValueOf<{
59
+ [K in keyof FieldDefs]: FieldDefs[K] extends React.FC<infer Props>
60
+ ? { type?: K } & ShallowMerge<Props, BaseFieldProps<TFieldValues>>
61
+ : never
62
+ }>
63
+
64
+ export type FieldProps<TFieldValues extends FieldValues = FieldValues> =
65
+ MergeFieldProps<DefaultFields, TFieldValues>
66
+
67
+ export type FormChildren<
68
+ FieldDefs,
69
+ TFieldValues extends FieldValues = FieldValues,
70
+ TContext extends object = object
71
+ > = MaybeRenderProp<
72
+ FormRenderContext<
73
+ TFieldValues,
74
+ TContext,
75
+ MergeFieldProps<
76
+ FieldDefs extends never
77
+ ? DefaultFields
78
+ : ShallowMerge<DefaultFields, FieldDefs>,
79
+ TFieldValues
80
+ >
81
+ >
82
+ >
83
+
84
+ export type WithFields<
85
+ TFormProps extends FormProps<any, any, any, any>,
86
+ FieldDefs
87
+ > = TFormProps extends FormProps<infer TFieldValues, infer TContext>
88
+ ? Omit<TFormProps, 'children'> & {
89
+ children: FormChildren<FieldDefs, TFieldValues, TContext>
90
+ }
91
+ : never
package/src/utils.ts CHANGED
@@ -1,4 +1,5 @@
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) => {
@@ -11,3 +12,17 @@ export const mapNestedFields = (name: string, children: React.ReactNode) => {
11
12
  return child
12
13
  })
13
14
  }
15
+
16
+ export const mapOptions = <TOption extends FieldOption = FieldOption>(
17
+ options: FieldOptions<TOption>
18
+ ) => {
19
+ return options.map((option) => {
20
+ if (typeof option === 'string') {
21
+ return {
22
+ label: option,
23
+ value: option,
24
+ }
25
+ }
26
+ return option
27
+ })
28
+ }