@saas-ui/forms 1.0.0-rc.0 → 1.0.0-rc.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. package/CHANGELOG.md +130 -0
  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 +104 -0
  9. package/dist/ajv/index.js.map +1 -0
  10. package/dist/ajv/index.modern.mjs +104 -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/submit-button.d.ts +2 -1
  26. package/dist/submit-button.d.ts.map +1 -1
  27. package/dist/use-step-form.d.ts +7 -3
  28. package/dist/use-step-form.d.ts.map +1 -1
  29. package/dist/yup/index.js +1 -1
  30. package/dist/yup/index.js.map +1 -1
  31. package/dist/yup/index.modern.mjs +1 -1
  32. package/dist/yup/index.modern.mjs.map +1 -1
  33. package/dist/zod/index.js +1 -1
  34. package/dist/zod/index.js.map +1 -1
  35. package/dist/zod/index.modern.mjs +1 -1
  36. package/dist/zod/index.modern.mjs.map +1 -1
  37. package/dist/zod/zod-resolver.d.ts +2 -2
  38. package/dist/zod/zod-resolver.d.ts.map +1 -1
  39. package/package.json +32 -17
  40. package/src/array-field.tsx +21 -22
  41. package/src/auto-form.tsx +22 -3
  42. package/src/field-resolver.ts +2 -2
  43. package/src/field.tsx +184 -73
  44. package/src/form.tsx +0 -1
  45. package/src/object-field.tsx +3 -3
  46. package/src/step-form.tsx +27 -20
  47. package/src/submit-button.tsx +32 -24
  48. package/src/use-step-form.tsx +27 -12
  49. package/yup/package.json +2 -2
  50. package/zod/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"zod-resolver.d.ts","sourceRoot":"","sources":["../../zod/src/zod-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEvD,OAAO,EAAE,WAAW,EAAE,CAAA;AAEtB,oBAAY,OAAO,GAAG;IACpB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAsBD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,WAAY,EAAE,UAAU,KAAG,UAAU,EA+BpE,CAAA;AAED,eAAO,MAAM,eAAe,WAAY,EAAE,UAAU,QAAQ,MAAM,QAEjE,CAAA;AAED,eAAO,MAAM,gBAAgB;;0BAKH,MAAM;CAI/B,CAAA;AAED,eAAO,MAAM,OAAO;;;;;8BANM,MAAM;;CAgB/B,CAAA;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB;AAED,eAAO,MAAM,OAAO,SAAU,OAAO,WAEpC,CAAA;AAED,eAAO,MAAM,YAAY,SAAU,MAAM,QAMxC,CAAA"}
