@saas-ui/forms 3.0.0-alpha.0 → 3.0.0-alpha.10

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/index.d.mts +1 -2
  3. package/dist/index.d.ts +1 -2
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/zod/index.d.mts +5 -3
  7. package/dist/zod/index.d.ts +5 -3
  8. package/dist/zod/index.js +1 -1
  9. package/dist/zod/index.js.map +1 -1
  10. package/dist/zod/index.mjs +1 -1
  11. package/dist/zod/index.mjs.map +1 -1
  12. package/package.json +9 -29
  13. package/dist/ajv/index.d.mts +0 -35
  14. package/dist/ajv/index.d.ts +0 -35
  15. package/dist/ajv/index.js +0 -57
  16. package/dist/ajv/index.js.map +0 -1
  17. package/dist/ajv/index.mjs +0 -30
  18. package/dist/ajv/index.mjs.map +0 -1
  19. package/src/array-field.tsx +0 -266
  20. package/src/auto-form.tsx +0 -71
  21. package/src/base-field.tsx +0 -69
  22. package/src/create-field.tsx +0 -171
  23. package/src/create-form.tsx +0 -100
  24. package/src/create-step-form.tsx +0 -118
  25. package/src/default-fields.tsx +0 -233
  26. package/src/display-field.tsx +0 -46
  27. package/src/display-if.tsx +0 -69
  28. package/src/field-resolver.ts +0 -66
  29. package/src/field.tsx +0 -44
  30. package/src/fields-context.tsx +0 -33
  31. package/src/fields.tsx +0 -93
  32. package/src/form-context.tsx +0 -80
  33. package/src/form-layout.tsx +0 -59
  34. package/src/form.tsx +0 -252
  35. package/src/index.ts +0 -310
  36. package/src/object-field.tsx +0 -58
  37. package/src/step-form.tsx +0 -191
  38. package/src/submit-button.tsx +0 -71
  39. package/src/types.ts +0 -242
  40. package/src/use-array-field.tsx +0 -158
  41. package/src/use-form.tsx +0 -24
  42. package/src/use-step-form.tsx +0 -202
  43. package/src/utils.ts +0 -35
  44. package/src/watch-field.tsx +0 -37
