@saas-ui/forms 0.5.3 → 0.6.0-next.2

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saas-ui/forms",
3
- "version": "0.5.3",
3
+ "version": "0.6.0-next.2",
4
4
  "description": "Theme and components agnostic SaasProvider",
5
5
  "source": "src/index.ts",
6
6
  "exports": {
@@ -8,12 +8,12 @@
8
8
  "require": "./dist/index.js",
9
9
  "default": "./dist/index.modern.js"
10
10
  },
11
- "./resolvers/yup": {
12
- "require": "./dist/resolvers/yup.js",
13
- "default": "./dist/resolvers/yup.modern.js"
14
- },
15
11
  "./src": {
16
12
  "default": "./src/index.ts"
13
+ },
14
+ "./yup": {
15
+ "require": "./dist/yup/index.js",
16
+ "default": "./dist/yup/index.modern.js"
17
17
  }
18
18
  },
19
19
  "main": "./dist/index.js",
@@ -21,14 +21,18 @@
21
21
  "types": "./dist/index.d.ts",
22
22
  "scripts": {
23
23
  "clean": "rimraf --no-glob ./dist",
24
- "build": "yarn clean && cross-env NODE_ENV=production microbundle --tsconfig ./tsconfig.json --jsx React.createElement --jsxFragment React.Fragment -f cjs,modern --compress",
24
+ "build": "yarn build:package && yarn build:yup",
25
+ "build:package": "yarn clean && cross-env NODE_ENV=production microbundle --tsconfig ./tsconfig.json --jsx React.createElement --jsxFragment React.Fragment -f cjs,modern --compress",
26
+ "build:yup": "cross-env NODE_ENV=production microbundle --cwd yup --tsconfig ./yup/tsconfig.json -f cjs,modern --compress",
25
27
  "lint": "eslint src --ext .ts,.tsx,.js,.jsx --config ../../.eslintrc.js",
26
28
  "lint:staged": "lint-staged --allow-empty --config ../../lint-staged.config.js",
27
29
  "typecheck": "tsc --noEmit"
28
30
  },
29
31
  "files": [
30
32
  "dist",
31
- "src"
33
+ "src",
34
+ "yup/package.json",
35
+ "yup/src"
32
36
  ],
33
37
  "sideEffects": false,
34
38
  "publishConfig": {
@@ -66,20 +70,19 @@
66
70
  "@chakra-ui/react-utils": "^1.2.1",
67
71
  "@chakra-ui/utils": "^1.10.4",
68
72
  "@hookform/resolvers": "^2.8.3",
69
- "@saas-ui/button": "0.3.0",
70
- "@saas-ui/input-right-button": "0.3.0",
73
+ "@saas-ui/button": "0.4.0-next.0",
74
+ "@saas-ui/input-right-button": "0.3.1-next.0",
71
75
  "@saas-ui/number-input": "0.3.0",
72
- "@saas-ui/password-input": "0.3.0",
76
+ "@saas-ui/password-input": "0.3.1-next.0",
73
77
  "@saas-ui/pin-input": "0.3.0",
74
78
  "@saas-ui/radio": "0.3.0",
75
79
  "@saas-ui/react-utils": "0.1.0",
76
- "@saas-ui/select": "0.3.0",
80
+ "@saas-ui/select": "0.4.0-next.0",
77
81
  "react-hook-form": "^7.22.0"
78
82
  },
79
83
  "peerDependencies": {
80
84
  "@chakra-ui/react": ">=1.8.0",
81
85
  "@chakra-ui/system": ">=1.0.0",
82
- "react": ">=16.8.6",
83
- "yup": "^0.32.11"
86
+ "react": ">=16.8.6"
84
87
  }
85
88
  }
package/src/auto-form.tsx CHANGED
@@ -3,14 +3,15 @@ import { FieldValues, UseFormReturn } from 'react-hook-form'
3
3
  import { forwardRef } from '@chakra-ui/react'
4
4
 
5
5
  import { Form, FormProps } from './form'
6
-
7
6
  import { FormLayout } from './layout'
8
7
  import { Fields } from './fields'
9
8
  import { SubmitButton } from './submit-button'
