@saas-ui/forms 0.4.0 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,166 @@
1
+ import * as React from 'react'
2
+
3
+ import { FieldValues, UseFormReturn } from 'react-hook-form'
4
+
5
+ import {
6
+ chakra,
7
+ HTMLChakraProps,
8
+ useMultiStyleConfig,
9
+ StylesProvider,
10
+ } from '@chakra-ui/system'
11
+
12
+ import { callAllHandlers, runIfFn, cx, __DEV__ } from '@chakra-ui/utils'
13
+
14
+ import {
15
+ StepperProvider,
16
+ StepperSteps,
17
+ StepperStepsProps,
18
+ StepperStep,
19
+ useStepperContext,
20
+ } from '@saas-ui/stepper'
21
+ import { Button, ButtonProps } from '@saas-ui/button'
22
+
23
+ import { Form, FormProps } from './form'
24
+ import { SubmitButton } from './submit-button'
25
+
26
+ import {
27
+ useStepForm,
28
+ useFormStep,
29
+ StepFormProvider,
30
+ UseStepFormProps,
31
+ } from './use-step-form'
32
+
33
+ export interface StepFormProps<TFieldValues extends FieldValues = FieldValues>
34
+ extends UseStepFormProps<TFieldValues>,
35
+ FormProps<TFieldValues> {}
36
+
37
+ export const StepForm = React.forwardRef(
38
+ <TFieldValues extends FieldValues = FieldValues>(
39
+ props: StepFormProps<TFieldValues>,
40
+ ref: React.ForwardedRef<UseFormReturn<TFieldValues>>
41
+ ) => {
42
+ const { children, onSubmit, ...rest } = props
43
+
44
+ const stepper = useStepForm<TFieldValues>(props)
45
+
46
+ const { getFormProps, ...ctx } = stepper
47
+
48
+ const context = React.useMemo(() => ctx, [ctx])
49
+
50
+ return (
51
+ <StepperProvider value={context}>
52
+ <StepFormProvider value={context}>
53
+ <Form ref={ref} {...rest} {...getFormProps(props)}>
54
+ {runIfFn(children, stepper)}
55
+ </Form>
56
+ </StepFormProvider>
57
+ </StepperProvider>
58
+ )
59
+ }
60
+ ) as <TFieldValues extends FieldValues>(
61
+ props: FormProps<TFieldValues> & {
62
+ ref?: React.ForwardedRef<UseFormReturn<TFieldValues>>
63
+ }
64
+ ) => React.ReactElement
65
+
66
+ export interface FormStepOptions {
67
+ /**
68
+ * The step name
69
+ */
70
+ name: string
71
+ /**
72
+ * Schema
73
+ */
74
+ schema?: any
75
+ }
76
+
77
+ export const FormStepper: React.FC<StepperStepsProps> = (props) => {
78
+ const styles = useMultiStyleConfig('Stepper', props)
79
+
80
+ const { children } = props
81
+
82
+ const elements = React.Children.map(children, (child) => {
83
+ if (React.isValidElement(child) && child?.type === FormStep) {
84
+ const { isCompleted } = useFormStep(child.props) // Register this step
85
+ return (
86
+ <StepperStep
87
+ name={child.props.name}
88
+ title={child.props.title}
89
+ isCompleted={isCompleted}
90
+ >
91
+ {child.props.children}
92
+ </StepperStep>
93
+ )
94
+ }
95
+ return child
96
+ })
97
+
98
+ return (
99
+ <StylesProvider value={styles}>
100
+ <StepperSteps mb="4" {...props}>
101
+ {elements}
102
+ </StepperSteps>
103
+ </StylesProvider>
104
+ )
105
+ }
106
+
107
+ export interface FormStepProps
108
+ extends FormStepOptions,
109
+ HTMLChakraProps<'div'> {}
110
+
111
+ export const FormStep: React.FC<FormStepProps> = (props) => {
112
+ const { name, schema, children, className, ...rest } = props
113
+ const step = useFormStep({ name, schema })
114
+
115
+ const { isActive } = step
116
+
117
+ return isActive ? (
118
+ <chakra.div {...rest} className={cx('saas-form__step', className)}>
119
+ {children}
120
+ </chakra.div>
121
+ ) : null
122
+ }
123
+
124
+ if (__DEV__) {
125
+ FormStep.displayName = 'FormStep'
126
+ }
127
+
128
+ export const PrevButton: React.FC<ButtonProps> = (props) => {
129
+ const { isFirstStep, isCompleted, prevStep } = useStepperContext()
130
+
131
+ return (
132
+ <Button
133
+ isDisabled={isFirstStep || isCompleted}
134
+ label="Back"
135
+ {...props}
136
+ className={cx('saas-form__prev-button', props.className)}
137
+ onClick={callAllHandlers(props.onClick, prevStep)}
138
+ />
139
+ )
140
+ }
141
+
142
+ if (__DEV__) {
143
+ PrevButton.displayName = 'PrevButton'
144
+ }
145
+
146
+ export interface NextButtonProps extends ButtonProps {
147
+ submitLabel?: string
148
+ }
149
+
150
+ export const NextButton: React.FC<NextButtonProps> = (props) => {
151
+ const { label = 'Next', submitLabel = 'Complete', ...rest } = props
152
+ const { isLastStep, isCompleted } = useStepperContext()
153
+
154
+ return (
155
+ <SubmitButton
156
+ isDisabled={isCompleted}
157
+ label={isLastStep || isCompleted ? submitLabel : label}
158
+ {...rest}
159
+ className={cx('saas-form__next-button', props.className)}
160
+ />
161
+ )
162
+ }
163
+
164
+ if (__DEV__) {
165
+ NextButton.displayName = 'NextButton'
166
+ }
@@ -69,7 +69,7 @@ export interface ArrayFieldOptions {
69
69
  */
70
70
  defaultValue?: Record<string, any>
71
71
  /**
72
- * Default key name for rows, change this if your data uses 'id'
72
+ * Default key name for rows, change this if your data uses a different 'id' field
73
73
  * @default "id"
74
74
  */
75
75
  keyName?: string
@@ -0,0 +1,118 @@
1
+ import * as React from 'react'
2
+ import { FieldValues, SubmitHandler } from 'react-hook-form'
3
+ import { createContext } from '@chakra-ui/react-utils'
4
+ import {
5
+ useStepper,
6
+ useStep,
7
+ UseStepperProps,
8
+ UseStepperReturn,
9
+ } from '@saas-ui/stepper'
10
+
11
+ export interface StepState {
12
+ name: string
13
+ schema?: any
14
+ isActive?: boolean
15
+ isCompleted?: boolean
16
+ }
17
+
18
+ export interface StepFormContext extends UseStepperReturn {
19
+ updateStep(state: StepState): void
20
+ steps: Record<string, StepState>
21
+ }
22
+
23
+ export const [StepFormProvider, useStepFormContext] =
24
+ createContext<StepFormContext>({
25
+ name: 'StepFormContext',
26
+ errorMessage:
27
+ 'useStepFormContext: `context` is undefined. Seems you forgot to wrap step form components in `<StepForm />`',
28
+ })
29
+
30
+ import { FormProps } from './form'
31
+
32
+ export interface UseStepFormProps<
33
+ TFieldValues extends FieldValues = FieldValues
34
+ > extends UseStepperProps,
35
+ FormProps<TFieldValues> {}
36
+
37
+ export function useStepForm<TFieldValues extends FieldValues = FieldValues>(
38
+ props: UseStepFormProps<TFieldValues>
39
+ ) {
40
+ const stepper = useStepper(props)
41
+
42
+ const { activeStep, isLastStep, nextStep } = stepper
43
+
44
+ const [steps, updateSteps] = React.useState({})
45
+
46
+ const onSubmitStep: SubmitHandler<TFieldValues> = React.useCallback(
47
+ async (data) => {
48
+ if (isLastStep) {
49
+ return props
50
+ .onSubmit?.(data)
51
+ .then(() => {
52
+ const step = steps[activeStep]
53
+ updateStep({
54
+ ...step,
55
+ isCompleted: true,
56
+ })
57
+ })
58
+ .then(nextStep) // Show completed step
59
+ }
60
+
61
+ nextStep()
62
+ },
63
+ [activeStep, isLastStep]
64
+ )
65
+
66
+ const getFormProps = React.useCallback(
67
+ (props) => {
68
+ const step = steps[activeStep]
69
+ return {
70
+ onSubmit: onSubmitStep,
71
+ schema: step?.schema,
72
+ }
73
+ },
74
+ [steps, onSubmitStep, activeStep]
75
+ )
76
+
77
+ const updateStep = React.useCallback(
78
+ (step) => {
79
+ updateSteps((steps) => {
80
+ return {
81
+ ...steps,
82
+ [step.name]: step,
83
+ }
84
+ })
85
+ },
86
+ [steps]
87
+ )
88
+
89
+ return {
90
+ getFormProps,
91
+ updateStep,
92
+ steps,
93
+ ...stepper,
94
+ }
95
+ }
96
+
97
+ export type UseStepFormReturn = ReturnType<typeof useStepForm>
98
+
99
+ export interface UseFormStepProps {
100
+ name: string
101
+ schema?: any
102
+ }
103
+
104
+ export function useFormStep(props: UseFormStepProps): StepState {
105
+ const { name, schema } = props
106
+ const step = useStep({ name })
107
+
108
+ const { steps, updateStep } = useStepFormContext()
109
+
110
+ React.useEffect(() => {
111
+ updateStep({ name, schema })
112
+ }, [name, schema])
113
+
114
+ return {
115
+ ...step,
116
+ ...(steps[name] || { name, schema }),
117
+ }
118
+ }