@saas-ui/forms 0.8.0-next.4 → 1.0.0-rc.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. package/CHANGELOG.md +130 -58
  2. package/README.md +29 -0
  3. package/ajv/package.json +18 -0
  4. package/dist/ajv/ajv-resolver.d.ts +11 -0
  5. package/dist/ajv/ajv-resolver.d.ts.map +1 -0
  6. package/dist/ajv/index.d.ts +2 -0
  7. package/dist/ajv/index.d.ts.map +1 -0
  8. package/dist/ajv/index.js +97 -0
  9. package/dist/ajv/index.js.map +1 -0
  10. package/dist/ajv/index.modern.mjs +97 -0
  11. package/dist/ajv/index.modern.mjs.map +1 -0
  12. package/dist/array-field.d.ts +14 -3
  13. package/dist/array-field.d.ts.map +1 -1
  14. package/dist/auto-form.d.ts +13 -1
  15. package/dist/auto-form.d.ts.map +1 -1
  16. package/dist/field.d.ts +82 -27
  17. package/dist/field.d.ts.map +1 -1
  18. package/dist/form.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.modern.mjs +1 -1
  22. package/dist/index.modern.mjs.map +1 -1
  23. package/dist/step-form.d.ts +4 -4
  24. package/dist/step-form.d.ts.map +1 -1
  25. package/dist/use-step-form.d.ts +7 -3
  26. package/dist/use-step-form.d.ts.map +1 -1
  27. package/dist/yup/index.js +1 -1
  28. package/dist/yup/index.js.map +1 -1
  29. package/dist/yup/index.modern.mjs +1 -1
  30. package/dist/yup/index.modern.mjs.map +1 -1
  31. package/dist/zod/index.js +1 -1
  32. package/dist/zod/index.js.map +1 -1
  33. package/dist/zod/index.modern.mjs +1 -1
  34. package/dist/zod/index.modern.mjs.map +1 -1
  35. package/package.json +32 -17
  36. package/src/array-field.tsx +21 -22
  37. package/src/auto-form.tsx +22 -3
  38. package/src/field-resolver.ts +2 -2
  39. package/src/field.tsx +186 -71
  40. package/src/form.tsx +0 -1
  41. package/src/object-field.tsx +3 -3
  42. package/src/step-form.tsx +23 -17
  43. package/src/submit-button.tsx +1 -1
  44. package/src/use-step-form.tsx +27 -12
  45. package/yup/package.json +2 -2
  46. package/zod/package.json +4 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saas-ui/forms",
3
- "version": "0.8.0-next.4",
3
+ "version": "1.0.0-rc.10",
4
4
  "description": "Fully functional forms for Chakra UI.",
5
5
  "source": "src/index.ts",
6
6
  "exports": {
@@ -24,6 +24,13 @@
24
24
  },
25
25
  "./zod/src": {
26
26
  "default": "./zod/src/index.ts"
27
+ },
28
+ "./ajv": {
29
+ "require": "./dist/ajv/index.js",
30
+ "default": "./dist/ajv/index.modern.mjs"
31
+ },
32
+ "./ajv/src": {
33
+ "default": "./ajv/src/index.ts"
27
34
  }
28
35
  },
29
36
  "main": "./dist/index.js",
@@ -31,10 +38,11 @@
31
38
  "types": "./dist/index.d.ts",
32
39
  "scripts": {
33
40
  "clean": "rimraf --no-glob ./dist",
34
- "build": "yarn build:package && yarn build:yup && yarn build:zod",
41
+ "build": "yarn build:package && yarn build:yup && yarn build:zod && yarn build:ajv",
35
42
  "build:package": "yarn clean && cross-env NODE_ENV=production microbundle --tsconfig ./tsconfig.json --jsx React.createElement --jsxFragment React.Fragment -f cjs,modern --compress",
36
43
  "build:yup": "cross-env NODE_ENV=production microbundle --cwd yup --tsconfig ./yup/tsconfig.json -f cjs,modern --compress",
37
44
  "build:zod": "cross-env NODE_ENV=production microbundle --cwd zod --tsconfig ./zod/tsconfig.json -f cjs,modern --compress",
45
+ "build:ajv": "cross-env NODE_ENV=production microbundle --cwd ajv --tsconfig ./ajv/tsconfig.json -f cjs,modern --compress",
38
46
  "lint": "eslint src --ext .ts,.tsx,.js,.jsx --config ../../.eslintrc.js",
39
47
  "lint:staged": "lint-staged --allow-empty --config ../../lint-staged.config.js",
40
48
  "typecheck": "tsc --noEmit"
@@ -45,7 +53,9 @@
45
53
  "yup/package.json",
46
54
  "yup/src",
47
55
  "zod/package.json",
48
- "zod/src"
56
+ "zod/src",
57
+ "ajv/package.json",
58
+ "ajv/src"
49
59
  ],
