@saas-ui/forms 1.0.0-rc.9 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saas-ui/forms",
3
- "version": "1.0.0-rc.9",
3
+ "version": "1.0.2",
4
4
  "description": "Fully functional forms for Chakra UI.",
5
5
  "source": "src/index.ts",
6
6
  "exports": {
@@ -93,16 +93,16 @@
93
93
  "@chakra-ui/react-utils": "^2.0.1",
94
94
  "@chakra-ui/utils": "^2.0.2",
95
95
  "@hookform/resolvers": "^2.9.0",
96
- "@saas-ui/button": "1.0.0-rc.4",
97
- "@saas-ui/input-right-button": "1.0.0-rc.4",
98
- "@saas-ui/number-input": "1.0.0-rc.3",
99
- "@saas-ui/password-input": "1.0.0-rc.4",
100
- "@saas-ui/pin-input": "1.0.0-rc.4",
101
- "@saas-ui/radio": "1.0.0-rc.3",
102
- "@saas-ui/react-utils": "1.0.0-rc.3",
103
- "@saas-ui/select": "1.0.0-rc.3",
104
- "@saas-ui/stepper": "1.0.0-rc.4",
105
- "react-hook-form": "^7.31.3"
96
+ "@saas-ui/button": "1.0.0",
97
+ "@saas-ui/input-right-button": "1.0.1",
98
+ "@saas-ui/number-input": "1.0.0",
99
+ "@saas-ui/password-input": "1.0.1",
100
+ "@saas-ui/pin-input": "1.0.0",
101
+ "@saas-ui/radio": "1.0.0",
102
+ "@saas-ui/react-utils": "1.0.0",
103
+ "@saas-ui/select": "1.0.0",
104
+ "@saas-ui/stepper": "1.0.0",
105
+ "react-hook-form": "^7.33.1"
106
106
  },