9
+ import { FieldResolver } from '.'
10
10
 
11
11
  interface AutoFormOptions {
12
- schema: any
13
12
  submitLabel?: false | string
13
+ schema: any
14
+ fieldResolver?: any
14
15
  }
15
16
 
16
17
  export interface AutoFormProps<TFieldValues extends FieldValues>
@@ -22,18 +23,19 @@ export const AutoForm = forwardRef(
22
23
  props: AutoFormProps<TFieldValues>,
23
24
  ref: React.ForwardedRef<UseFormReturn<TFieldValues>>
24
25
  ) => {
25
- const { schema, submitLabel = 'Submit', ...rest } = props
26
+ const { schema, submitLabel = 'Submit', fieldResolver, ...rest } = props
27
+
26
28
  return (
27
29
  <Form {...rest} schema={schema} ref={ref}>
28
30
  <FormLayout>
29
- {<Fields schema={schema} />}
31
+ {<Fields schema={schema} fieldResolver={fieldResolver} />}
30
32
  {submitLabel && <SubmitButton label={submitLabel} />}
31
33
  </FormLayout>
32
34
  </Form>
33
35
  )
34
36
  }
35
- ) as <TFieldValues extends FieldValues>(
37
+ ) as (<TFieldValues extends FieldValues>(
36
38
  props: AutoFormProps<TFieldValues> & {
37
39
  ref?: React.ForwardedRef<UseFormReturn<TFieldValues>>
38
40
  }
39
- ) => React.ReactElement
41
+ ) => React.ReactElement) & { getFieldResolver?: (schema: any) => FieldResolver }
@@ -0,0 +1,39 @@
1
+ import { FieldProps } from './field'
2
+
3
+ import { get } from '@chakra-ui/utils'
4
+
5
+ export type FieldResolver = {
6
+ getFields(): FieldProps[]
7
+ getNestedFields(name: string): FieldProps[]
8
+ }
9
+
10
+ // @todo finalize this
11
+ export type FormSchema = Record<string, any>
12
+
13
+ const mapFields = (schema: FormSchema) =>
14
+ Object.entries(schema).map(([name, field]) => {
15
+ return {
16
+ name,
17
+ ...field,
18
+ }
19
+ })
20
+
21
+ export const defaultFieldResolver = (schema: FormSchema): FieldResolver => {
22
+ const getFields = () => {
23
+ return mapFields(schema)
24
+ }
25
+ const getNestedFields = (name: string) => {
26
+ const field = get(schema, name)
27
+
28
+ if (!field) return []
29
+
30
+ if (field.items?.type === 'object') {
31
+ return mapFields(field.items.properties)
32
+ } else if (field.type === 'object') {
33
+ return mapFields(field.properties)
34
+ }
35
+ return [field.items]
36
+ }
37
+
38
+ return { getFields, getNestedFields }
39
+ }
package/src/fields.tsx CHANGED
@@ -1,51 +1,66 @@
1
1
  import * as React from 'react'
2
- import { getFieldsFromSchema, getNestedSchema } from './resolvers/yup'
3
2
 
3
+ import { Form } from './form'
4
4
  import { FormLayout } from './layout'
5
5
  import { Field, FieldProps } from './field'
6
6
 
7
7
  import { ArrayField } from './array-field'
8
8
  import { ObjectField } from './object-field'
9
+ import { FieldResolver } from './field-resolver'
9
10
 
10
11
  export interface FieldsProps {
11
12
  schema: any
13
+ fieldResolver?: FieldResolver
12
14
  }
13
15
 
