@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.
- package/CHANGELOG.md +30 -0
- package/dist/array-field.d.ts +3 -3
- package/dist/array-field.d.ts.map +1 -1
- package/dist/display-field.d.ts +3 -0
- package/dist/display-field.d.ts.map +1 -1
- package/dist/field.d.ts +3 -2
- package/dist/field.d.ts.map +1 -1
- package/dist/form.d.ts +1 -1
- package/dist/form.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.modern.js +1 -1
- package/dist/index.modern.js.map +1 -1
- package/dist/step-form.d.ts +32 -0
- package/dist/step-form.d.ts.map +1 -0
- package/dist/use-array-field.d.ts +1 -1
- package/dist/use-step-form.d.ts +44 -0
- package/dist/use-step-form.d.ts.map +1 -0
- package/package.json +3 -1
- package/src/array-field.tsx +56 -45
- package/src/display-field.tsx +8 -3
- package/src/field.tsx +3 -2
- package/src/form.tsx +2 -2
- package/src/index.ts +2 -0
- package/src/step-form.tsx +166 -0
- package/src/use-array-field.tsx +1 -1
- package/src/use-step-form.tsx +118 -0
@@ -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
|
+
}
|
package/src/use-array-field.tsx
CHANGED
@@ -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
|
+
}
|