50
60
  "sideEffects": false,
51
61
  "publishConfig": {
@@ -79,24 +89,29 @@
79
89
  "url": "https://storybook.saas-ui.dev"
80
90
  },
81
91
  "dependencies": {
82
- "@chakra-ui/icons": "^2.0.0",
83
- "@chakra-ui/react-utils": "^2.0.0",
84
- "@chakra-ui/utils": "^2.0.0",
85
- "@hookform/resolvers": "^2.8.10",
86
- "@saas-ui/button": "0.5.0-next.4",
87
- "@saas-ui/input-right-button": "0.4.0-next.4",
88
- "@saas-ui/number-input": "0.4.0-next.4",
89
- "@saas-ui/password-input": "0.4.0-next.4",
90
- "@saas-ui/pin-input": "0.4.0-next.4",
91
- "@saas-ui/radio": "0.4.0-next.4",
92
- "@saas-ui/react-utils": "0.2.0-next.4",
93
- "@saas-ui/select": "0.5.0-next.4",
94
- "@saas-ui/stepper": "0.3.0-next.4",
95
- "react-hook-form": "^7.31.2"
92
+ "@chakra-ui/icons": "^2.0.2",
93
+ "@chakra-ui/react-utils": "^2.0.1",
94
+ "@chakra-ui/utils": "^2.0.2",
95
+ "@hookform/resolvers": "^2.9.0",
96
+ "@saas-ui/button": "1.0.0-rc.4",
97
+ "@saas-ui/input-right-button": "1.0.0-rc.4",
98
+ "@saas-ui/number-input": "1.0.0-rc.3",
99
+ "@saas-ui/password-input": "1.0.0-rc.4",
100
+ "@saas-ui/pin-input": "1.0.0-rc.4",
101
+ "@saas-ui/radio": "1.0.0-rc.3",
102
+ "@saas-ui/react-utils": "1.0.0-rc.3",
103
+ "@saas-ui/select": "1.0.0-rc.3",
104
+ "@saas-ui/stepper": "1.0.0-rc.4",
105
+ "react-hook-form": "^7.31.3"
96
106
  },
97
107
  "peerDependencies": {
98
108
  "@chakra-ui/react": ">=2.1.0",
99
109
  "@chakra-ui/system": ">=2.1.0",
100
110
  "react": ">=18.0.0"
111
+ },
112
+ "devDependencies": {
113
+ "ajv": "^8.11.0",
114
+ "yup": "^0.32.11",
115
+ "zod": "^3.17.3"
101
116
  }
102
117
  }
@@ -1,11 +1,11 @@
1
1
  import * as React from 'react'
2
2
 
3
- import { chakra, ResponsiveValue } from '@chakra-ui/system'
3
+ import { chakra, ResponsiveValue, forwardRef } from '@chakra-ui/system'
4
4
  import { __DEV__ } from '@chakra-ui/utils'
5
5
  import { AddIcon, MinusIcon } from '@chakra-ui/icons'
6
6
  import { IconButton, ButtonProps } from '@saas-ui/button'
7
7
 
8
- import { FormLayout } from './layout'
8
+ import { FormLayout, FormLayoutProps } from './layout'
9
9
  import { BaseField, FieldProps } from './field'
10
10
 
11
11
  import { mapNestedFields } from './utils'
@@ -28,7 +28,7 @@ interface ArrayField {
28
28
  [key: string]: unknown
29
29
  }
30
30
 
