@saas-ui/forms 3.0.0-alpha.1 → 3.0.0-alpha.2

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.
@@ -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 { StepperProvider } from '@saas-ui/core'
4
- import { runIfFn } from '@saas-ui/core/utils'
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'
@@ -1,69 +0,0 @@
1
- import * as React from 'react'
2
- import {
3
- useWatch,
4
- FieldValues,
5
- UseFormReturn,
6
- FieldPath,
7
- } from 'react-hook-form'
8
-
9
- import { useFormContext } from './form-context'
10
-
11
- export interface DisplayIfProps<
12
- TFieldValues extends FieldValues = FieldValues,
13
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
14
- > {
15
- children: React.ReactElement
16
- name: TName
17
- defaultValue?: unknown
18
- isDisabled?: boolean
19
- isExact?: boolean
20
- condition?: (value: unknown, context: UseFormReturn<TFieldValues>) => boolean
21
- onToggle?: (
22
- conditionMatched: boolean,
23
- context: UseFormReturn<TFieldValues>
24
- ) => void
25
- }
26
- /**
27
- * Conditionally render parts of a form.
28
- *
29
- * @see Docs https://saas-ui.dev/docs/components/forms/form
30
- */
31
- export const DisplayIf = <
32
- TFieldValues extends FieldValues = FieldValues,
33
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
34
- >({
35
- children,
36
- name,
37
- defaultValue,
38
- isDisabled,
39
- isExact,
40
- condition = (value) => !!value,
41
- onToggle,
42
- }: DisplayIfProps<TFieldValues, TName>) => {
43
- const initializedRef = React.useRef(false)
44
- const matchesRef = React.useRef(false)
45
-
46
- const value = useWatch<TFieldValues>({
47
- name,
48
- defaultValue: defaultValue as any,
49
- disabled: isDisabled,
50
- exact: isExact,
51
- })
52
- const context = useFormContext() as any
53
-
54
- const matches = condition(value, context)
55
-
56
- React.useEffect(() => {
57
- if (!initializedRef.current) {
58
- initializedRef.current = true
59
- return
60
- }
61
- if (matchesRef.current === matches) return
62
- matchesRef.current = matches
63
- onToggle?.(matches, context)
64
- }, [value])
65
-
66
- return matches ? children : null
67
- }
68
-
69
- DisplayIf.displayName = 'DisplayIf'
@@ -1,66 +0,0 @@
1
- import { get } from '@saas-ui/core/utils'
2
-
3
- import { ArrayFieldProps } from './array-field'
4
- import { DefaultFields } from './default-fields'
5
- import { ObjectFieldProps } from './object-field'
6
- import { BaseFieldProps, ValueOf } from './types'
7
-
8
- export type FieldResolver = {
9
- getFields(): BaseFieldProps[]
10
- getNestedFields(name: string): BaseFieldProps[]
11
- }
12
-
13
- export type GetFieldResolver<TSchema = any> = (schema: TSchema) => FieldResolver
14
-
15
- type FieldTypes<FieldDefs = DefaultFields> = ValueOf<{
16
- [K in keyof FieldDefs]: FieldDefs[K] extends React.FC<infer Props>
17
- ? { type?: K } & Omit<Props, 'name'>
18
- : never
19
- }>
20
-
21
- type SchemaField<FieldDefs = DefaultFields> =
22
- | FieldTypes<FieldDefs>
23
- | (Omit<ObjectFieldProps, 'name' | 'children'> & {
24
- type: 'object'
25
- properties?: Record<string, SchemaField<FieldDefs>>
26
- })
27
- | (Omit<ArrayFieldProps, 'name' | 'children'> & {
28
- type: 'array'
29
- items?: SchemaField<FieldDefs>
30
- })
31
-
32
- export type ObjectSchema<FieldDefs = DefaultFields> = Record<
33
- string,
34
- SchemaField<FieldDefs>
35
- >
36
-
37
- const mapFields = (schema: ObjectSchema): BaseFieldProps[] =>
38
- schema &&
39
- Object.entries(schema).map(([name, props]) => {
40
- const { items, label, title, ...field } = props as any
41
- return {
42
- ...field,
43
- name,
44
- label: label || title || name, // json schema compatibility
45
- }
46
- })
47
-
48
- export const objectFieldResolver: GetFieldResolver<ObjectSchema> = (schema) => {
49
- const getFields = (): BaseFieldProps[] => {
50
- return mapFields(schema)
51
- }
52
- const getNestedFields = (name: string): BaseFieldProps[] => {
53
- const field = get(schema, name)
54
-
55
- if (!field) return []
56
-
57
- if (field.items?.type === 'object') {
58
- return mapFields(field.items.properties)
59
- } else if (field.type === 'object') {
60
- return mapFields(field.properties)
61
- }
62
- return [field.items]
63
- }
64
-
65
- return { getFields, getNestedFields }
66
- }
package/src/field.tsx DELETED
@@ -1,44 +0,0 @@
1
- import * as React from 'react'
2
-
3
- import { FieldValues, RegisterOptions } from 'react-hook-form'
4
-
5
- import { InputField } from './default-fields'
6
- import { useField } from './fields-context'
7
- import { useFieldProps } from './form-context'
8
- import { FieldProps, type FocusableElement } from './types'
9
-
10
- export type FieldRules = Pick<
11
- RegisterOptions,
12
- 'required' | 'min' | 'max' | 'maxLength' | 'minLength' | 'pattern'
13
- >
14
-
15
- const defaultInputType = 'text'
16
-
17
- /**
18
- * Form field component.
19
- *
20
- * Build-in types:
21
- * text, number, password, textarea, select, native-select, checkbox, radio, switch, pin
22
- *
23
- * Will default to a text field if there is no matching type.
24
-
25
- * @see Docs https://saas-ui.dev/docs/components/forms/field
26
- */
27
- export const Field = React.forwardRef(
28
- <TFieldValues extends FieldValues = FieldValues>(
29
- props: FieldProps<TFieldValues>,
30
- ref: React.ForwardedRef<FocusableElement>,
31
- ) => {
32
- const { type = defaultInputType, name } = props
33
- const overrides = useFieldProps(name)
34
- const InputComponent = useField(overrides?.type || type, InputField)
35
-
36
- return <InputComponent ref={ref} {...props} {...overrides} />
37
- },
38
- ) as (<TFieldValues extends FieldValues>(
39
- props: FieldProps<TFieldValues> & {
40
- ref?: React.ForwardedRef<FocusableElement>
41
- },
42
- ) => React.ReactElement) & {
43
- displayName?: string
44
- }
@@ -1,33 +0,0 @@
1
- import React from 'react'
2
-
3
- import type { GetBaseField } from './types'
4
-
5
- export interface FieldsContextValue {
6
- fields: Record<string, React.FC<any>>
7
- getBaseField?: GetBaseField<any>
8
- }
9
-
10
- const FieldsContext = React.createContext<FieldsContextValue | null>(null)
11
-
12
- export const FieldsProvider: React.FC<{
13
- value: FieldsContextValue
14
- children: React.ReactNode
15
- }> = (props) => {
16
- return (
17
- <FieldsContext.Provider value={props.value}>
18
- {props.children}
19
- </FieldsContext.Provider>
20
- )
21
- }
22
-
23
- export const useFieldsContext = () => {
24
- return React.useContext(FieldsContext)
25
- }
26
-
27
- export const useField = (
28
- type: string,
29
- fallback: React.FC<any>,
30
- ): React.FC<any> => {
31
- const context = React.useContext(FieldsContext)
32
- return context?.fields?.[type] || fallback
33
- }