@saas-ui/forms 0.4.0 → 0.5.3

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.
@@ -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
+ }