31
- interface ArrayFieldRowProps {
31
+ interface ArrayFieldRowProps extends FormLayoutProps {
32
32
  /**
33
33
  * Amount of field columns
34
34
  */
@@ -41,21 +41,20 @@ interface ArrayFieldRowProps {
41
41
  * The array index
42
42
  */
43
43
  index: number
44
-
44
+ /**
45
+ * The fields
46
+ */
45
47
  children: React.ReactNode
46
48
  }
47
49
 
48
50
  export const ArrayFieldRow: React.FC<ArrayFieldRowProps> = ({
49
51
  children,
50
- columns,
51
- spacing,
52
52
  index,
53
+ ...rowFieldsProps
53
54
  }) => {
54
55
  return (
55
56
  <ArrayFieldRowContainer index={index}>
56
- <ArrayFieldRowFields columns={columns} spacing={spacing}>
57
- {children}
58
- </ArrayFieldRowFields>
57
+ <ArrayFieldRowFields {...rowFieldsProps}>{children}</ArrayFieldRowFields>
59
58
  <ArrayFieldRemoveButton />
60
59
  </ArrayFieldRowContainer>
61
60
  )
@@ -65,7 +64,7 @@ if (__DEV__) {
65
64
  ArrayFieldRow.displayName = 'ArrayFieldRow'
66
65
  }
67
66
 
68
- export interface ArrayFieldRowFieldsProps {
67
+ export interface ArrayFieldRowFieldsProps extends FormLayoutProps {
69
68
  /**
70
69
  * Amount of field columns
71
70
  */
@@ -74,25 +73,19 @@ export interface ArrayFieldRowFieldsProps {
74
73
  * Spacing between fields
75
74
  */
76
75
  spacing?: ResponsiveValue<string | number>
77
-
76
+ /**
77
+ * The fields
78
+ */
78
79
  children: React.ReactNode
79
80
  }
80
81
 
81
82
  export const ArrayFieldRowFields: React.FC<ArrayFieldRowFieldsProps> = ({
82
83
  children,
83
- columns,
84
- spacing,
85
84
  ...layoutProps
86
85
  }) => {
87
86
  const { name } = useArrayFieldRowContext()
88
87
  return (
89
- <FormLayout
90
- flex="1"
91
- columns={columns}
92
- gridGap={spacing}
93
- mr="2"
94
- {...layoutProps}
95
- >
88
+ <FormLayout flex="1" mr="2" {...layoutProps}>
96
89
  {mapNestedFields(name, children)}
97
90
  </FormLayout>
98
91
  )
@@ -162,7 +155,7 @@ export interface ArrayFieldProps
162
155
  extends ArrayFieldOptions,
163
156
  Omit<FieldProps, 'defaultValue'> {}
164
157
 
165
- export const ArrayField = React.forwardRef(
158
+ export const ArrayField = forwardRef(
166
159
  (props: ArrayFieldProps, ref: React.ForwardedRef<UseArrayFieldReturn>) => {
167
160
  const { children, ...containerProps } = props
168
161
 
@@ -183,7 +176,13 @@ export const ArrayField = React.forwardRef(
183
176
  </ArrayFieldContainer>
184
177
  )
185
178
  }
186
- )
179
+ ) as ((
180
+ props: ArrayFieldProps & {
181
+ ref?: React.ForwardedRef<UseArrayFieldReturn>
182
+ }
183
+ ) => React.ReactElement) & {
184
+ displayName: string
185
+ }
187
186
 
188
187
  if (__DEV__) {
189
188
  ArrayField.displayName = 'ArrayField'
package/src/auto-form.tsx CHANGED
@@ -6,12 +6,24 @@ import { __DEV__ } from '@chakra-ui/utils'
6
6
  import { Form, FormProps } from './form'
7
7
  import { FormLayout } from './layout'
8
8
  import { Fields } from './fields'
9
- import { SubmitButton } from './submit-button'
9
+ import { SubmitButton, SubmitButtonProps } from './submit-button'
10
10
  import { FieldResolver } from '.'
11
11
 
12
12
  interface AutoFormOptions {
13
- submitLabel?: false | string
13
+ /**
14
+ * The submit button label.
15
+ * Pass `null` to render no submit button.
16
+ */
17
+ submitLabel?: React.ReactNode
18
+ /**
19
+ * The schema.
20
+ * Supports object schema, Yup or Zod.
21
+ * @see https://www.saas-ui.dev/docs/forms/auto-form
22
+ */
14
23
  schema: any
24
+ /**
25
+ * The field resolver.
26
+ */
15
27
  fieldResolver?: any
16
28
  }
17
29
 
@@ -24,13 +36,20 @@ export const AutoForm = forwardRef(
24
36
  props: AutoFormProps<TFieldValues>,
25
37
  ref: React.ForwardedRef<UseFormReturn<TFieldValues>>
26
38
  ) => {
27
- const { schema, submitLabel = 'Submit', fieldResolver, ...rest } = props
39
+ const {
40
+ schema,
41
+ submitLabel = 'Submit',
42
+ fieldResolver,
43
+ children,
44
+ ...rest
45
+ } = props
28
46
 
29
47
  return (
30
48
  <Form {...rest} schema={schema} ref={ref}>
31
49
  <FormLayout>
32
50
  {<Fields schema={schema} fieldResolver={fieldResolver} />}
33
51
  {submitLabel && <SubmitButton label={submitLabel} />}
52
+ {children}
34
53
  </FormLayout>
35
54
  </Form>
36
55
  )
@@ -14,9 +14,9 @@ interface SchemaField extends FieldProps {
14
14
 
15
15
  export type ObjectSchema = Record<string, SchemaField>
16
16
 
17
- const mapFields = (schema: ObjectSchema) =>
17
+ const mapFields = (schema: ObjectSchema): FieldProps[] =>
18
18
  schema &&
19
- Object.entries(schema).map(([name, field]) => {
19
+ Object.entries(schema).map(([name, { items, ...field }]) => {
20
20
  return {
21
21
  ...field,
22
22
  name,
package/src/field.tsx CHANGED
@@ -22,14 +22,30 @@ import {
22
22
  Checkbox,
23
23
  Switch,
24
24
  useMergeRefs,
25
+ InputGroup,
26
+ InputProps,
27
+ TextareaProps,
28
+ SwitchProps,
29
+ CheckboxProps,
30
+ PinInputProps,
31
+ PinInputField,
32
+ HStack,
33
+ PinInput,
34
+ UsePinInputProps,
35
+ SystemProps,
25
36
  } from '@chakra-ui/react'
26
37
  import { __DEV__ } from '@chakra-ui/utils'
27
38
 
28
- import { NumberInput } from '@saas-ui/number-input'
29
- import { PasswordInput } from '@saas-ui/password-input'
30
- import { RadioInput } from '@saas-ui/radio'
31
- import { PinInput } from '@saas-ui/pin-input'
32
- import { Select, NativeSelect } from '@saas-ui/select'
39
+ import { NumberInput, NumberInputProps } from '@saas-ui/number-input'
40
+ import { PasswordInput, PasswordInputProps } from '@saas-ui/password-input'
41
+ import { RadioInput, RadioInputProps } from '@saas-ui/radio'
42
+
43
+ import {
44
+ Select,
45
+ SelectProps,
46
+ NativeSelect,
47
+ NativeSelectProps,
48
+ } from '@saas-ui/select'
33
49
  import { FocusableElement } from '@chakra-ui/utils'
34
50
 
35
51
  export interface Option {
@@ -43,19 +59,6 @@ export type FieldRules = Pick<
43
59
  'required' | 'min' | 'max' | 'maxLength' | 'minLength' | 'pattern'
44
60
  >
45
61
 
46
- export type FieldTypes =
47
- | 'text'
48
- | 'number'
49
- | 'password'
50
- | 'textarea'
51
- | 'select'
52
- | 'native-select'
53
- | 'checkbox'
54
- | 'radio'
55
- | 'switch'
56
- | 'pin'
57
- | string
58
-
59
62
  export interface FieldProps<
60
63
  TFieldValues extends FieldValues = FieldValues,
61
64
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
@@ -84,11 +87,6 @@ export interface FieldProps<
84
87
  'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'
85
88
  >
86
89
  /**
87
- * Options used for selects and radio fields
88
- */
89
- options?: Option[]
90
- /**
91
- * The field type
92
90
  * Build-in types:
93
91
  * - text
94
92
  * - number
@@ -102,16 +100,15 @@ export interface FieldProps<
102
100
  * - pin
103
101
  *
104
102
  * Will default to a text field if there is no matching type.
105
- * @default 'text'
106
103
  */
107
- type?: FieldTypes
104
+ type?: string
108
105
  /**
109
106
  * The input placeholder
110
107
  */
111
108
  placeholder?: string
112
109
  }
113
110
 
114
- const inputTypes: Record<FieldTypes, any> = {}
111
+ const inputTypes: Record<string, React.FC<any>> = {}
115
112
 
116
113
  const defaultInputType = 'text'
117
114
 
@@ -160,11 +157,30 @@ if (__DEV__) {
160
157
  BaseField.displayName = 'BaseField'
161
158
  }
162
159
 
163
- export const Field = forwardRef(
160
+ export type As<Props = any> = React.ElementType<Props>
161
+
162
+ export type PropsOf<T extends As> = React.ComponentPropsWithoutRef<T> & {
163
+ type?: FieldTypes
164
+ }
165
+
166
+ /**
167
+ * Build-in types:
168
+ * - text
169
+ * - number
170
+ * - password
171
+ * - textarea
172
+ * - select
173
+ * - native-select
174
+ * - checkbox
175
+ * - radio
176
+ * - switch
177
+ * - pin
178
+ *
179
+ * Will default to a text field if there is no matching type.
180
+ */
181
+ export const Field = React.forwardRef(
164
182
  <TFieldValues extends FieldValues = FieldValues>(
165
- props: FieldProps<TFieldValues> & {
166
- [key: string]: unknown // Make sure attributes of custom components work. Need to change this to a global typedef at some point.
167
- },
183
+ props: FieldProps<TFieldValues> | FieldTypeProps,
168
184
  ref: React.ForwardedRef<FocusableElement>
169
185
  ) => {
170
186
  const { type = defaultInputType } = props
@@ -172,13 +188,14 @@ export const Field = forwardRef(
172
188
 
173
189
  return <InputComponent ref={ref} {...props} />
174
190
  }
175
- ) as <TFieldValues extends FieldValues>(
176
- props: FieldProps<TFieldValues> & {
177
- [key: string]: unknown
178
- } & {
179
- ref?: React.ForwardedRef<FocusableElement>
180
- }
181
- ) => React.ReactElement
191
+ ) as (<TFieldValues extends FieldValues>(
192
+ props: FieldProps<TFieldValues> &
193
+ FieldTypeProps & {
194
+ ref?: React.ForwardedRef<FocusableElement>
195
+ }
196
+ ) => React.ReactElement) & {
197
+ displayName?: string
198
+ }
182
199
 
183
200
  interface CreateFieldProps {
184
201
  displayName: string
@@ -190,7 +207,7 @@ const createField = (
190
207
  InputComponent: React.FC<any>,
191
208
  { displayName, hideLabel, BaseField }: CreateFieldProps
192
209
  ) => {
193
- const Field = forwardRef<FieldProps, typeof FormControl>((props, ref) => {
210
+ const Field = forwardRef((props, ref) => {
194
211
  const {
195
212
  id,
196
213
  name,
@@ -200,6 +217,7 @@ const createField = (
200
217
  isInvalid,
201
218
  isReadOnly,
202
219
  isRequired,
220
+ isOptional,
203
221
  rules,
204
222
  variant,
205
223
  ...inputProps
@@ -221,13 +239,14 @@ const createField = (
221
239
  isInvalid={isInvalid}
222
240
  isReadOnly={isReadOnly}
223
241
  isRequired={isRequired}
242
+ isOptional={isOptional}
224
243
  variant={variant}
225
244
  >
226
245
  <InputComponent
227
246
  ref={ref}
228
247
  id={id}
229
248
  name={name}
230
- label={label}
249
+ label={hideLabel ? label : undefined} // Only pass down the label when it should be inline.
231
250
  rules={inputRules}
232
251
  {...inputProps}
233
252
  />
@@ -239,7 +258,7 @@ const createField = (
239
258
  return Field
240
259
  }
241
260
 
242
- export const withControlledInput = (InputComponent: any) => {
261
+ export const withControlledInput = (InputComponent: React.FC<any>) => {
243
262
  return forwardRef<FieldProps, typeof InputComponent>(
244
263
  ({ name, rules, ...inputProps }, ref) => {
245
264
  const { control } = useFormContext()
@@ -262,7 +281,7 @@ export const withControlledInput = (InputComponent: any) => {
262
281
  )
263
282
  }
264
283
 
265
- export const withUncontrolledInput = (InputComponent: any) => {
284
+ export const withUncontrolledInput = (InputComponent: React.FC<any>) => {
266
285
  return forwardRef<FieldProps, typeof InputComponent>(
267
286
  ({ name, rules, ...inputProps }, ref) => {
268
287
  const { register } = useFormContext()
@@ -292,11 +311,11 @@ export interface RegisterFieldTypeOptions {
292
311
  * @param component The React component
293
312
  * @param options
294
313
  * @param options.isControlled Set this to true if this is a controlled field.
295
- * @param options.hideLabel Hide the field label, for example for checkbox or switch field.
314
+ * @param options.hideLabel Hide the field label, for example for the checkbox field.
296
315
  */
297
- export const registerFieldType = (
316
+ export const registerFieldType = <T extends object>(
298
317
  type: string,
299
- component: React.FC<any>,
318
+ component: React.FC<T>,
300
319
  options?: RegisterFieldTypeOptions
301
320
  ) => {
302
321
  let InputComponent
@@ -313,41 +332,75 @@ export const registerFieldType = (
313
332
  .join('')}Field`,
314
333
  hideLabel: options?.hideLabel,
315
334
  BaseField: options?.BaseField || BaseField,
316
- })
335
+ }) as React.FC<T & FieldProps>
317
336
 
318
337
  inputTypes[type] = Field
319
338
 
320
339
  return Field
321
340
  }
322
341
 
323
- // @todo Consider not registering all fields by default to lower the package size and computations.
324
- // Not all types may be required in a project.
325
- export const InputField = registerFieldType('text', Input)
326
- export const NumberInputField = registerFieldType('number', NumberInput, {
327
- isControlled: true,
328
- })
329
- export const PasswordInputFIeld = registerFieldType('password', PasswordInput)
330
- export const TextareaField = registerFieldType('textarea', Textarea)
331
- export const SwitchField = registerFieldType(
342
+ export interface InputFieldProps extends InputProps {
343
+ type?: string
344
+ leftAddon?: React.ReactNode
345
+ rightAddon?: React.ReactNode
346
+ }
347
+
348
+ export const InputField = registerFieldType<InputFieldProps>(
349
+ 'text',
350
+ forwardRef(({ type = 'text', leftAddon, rightAddon, size, ...rest }, ref) => {
351
+ const input = <Input type={type} size={size} {...rest} ref={ref} />
352
+ if (leftAddon || rightAddon) {
353
+ return (
354
+ <InputGroup size={size}>
355
+ {leftAddon}
356
+ {input}
357
+ {rightAddon}
358
+ </InputGroup>
359
+ )
360
+ }
361
+ return input
362
+ })
363
+ )
364
+
365
+ export interface NumberInputFieldProps extends NumberInputProps {
366
+ type: 'number'
367
+ }
368
+
369
+ export const NumberInputField = registerFieldType<NumberInputFieldProps>(
370
+ 'number',
371
+ NumberInput,
372
+ {
373
+ isControlled: true,
374
+ }
375
+ )
376
+
377
+ export const PasswordInputField = registerFieldType<PasswordInputProps>(
378
+ 'password',
379
+ forwardRef((props, ref) => <PasswordInput ref={ref} {...props} />)
380
+ )
381
+
382
+ export const TextareaField = registerFieldType<TextareaProps>(
383
+ 'textarea',
384
+ Textarea
385
+ )
386
+
387
+ export const SwitchField = registerFieldType<SwitchProps>(
332
388
  'switch',
333
- forwardRef(({ label, ...props }: { label?: string }, ref) => {
334
- return (
335
- <Switch ref={ref} {...props}>
336
- {label}
337
- </Switch>
338
- )
389
+ forwardRef(({ type, ...rest }, ref) => {
390
+ return <Switch {...rest} ref={ref} />
339
391
  }),
340
392
  {
341
393
  isControlled: true,
342
- hideLabel: true,
343
394
  }
344
395
  )
345
- export const SelectField = registerFieldType('select', Select, {
396
+
397
+ export const SelectField = registerFieldType<SelectProps>('select', Select, {
346
398
  isControlled: true,
347
399
  })
348
- export const CheckboxField = registerFieldType(
400
+
401
+ export const CheckboxField = registerFieldType<CheckboxProps>(
349
402
  'checkbox',
350
- forwardRef(({ label, ...props }: { label?: string }, ref) => {
403
+ forwardRef(({ label, type, ...props }, ref) => {
351
404
  return (
352
405
  <Checkbox ref={ref} {...props}>
353
406
  {label}
@@ -358,14 +411,76 @@ export const CheckboxField = registerFieldType(
358
411
  hideLabel: true,
359
412
  }
360
413
  )
361
- export const RadioField = registerFieldType('radio', RadioInput, {
362
- isControlled: true,
363
- })
364
- export const PinField = registerFieldType('pin', PinInput, {
365
- isControlled: true,
366
- })
367
- export const NativeSelectField = registerFieldType(
414
+
415
+ export const RadioField = registerFieldType<RadioInputProps>(
416
+ 'radio',
417
+ RadioInput,
418
+ {
419
+ isControlled: true,
420
+ }
421
+ )
422
+
423
+ export const NativeSelectField = registerFieldType<NativeSelectProps>(
368
424
  'native-select',
369
425
  NativeSelect,
370
426
  { isControlled: true }
371
427
  )
428
+
429
+ export interface PinFieldProps extends Omit<UsePinInputProps, 'type'> {
430
+ pinLength?: number
431
+ pinType?: 'alphanumeric' | 'number'
432
+ spacing?: SystemProps['margin']
433
+ }
434
+
435
+ export const PinField = registerFieldType<PinFieldProps>(
436
+ 'pin',
437
+ forwardRef((props, ref) => {
438
+ const { pinLength = 4, pinType, spacing, ...inputProps } = props
439
+
440
+ const inputs: React.ReactNode[] = []
441
+ for (let i = 0; i < pinLength; i++) {
442
+ inputs.push(<PinInputField key={i} ref={ref} />)
443
+ }
444
+
445
+ return (
446
+ <HStack spacing={spacing}>
447
+ <PinInput {...inputProps} type={pinType}>
448
+ {inputs}
449
+ </PinInput>
450
+ </HStack>
451
+ )
452
+ }),
453
+ {
454
+ isControlled: true,
455
+ }
456
+ )
457
+
458
+ const fieldTypes = {
459
+ text: InputField,
460
+ email: InputField,
461
+ url: InputField,
462
+ phone: InputField,
463
+ number: NumberInputField,
464
+ password: PasswordInputField,
465
+ textarea: TextareaField,
466
+ switch: SwitchField,
467
+ checkbox: CheckboxField,
468
+ radio: RadioField,
469
+ pin: PinField,
470
+ select: SelectField,
471
+ 'native-select': NativeSelectField,
472
+ }
473
+
474
+ type FieldTypes = typeof fieldTypes
475
+
476
+ type FieldType<Props = any> = React.ElementType<Props>
477
+
478
+ type TypeProps<P extends FieldType, T> = React.ComponentPropsWithoutRef<P> & {
479
+ type: T
480
+ }
481
+
482
+ type FieldTypeProps =
483
+ | {
484
+ [Property in keyof FieldTypes]: TypeProps<FieldTypes[Property], Property>
485
+ }[keyof FieldTypes]
486
+ | { type?: string }
package/src/form.tsx CHANGED
@@ -16,7 +16,6 @@ import {
16
16
  ResolverResult,
17
17
  } from 'react-hook-form'
18
18
  import { objectFieldResolver, FieldResolver } from './field-resolver'
19
- import { css } from '@emotion/react'
20
19
 
21
20
  export type { UseFormReturn, FieldValues, SubmitHandler }
22
21
 
@@ -4,7 +4,7 @@ import {
4
4
  FormLabel,
5
5
  FormLabelProps,
6
6
  ResponsiveValue,
7
- useStyles,
7
+ useStyleConfig,
8
8
  } from '@chakra-ui/react'
9
9
  import { __DEV__ } from '@chakra-ui/utils'
10
10
 
@@ -21,8 +21,8 @@ export interface ObjectFieldProps extends FieldProps {
21
21
  }
22
22
 
23
23
  export const FormLegend = (props: FormLabelProps) => {
24
- const styles = useStyles()
25
- return <FormLabel as="legend" sx={styles.legend} {...props} />
24
+ const styles = useStyleConfig('FormLegend')
25
+ return <FormLabel as="legend" sx={styles} {...props} />
26
26
  }
27
27
 
28
28
  export const ObjectField: React.FC<ObjectFieldProps> = (props) => {