@@ -1,171 +0,0 @@
1
- import React, {
2
- type ForwardRefRenderFunction,
3
- type PropsWithoutRef,
4
- forwardRef,
5
- } from 'react'
6
-
7
- import { callAll, mergeRefs, splitProps } from '@saas-ui/core/utils'
8
- import { Controller, type RegisterOptions } from 'react-hook-form'
9
-
10
- import { BaseField } from './base-field'
11
- import { useFieldsContext } from './fields-context'
12
- import { useFormContext } from './form-context'
13
- import { BaseFieldProps, GetBaseField } from './types'
14
-
15
- interface CreateFieldProps<ExtraFieldProps extends object = object> {
16
- displayName: string
17
- hideLabel?: boolean
18
- getBaseField: GetBaseField<ExtraFieldProps>
19
- }
20
-
21
- const _createField = (
22
- InputComponent: React.FC<any>,
23
- { displayName, hideLabel, getBaseField: getBaseFieldProp }: CreateFieldProps,
24
- ) => {
25
- const Field = forwardRef<HTMLDivElement, any>((props, ref) => {
26
- const { id, name, label, isRequired, rules } = props
27
-
28
- const inputRules = {
29
- required: isRequired,
30
- ...rules,
31
- }
32
-
33
- const fieldContext = useFieldsContext()
34
-
35
- const getBaseField = fieldContext?.getBaseField ?? getBaseFieldProp
36
-
37
- const { extraProps, BaseField } = React.useMemo(
38
- () => getBaseField(),
39
- [getBaseField],
40
- )
41
-
42
- const [, inputProps] = splitProps(
43
- props,
44
- [
45
- 'children',
46
- 'name',
47
- 'label',
48
- 'required',
49
- 'disabled',
50
- 'invalid',
51
- 'readOnly',
52
- 'help',
53
- 'hideLabel',
54
- ].concat(extraProps),
55
- )
56
-
57
- return (
58
- <BaseField name={name} hideLabel={hideLabel} {...props}>
59
- <InputComponent
60
- ref={ref}
61
- id={id}
62
- name={name}
63
- label={hideLabel ? label : undefined} // Only pass down the label when it should be inline.
64
- {...inputProps}
65
- rules={inputRules}
66
- />
67
- </BaseField>
68
- )
69
- })
70
-
71
- Field.displayName = displayName
72
-
73
- return Field
74
- }
75
-
76
- const withControlledInput = (InputComponent: React.FC<any>) => {
77
- return forwardRef<typeof InputComponent, ControlProps>((props, ref) => {
78
- const { name, rules, ...inputProps } = props
79
- const { control } = useFormContext()
80
-
81
- const onChange = inputProps.onChange as (...event: any[]) => void
82
-
83
- return (
84
- <Controller
85
- name={name}
86
- control={control}
87
- rules={rules}
88
- render={({ field: { ref: _ref, ...field } }) => (
89
- <InputComponent
90
- {...field}
91
- {...inputProps}
92
- onChange={callAll(onChange, field.onChange)}
93
- onBlur={callAll(inputProps.onBlur, field.onBlur)}
94
- ref={mergeRefs(ref, _ref)}
95
- />
96
- )}
97
- />
98
- )
99
- })
100
- }
101
-
102
- const withUncontrolledInput = (InputComponent: React.FC<any>) => {
103
- return forwardRef<typeof InputComponent, ControlProps>(
104
- ({ name, rules, ...inputProps }, ref) => {
105
- const { register } = useFormContext()
106
-
107
- const { ref: _ref, ...field } = register(name, rules)
108
-
109
- const onChange = inputProps.onChange as (...event: any[]) => void
110
-
111
- return (
112
- <InputComponent
113
- {...field}
114
- {...inputProps}
115
- onChange={callAll(onChange, field.onChange)}
116
- onBlur={callAll(inputProps.onBlur, field.onBlur)}
117
- ref={mergeRefs(ref, _ref)}
118
- />
119
- )
120
- },
121
- )
122
- }
123
-
124
- export interface CreateFieldOptions {
125
- isControlled?: boolean
126
- hideLabel?: boolean
127
- BaseField?: React.FC<any>
128
- }
129
-
130
- interface ControlProps {
131
- name: string
132
- onChange: (...event: any[]) => void
133
- onBlur: (...event: any[]) => void
134
- value: unknown
135
- disabled?: boolean
136
- rules?: RegisterOptions
137
- }
138
-
139
- /**
140
- * Register a new field type
141
- * @param type The name for this field in kebab-case, eg `email` or `array-field`
142
- * @param component The React component
143
- * @param options
144
- * @param options.isControlled Set this to true if this is a controlled field.
145
- * @param options.hideLabel Hide the field label, for example for the checkbox field.
146
- */
147
- export const createField = <TType = unknown, TProps extends object = object>(
148
- component: ForwardRefRenderFunction<
149
- TType,
150
- PropsWithoutRef<TProps & ControlProps>
151
- >,
152
- options?: CreateFieldOptions,
153
- ) => {
154
- let InputComponent
155
- if (options?.isControlled) {
156
- InputComponent = withControlledInput(forwardRef(component))
157
- } else {
158
- InputComponent = withUncontrolledInput(forwardRef(component))
159
- }
160
-
161
- const Field = _createField(InputComponent, {
162
- displayName: `${component.displayName ?? 'Custom'}Field`,
163
- hideLabel: options?.hideLabel,
164
- getBaseField: () => ({
165
- extraProps: [],
166
- BaseField,
167
- }),
168
- }) as React.FC<Omit<BaseFieldProps, keyof TProps> & TProps>
169
-
170
- return Field
171
- }
@@ -1,100 +0,0 @@
1
- import React, { ForwardedRef, forwardRef, useMemo } from 'react'
2
-
3
- import { defaultFieldTypes } from './default-fields'
4
- import { objectFieldResolver } from './field-resolver'
5
- import { GetFieldResolver } from './field-resolver'
6
- import { FieldsProvider } from './fields-context'
7
- import { FieldValues, Form, FormProps, GetResolver } from './form'
8
- import { GetBaseField, WithFields } from './types'
9
-
10
- export interface CreateFormProps<
11
- FieldDefs,
12
- TGetBaseField extends GetBaseField = GetBaseField,
13
- > {
14
- resolver?: GetResolver
15
- fieldResolver?: GetFieldResolver
16
- fields?: FieldDefs extends Record<string, React.FC<any>> ? FieldDefs : never
17
- getBaseField?: TGetBaseField
18
- }
19
-
20
- export type FormType<
21
- FieldDefs,
22
- ExtraProps = object,
23
- ExtraFieldProps extends object = object,
24
- ExtraOverrides = object,
25
- > = (<
26
- TSchema = unknown,
27
- TFieldValues extends FieldValues = FieldValues,
28
- TContext extends object = object,
29
- >(
30
- props: WithFields<
31
- FormProps<TSchema, TFieldValues, TContext, ExtraFieldProps>,
32
- FieldDefs,
33
- ExtraOverrides
34
- > & {
35
- ref?: React.ForwardedRef<HTMLFormElement>
36
- } & ExtraProps,
37
- ) => React.ReactElement) & {
38
- displayName?: string
39
- id?: string
40
- }
41
-
42
- export function createForm<
43
- FieldDefs,
44
- TGetBaseField extends GetBaseField<any> = GetBaseField<any>,
45
- >({
46
- resolver,
47
- fieldResolver = objectFieldResolver,
48
- fields,
49
- getBaseField,
50
- }: CreateFormProps<FieldDefs, TGetBaseField> = {}) {
51
- type ExtraFieldProps =
52
- TGetBaseField extends GetBaseField<infer ExtraFieldProps>
53
- ? ExtraFieldProps
54
- : object
55
-
56
- const DefaultForm = forwardRef(
57
- <
58
- TSchema = any,
59
- TFieldValues extends FieldValues = FieldValues,
60
- TContext extends object = object,
61
- >(
62
- props: WithFields<
63
- FormProps<TSchema, TFieldValues, TContext, ExtraFieldProps>,
64
- FieldDefs
65
- >,
66
- ref: ForwardedRef<HTMLFormElement>,
67
- ) => {
68
- const {
69
- schema,
70
- resolver: resolverProp,
71
- fieldResolver: fieldResolverProp,
72
- ...rest
73
- } = props
74
-
75
- const fieldsContext = useMemo(
76
- () => ({
77
- fields: { ...defaultFieldTypes, ...fields },
78
- getBaseField,
79
- }),
80
- [fields, getBaseField],
81
- )
82
-
83
- return (
84
- <FieldsProvider value={fieldsContext}>
85
- <Form
86
- ref={ref}
87
- resolver={resolverProp ?? resolver?.(props.schema)}
88
- fieldResolver={fieldResolverProp ?? fieldResolver?.(schema)}
89
- {...rest}
90
- />
91
- </FieldsProvider>
92
- )
93
- },
94
- ) as FormType<FieldDefs, object, ExtraFieldProps>
95
-
96
- DefaultForm.displayName = 'Form'
97
- DefaultForm.id = 'Form'
98
-
99
- return DefaultForm
100
- }
@@ -1,118 +0,0 @@
1
- import React, { forwardRef, useMemo } from 'react'
2
-
3
- import { runIfFn } from '@chakra-ui/utils'
4
- import { StepperProvider } from '@saas-ui/core'
5
-
6
- import {
7
- ArrayField,
8
- DisplayIf,
9
- FieldProps,
10
- FieldValues,
11
- FieldsProvider,
12
- GetFieldResolver,
13
- ObjectField,
14
- defaultFieldTypes,
15
- } from './'
16
- import { Field } from './field'
17
- import { Form } from './form'
18
- import { GetResolver } from './form'
19
- import { FormStep, StepsOptions } from './step-form'
20
- import { GetBaseField, WithStepFields } from './types'
21
- import {
22
- StepFormProvider,
23
- UseStepFormProps,
24
- useStepForm,
25
- } from './use-step-form'
26
-
27
- export type StepFormType<
28
- FieldDefs,
29
- ExtraProps = object,
30
- ExtraFieldProps extends object = object,
31
- ExtraOverrides = object,
32
- > = (<
33
- TSteps extends StepsOptions<any> = StepsOptions<any>,
34
- TFieldValues extends FieldValues = FieldValues,
35
- TContext extends object = object,
36
- TFieldTypes = FieldProps<TFieldValues, ExtraFieldProps>,
37
- >(
38
- props: WithStepFields<
39
- UseStepFormProps<TSteps, TFieldValues, TContext>,
40
- FieldDefs,
41
- ExtraOverrides
42
- > & {
43
- ref?: React.ForwardedRef<HTMLFormElement>
44
- } & ExtraProps,
45
- ) => React.ReactElement) & {
46
- displayName?: string
47
- id?: string
48
- }
49
-
50
- export interface CreateStepFormProps<
51
- FieldDefs,
52
- TGetBaseField extends GetBaseField = GetBaseField,
53
- > {
54
- resolver?: GetResolver
55
- fieldResolver?: GetFieldResolver
56
- fields?: FieldDefs extends Record<string, React.FC<any>> ? FieldDefs : never
57
- getBaseField?: TGetBaseField
58
- }
59
-
60
- export function createStepForm<
61
- FieldDefs,
62
- TGetBaseField extends GetBaseField<any> = GetBaseField<any>,
63
- >({
64
- fields,
65
- resolver,
66
- fieldResolver,
67
- getBaseField,
68
- }: CreateStepFormProps<FieldDefs, TGetBaseField> = {}) {
69
- type ExtraFieldProps =
70
- TGetBaseField extends GetBaseField<infer ExtraFieldProps>
71
- ? ExtraFieldProps
72
- : object
73
-
74
- const StepForm = forwardRef<HTMLFormElement, any>((props, ref) => {
75
- const { children, steps, ...rest } = props
76
-
77
- const stepper = useStepForm({
78
- // resolver: resolver(props),
79
- // fieldResolver: fieldResolver(,
80
- ...props,
81
- })
82
-
83
- const { getFormProps, ...ctx } = stepper
84
-
85
- const context = useMemo(() => ctx, [ctx])
86
-
87
- const fieldsContext = {
88
- fields: {
89
- ...defaultFieldTypes,
90
- ...fields,
91
- },
92
- getBaseField,
93
- }
94
-
95
- return (
96
- <StepperProvider value={context}>
97
- <StepFormProvider value={context}>
98
- <FieldsProvider value={fieldsContext}>
99
- <Form ref={ref} {...rest} {...getFormProps()}>
100
- {runIfFn(children, {
101
- ...stepper,
102
- Field: Field as any,
103
- FormStep: FormStep as any,
104
- DisplayIf: DisplayIf as any,
105
- ArrayField: ArrayField as any,
106
- ObjectField: ObjectField as any,
107
- })}
108
- </Form>
109
- </FieldsProvider>
110
- </StepFormProvider>
111
- </StepperProvider>
112
- )
113
- }) as StepFormType<FieldDefs, object, ExtraFieldProps>
114
-
115
- StepForm.displayName = `Step${Form.displayName || Form.name}`
116
-
117
- return StepForm
118
- }
@@ -1,233 +0,0 @@
1
- import React from 'react'
2
-
3
- import {
4
- Input,
5
- InputProps,
6
- Stack,
7
- type SystemStyleObject,
8
- Textarea,
9
- TextareaProps,
10
- createListCollection,
11
- } from '@chakra-ui/react'
12
- import { Checkbox, type CheckboxProps } from '@saas-ui/react/checkbox'
13
- import { InputGroup } from '@saas-ui/react/input-group'
14
- import { NumberInput, type NumberInputProps } from '@saas-ui/react/number-input'
15
- import {
16
- PasswordInput,
17
- type PasswordInputProps,
18
- } from '@saas-ui/react/password-input'
19
- import { PinInput, type PinInputProps } from '@saas-ui/react/pin-input'
20
- import { Radio, RadioGroup, type RadioGroupProps } from '@saas-ui/react/radio'
21
- import { Select } from '@saas-ui/react/select'
22
- import { Switch, type SwitchProps } from '@saas-ui/react/switch'
23
-
24
- import { createField } from './create-field.tsx'
25
- import type { FieldOption, FieldOptions } from './types.ts'
26
-
27
- export interface InputFieldProps extends InputProps {
28
- type?: string
29
- startElement?: React.ReactNode
30
- endElement?: React.ReactNode
31
- }
32
-
33
- export const InputField = createField<HTMLInputElement, InputFieldProps>(
34
- ({ type = 'text', startElement, endElement, size, ...rest }, ref) => {
35
- return (
36
- <InputGroup startElement={startElement} endElement={endElement}>
37
- <Input type={type} size={size} {...rest} ref={ref} />
38
- </InputGroup>
39
- )
40
- },
41
- )
42
-
43
- export interface NumberInputFieldProps extends NumberInputProps {
44
- type: 'number'
45
- }
46
-
47
- export const NumberInputField = createField<
48
- HTMLInputElement,
49
- NumberInputFieldProps
50
- >((props, ref) => <NumberInput {...props} ref={ref} />, {
51
- isControlled: true,
52
- })
53
-
54
- export const PasswordInputField = createField<
55
- HTMLInputElement,
56
- PasswordInputProps
57
- >(({ type = 'password', ...props }, ref) => (
58
- <PasswordInput ref={ref} {...props} />
59
- ))
60
-
61
- export interface TextareaFieldProps extends TextareaProps {}
62
-
63
- export const TextareaField = createField<
64
- HTMLTextAreaElement,
65
- TextareaFieldProps
66
- >((props, ref) => <Textarea {...props} ref={ref} />)
67
-
68
- export interface SwitchFieldProps extends SwitchProps {
69
- type: 'switch'
70
- }
71
-
72
- export const SwitchField = createField<HTMLInputElement, SwitchFieldProps>(
73
- ({ type, value, ...rest }, ref) => {
74
- return <Switch checked={!!value} {...rest} ref={ref} />
75
- },
76
- {
77
- isControlled: true,
78
- },
79
- )
80
-
81
- export interface SelectFieldProps
82
- extends Omit<Select.RootProps<FieldOption>, 'collection'> {
83
- options: FieldOptions
84
- placeholder?: string
85
- triggerProps?: Select.TriggerProps
86
- contentProps?: Select.ContentProps
87
- }
88
-
89
- export const SelectField = createField<HTMLDivElement, SelectFieldProps>(
90
- (props, ref) => {
91
- const {
92
- triggerProps,
93
- contentProps,
94
- options,
95
- placeholder,
96
- onChange,
97
- onValueChange,
98
- onBlur,
99
- ...rest
100
- } = props
101
-
102
- const collection = createListCollection({
103
- items: options,
104
- })
105
-
106
- return (
107
- <Select.Root
108
- ref={ref}
109
- collection={collection}
110
- onValueChange={(details) => {
111
- onChange(details.value)
112
- }}
113
- onInteractOutside={() => onBlur()}
114
- {...rest}
115
- >
116
- <Select.Trigger {...triggerProps}>
117
- <Select.ValueText placeholder={placeholder} />
118
- </Select.Trigger>
119
- <Select.Content {...contentProps}>
120
- {collection.items.map((option) => (
121
- <Select.Item key={option.value} item={option}>
122
- {option.label || option.value}
123
- </Select.Item>
124
- ))}
125
- </Select.Content>
126
- </Select.Root>
127
- )
128
- },
129
- {
130
- isControlled: true,
131
- },
132
- )
133
-
134
- export interface CheckboxFieldProps extends CheckboxProps {
135
- type: 'checkbox'
136
- label?: string
137
- }
138
-
139
- export const CheckboxField = createField<HTMLInputElement, CheckboxFieldProps>(
140
- ({ label, type, ...props }, ref) => {
141
- return (
142
- <Checkbox ref={ref} {...props}>
143
- {label}
144
- </Checkbox>
145
- )
146
- },
147
- {
148
- hideLabel: true,
149
- },
150
- )
151
-
152
- export interface RadioFieldProps extends RadioGroupProps {
153
- type: 'radio'
154
- options: FieldOptions
155
- flexDirection?: SystemStyleObject['flexDirection']
156
- gap?: SystemStyleObject['gap']
157
- }
158
-
159
- export const RadioField = createField<HTMLInputElement, RadioFieldProps>(
160
- (props, ref) => {
161
- const { options, onChange, flexDirection = 'column', gap, ...rest } = props
162
- return (
163
- <RadioGroup
164
- ref={ref}
165
- onValueChange={({ value }) => {
166
- onChange?.(value)
167
- }}
168
- {...rest}
169
- >
170
- <Stack flexDirection={flexDirection} gap={gap}>
171
- {options.map((option) => (
172
- <Radio key={option.value} value={option.value}>
173
- {option.label || option.value}
174
- </Radio>
175
- ))}
176
- </Stack>
177
- </RadioGroup>
178
- )
179
- },
180
- {
181
- isControlled: true,
182
- },
183
- )
184
-
185
- export interface PinFieldProps
186
- extends Omit<PinInputProps, 'type' | 'value' | 'onChange'> {
187
- type: 'pin'
188
- pinLength?: number
189
- pinType?: PinInputProps['type']
190
- value?: string
191
- onChange?: (value: string) => void
192
- }
193
-
194
- export const PinField = createField<HTMLInputElement, PinFieldProps>(
195
- (props, ref) => {
196
- const { pinType, value: valueProp, onChange, ...inputProps } = props
197
-
198
- const value = valueProp?.split('') || []
199
-
200
- return (
201
- <PinInput
202
- ref={ref}
203
- {...inputProps}
204
- value={value}
205
- onValueChange={(details) => {
206
- onChange(details.valueAsString)
207
- }}
208
- type={pinType}
209
- />
210
- )
211
- },
212
- {
213
- isControlled: true,
214
- },
215
- )
216
-
217
- export const defaultFieldTypes = {
218
- text: InputField,
219
- email: InputField,
220
- url: InputField,
221
- phone: InputField,
222
- time: InputField,
223
- number: NumberInputField,
224
- pin: PinField,
225
- checkbox: CheckboxField,
226
- radio: RadioField,
227
- password: PasswordInputField,
228
- select: SelectField,
229
- switch: SwitchField,
230
- textarea: TextareaField,
231
- }
232
-
233
- export type DefaultFields = typeof defaultFieldTypes
@@ -1,46 +0,0 @@
1
- import * as React from 'react'
2
-
3
- import { Field as FieldPrimivite, Text } from '@chakra-ui/react'
4
- import type { FieldPath, FieldValues } from 'react-hook-form'
5
-
6
- import { useFormContext } from './form-context.tsx'
7
- import type { ArrayFieldPath } from './types.ts'
8
-
9
- export interface DisplayFieldProps<
10
- TFieldValues extends FieldValues = FieldValues,
11
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
12
- > extends Omit<FieldPrimivite.RootProps, 'type' | 'onChange' | 'defaultValue'> {
13
- name: TName | ArrayFieldPath<TName>
14
- label?: string
15
- }
16
-
17
- /**
18
- * Display a field value.
19
- *
20
- * @see Docs https://saas-ui.dev/
21
- */
22
- export const DisplayField: React.FC<DisplayFieldProps> = ({
23
- name,
24
- label,
25
- ...props
26
- }) => {
27
- return (
28
- <FieldPrimivite.Root {...props}>
29
- {label ? (
30
- <FieldPrimivite.Label htmlFor={name}>{label}</FieldPrimivite.Label>
31
- ) : null}
32
- <Text fontSize="md">
33
- <FormValue name={name} />
34
- </Text>
35
- </FieldPrimivite.Root>
36
- )
37
- }
38
-
39
- DisplayField.displayName = 'DisplayField'
40
-
41
- export const FormValue: React.FC<{ name: string }> = ({ name }) => {
42
- const { getValues } = useFormContext()
43
- return getValues(name) || null
44
- }
45
-
46
- FormValue.displayName = 'FormValue'