@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.
- package/CHANGELOG.md +13 -0
- package/dist/ajv/index.d.ts +358 -11
- package/dist/ajv/index.js +7 -9
- package/dist/ajv/index.js.map +1 -1
- package/dist/ajv/index.mjs +7 -10
- package/dist/ajv/index.mjs.map +1 -1
- package/dist/index.d.ts +296 -194
- package/dist/index.js +373 -2613
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +373 -2607
- package/dist/index.mjs.map +1 -1
- package/dist/yup/index.d.ts +573 -106
- package/dist/yup/index.js +6 -10
- package/dist/yup/index.js.map +1 -1
- package/dist/yup/index.mjs +4 -8
- package/dist/yup/index.mjs.map +1 -1
- package/dist/zod/index.d.ts +490 -14
- package/dist/zod/index.js +5 -0
- package/dist/zod/index.js.map +1 -1
- package/dist/zod/index.mjs +5 -1
- package/dist/zod/index.mjs.map +1 -1
- package/package.json +15 -8
- package/src/array-field.tsx +34 -17
- package/src/base-field.tsx +4 -9
- package/src/create-field.tsx +2 -1
- package/src/create-form.tsx +33 -10
- package/src/default-fields.tsx +21 -4
- package/src/display-field.tsx +1 -2
- package/src/display-if.tsx +14 -8
- package/src/field-resolver.ts +10 -8
- package/src/field.tsx +6 -3
- package/src/fields.tsx +16 -13
- package/src/form-context.tsx +84 -0
- package/src/form.tsx +44 -17
- package/src/index.ts +17 -15
- package/src/object-field.tsx +6 -2
- package/src/password-input/password-input.tsx +1 -1
- package/src/select/select-context.tsx +130 -0
- package/src/select/select.stories.tsx +116 -85
- package/src/select/select.tsx +152 -142
- package/src/types.ts +59 -6
- package/src/use-array-field.tsx +9 -3
- package/src/utils.ts +8 -1
- package/src/watch-field.tsx +2 -6
package/src/select/select.tsx
CHANGED
@@ -10,180 +10,190 @@ import {
|
|
10
10
|
MenuListProps,
|
11
11
|
MenuItemOption,
|
12
12
|
MenuOptionGroup,
|
13
|
-
MenuOptionGroupProps,
|
14
13
|
Button,
|
15
14
|
ButtonProps,
|
16
15
|
omitThemingProps,
|
17
16
|
useMultiStyleConfig,
|
18
17
|
SystemStyleObject,
|
19
|
-
useFormControl,
|
20
|
-
HTMLChakraProps,
|
21
18
|
MenuItemOptionProps,
|
19
|
+
useFormControlContext,
|
22
20
|
} from '@chakra-ui/react'
|
23
|
-
import { cx } from '@chakra-ui/utils'
|
21
|
+
import { cx, dataAttr } from '@chakra-ui/utils'
|
24
22
|
import { ChevronDownIcon } from '@saas-ui/core'
|
25
23
|
|
26
|
-
import {
|
27
|
-
|
24
|
+
import { FieldOption } from '../types'
|
25
|
+
|
26
|
+
import {
|
27
|
+
SelectOptions,
|
28
|
+
SelectProvider,
|
29
|
+
useSelect,
|
30
|
+
useSelectContext,
|
31
|
+
} from './select-context'
|
28
32
|
|
29
33
|
export interface SelectOption
|
30
34
|
extends Omit<MenuItemOptionProps, 'value'>,
|
31
35
|
FieldOption {}
|
32
36
|
|
33
|
-
interface SelectOptions {
|
34
|
-
/**
|
35
|
-
* An array of options
|
36
|
-
* If you leave this empty the children prop will be rendered.
|
37
|
-
*/
|
38
|
-
options?: FieldOptions<SelectOption>
|
39
|
-
/**
|
40
|
-
* Props passed to the MenuList.
|
41
|
-
*/
|
42
|
-
menuListProps?: MenuListProps
|
43
|
-
/**
|
44
|
-
* Customize how the value is rendered.
|
45
|
-
* @type (value?: string[]) => React.ReactElement
|
46
|
-
*/
|
47
|
-
renderValue?: (value?: string[]) => React.ReactElement | undefined
|
48
|
-
/**
|
49
|
-
* Enable multiple select.
|
50
|
-
*/
|
51
|
-
multiple?: boolean
|
52
|
-
}
|
53
|
-
|
54
37
|
export interface SelectProps
|
55
38
|
extends Omit<MenuProps, 'children'>,
|
56
|
-
Pick<ButtonProps, 'isDisabled' | 'leftIcon' | 'rightIcon'>,
|
57
|
-
Pick<MenuOptionGroupProps, 'onChange'>,
|
58
39
|
SelectOptions {}
|
59
40
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
41
|
+
export interface SelectButtonProps extends ButtonProps {}
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Button that opens the select menu and displays the selected value.
|
45
|
+
*
|
46
|
+
* @see https://saas-ui.dev/docs/components/forms/select
|
47
|
+
*/
|
48
|
+
export const SelectButton = forwardRef<SelectButtonProps, 'button'>(
|
49
|
+
(props, ref) => {
|
50
|
+
const styles = useMultiStyleConfig('SuiSelect', props)
|
51
|
+
|
52
|
+
const {
|
53
|
+
displayValue,
|
54
|
+
renderValue,
|
55
|
+
placeholder,
|
56
|
+
isDisabled: isSelectDisabled,
|
57
|
+
} = useSelectContext()
|
58
|
+
|
59
|
+
const {
|
60
|
+
isInvalid,
|
61
|
+
isReadOnly,
|
62
|
+
isDisabled,
|
63
|
+
isFocused,
|
64
|
+
isRequired,
|
65
|
+
id,
|
66
|
+
onBlur,
|
67
|
+
onFocus,
|
68
|
+
} = useFormControlContext()
|
69
|
+
|
70
|
+
const { rightIcon = <ChevronDownIcon />, ...rest } = props
|
71
|
+
|
72
|
+
/* @ts-ignore */
|
73
|
+
const focusStyles = styles.field?._focusVisible
|
74
|
+
/* @ts-ignore */
|
75
|
+
const readOnlyStyles = styles.field?._readOnly
|
76
|
+
/* @ts-ignore */
|
77
|
+
const invalid = styles.field?._invalid
|
78
|
+
|
79
|
+
const height = styles.field?.h || styles.field?.height
|
80
|
+
|
81
|
+
const buttonStyles: SystemStyleObject = {
|
82
|
+
fontWeight: 'normal',
|
83
|
+
textAlign: 'left',
|
84
|
+
color: 'inherit',
|
85
|
+
_active: {
|
86
|
+
bg: 'transparent',
|
87
|
+
},
|
88
|
+
minH: height,
|
89
|
+
_focus: focusStyles,
|
90
|
+
_expanded: focusStyles,
|
91
|
+
_readOnly: readOnlyStyles,
|
92
|
+
_invalid: invalid,
|
93
|
+
...styles.field,
|
94
|
+
h: 'auto',
|
95
|
+
}
|
96
|
+
|
97
|
+
// Using a Button, so we can simply use leftIcon and rightIcon
|
98
|
+
return (
|
99
|
+
<MenuButton
|
100
|
+
as={Button}
|
101
|
+
id={id}
|
102
|
+
{...rest}
|
103
|
+
onFocus={onFocus}
|
104
|
+
onBlur={onBlur}
|
105
|
+
isDisabled={isDisabled || isSelectDisabled}
|
106
|
+
data-invalid={dataAttr(isInvalid)}
|
107
|
+
data-read-only={dataAttr(isReadOnly)}
|
108
|
+
data-focus={dataAttr(isFocused)}
|
109
|
+
data-required={dataAttr(isRequired)}
|
110
|
+
rightIcon={rightIcon}
|
111
|
+
ref={ref}
|
112
|
+
sx={buttonStyles}
|
113
|
+
>
|
114
|
+
{renderValue(displayValue) || placeholder}
|
115
|
+
</MenuButton>
|
116
|
+
)
|
80
117
|
}
|
81
|
-
|
82
|
-
// Using a Button, so we can simply use leftIcon and rightIcon
|
83
|
-
return <MenuButton as={Button} {...props} ref={ref} sx={buttonStyles} />
|
84
|
-
})
|
118
|
+
)
|
85
119
|
|
86
120
|
SelectButton.displayName = 'SelectButton'
|
87
121
|
|
122
|
+
/**
|
123
|
+
* Allow users to select a value from a list of options.
|
124
|
+
*
|
125
|
+
* @see https://saas-ui.dev/docs/components/forms/select
|
126
|
+
*/
|
88
127
|
export const Select = forwardRef<SelectProps, 'select'>((props, ref) => {
|
89
|
-
const {
|
90
|
-
|
91
|
-
options: optionsProp,
|
92
|
-
children,
|
93
|
-
onChange,
|
94
|
-
defaultValue,
|
95
|
-
value,
|
96
|
-
placeholder,
|
97
|
-
isDisabled,
|
98
|
-
leftIcon,
|
99
|
-
rightIcon = <ChevronDownIcon />,
|
100
|
-
multiple,
|
101
|
-
size,
|
102
|
-
variant,
|
103
|
-
menuListProps,
|
104
|
-
renderValue = (value) => value?.join(', '),
|
105
|
-
...rest
|
106
|
-
} = props
|
128
|
+
const { name, children, isDisabled, multiple, ...rest } = props
|
129
|
+
|
107
130
|
const menuProps = omitThemingProps(rest)
|
108
131
|
|
109
|
-
const
|
132
|
+
const context = useSelect(props)
|
110
133
|
|
111
|
-
const
|
134
|
+
const { value, controlProps } = context
|
112
135
|
|
113
|
-
|
114
|
-
|
115
|
-
|
136
|
+
return (
|
137
|
+
<SelectProvider value={context}>
|
138
|
+
<Menu {...menuProps} closeOnSelect={!multiple}>
|
139
|
+
<chakra.div className={cx('sui-select')}>
|
140
|
+
{children}
|
141
|
+
<chakra.input
|
142
|
+
{...controlProps}
|
143
|
+
ref={ref}
|
144
|
+
name={name}
|
145
|
+
type="hidden"
|
146
|
+
value={value || ''}
|
147
|
+
className="saas-select__input"
|
148
|
+
/>
|
149
|
+
</chakra.div>
|
150
|
+
</Menu>
|
151
|
+
</SelectProvider>
|
116
152
|
)
|
153
|
+
})
|
117
154
|
|
118
|
-
|
119
|
-
setCurrentValue(value)
|
120
|
-
onChange?.(value)
|
121
|
-
}
|
122
|
-
|
123
|
-
const buttonProps = {
|
124
|
-
isDisabled,
|
125
|
-
leftIcon,
|
126
|
-
rightIcon,
|
127
|
-
size,
|
128
|
-
variant,
|
129
|
-
}
|
130
|
-
|
131
|
-
const getDisplayValue = React.useCallback(
|
132
|
-
(value: string) => {
|
133
|
-
if (!options) {
|
134
|
-
return value
|
135
|
-
}
|
136
|
-
|
137
|
-
for (const option of options) {
|
138
|
-
if (option.label && option.value === value) {
|
139
|
-
return option.label
|
140
|
-
}
|
141
|
-
}
|
142
|
-
|
143
|
-
return value
|
144
|
-
},
|
145
|
-
[options]
|
146
|
-
)
|
155
|
+
export interface SelectListProps extends MenuListProps {}
|
147
156
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
157
|
+
/**
|
158
|
+
* The list of options to choose from.
|
159
|
+
*
|
160
|
+
* @see https://saas-ui.dev/docs/components/forms/select
|
161
|
+
*/
|
162
|
+
export const SelectList: React.FC<SelectListProps> = (props) => {
|
163
|
+
const { defaultValue, value, options, multiple, onChange } =
|
164
|
+
useSelectContext()
|
153
165
|
|
154
166
|
return (
|
155
|
-
<
|
156
|
-
<
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
{label || value}
|
172
|
-
</MenuItemOption>
|
173
|
-
))
|
174
|
-
: children}
|
175
|
-
</MenuOptionGroup>
|
176
|
-
</MenuList>
|
177
|
-
<chakra.input
|
178
|
-
{...controlProps}
|
179
|
-
name={name}
|
180
|
-
type="hidden"
|
181
|
-
value={currentValue}
|
182
|
-
className="saas-select__input"
|
183
|
-
/>
|
184
|
-
</chakra.div>
|
185
|
-
</Menu>
|
167
|
+
<MenuList maxH="100vh" overflowY="auto" {...props}>
|
168
|
+
<MenuOptionGroup
|
169
|
+
defaultValue={(defaultValue || value) as string | string[] | undefined}
|
170
|
+
value={value}
|
171
|
+
onChange={onChange}
|
172
|
+
type={multiple ? 'checkbox' : 'radio'}
|
173
|
+
>
|
174
|
+
{options
|
175
|
+
? options.map(({ value, label, ...rest }, i) => (
|
176
|
+
<SelectOption key={i} value={value} {...rest}>
|
177
|
+
{label || value}
|
178
|
+
</SelectOption>
|
179
|
+
))
|
180
|
+
: props.children}
|
181
|
+
</MenuOptionGroup>
|
182
|
+
</MenuList>
|
186
183
|
)
|
187
|
-
}
|
184
|
+
}
|
188
185
|
|
189
186
|
Select.displayName = 'Select'
|
187
|
+
|
188
|
+
/**
|
189
|
+
* An option in a select list
|
190
|
+
*
|
191
|
+
* @see https://saas-ui.dev/docs/components/forms/select
|
192
|
+
*/
|
193
|
+
export const SelectOption = forwardRef<MenuItemOptionProps, 'button'>(
|
194
|
+
(props, ref) => {
|
195
|
+
return <MenuItemOption ref={ref} {...props} />
|
196
|
+
}
|
197
|
+
)
|
198
|
+
SelectOption.id = 'MenuItemOption'
|
199
|
+
SelectOption.displayName = 'SelectOption'
|
package/src/types.ts
CHANGED
@@ -3,8 +3,9 @@ import { MaybeRenderProp } from '@chakra-ui/react-utils'
|
|
3
3
|
import { FieldPath, FieldValues, RegisterOptions } from 'react-hook-form'
|
4
4
|
import { DefaultFields } from './default-fields'
|
5
5
|
import { FormProps, FormRenderContext } from './form'
|
6
|
+
import { SubmitButtonProps } from './submit-button'
|
6
7
|
|
7
|
-
export type FieldOption = { label
|
8
|
+
export type FieldOption = { label?: string; value: string }
|
8
9
|
export type FieldOptions<TOption extends FieldOption = FieldOption> =
|
9
10
|
| Array<string>
|
10
11
|
| Array<TOption>
|
@@ -12,6 +13,28 @@ export type FieldOptions<TOption extends FieldOption = FieldOption> =
|
|
12
13
|
export type ValueOf<T> = T[keyof T]
|
13
14
|
export type ShallowMerge<A, B> = Omit<A, keyof B> & B
|
14
15
|
|
16
|
+
type Split<S extends string, D extends string> = string extends S
|
17
|
+
? string[]
|
18
|
+
: S extends ''
|
19
|
+
? []
|
20
|
+
: S extends `${infer T}${D}${infer U}`
|
21
|
+
? [T, ...Split<U, D>]
|
22
|
+
: [S]
|
23
|
+
|
24
|
+
type MapPath<T extends string[]> = T extends [infer U, ...infer R]
|
25
|
+
? U extends string
|
26
|
+
? `${U extends `${number}` ? '$' : U}${R[0] extends string
|
27
|
+
? '.'
|
28
|
+
: ''}${R extends string[] ? MapPath<R> : ''}`
|
29
|
+
: ''
|
30
|
+
: ''
|
31
|
+
|
32
|
+
type TransformPath<T extends string> = MapPath<Split<T, '.'>>
|
33
|
+
|
34
|
+
export type ArrayFieldPath<Name extends string> = Name extends string
|
35
|
+
? TransformPath<Name>
|
36
|
+
: never
|
37
|
+
|
15
38
|
export interface BaseFieldProps<
|
16
39
|
TFieldValues extends FieldValues = FieldValues,
|
17
40
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
@@ -19,7 +42,7 @@ export interface BaseFieldProps<
|
|
19
42
|
/**
|
20
43
|
* The field name
|
21
44
|
*/
|
22
|
-
name: TName
|
45
|
+
name: TName | ArrayFieldPath<TName>
|
23
46
|
/**
|
24
47
|
* The field label
|
25
48
|
*/
|
@@ -52,12 +75,18 @@ export interface BaseFieldProps<
|
|
52
75
|
placeholder?: string
|
53
76
|
}
|
54
77
|
|
78
|
+
type FieldPathWithArray<
|
79
|
+
TFieldValues extends FieldValues,
|
80
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
81
|
+
> = TName | ArrayFieldPath<TName>
|
82
|
+
|
55
83
|
type MergeFieldProps<
|
56
84
|
FieldDefs,
|
57
|
-
TFieldValues extends FieldValues = FieldValues
|
85
|
+
TFieldValues extends FieldValues = FieldValues,
|
86
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
58
87
|
> = ValueOf<{
|
59
88
|
[K in keyof FieldDefs]: FieldDefs[K] extends React.FC<infer Props>
|
60
|
-
? { type?: K } & ShallowMerge<Props, BaseFieldProps<TFieldValues>>
|
89
|
+
? { type?: K } & ShallowMerge<Props, BaseFieldProps<TFieldValues, TName>>
|
61
90
|
: never
|
62
91
|
}>
|
63
92
|
|
@@ -81,11 +110,35 @@ export type FormChildren<
|
|
81
110
|
>
|
82
111
|
>
|
83
112
|
|
113
|
+
export type DefaultFieldOverrides = {
|
114
|
+
submit?: SubmitButtonProps
|
115
|
+
[key: string]: any
|
116
|
+
}
|
117
|
+
|
118
|
+
export type FieldOverrides<
|
119
|
+
FieldDefs,
|
120
|
+
TFieldValues extends FieldValues = FieldValues,
|
121
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
122
|
+
> = {
|
123
|
+
[K in FieldPathWithArray<TFieldValues, TName>]?: Omit<
|
124
|
+
MergeFieldProps<
|
125
|
+
FieldDefs extends never
|
126
|
+
? DefaultFields
|
127
|
+
: ShallowMerge<DefaultFields, FieldDefs>,
|
128
|
+
TFieldValues
|
129
|
+
>,
|
130
|
+
'name'
|
131
|
+
>
|
132
|
+
}
|
133
|
+
|
84
134
|
export type WithFields<
|
85
135
|
TFormProps extends FormProps<any, any, any, any>,
|
86
136
|
FieldDefs
|
87
137
|
> = TFormProps extends FormProps<infer TFieldValues, infer TContext>
|
88
|
-
? Omit<TFormProps, 'children'> & {
|
89
|
-
children
|
138
|
+
? Omit<TFormProps, 'children' | 'fields'> & {
|
139
|
+
children?: FormChildren<FieldDefs, TFieldValues, TContext>
|
140
|
+
fields?: FieldOverrides<FieldDefs, TFieldValues> & {
|
141
|
+
submit?: SubmitButtonProps
|
142
|
+
}
|
90
143
|
}
|
91
144
|
: never
|
package/src/use-array-field.tsx
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
import * as React from 'react'
|
2
2
|
import {
|
3
3
|
useFieldArray,
|
4
|
-
useFormContext,
|
5
4
|
UseFieldArrayReturn,
|
5
|
+
FieldValues,
|
6
|
+
FieldPath,
|
6
7
|
} from 'react-hook-form'
|
7
8
|
|
9
|
+
import { useFormContext } from './form-context'
|
10
|
+
|
8
11
|
import { createContext } from '@chakra-ui/react-utils'
|
9
12
|
|
10
13
|
export interface UseArrayFieldReturn extends UseFieldArrayReturn {
|
@@ -59,11 +62,14 @@ export const [ArrayFieldRowProvider, useArrayFieldRowContext] =
|
|
59
62
|
name: 'ArrayFieldRowContext',
|
60
63
|
})
|
61
64
|
|
62
|
-
export interface ArrayFieldOptions
|
65
|
+
export interface ArrayFieldOptions<
|
66
|
+
TFieldValues extends FieldValues = FieldValues,
|
67
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
68
|
+
> {
|
63
69
|
/**
|
64
70
|
* The field name
|
65
71
|
*/
|
66
|
-
name:
|
72
|
+
name: TName
|
67
73
|
/**
|
68
74
|
* Default value for new values in the array
|
69
75
|
*/
|
package/src/utils.ts
CHANGED
@@ -4,9 +4,16 @@ import { FieldOption, FieldOptions } from './types'
|
|
4
4
|
export const mapNestedFields = (name: string, children: React.ReactNode) => {
|
5
5
|
return React.Children.map(children, (child) => {
|
6
6
|
if (React.isValidElement(child) && child.props.name) {
|
7
|
+
let childName = child.props.name
|
8
|
+
if (childName.includes('.')) {
|
9
|
+
childName = childName.replace(/^.*\.(.*)/, '$1')
|
10
|
+
} else if (childName.includes('.$')) {
|
11
|
+
childName = childName.replace(/^.*\.\$(.*)/, '$1')
|
12
|
+
}
|
13
|
+
|
7
14
|
return React.cloneElement(child, {
|
8
15
|
...child.props,
|
9
|
-
name: `${name}.${
|
16
|
+
name: `${name}.${childName}`,
|
10
17
|
})
|
11
18
|
}
|
12
19
|
return child
|
package/src/watch-field.tsx
CHANGED
@@ -1,9 +1,5 @@
|
|
1
|
-
import {
|
2
|
-
|
3
|
-
useFormContext,
|
4
|
-
UseFormReturn,
|
5
|
-
useWatch,
|
6
|
-
} from 'react-hook-form'
|
1
|
+
import { FieldValues, useWatch } from 'react-hook-form'
|
2
|
+
import { useFormContext, UseFormReturn } from './form-context'
|
7
3
|
|
8
4
|
export interface WatchFieldProps<
|
9
5
|
Value = unknown,
|