1
+ {"version":3,"file":"zod-resolver.d.ts","sourceRoot":"","sources":["../../zod/src/zod-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE3C,OAAO,EAAE,WAAW,EAAE,CAAA;AAEtB,oBAAY,OAAO,GAAG;IACpB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAsBD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,WAAY,EAAE,UAAU,KAAG,UAAU,EA+BpE,CAAA;AAED,eAAO,MAAM,eAAe,WAAY,EAAE,UAAU,QAAQ,MAAM,QAEjE,CAAA;AAED,eAAO,MAAM,gBAAgB;;0BAKH,MAAM;CAI/B,CAAA;AAED,eAAO,MAAM,OAAO;;;;;8BANM,MAAM;;CAgB/B,CAAA;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,eAAO,MAAM,OAAO,SAAU,OAAO,WAEpC,CAAA;AAED,eAAO,MAAM,YAAY,SAAU,MAAM,QAMxC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saas-ui/forms",
3
- "version": "1.0.0-rc.0",
3
+ "version": "1.0.0-rc.11",
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": "1.0.0-rc.0",
87
- "@saas-ui/input-right-button": "1.0.0-rc.0",
88
- "@saas-ui/number-input": "1.0.0-rc.0",
89
- "@saas-ui/password-input": "1.0.0-rc.0",
90
- "@saas-ui/pin-input": "1.0.0-rc.0",
91
- "@saas-ui/radio": "1.0.0-rc.0",
92
- "@saas-ui/react-utils": "1.0.0-rc.0",
93
- "@saas-ui/select": "1.0.0-rc.0",
94
- "@saas-ui/stepper": "1.0.0-rc.0",
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.5",
97
+ "@saas-ui/input-right-button": "1.0.0-rc.5",
98
+ "@saas-ui/number-input": "1.0.0-rc.4",
99
+ "@saas-ui/password-input": "1.0.0-rc.5",
100
+ "@saas-ui/pin-input": "1.0.0-rc.5",
101
+ "@saas-ui/radio": "1.0.0-rc.4",
102
+ "@saas-ui/react-utils": "1.0.0-rc.4",
103
+ "@saas-ui/select": "1.0.0-rc.4",
104
+ "@saas-ui/stepper": "1.0.0-rc.5",
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
@@ -10,8 +10,20 @@ import { SubmitButton } 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
- {submitLabel && <SubmitButton label={submitLabel} />}
51
+ {submitLabel && <SubmitButton>{submitLabel}</SubmitButton>}
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,15 +22,29 @@ import {
22
22
  Checkbox,
23
23
  Switch,
24
24
  useMergeRefs,
25
+ InputGroup,
26
+ InputProps,
27
+ TextareaProps,
28
+ SwitchProps,
29
+ CheckboxProps,
30
+ PinInputField,
31
+ HStack,
32
+ PinInput,
33
+ UsePinInputProps,
34
+ SystemProps,
25
35
  } from '@chakra-ui/react'
26
- import { __DEV__ } from '@chakra-ui/utils'
36
+ import { __DEV__, FocusableElement } from '@chakra-ui/utils'
27
37
 
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'
33
- import { FocusableElement } from '@chakra-ui/utils'
38
+ import { NumberInput, NumberInputProps } from '@saas-ui/number-input'
39
+ import { PasswordInput, PasswordInputProps } from '@saas-ui/password-input'
40
+ import { RadioInput, RadioInputProps } from '@saas-ui/radio'
41
+
42
+ import {
43
+ Select,
44
+ SelectProps,
45
+ NativeSelect,
46
+ NativeSelectProps,
47
+ } from '@saas-ui/select'
34
48
 
35
49
  export interface Option {
36
50
  value: string
@@ -43,19 +57,6 @@ export type FieldRules = Pick<
43
57
  'required' | 'min' | 'max' | 'maxLength' | 'minLength' | 'pattern'
44
58
  >
45
59
 
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
60
  export interface FieldProps<
60
61
  TFieldValues extends FieldValues = FieldValues,
61
62
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
@@ -84,11 +85,6 @@ export interface FieldProps<
84
85
  'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'
85
86
  >
86
87
  /**
87
- * Options used for selects and radio fields
88
- */
89
- options?: Option[]
90
- /**
91
- * The field type
92
88
  * Build-in types:
93
89
  * - text
94
90
  * - number
@@ -102,16 +98,15 @@ export interface FieldProps<
102
98
  * - pin
103
99
  *
104
100
  * Will default to a text field if there is no matching type.
105
- * @default 'text'
106
101
  */
107
- type?: FieldTypes
102
+ type?: string
108
103
  /**
109
104
  * The input placeholder
110
105
  */
111
106
  placeholder?: string
112
107
  }
113
108
 
114
- const inputTypes: Record<FieldTypes, any> = {}
109
+ const inputTypes: Record<string, React.FC<any>> = {}
115
110
 
116
111
  const defaultInputType = 'text'
117
112
 
@@ -160,11 +155,30 @@ if (__DEV__) {
160
155
  BaseField.displayName = 'BaseField'
161
156
  }
162
157
 
163
- export const Field = forwardRef(
158
+ export type As<Props = any> = React.ElementType<Props>
159
+
160
+ export type PropsOf<T extends As> = React.ComponentPropsWithoutRef<T> & {
161
+ type?: FieldTypes
162
+ }
163
+
164
+ /**
165
+ * Build-in types:
166
+ * - text
167
+ * - number
168
+ * - password
169
+ * - textarea
170
+ * - select
171
+ * - native-select
172
+ * - checkbox
173
+ * - radio
174
+ * - switch
175
+ * - pin
176
+ *
177
+ * Will default to a text field if there is no matching type.
178
+ */
179
+ export const Field = React.forwardRef(
164
180
  <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
- },
181
+ props: FieldProps<TFieldValues> | FieldTypeProps,
168
182
  ref: React.ForwardedRef<FocusableElement>
169
183
  ) => {
170
184
  const { type = defaultInputType } = props
@@ -172,13 +186,14 @@ export const Field = forwardRef(
172
186
 
173
187
  return <InputComponent ref={ref} {...props} />
174
188
  }
175
- ) as <TFieldValues extends FieldValues>(
176
- props: FieldProps<TFieldValues> & {
177
- [key: string]: unknown
178
- } & {
179
- ref?: React.ForwardedRef<FocusableElement>
180
- }
181
- ) => React.ReactElement
189
+ ) as (<TFieldValues extends FieldValues>(
190
+ props: FieldProps<TFieldValues> &
191
+ FieldTypeProps & {
192
+ ref?: React.ForwardedRef<FocusableElement>
193
+ }
194
+ ) => React.ReactElement) & {
195
+ displayName?: string
196
+ }
182
197
 
183
198
  interface CreateFieldProps {
184
199
  displayName: string
@@ -190,7 +205,7 @@ const createField = (
190
205
  InputComponent: React.FC<any>,
191
206
  { displayName, hideLabel, BaseField }: CreateFieldProps
192
207
  ) => {
193
- const Field = forwardRef<FieldProps, typeof FormControl>((props, ref) => {
208
+ const Field = forwardRef((props, ref) => {
194
209
  const {
195
210
  id,
196
211
  name,
@@ -227,7 +242,7 @@ const createField = (
227
242
  ref={ref}
228
243
  id={id}
229
244
  name={name}
230
- label={label}
245
+ label={hideLabel ? label : undefined} // Only pass down the label when it should be inline.
231
246
  rules={inputRules}
232
247
  {...inputProps}
233
248
  />
@@ -239,7 +254,7 @@ const createField = (
239
254
  return Field
240
255
  }
241
256
 
242
- export const withControlledInput = (InputComponent: any) => {
257
+ export const withControlledInput = (InputComponent: React.FC<any>) => {
243
258
  return forwardRef<FieldProps, typeof InputComponent>(
244
259
  ({ name, rules, ...inputProps }, ref) => {
245
260
  const { control } = useFormContext()
@@ -262,7 +277,7 @@ export const withControlledInput = (InputComponent: any) => {
262
277
  )
263
278
  }
264
279
 
265
- export const withUncontrolledInput = (InputComponent: any) => {
280
+ export const withUncontrolledInput = (InputComponent: React.FC<any>) => {
266
281
  return forwardRef<FieldProps, typeof InputComponent>(
267
282
  ({ name, rules, ...inputProps }, ref) => {
268
283
  const { register } = useFormContext()
@@ -292,11 +307,11 @@ export interface RegisterFieldTypeOptions {
292
307
  * @param component The React component
293
308
  * @param options
294
309
  * @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.
310
+ * @param options.hideLabel Hide the field label, for example for the checkbox field.
296
311
  */
297
- export const registerFieldType = (
312
+ export const registerFieldType = <T extends object>(
298
313
  type: string,
299
- component: React.FC<any>,
314
+ component: React.FC<T>,
300
315
  options?: RegisterFieldTypeOptions
301
316
  ) => {
302
317
  let InputComponent
@@ -313,41 +328,75 @@ export const registerFieldType = (
313
328
  .join('')}Field`,
314
329
  hideLabel: options?.hideLabel,
315
330
  BaseField: options?.BaseField || BaseField,
316
- })
331
+ }) as React.FC<T & FieldProps>
317
332
 
318
333
  inputTypes[type] = Field
319
334
 
320
335
  return Field
321
336
  }
322
337
 
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(
338
+ export interface InputFieldProps extends InputProps {
339
+ type?: string
340
+ leftAddon?: React.ReactNode
341
+ rightAddon?: React.ReactNode
342
+ }
343
+
344
+ export const InputField = registerFieldType<InputFieldProps>(
345
+ 'text',
346
+ forwardRef(({ type = 'text', leftAddon, rightAddon, size, ...rest }, ref) => {
347
+ const input = <Input type={type} size={size} {...rest} ref={ref} />
348
+ if (leftAddon || rightAddon) {
349
+ return (
350
+ <InputGroup size={size}>
351
+ {leftAddon}
352
+ {input}
353
+ {rightAddon}
354
+ </InputGroup>
355
+ )
356
+ }
357
+ return input
358
+ })
359
+ )
360
+
361
+ export interface NumberInputFieldProps extends NumberInputProps {
362
+ type: 'number'
363
+ }
364
+
365
+ export const NumberInputField = registerFieldType<NumberInputFieldProps>(
366
+ 'number',
367
+ NumberInput,
368
+ {
369
+ isControlled: true,
370
+ }
371
+ )
372
+
373
+ export const PasswordInputField = registerFieldType<PasswordInputProps>(
374
+ 'password',
375
+ forwardRef((props, ref) => <PasswordInput ref={ref} {...props} />)
376
+ )
377
+
378
+ export const TextareaField = registerFieldType<TextareaProps>(
379
+ 'textarea',
380
+ Textarea
381
+ )
382
+
383
+ export const SwitchField = registerFieldType<SwitchProps>(
332
384
  'switch',
333
- forwardRef(({ label, ...props }: { label?: string }, ref) => {
334
- return (
335
- <Switch ref={ref} {...props}>
336
- {label}
337
- </Switch>
338
- )
385
+ forwardRef(({ type, ...rest }, ref) => {
386
+ return <Switch {...rest} ref={ref} />
339
387
  }),
340
388
  {
341
389
  isControlled: true,
342
- hideLabel: true,
343
390
  }
344
391
  )
345
- export const SelectField = registerFieldType('select', Select, {
392
+
393
+ export const SelectField = registerFieldType<SelectProps>('select', Select, {
346
394
  isControlled: true,
347
395
  })
348
- export const CheckboxField = registerFieldType(
396
+
397
+ export const CheckboxField = registerFieldType<CheckboxProps>(
349
398
  'checkbox',
350
- forwardRef(({ label, ...props }: { label?: string }, ref) => {
399
+ forwardRef(({ label, type, ...props }, ref) => {
351
400
  return (
352
401
  <Checkbox ref={ref} {...props}>
353
402
  {label}
@@ -358,14 +407,76 @@ export const CheckboxField = registerFieldType(
358
407
  hideLabel: true,
359
408
  }
360
409
  )
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(
410
+
411
+ export const RadioField = registerFieldType<RadioInputProps>(
412
+ 'radio',
413
+ RadioInput,
414
+ {
415
+ isControlled: true,
416
+ }
417
+ )
418
+
419
+ export const NativeSelectField = registerFieldType<NativeSelectProps>(
368
420
  'native-select',
369
421
  NativeSelect,
370
422
  { isControlled: true }
371
423
  )
424
+
425
+ export interface PinFieldProps extends Omit<UsePinInputProps, 'type'> {
426
+ pinLength?: number
427
+ pinType?: 'alphanumeric' | 'number'
428
+ spacing?: SystemProps['margin']
429
+ }
430
+
431
+ export const PinField = registerFieldType<PinFieldProps>(
432
+ 'pin',
433
+ forwardRef((props, ref) => {
434
+ const { pinLength = 4, pinType, spacing, ...inputProps } = props
435
+
436
+ const inputs: React.ReactNode[] = []
437
+ for (let i = 0; i < pinLength; i++) {
438
+ inputs.push(<PinInputField key={i} ref={ref} />)
439
+ }
440
+
441
+ return (
442
+ <HStack spacing={spacing}>
443
+ <PinInput {...inputProps} type={pinType}>
444
+ {inputs}
445
+ </PinInput>
446
+ </HStack>
447
+ )
448
+ }),
449
+ {
450
+ isControlled: true,
451
+ }
452
+ )
453
+
454
+ const fieldTypes = {
455
+ text: InputField,
456
+ email: InputField,
457
+ url: InputField,
458
+ phone: InputField,
459
+ number: NumberInputField,
460
+ password: PasswordInputField,
461
+ textarea: TextareaField,
462
+ switch: SwitchField,
463
+ checkbox: CheckboxField,
464
+ radio: RadioField,
465
+ pin: PinField,
466
+ select: SelectField,
467
+ 'native-select': NativeSelectField,
468
+ }
469
+
470
+ type FieldTypes = typeof fieldTypes
471
+
472
+ type FieldType<Props = any> = React.ElementType<Props>
473
+
474
+ type TypeProps<P extends FieldType, T> = React.ComponentPropsWithoutRef<P> & {
475
+ type: T
476
+ }
477
+
478
+ type FieldTypeProps =
479
+ | {
480
+ [Property in keyof FieldTypes]: TypeProps<FieldTypes[Property], Property>
481
+ }[keyof FieldTypes]
482
+ | { 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) => {