@saas-ui/forms 2.0.0-next.5 → 2.0.0-next.6

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 +13 -0
  2. package/dist/ajv/index.d.ts +358 -11
  3. package/dist/ajv/index.js +7 -9
  4. package/dist/ajv/index.js.map +1 -1
  5. package/dist/ajv/index.mjs +7 -10
  6. package/dist/ajv/index.mjs.map +1 -1
  7. package/dist/index.d.ts +296 -194
  8. package/dist/index.js +373 -2613
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +373 -2607
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/yup/index.d.ts +573 -106
  13. package/dist/yup/index.js +6 -10
  14. package/dist/yup/index.js.map +1 -1
  15. package/dist/yup/index.mjs +4 -8
  16. package/dist/yup/index.mjs.map +1 -1
  17. package/dist/zod/index.d.ts +490 -14
  18. package/dist/zod/index.js +5 -0
  19. package/dist/zod/index.js.map +1 -1
  20. package/dist/zod/index.mjs +5 -1
  21. package/dist/zod/index.mjs.map +1 -1
  22. package/package.json +15 -8
  23. package/src/array-field.tsx +34 -17
  24. package/src/base-field.tsx +4 -9
  25. package/src/create-field.tsx +2 -1
  26. package/src/create-form.tsx +33 -10
  27. package/src/default-fields.tsx +21 -4
  28. package/src/display-field.tsx +1 -2
  29. package/src/display-if.tsx +14 -8
  30. package/src/field-resolver.ts +10 -8
  31. package/src/field.tsx +6 -3
  32. package/src/fields.tsx +16 -13
  33. package/src/form-context.tsx +84 -0
  34. package/src/form.tsx +44 -17
  35. package/src/index.ts +17 -15
  36. package/src/object-field.tsx +6 -2
  37. package/src/password-input/password-input.tsx +1 -1
  38. package/src/select/select-context.tsx +130 -0
  39. package/src/select/select.stories.tsx +116 -85
  40. package/src/select/select.tsx +152 -142
  41. package/src/types.ts +59 -6
  42. package/src/use-array-field.tsx +9 -3
  43. package/src/utils.ts +8 -1
  44. package/src/watch-field.tsx +2 -6
package/src/form.tsx CHANGED
@@ -5,7 +5,6 @@ import { cx, runIfFn } from '@chakra-ui/utils'
5
5
 
