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

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/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
- }