14
- const getNestedFields = (schema: any, name: string) => {
15
- return getFieldsFromSchema(getNestedSchema(schema, name)).map(
16
- ({ name, type, ...nestedFieldProps }: FieldProps): React.ReactNode => (
17
- <Field key={name} name={name} type={type} {...nestedFieldProps} />
16
+ const mapNestedFields = (resolver: FieldResolver, name: string) => {
17
+ return resolver
18
+ .getNestedFields(name)
19
+ ?.map(
20
+ ({ name, type, ...nestedFieldProps }: FieldProps, i): React.ReactNode => (
21
+ <Field key={name || i} name={name} type={type} {...nestedFieldProps} />
22
+ )
18
23
  )
19
- )
20
24
  }
21
25
 
22
- export const Fields: React.FC<FieldsProps> = ({ schema, ...props }) => {
26
+ export const Fields: React.FC<FieldsProps> = ({
27
+ schema,
28
+ fieldResolver,
29
+ ...props
30
+ }) => {
31
+ const resolver = React.useMemo(
32
+ () => fieldResolver || Form.getFieldResolver(schema),
33
+ [schema, fieldResolver]
34
+ )
35
+
23
36
  return (
24
37
  <FormLayout {...props}>
25
- {getFieldsFromSchema(schema).map(
26
- ({
27
- name,
28
- type,
29
- defaultValue,
30
- ...fieldProps
31
- }: FieldProps): React.ReactNode => {
32
- if (type === 'array') {
33
- return (
34
- <ArrayField name={name} {...fieldProps}>
35
- {getNestedFields(schema, name)}
36
- </ArrayField>
37
- )
38
- } else if (type === 'object') {
39
- return (
40
- <ObjectField name={name} {...fieldProps}>
41
- {getNestedFields(schema, name)}
42
- </ObjectField>
43
- )
44
- }
38
+ {resolver
39
+ .getFields()
40
+ .map(
41
+ ({
42
+ name,
43
+ type,
44
+ defaultValue,
45
+ ...fieldProps
46
+ }: FieldProps): React.ReactNode => {
47
+ if (type === 'array') {
48
+ return (
49
+ <ArrayField key={name} name={name} {...fieldProps}>
50
+ {mapNestedFields(resolver, name)}
51
+ </ArrayField>
52
+ )
53
+ } else if (type === 'object') {
54
+ return (
55
+ <ObjectField key={name} name={name} {...fieldProps}>
56
+ {mapNestedFields(resolver, name)}
57
+ </ObjectField>
58
+ )
59
+ }
45
60
 
46
- return <Field key={name} name={name} type={type} {...fieldProps} />
47
- }
48
- )}
61
+ return <Field key={name} name={name} type={type} {...fieldProps} />
62
+ }
63
+ )}
49
64
  </FormLayout>
50
65
  )
51
66
  }
package/src/form.tsx CHANGED
@@ -10,11 +10,13 @@ import {
10
10
  FieldValues,
11
11
  SubmitHandler,
12
12
  SubmitErrorHandler,
13
+ UnpackNestedValue,
14
+ ResolverOptions,
15
+ ResolverResult,
13
16
  } from 'react-hook-form'
17
+ import { defaultFieldResolver, FieldResolver } from './field-resolver'
14
18
 
15
- import { yupResolver } from './resolvers/yup'
16
-
17
- export type { UseFormReturn, FieldValues }
19
+ export type { UseFormReturn, FieldValues, SubmitHandler }
18
20
 
19
21
  interface FormOptions<TFieldValues extends FieldValues = FieldValues> {
20
22
  /**
@@ -35,15 +37,15 @@ interface FormOptions<TFieldValues extends FieldValues = FieldValues> {
35
37
  formRef?: React.MutableRefObject<HTMLFormElement>
36
38
  }
37
39
 
40
+ /**
41
+ * @todo Figure out how to pass down FieldValues to all Field components,
42
+ * if at all possible.
43
+ */
38
44
  export interface FormProps<TFieldValues extends FieldValues = FieldValues>
39
45
  extends UseFormProps<TFieldValues>,
40
46
  Omit<HTMLChakraProps<'form'>, 'onSubmit' | 'onError'>,
41
47
  FormOptions<TFieldValues> {}
42
48
 
43
- /**
44
- * @todo Figure out how to pass down FieldValues to all Field components,
45
- * if at all possible.
46
- */
47
49
  export const Form = forwardRef(
48
50
  <TFieldValues extends FieldValues = FieldValues>(
49
51
  props: FormProps<TFieldValues>,
@@ -79,9 +81,8 @@ export const Form = forwardRef(
79
81
  delayError,
80
82
  }
81
83
 
82
- // @todo remove yup dependency and just use resolver prop?
83
- if (schema) {
84
- form.resolver = yupResolver(schema)
84
+ if (schema && !resolver) {
85
+ form.resolver = Form.getResolver?.(schema)
85
86
  }
86
87
 
87
88
  const methods = useForm<TFieldValues>(form)
@@ -102,8 +103,23 @@ export const Form = forwardRef(
102
103
  </FormProvider>
103
104
  )
104
105
  }
105
- ) as <TFieldValues extends FieldValues>(
106
+ ) as (<TFieldValues extends FieldValues>(
106
107
  props: FormProps<TFieldValues> & {
107
108
  ref?: React.ForwardedRef<UseFormReturn<TFieldValues>>
108
109
  }
109
- ) => React.ReactElement
110
+ ) => React.ReactElement) & {
111
+ getResolver?: GetResolver
112
+ getFieldResolver: GetFieldResolver
113
+ }
114
+
115
+ Form.getFieldResolver = defaultFieldResolver
116
+
117
+ export type GetResolver = (
118
+ schema: any
119
+ ) => <TFieldValues extends FieldValues, TContext>(
120
+ values: UnpackNestedValue<TFieldValues>,
121
+ context: TContext | undefined,
122
+ options: ResolverOptions<TFieldValues>
123
+ ) => Promise<ResolverResult<TFieldValues>>
124
+
125
+ export type GetFieldResolver = (schema: any) => FieldResolver
package/src/index.ts CHANGED
@@ -11,7 +11,7 @@ export * from './object-field'
11
11
  export * from './display-if'
12
12
  export * from './step-form'
13
13
  export * from './use-step-form'
14
-
14
+ export * from './field-resolver'
15
15
  export * from '@saas-ui/input-right-button'
16
16
 
17
17
  export type { FieldErrors } from 'react-hook-form'
package/src/step-form.tsx CHANGED
@@ -72,6 +72,10 @@ export interface FormStepOptions {
72
72
  * Schema
73
73
  */
74
74
  schema?: any
75
+ /**
76
+ * Hook Form Resolver
77
+ */
78
+ resolver?: any
75
79
  }
76
80
 
77
81
  export const FormStepper: React.FC<StepperStepsProps> = (props) => {
@@ -109,8 +113,8 @@ export interface FormStepProps
109
113
  HTMLChakraProps<'div'> {}
110
114
 
111
115
  export const FormStep: React.FC<FormStepProps> = (props) => {
112
- const { name, schema, children, className, ...rest } = props
113
- const step = useFormStep({ name, schema })
116
+ const { name, schema, resolver, children, className, ...rest } = props
117
+ const step = useFormStep({ name, schema, resolver })
114
118
 
115
119
  const { isActive } = step
116
120
 
@@ -11,6 +11,7 @@ import {
11
11
  export interface StepState {
12
12
  name: string
13
13
  schema?: any
14
+ resolver?: any
14
15
  isActive?: boolean
15
16
  isCompleted?: boolean
16
17
  }
@@ -69,6 +70,7 @@ export function useStepForm<TFieldValues extends FieldValues = FieldValues>(
69
70
  return {
70
71
  onSubmit: onSubmitStep,
71
72
  schema: step?.schema,
73
+ resolver: step?.resolver,
72
74
  }
73
75
  },
74
76
  [steps, onSubmitStep, activeStep]
@@ -99,16 +101,17 @@ export type UseStepFormReturn = ReturnType<typeof useStepForm>
99
101
  export interface UseFormStepProps {
100
102
  name: string
101
103
  schema?: any
104
+ resolver?: any
102
105
  }
103
106
 
104
107
  export function useFormStep(props: UseFormStepProps): StepState {
105
- const { name, schema } = props
108
+ const { name, schema, resolver } = props
106
109
  const step = useStep({ name })
107
110
 
108
111
  const { steps, updateStep } = useStepFormContext()
109
112
 
110
113
  React.useEffect(() => {
111
- updateStep({ name, schema })
114
+ updateStep({ name, schema, resolver })
112
115
  }, [name, schema])
113
116
 
114
117
  return {
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "yup",
3
+ "description": "Saas UI Forms field resolver: yup",
4
+ "version": "1.0.0",
5
+ "private": true,
6
+ "source": "./src/index.ts",
7
+ "exports": {
8
+ ".": {
9
+ "require": "./../dist/yup/index.js",
10
+ "types": "./../dist/yup/src/index.d.ts",
11
+ "default": "./../dist/yup/index.modern.js"
12
+ }
13
+ },
14
+ "main": "../dist/yup/index.js",
15
+ "module": "../dist/yup/index.modern.js",
16
+ "types": "../dist/yup/src/index.d.ts",
17
+ "author": "Eelco Wiersma <eelco@appulse.nl>",
18
+ "license": "MIT",
19
+ "peerDependencies": {
20
+ "@hookform/resolvers": "^2.8.3",
21
+ "react-hook-form": "^7.22.0",
22
+ "yup": "^0.32.11"
23
+ }
24
+ }
@@ -1,12 +0,0 @@
1
- import { SchemaOf, AnySchema } from 'yup';
2
- export { yupResolver } from '@hookform/resolvers/yup';
3
- import { FieldProps } from '../field';
4
- /**
5
- * A helper function to render forms automatically based on a Yup schema
6
- *
7
- * @param schema The Yup schema
8
- * @returns {FieldProps[]}
9
- */
10
- export declare const getFieldsFromSchema: (schema: SchemaOf<AnySchema>) => FieldProps[];
11
- export declare const getNestedSchema: (schema: SchemaOf<AnySchema>, path: string) => any;
12
- //# sourceMappingURL=yup.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"yup.d.ts","sourceRoot":"","sources":["../../src/resolvers/yup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAS,MAAM,KAAK,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAmCrC;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,WACtB,SAAS,SAAS,CAAC,KAC1B,UAAU,EA4BZ,CAAA;AAED,eAAO,MAAM,eAAe,WAAY,SAAS,SAAS,CAAC,QAAQ,MAAM,QAExE,CAAA"}
@@ -1,79 +0,0 @@
1
- import { SchemaOf, AnySchema, reach } from 'yup'
2
- export { yupResolver } from '@hookform/resolvers/yup'
3
-
4
- import { FieldProps } from '../field'
5
-
6
- // @TODO get proper typings for the schema fields
7
-
8
- const getType = (field: any) => {
9
- if (field.spec.meta?.type) {
10
- return field.spec.meta.type
11
- }
12
-
13
- switch (field.type) {
14
- case 'array':
15
- return 'array'
16
- case 'object':
17
- return 'object'
18
- case 'number':
19
- return 'number'
20
- case 'date':
21
- return 'date'
22
- case 'string':
23
- default:
24
- return 'text'
25
- }
26
- }
27
-
28
- type Options = {
29
- min?: number
30
- max?: number
31
- }
32
-
33
- const getArrayOption = (field: any, name: string) => {
34
- for (const test of field.tests) {
35
- if (test.OPTIONS?.params[name]) return test.OPTIONS.params[name]
36
- }
37
- }
38
-
39
- /**
40
- * A helper function to render forms automatically based on a Yup schema
41
- *
42
- * @param schema The Yup schema
43
- * @returns {FieldProps[]}
44
- */
45
- export const getFieldsFromSchema = (
46
- schema: SchemaOf<AnySchema>
47
- ): FieldProps[] => {
48
- const fields = []
49
-
50
- let schemaFields: Record<string, any> = {}
51
- if (schema.type === 'array') {
52
- /* @ts-ignore this is actually valid */
53
- schemaFields = schema.innerType.fields
54
- } else {
55
- schemaFields = schema.fields
56
- }
57
-
58
- for (const name in schemaFields) {
59
- const field = schemaFields[name]
60
-
61
- const options: Options = {}
62
- if (field.type === 'array') {
63
- options.min = getArrayOption(field, 'min')
64
- options.max = getArrayOption(field, 'max')
65
- }
66
-
67
- fields.push({
68
- name,
69
- label: field.spec.label || name,
70
- type: getType(field),
71
- ...options,
72
- })
73
- }
74
- return fields
75
- }
76
-
77
- export const getNestedSchema = (schema: SchemaOf<AnySchema>, path: string) => {
78
- return reach(schema, path)
79
- }