107
107
  "peerDependencies": {
108
108
  "@chakra-ui/react": ">=2.1.0",
package/src/auto-form.tsx CHANGED
@@ -6,7 +6,7 @@ import { __DEV__ } from '@chakra-ui/utils'
6
6
  import { Form, FormProps } from './form'
7
7
  import { FormLayout } from './layout'
8
8
  import { Fields } from './fields'
9
- import { SubmitButton, SubmitButtonProps } from './submit-button'
9
+ import { SubmitButton } from './submit-button'
10
10
  import { FieldResolver } from '.'
11
11
 
12
12
  interface AutoFormOptions {
@@ -48,7 +48,7 @@ export const AutoForm = forwardRef(
48
48
  <Form {...rest} schema={schema} ref={ref}>
49
49
  <FormLayout>
50
50
  {<Fields schema={schema} fieldResolver={fieldResolver} />}
51
- {submitLabel && <SubmitButton label={submitLabel} />}
51
+ {submitLabel && <SubmitButton>{submitLabel}</SubmitButton>}
52
52
  {children}
53
53
  </FormLayout>
54
54
  </Form>
@@ -16,10 +16,11 @@ export type ObjectSchema = Record<string, SchemaField>
16
16
 
17
17
  const mapFields = (schema: ObjectSchema): FieldProps[] =>
18
18
  schema &&
19
- Object.entries(schema).map(([name, { items, ...field }]) => {
19
+ Object.entries(schema).map(([name, { items, label, title, ...field }]) => {
20
20
  return {
21
21
  ...field,
22
22
  name,
23
+ label: label || title, // json schema compatibility
23
24
  }
24
25
  })
25
26
 
package/src/field.tsx CHANGED
@@ -27,14 +27,13 @@ import {
27
27
  TextareaProps,
28
28
  SwitchProps,
29
29
  CheckboxProps,
30
- PinInputProps,
31
30
  PinInputField,
32
31
  HStack,
33
32
  PinInput,
34
33
  UsePinInputProps,
35
34
  SystemProps,
36
35
  } from '@chakra-ui/react'
37
- import { __DEV__ } from '@chakra-ui/utils'
36
+ import { __DEV__, FocusableElement } from '@chakra-ui/utils'
38
37
 
39
38
  import { NumberInput, NumberInputProps } from '@saas-ui/number-input'
40
39
  import { PasswordInput, PasswordInputProps } from '@saas-ui/password-input'
@@ -46,7 +45,6 @@ import {
46
45
  NativeSelect,
47
46
  NativeSelectProps,
48
47
  } from '@saas-ui/select'
49
- import { FocusableElement } from '@chakra-ui/utils'
50
48
 
51
49
  export interface Option {
52
50
  value: string
@@ -217,7 +215,6 @@ const createField = (
217
215
  isInvalid,
218
216
  isReadOnly,
219
217
  isRequired,
220
- isOptional,
221
218
  rules,
222
219
  variant,
223
220
  ...inputProps
@@ -239,7 +236,6 @@ const createField = (
239
236
  isInvalid={isInvalid}
240
237
  isReadOnly={isReadOnly}
241
238
  isRequired={isRequired}
242
- isOptional={isOptional}
243
239
  variant={variant}
244
240
  >
245
241
  <InputComponent
package/src/fields.tsx CHANGED
@@ -8,10 +8,12 @@ import { Field, FieldProps } from './field'
8
8
  import { ArrayField } from './array-field'
9
9
  import { ObjectField } from './object-field'
10
10
  import { FieldResolver } from './field-resolver'
11
+ import { useFormContext } from 'react-hook-form'
11
12
 
12
13
  export interface FieldsProps {
13
14
  schema: any
14
15
  fieldResolver?: FieldResolver
16
+ focusFirstField?: boolean
15
17
  }
16
18
 
17
19
  const mapNestedFields = (resolver: FieldResolver, name: string) => {
@@ -27,6 +29,7 @@ const mapNestedFields = (resolver: FieldResolver, name: string) => {
27
29
  export const Fields: React.FC<FieldsProps> = ({
28
30
  schema,
29
31
  fieldResolver,
32
+ focusFirstField,
30
33
  ...props
31
34
  }) => {
32
35
  const resolver = React.useMemo(
@@ -34,34 +37,42 @@ export const Fields: React.FC<FieldsProps> = ({
34
37
  [schema, fieldResolver]
35
38
  )
36
39
 
40
+ const fields = React.useMemo(() => resolver.getFields(), [resolver])
41
+
42
+ const form = useFormContext()
43
+
44
+ React.useEffect(() => {
45
+ if (focusFirstField && fields[0]?.name) {
46
+ form.setFocus(fields[0].name)
47
+ }
48
+ }, [schema, fieldResolver, focusFirstField])
49
+
37
50
  return (
38
51
  <FormLayout {...props}>
39
- {resolver
40
- .getFields()
41
- .map(
42
- ({
43
- name,
44
- type,
45
- defaultValue,
46
- ...fieldProps
47
- }: FieldProps): React.ReactNode => {
48
- if (type === 'array') {
49
- return (
50
- <ArrayField key={name} name={name} {...fieldProps}>
51
- {mapNestedFields(resolver, name)}
52
- </ArrayField>
53
- )
54
- } else if (type === 'object') {
55
- return (
56
- <ObjectField key={name} name={name} {...fieldProps}>
57
- {mapNestedFields(resolver, name)}
58
- </ObjectField>
59
- )
60
- }
61
-
62
- return <Field key={name} name={name} type={type} {...fieldProps} />
52
+ {fields.map(
53
+ ({
54
+ name,
55
+ type,
56
+ defaultValue,
57
+ ...fieldProps
58
+ }: FieldProps): React.ReactNode => {
59
+ if (type === 'array') {
60
+ return (
61
+ <ArrayField key={name} name={name} {...fieldProps}>
62
+ {mapNestedFields(resolver, name)}
63
+ </ArrayField>
64
+ )
65
+ } else if (type === 'object') {
66
+ return (
67
+ <ObjectField key={name} name={name} {...fieldProps}>
68
+ {mapNestedFields(resolver, name)}
69
+ </ObjectField>
70
+ )
63
71
  }
64
- )}
72
+
73
+ return <Field key={name} name={name} type={type} {...fieldProps} />
74
+ }
75
+ )}
65
76
  </FormLayout>
66
77
  )
67
78
  }
package/src/form.tsx CHANGED
@@ -11,7 +11,6 @@ import {
11
11
  FieldValues,
12
12
  SubmitHandler,
13
13
  SubmitErrorHandler,
14
- UnpackNestedValue,
15
14
  ResolverOptions,
16
15
  ResolverResult,
17
16
  } from 'react-hook-form'
@@ -35,7 +34,7 @@ interface FormOptions<TFieldValues extends FieldValues = FieldValues> {
35
34
  /**
36
35
  * Ref on the HTMLFormElement.
37
36
  */
38
- formRef?: React.MutableRefObject<HTMLFormElement>
37
+ formRef?: React.RefObject<HTMLFormElement>
39
38
  }
40
39
 
41
40
  /**
@@ -124,7 +123,7 @@ if (__DEV__) {
124
123
  export type GetResolver = (
125
124
  schema: any
126
125
  ) => <TFieldValues extends FieldValues, TContext>(
127
- values: UnpackNestedValue<TFieldValues>,
126
+ values: TFieldValues,
128
127
  context: TContext | undefined,
129
128
  options: ResolverOptions<TFieldValues>
130
129
  ) => Promise<ResolverResult<TFieldValues>>
package/src/step-form.tsx CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  } from '@saas-ui/stepper'
17
17
  import { Button, ButtonProps } from '@saas-ui/button'
18
18
 
19
- import { Form, FormProps } from './form'
19
+ import { Form } from './form'
20
20
  import { SubmitButton } from './submit-button'
21
21
 
22
22
  import {
@@ -24,6 +24,7 @@ import {
24
24
  useFormStep,
25
25
  StepFormProvider,
26
26
  UseStepFormProps,
27
+ FormStepSubmitHandler,
27
28
  } from './use-step-form'
28
29
 
29
30
  export interface StepFormProps<TFieldValues extends FieldValues = FieldValues>
@@ -113,11 +114,14 @@ export const FormStepper: React.FC<StepperStepsProps> = (props) => {
113
114
 
114
115
  export interface FormStepProps
115
116
  extends FormStepOptions,
116
- HTMLChakraProps<'div'> {}
117
+ Omit<HTMLChakraProps<'div'>, 'onSubmit'> {
118
+ onSubmit?: FormStepSubmitHandler
119
+ }
117
120
 
118
121
  export const FormStep: React.FC<FormStepProps> = (props) => {
119
- const { name, schema, resolver, children, className, ...rest } = props
120
- const step = useFormStep({ name, schema, resolver })
122
+ const { name, schema, resolver, children, className, onSubmit, ...rest } =
123
+ props
124
+ const step = useFormStep({ name, schema, resolver, onSubmit })
121
125
 
122
126
  const { isActive } = step
123
127
 
@@ -160,11 +164,12 @@ export const NextButton: React.FC<NextButtonProps> = (props) => {
160
164
 
161
165
  return (
162
166
  <SubmitButton
163
- isDisabled={isCompleted}
164
- label={isLastStep || isCompleted ? submitLabel : label}
165
167
  {...rest}
168
+ isDisabled={isCompleted}
166
169
  className={cx('saas-form__next-button', props.className)}
167
- />
170
+ >
171
+ {isLastStep || isCompleted ? submitLabel : label}
172
+ </SubmitButton>
168
173
  )
169
174
  }
170
175
 
@@ -4,7 +4,6 @@ import { useFormContext } from 'react-hook-form'
4
4
 
5
5
  import { Button, ButtonProps } from '@saas-ui/button'
6
6
 
7
- import { forwardRef } from '@chakra-ui/system'
8
7
  import { __DEV__ } from '@chakra-ui/utils'
9
8
 
10
9
  export interface SubmitButtonProps extends ButtonProps {
@@ -24,29 +23,38 @@ export interface SubmitButtonProps extends ButtonProps {
24
23
  disableIfInvalid?: boolean
25
24
  }
26
25
 
27
- export const SubmitButton = forwardRef<SubmitButtonProps, 'button'>(
28
- (props, ref) => {
29
- const { children, disableIfUntouched, disableIfInvalid, ...rest } = props
30
- const { formState } = useFormContext()
31
-
32
- const isDisabled =
33
- (disableIfUntouched && !formState.isDirty) ||
34
- (disableIfInvalid && !formState.isValid)
35
-
36
- return (
37
- <Button
38
- type="submit"
39
- isLoading={formState.isSubmitting}
40
- colorScheme="primary"
41
- ref={ref}
42
- isDisabled={isDisabled}
43
- {...rest}
44
- >
45
- {children}
46
- </Button>
47
- )
48
- }
49
- )
26
+ export const SubmitButton = React.forwardRef<
27
+ HTMLButtonElement,
28
+ SubmitButtonProps
29
+ >((props, ref) => {
30
+ const {
31
+ children,
32
+ disableIfUntouched,
33
+ disableIfInvalid,
34
+ isDisabled: isDisabledProp,
35
+ isLoading,
36
+ ...rest
37
+ } = props
38
+ const { formState } = useFormContext()
39
+
40
+ const isDisabled =
41
+ (disableIfUntouched && !formState.isDirty) ||
42
+ (disableIfInvalid && !formState.isValid) ||
43
+ isDisabledProp
44
+
45
+ return (
46
+ <Button
47
+ {...rest}
48
+ ref={ref}
49
+ variant="primary"
50
+ type="submit"
51
+ isLoading={formState.isSubmitting || isLoading}
52
+ isDisabled={isDisabled}
53
+ >
54
+ {children}
55
+ </Button>
56
+ )
57
+ })
50
58
 
51
59
  SubmitButton.defaultProps = {
52
60
  label: 'Submit',
@@ -14,8 +14,13 @@ export interface StepState {
14
14
  resolver?: any
15
15
  isActive?: boolean
16
16
  isCompleted?: boolean
17
+ onSubmit?: FormStepSubmitHandler
17
18
  }
18
19
 
20
+ export type FormStepSubmitHandler<
21
+ TFieldValues extends FieldValues = FieldValues
22
+ > = (data: TFieldValues, stepper: UseStepperReturn) => Promise<void>
23
+
19
24
  export interface StepFormContext extends UseStepperReturn {
20
25
  updateStep(state: StepState): void
21
26
  steps: Record<string, StepState>
@@ -61,11 +66,12 @@ export function useStepForm<TFieldValues extends FieldValues = FieldValues>(
61
66
 
62
67
  const onSubmitStep: SubmitHandler<TFieldValues> = React.useCallback(
63
68
  async (data) => {
69
+ const step = steps[activeStep]
70
+
64
71
  if (isLastStep) {
65
72
  return props
66
73
  .onSubmit?.(data)
67
74
  .then(() => {
68
- const step = steps[activeStep]
69
75
  updateStep({
70
76
  ...step,
71
77
  isCompleted: true,
@@ -74,9 +80,15 @@ export function useStepForm<TFieldValues extends FieldValues = FieldValues>(
74
80
  .then(nextStep) // Show completed step
75
81
  }
76
82
 
77
- nextStep()
83
+ try {
84
+ await step.onSubmit?.(data, stepper)
85
+
86
+ nextStep()
87
+ } catch (e) {
88
+ // Step submission failed.
89
+ }
78
90
  },
79
- [activeStep, isLastStep]
91
+ [steps, activeStep, isLastStep]
80
92
  )
81
93
 
82
94
  const getFormProps = React.useCallback(() => {
@@ -112,16 +124,17 @@ export interface UseFormStepProps {
112
124
  name: string
113
125
  schema?: any
114
126
  resolver?: any
127
+ onSubmit?: FormStepSubmitHandler
115
128
  }
116
129
 
117
130
  export function useFormStep(props: UseFormStepProps): StepState {
118
- const { name, schema, resolver } = props
131
+ const { name, schema, resolver, onSubmit } = props
119
132
  const step = useStep({ name })
120
133
 
121
134
  const { steps, updateStep } = useStepFormContext()
122
135
 
123
136
  React.useEffect(() => {
124
- updateStep({ name, schema, resolver })
137
+ updateStep({ name, schema, resolver, onSubmit })
125
138
  }, [name, schema])
126
139
 
127
140
  return {
package/yup/package.json CHANGED
@@ -10,8 +10,8 @@
10
10
  "author": "Eelco Wiersma <eelco@appulse.nl>",
11
11
  "license": "MIT",
12
12
  "peerDependencies": {
13
- "@hookform/resolvers": "^2.9.0",
14
- "react-hook-form": "^7.31.3",
13
+ "@hookform/resolvers": "^2.9.3",
14
+ "react-hook-form": "^7.33.1",
15
15
  "yup": "^0.32.11"
16
16
  }
17
17
  }
package/zod/package.json CHANGED
@@ -11,8 +11,8 @@
11
11
  "license": "MIT",
12
12
  "peerDependencies": {
13
13
  "@chakra-ui/utils": "^2.0.2",
14
- "@hookform/resolvers": "^2.9.0",
15
- "react-hook-form": "^7.31.3",
14
+ "@hookform/resolvers": "^2.9.3",
15
+ "react-hook-form": "^7.33.1",
16
16
  "zod": "^3.17.3"
17
17
  }
18
18
  }