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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/README.md +53 -6
  3. package/dist/ajv/index.d.ts +24 -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 +519 -280
  9. package/dist/index.js +777 -696
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +756 -676
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/yup/index.d.ts +525 -21
  14. package/dist/yup/index.js +21 -9
  15. package/dist/yup/index.js.map +1 -1
  16. package/dist/yup/index.mjs +21 -10
  17. package/dist/yup/index.mjs.map +1 -1
  18. package/dist/zod/index.d.ts +525 -12
  19. package/dist/zod/index.js +21 -1
  20. package/dist/zod/index.js.map +1 -1
  21. package/dist/zod/index.mjs +21 -3
  22. package/dist/zod/index.mjs.map +1 -1
  23. package/package.json +33 -10
  24. package/src/array-field.tsx +88 -48
  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 +68 -0
  29. package/src/create-step-form.tsx +100 -0
  30. package/src/default-fields.tsx +163 -0
  31. package/src/display-field.tsx +9 -11
  32. package/src/display-if.tsx +20 -13
  33. package/src/field-resolver.ts +10 -8
  34. package/src/field.tsx +18 -445
  35. package/src/fields-context.tsx +23 -0
  36. package/src/fields.tsx +34 -21
  37. package/src/form-context.tsx +84 -0
  38. package/src/form.tsx +77 -55
  39. package/src/index.ts +58 -4
  40. package/src/input-right-button/input-right-button.stories.tsx +1 -1
  41. package/src/input-right-button/input-right-button.tsx +0 -2
  42. package/src/layout.tsx +16 -11
  43. package/src/number-input/number-input.tsx +9 -5
  44. package/src/object-field.tsx +35 -13
  45. package/src/password-input/password-input.stories.tsx +23 -2
  46. package/src/password-input/password-input.tsx +6 -6
  47. package/src/pin-input/pin-input.tsx +1 -5
  48. package/src/radio/radio-input.stories.tsx +1 -1
  49. package/src/radio/radio-input.tsx +12 -10
  50. package/src/select/native-select.tsx +1 -4
  51. package/src/select/select-context.tsx +130 -0
  52. package/src/select/select.stories.tsx +116 -85
  53. package/src/select/select.test.tsx +1 -1
  54. package/src/select/select.tsx +162 -146
  55. package/src/step-form.tsx +76 -76
  56. package/src/submit-button.tsx +5 -1
  57. package/src/types.ts +149 -0
  58. package/src/use-array-field.tsx +9 -3
  59. package/src/use-step-form.tsx +54 -9
  60. package/src/utils.ts +23 -1
  61. package/src/watch-field.tsx +2 -6
  62. /package/src/radio/{radio.test.tsx → radio-input.test.tsx} +0 -0
package/src/types.ts ADDED
@@ -0,0 +1,149 @@
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
+ ExtraOverrides = object
138
+ > = TFormProps extends FormProps<
139
+ infer TSchema,
140
+ infer TFieldValues,
141
+ infer TContext
142
+ >
143
+ ? Omit<TFormProps, 'children' | 'fields'> & {
144
+ children?: FormChildren<FieldDefs, TFieldValues, TContext>
145
+ fields?: FieldOverrides<FieldDefs, TFieldValues> & {
146
+ submit?: SubmitButtonProps
147
+ } & ExtraOverrides
148
+ }
149
+ : 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
  */
@@ -34,12 +34,43 @@ export const [StepFormProvider, useStepFormContext] =
34
34
  })
35
35
 
36
36
  import { FormProps } from './form'