6
6
  import {
7
7
  useForm,
8
- FormProvider,
9
8
  UseFormProps,
10
9
  UseFormReturn,
11
10
  FieldValues,
@@ -15,14 +14,21 @@ import {
15
14
  ResolverResult,
16
15
  WatchObserver,
17
16
  } from 'react-hook-form'
18
- import { objectFieldResolver, FieldResolver } from './field-resolver'
17
+ import { FormProvider } from './form-context'
18
+ import { FieldResolver } from './field-resolver'
19
19
  import { MaybeRenderProp } from '@chakra-ui/react-utils'
20
20
 
21
21
  export type { UseFormReturn, FieldValues, SubmitHandler }
22
22
 
23
- import { FieldProps } from './types'
23
+ import { FieldProps, DefaultFieldOverrides } from './types'
24
24
 
25
25
  import { Field as DefaultField } from './field'
26
+ import { FormLayout } from './layout'
27
+ import { AutoFields } from './fields'
28
+ import { SubmitButton } from './submit-button'
29
+ import { DisplayIf, DisplayIfProps } from './display-if'
30
+ import { ArrayField, ArrayFieldProps } from './array-field'
31
+ import { ObjectField, ObjectFieldProps } from './object-field'
26
32
 
27
33
  export interface FormRenderContext<
28
34
  TFieldValues extends FieldValues = FieldValues,
@@ -30,6 +36,9 @@ export interface FormRenderContext<
30
36
  TFieldTypes = FieldProps<TFieldValues>
31
37
  > extends UseFormReturn<TFieldValues, TContext> {
32
38
  Field: React.FC<TFieldTypes>
39
+ DisplayIf: React.FC<DisplayIfProps<TFieldValues>>
40
+ ArrayField: React.FC<ArrayFieldProps<TFieldValues>>
41
+ ObjectField: React.FC<ObjectFieldProps<TFieldValues>>
33
42
  }
34
43
 
35
44
  interface FormOptions<
@@ -39,7 +48,7 @@ interface FormOptions<
39
48
  TFieldTypes = FieldProps<TFieldValues>
40
49
  > {
41
50
  /**
42
- * The form schema, supports Yup, Zod, and AJV.
51
+ * The form schema.
43
52
  */
44
53
  schema?: TSchema
45
54
  /**
@@ -64,6 +73,14 @@ interface FormOptions<
64
73
  children?: MaybeRenderProp<
65
74
  FormRenderContext<TFieldValues, TContext, TFieldTypes>
66
75
  >
76
+ /**
77
+ * The field resolver, used to resolve the fields from schemas.
78
+ */
79
+ fieldResolver?: FieldResolver
80
+ /**
81
+ * Field overrides
82
+ */
83
+ fields?: DefaultFieldOverrides
67
84
  }
68
85
 
69
86
  export interface FormProps<
@@ -90,12 +107,14 @@ export const Form = forwardRef(
90
107
  TSchema = any,
91
108
  TFieldTypes = FieldProps<TFieldValues>
92
109
  >(
93
- props: FormProps<TFieldValues, TContext, TSchema>,
110
+ props: FormProps<TFieldValues, TContext, TSchema, TFieldTypes>,
94
111
  ref: React.ForwardedRef<HTMLFormElement>
95
112
  ) => {
96
113
  const {
97
114
  mode = 'all',
98
115
  resolver,
116
+ fieldResolver,
117
+ fields,
99
118
  reValidateMode,
100
119
  shouldFocusError,
101
120
  shouldUnregister,
@@ -130,10 +149,6 @@ export const Form = forwardRef(
130
149
  resetOptions,
131
150
  }
132
151
 
133
- if (schema && !resolver) {
134
- form.resolver = Form.getResolver?.(schema)
135
- }
136
-
137
152
  const methods = useForm<TFieldValues, TContext>(form)
138
153
  const { handleSubmit } = methods
139
154
 
@@ -148,16 +163,34 @@ export const Form = forwardRef(
148
163
  return () => subscription?.unsubscribe()
149
164
  }, [methods, onChange])
150
165
 
166
+ let _children = children
167
+ if (!_children && fieldResolver) {
168
+ _children = (
169
+ <FormLayout>
170
+ <AutoFields />
171
+ <SubmitButton {...fields?.submit} />
172
+ </FormLayout>
173
+ )
174
+ }
175
+
151
176
  return (
152
- <FormProvider {...methods}>
177
+ <FormProvider
178
+ {...methods}
179
+ schema={schema}
180
+ fieldResolver={fieldResolver}
181
+ fields={fields}
182
+ >
153
183
  <chakra.form
154
184
  ref={ref}
155
185
  onSubmit={handleSubmit(onSubmit, onError)}
156
186
  {...rest}
157
187
  className={cx('sui-form', props.className)}
158
188
  >
159
- {runIfFn(children, {
189
+ {runIfFn(_children, {
160
190
  Field: DefaultField as any,
191
+ DisplayIf: DisplayIf,
192
+ ArrayField: ArrayField as any,
193
+ ObjectField: ObjectField as any,
161
194
  ...methods,
162
195
  })}
163
196
  </chakra.form>
@@ -175,12 +208,8 @@ export const Form = forwardRef(
175
208
  }
176
209
  ) => React.ReactElement) & {
177
210
  displayName?: string
178
- getResolver?: GetResolver
179
- getFieldResolver: GetFieldResolver
180
211
  }
181
212
 
182
- Form.getFieldResolver = objectFieldResolver
183
-
184
213
  Form.displayName = 'Form'
185
214
 
186
215
  export type GetResolver = <
@@ -193,5 +222,3 @@ export type GetResolver = <
193
222
  context: TContext | undefined,
194
223
  options: ResolverOptions<TFieldValues>
195
224
  ) => Promise<ResolverResult<TFieldValues>>
196
-
197
- export type GetFieldResolver = (schema: any) => FieldResolver
package/src/index.ts CHANGED
@@ -2,8 +2,7 @@ export * from './display-field'
2
2
  export * from './field'
3
3
  export * from './fields'
4
4
  export * from './fields-context'
5
- export * from './form'
6
- export * from './auto-form'
5
+ // export * from './auto-form'
7
6
  export * from './layout'
8
7
  export * from './submit-button'
9
8
  export * from './array-field'
@@ -33,13 +32,10 @@ export {
33
32
  SwitchField,
34
33
  TextareaField,
35
34
  defaultFieldTypes,
36
- } from './default-fields'
37
-
38
- export type {
39
- DefaultFields,
40
- InputFieldProps,
41
- NumberInputFieldProps,
42
- PinFieldProps,
35
+ type DefaultFields,
36
+ type InputFieldProps,
37
+ type NumberInputFieldProps,
38
+ type PinFieldProps,
43
39
  } from './default-fields'
44
40
 
45
41
  export type {
@@ -49,11 +45,19 @@ export type {
49
45
  FieldOptions,
50
46
  } from './types'
51
47
 
52
- export { createForm } from './create-form'
53
- export type { CreateFormProps } from './create-form'
48
+ export { createForm, type CreateFormProps } from './create-form'
49
+ export { createField, type CreateFieldOptions } from './create-field'
50
+
51
+ export {
52
+ Form as BaseForm,
53
+ type FormProps,
54
+ type FormRenderContext,
55
+ } from './form'
56
+
57
+ export { FormProvider, useFormContext } from './form-context'
54
58
 
55
- export { createField } from './create-field'
56
- export type { CreateFieldOptions } from './create-field'
59
+ import { createForm } from './create-form'
60
+ export const Form = createForm()
57
61
 
58
62
  export type {
59
63
  BatchFieldArrayUpdate,
@@ -158,9 +162,7 @@ export {
158
162
  useController,
159
163
  useFieldArray,
160
164
  useForm,
161
- useFormContext,
162
165
  useFormState,
163
166
  useWatch,
164
167
  Controller,
165
- FormProvider,
166
168
  } from 'react-hook-form'
@@ -11,9 +11,13 @@ import { FormLayout } from './layout'
11
11
  import { BaseFieldProps } from './types'
12
12
 
13
13
  import { mapNestedFields } from './utils'
14
+ import { FieldPath, FieldValues } from 'react-hook-form'
14
15
 
15
- export interface ObjectFieldProps extends BaseFieldProps {
16
- name: string
16
+ export interface ObjectFieldProps<
17
+ TFieldValues extends FieldValues = FieldValues,
18
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
19
+ > extends BaseFieldProps {
20
+ name: TName
17
21
  children: React.ReactNode
18
22
  columns?: ResponsiveValue<number>
19
23
  spacing?: ResponsiveValue<string | number>
@@ -2,7 +2,7 @@ import React, { useState } from 'react'
2
2
 
3
3
  import { forwardRef, InputGroup, Input, InputProps } from '@chakra-ui/react'
4
4
 
5
- import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'
5
+ import { ViewIcon, ViewOffIcon } from '@saas-ui/core/icons'
6
6
 
7
7
  import { InputRightButton } from '../input-right-button'
8
8
 
@@ -0,0 +1,130 @@
1
+ import {
2
+ HTMLChakraProps,
3
+ useControllableState,
4
+ useFormControl,
5
+ } from '@chakra-ui/react'
6
+ import { createContext } from '@chakra-ui/react-utils'
7
+ import React from 'react'
8
+ import { FieldOptions } from '../types'
9
+ import { mapOptions } from '../utils'
10
+ import { SelectOption } from './select'
11
+
12
+ export const [SelectProvider, useSelectContext] = createContext<
13
+ ReturnType<typeof useSelect>
14
+ >({
15
+ strict: true,
16
+ })
17
+
18
+ export interface SelectOptions {
19
+ /**
20
+ * The name of the input field in a native form.
21
+ */
22
+ name: string
23
+ /**
24
+ * The value of the select field.
25
+ */
26
+ value?: string | string[]
27
+ /**
28
+ * The initial value of the select field.
29
+ */
30
+ defaultValue?: string | string[]
31
+ /**
32
+ * The callback invoked when the value of the select field changes.
33
+ * @param value The value of the select field.
34
+ */
35
+ onChange?: (value: string | string[]) => void
36
+ /**
37
+ * The placeholder text when there's no value.
38
+ */
39
+ placeholder?: string
40
+ /**
41
+ * If `true`, the select will be disabled.
42
+ */
43
+ isDisabled?: boolean
44
+ /**
45
+ * An array of options
46
+ * If you leave this empty the children prop will be rendered.
47
+ */
48
+ options?: FieldOptions<SelectOption>
49
+ /**
50
+ * Enable multiple select.
51
+ */
52
+ multiple?: boolean
53
+ /**
54
+ * The function used to render the value of the select field.
55
+ * @param value The value of the select field.
56
+ * @returns The rendered value.
57
+ */
58
+ renderValue?: (value: string | string[]) => React.ReactNode
59
+ }
60
+
61
+ export const useSelect = (props: SelectOptions) => {
62
+ const {
63
+ name,
64
+ value,
65
+ defaultValue,
66
+ onChange,
67
+ multiple,
68
+ placeholder,
69
+ options: optionsProp,
70
+ isDisabled,
71
+ renderValue = (value) =>
72
+ typeof value === 'string' ? value : value?.join(', '),
73
+ } = props
74
+ const [currentValue, setCurrentValue] = useControllableState({
75
+ value,
76
+ defaultValue,
77
+ onChange,
78
+ })
79
+
80
+ const controlProps = useFormControl({ name } as HTMLChakraProps<'input'>)
81
+
82
+ const options = React.useMemo(
83
+ () => optionsProp && mapOptions(optionsProp),
84
+ [optionsProp]
85
+ )
86
+
87
+ const handleChange = (value: string | string[]) => {
88
+ setCurrentValue(value)
89
+ }
90
+
91
+ const getDisplayValue = React.useCallback(
92
+ (value: string) => {
93
+ if (!options) {
94
+ return value
95
+ }
96
+
97
+ for (const option of options) {
98
+ if (option.value === value) {
99
+ return option.label || option.value
100
+ }
101
+ }
102
+
103
+ return value
104
+ },
105
+ [options]
106
+ )
107
+
108
+ const displayValue = React.useMemo(
109
+ () =>
110
+ currentValue
111
+ ? (Array.isArray(currentValue) ? currentValue : [currentValue]).map(
112
+ getDisplayValue
113
+ )
114
+ : [],
115
+ [currentValue, getDisplayValue]
116
+ )
117
+
118
+ return {
119
+ defaultValue,
120
+ value: currentValue,
121
+ displayValue,
122
+ renderValue,
123
+ onChange: handleChange,
124
+ options,
125
+ multiple,
126
+ controlProps,
127
+ placeholder,
128
+ isDisabled,
129
+ }
130
+ }
@@ -8,15 +8,23 @@ import {
8
8
  } from '@chakra-ui/react'
9
9
  import * as React from 'react'
10
10
 
11
- import { ComponentStory } from '@storybook/react'
11
+ import { StoryFn } from '@storybook/react'
12
12
 
13
- import { Select } from './select'
13
+ import { Select, SelectButton, SelectList, SelectOption } from './select'
14
14
  import { NativeSelect } from './native-select'
15
15
 
16
16
  import { FiSmile } from 'react-icons/fi'
17
17
 
18
+ const Template: StoryFn<typeof Select> = (args) => (
19
+ <Select placeholder="Select an option..." {...args}>
20
+ <SelectButton />
21
+ <SelectList />
22
+ </Select>
23
+ )
24
+
18
25
  export default {
19
26
  title: 'Components/Forms/Select',
27
+ component: Template,
20
28
  decorators: [
21
29
  (Story: any) => (
22
30
  <Container mt="40px" maxW="320px">
@@ -34,111 +42,134 @@ const getOptions = (length = 6) =>
34
42
 
35
43
  const options = getOptions()
36
44
 
37
- const Template: ComponentStory<typeof Select> = (args) => (
38
- <Select placeholder="Select an option..." {...args} />
39
- )
40
-
41
- export const Basic = Template.bind({})
42
- Basic.args = {
43
- name: 'select',
44
- options,
45
+ export const Basic = {
46
+ args: {
47
+ name: 'select',
48
+ options,
49
+ },
45
50
  }
46
51
 
47
- export const DefaultValue = Template.bind({})
48
- DefaultValue.args = {
49
- name: 'select',
50
- options,
51
- defaultValue: 1,
52
+ export const DefaultValue = {
53
+ args: {
54
+ name: 'select',
55
+ options,
56
+ defaultValue: '1',
57
+ },
52
58
  }
53
59
 
54
- export const Placeholder = Template.bind({})
55
- Placeholder.args = {
56
- name: 'select',
57
- options,
58
- placeholder: 'Select an option...',
60
+ export const Placeholder = {
61
+ args: {
62
+ name: 'select',
63
+ options,
64
+ placeholder: 'Select an option...',
65
+ },
59
66
  }
60
-
61
- export const Disabled = Template.bind({})
62
- Disabled.args = {
63
- name: 'select',
64
- options,
65
- placeholder: 'Disabled.',
66
- isDisabled: true,
67
+ export const Disabled = {
68
+ args: {
69
+ name: 'select',
70
+ options,
71
+ placeholder: 'Disabled.',
72
+ isDisabled: true,
73
+ },
67
74
  }
68
75
 
69
- export const Multi = Template.bind({})
70
- Multi.args = {
71
- name: 'select',
72
- options,
73
- placeholder: 'Multiple.',
74
- multiple: true,
76
+ export const Multi = {
77
+ args: {
78
+ name: 'select',
79
+ options,
80
+ placeholder: 'Multiple.',
81
+ multiple: true,
82
+ },
75
83
  }
76
84
 
77
- export const MultiWithDefaultValue = Template.bind({})
78
- MultiWithDefaultValue.args = {
79
- name: 'select',
80
- options,
81
- placeholder: 'Select an option...',
82
- multiple: true,
83
- defaultValue: ['1'],
85
+ export const MultiWithDefaultValue = {
86
+ args: {
87
+ name: 'select',
88
+ options,
89
+ placeholder: 'Select an option...',
90
+ multiple: true,
91
+ defaultValue: ['1'],
92
+ },
84
93
  }
85
94
 
86
- export const MultiWithTags = Template.bind({})
87
- MultiWithTags.args = {
88
- name: 'select',
89
- options,
90
- placeholder: 'Select options...',
91
- multiple: true,
92
- renderValue: (selected) => {
93
- if (selected?.length) {
94
- return (
95
- <Wrap py="1">
96
- {selected.map((value) => (
97
- <WrapItem>
98
- <Tag>{value}</Tag>
99
- </WrapItem>
100
- ))}
101
- </Wrap>
102
- )
103
- }
95
+ export const MultiWithTags = {
96
+ args: {
97
+ name: 'select',
98
+ options,
99
+ placeholder: 'Select options...',
100
+ multiple: true,
101
+ renderValue: (selected) => {
102
+ if (selected?.length) {
103
+ return (
104
+ <Wrap py="1">
105
+ {selected.map((value) => (
106
+ <WrapItem>
107
+ <Tag variant="solid">{value}</Tag>
108
+ </WrapItem>
109
+ ))}
110
+ </Wrap>
111
+ )
112
+ }
113
+ },
104
114
  },
105
115
  }
106
116
 
107
- export const WithIcons = Template.bind({})
108
- WithIcons.args = {
109
- name: 'select',
110
- options,
111
- value: 1,
112
- leftIcon: <Icon as={FiSmile} />,
117
+ export const Test = {
118
+ render: () => (
119
+ <Tag variant="outline" colorScheme="teal">
120
+ Test
121
+ </Tag>
122
+ ),
113
123
  }
114
124
 
115
- export const MaxHeight = Template.bind({})
116
- MaxHeight.args = {
117
- name: 'select',
118
- options: getOptions(100),
125
+ export const WithIcons = {
126
+ render: (args) => (
127
+ <Select placeholder="Select an option..." {...args}>
128
+ <SelectButton leftIcon={<Icon as={FiSmile} />} />
129
+ <SelectList />
130
+ </Select>
131
+ ),
132
+ args: {
133
+ name: 'select',
134
+ options,
135
+ value: '1',
136
+ },
119
137
  }
120
138
 
121
- export const WithChildren = () => {
122
- return (
123
- <Select name="select" value="1">
124
- <MenuItemOption value="1">Option 1</MenuItemOption>
125
- <MenuItemOption value="2">Option 1</MenuItemOption>
139
+ export const MaxHeight = {
140
+ args: {
141
+ name: 'select',
142
+ options: getOptions(100),
143
+ },
144
+ }
145
+
146
+ export const WithChildren = {
147
+ render: () => (
148
+ <Select name="select" defaultValue="1">
149
+ <SelectButton />
150
+ <SelectList>
151
+ <SelectOption value="1">Option 1</SelectOption>
152
+ <SelectOption value="2">Option 2</SelectOption>
153
+ </SelectList>
126
154
  </Select>
127
- )
155
+ ),
128
156
  }
129
157
 
130
- export const WithEmptyOption = () => {
131
- return (
132
- <Select name="select" value="1">
133
- <MenuItemOption value="">None</MenuItemOption>
134
- <MenuItemOption value="1">Option 1</MenuItemOption>
135
- <MenuItemOption value="2">Option 1</MenuItemOption>
158
+ export const WithEmptyOption = {
159
+ render: () => (
160
+ <Select name="select" defaultValue="1">
161
+ <SelectButton />
162
+ <SelectList>
163
+ <SelectOption value="">None</SelectOption>
164
+ <SelectOption value="1">Option 1</SelectOption>
165
+ <SelectOption value="2">Option 2</SelectOption>
166
+ </SelectList>
136
167
  </Select>
137
- )
168
+ ),
138
169
  }
139
170
 
140
- export const WithNativeSelect = () => (
141
- <>
171
+ export const WithNativeSelect = {
172
+ render: () => (
142
173
  <NativeSelect name="select" options={options} aria-label="Select" />
143
- </>
144
- )
174
+ ),
175
+ }