37
+ import { FormStepProps, StepsOptions } from './step-form'
38
+ import { FieldProps } from './types'
39
+ import { FocusableElement } from '@chakra-ui/utils'
40
+ import { DisplayIfProps } from './display-if'
41
+ import { ArrayFieldProps } from './array-field'
42
+ import { UseArrayFieldReturn } from './use-array-field'
43
+ import { ObjectFieldProps } from './object-field'
44
+
45
+ type StepName<T extends { [k: number]: { readonly name: string } }> =
46
+ T[number]['name']
47
+
48
+ interface StepFormRenderContext<
49
+ TSteps extends StepsOptions<any> = StepsOptions<any>,
50
+ TFieldValues extends FieldValues = FieldValues,
51
+ TContext extends object = object,
52
+ TFieldTypes = FieldProps<TFieldValues>
53
+ > extends UseStepFormReturn<TFieldValues> {
54
+ Field: React.FC<TFieldTypes & React.RefAttributes<FocusableElement>>
55
+ FormStep: React.FC<FormStepProps<StepName<TSteps>>>
56
+ DisplayIf: React.FC<DisplayIfProps<TFieldValues>>
57
+ ArrayField: React.FC<
58
+ ArrayFieldProps<TFieldValues> & React.RefAttributes<UseArrayFieldReturn>
59
+ >
60
+ ObjectField: React.FC<ObjectFieldProps<TFieldValues>>
61
+ }
37
62
 
38
63
  export interface UseStepFormProps<
39
- TFieldValues extends FieldValues = FieldValues
64
+ TSteps extends StepsOptions<any> = StepsOptions<any>,
65
+ TFieldValues extends FieldValues = FieldValues,
66
+ TContext extends object = object,
67
+ TFieldTypes = FieldProps<TFieldValues>
40
68
  > extends Omit<UseStepperProps, 'onChange'>,
41
- Omit<FormProps<TFieldValues>, 'children'> {
42
- children: MaybeRenderProp<UseStepFormReturn<TFieldValues>>
69
+ Omit<FormProps<any, TFieldValues, TContext, TFieldTypes>, 'children'> {
70
+ steps?: TSteps
71
+ children: MaybeRenderProp<
72
+ StepFormRenderContext<TSteps, TFieldValues, TContext, TFieldTypes>
73
+ >
43
74
  }
44
75
 
45
76
  export interface UseStepFormReturn<
@@ -54,12 +85,19 @@ export interface UseStepFormReturn<
54
85
  steps: Record<string, any>
55
86
  }
56
87
 
57
- export function useStepForm<TFieldValues extends FieldValues = FieldValues>(
58
- props: UseStepFormProps<TFieldValues>
88
+ export function useStepForm<
89
+ TSteps extends StepsOptions<any> = StepsOptions<any>,
90
+ TFieldValues extends FieldValues = FieldValues,
91
+ TContext extends object = object,
92
+ TFieldTypes = FieldProps<TFieldValues>
93
+ >(
94
+ props: UseStepFormProps<TSteps, TFieldValues, TContext, TFieldTypes>
59
95
  ): UseStepFormReturn<TFieldValues> {
60
- const { onChange, ...rest } = props
96
+ const { onChange, steps: stepsOptions, resolver, ...rest } = props
61
97
  const stepper = useStepper(rest)
62
98
 
99
+ const [options, setOptions] = React.useState<TSteps | undefined>(stepsOptions)
100
+
63
101
  const { activeStep, isLastStep, nextStep } = stepper
64
102
 
65
103
  const [steps, updateSteps] = React.useState<Record<string, StepState>>({})
@@ -93,23 +131,30 @@ export function useStepForm<TFieldValues extends FieldValues = FieldValues>(
93
131
 
94
132
  const getFormProps = React.useCallback(() => {
95
133
  const step = steps[activeStep]
134
+
96
135
  return {
97
136
  onSubmit: onSubmitStep,
98
137
  schema: step?.schema,
99
- resolver: step?.resolver,
138
+ resolver: step?.schema
139
+ ? /* @todo fix resolver type */ (resolver as any)?.(step.schema)
140
+ : undefined,
100
141
  }
101
142
  }, [steps, onSubmitStep, activeStep])
102
143
 
103
144
  const updateStep = React.useCallback(
104
145
  (step: StepState) => {
146
+ const stepOptions = options?.find((s) => s.name === step.name)
105
147
  updateSteps((steps) => {
106
148
  return {
107
149
  ...steps,
108
- [step.name]: step,
150
+ [step.name]: {
151
+ ...step,
152
+ schema: stepOptions?.schema,
153
+ },
109
154
  }
110
155
  })
111
156
  },
112
- [steps]
157
+ [steps, options]
113
158
  )
114
159
 
115
160
  return {
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,