@ttoss/forms 0.22.2 → 0.22.4

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": "@ttoss/forms",
3
- "version": "0.22.2",
3
+ "version": "0.22.4",
4
4
  "author": "ttoss",
5
5
  "contributors": [
6
6
  "Pedro Arantes <pedro@arantespp.com> (https://arantespp.com/contact)"
@@ -10,41 +10,51 @@
10
10
  "url": "https://github.com/ttoss/ttoss.git",
11
11
  "directory": "packages/forms"
12
12
  },
13
- "main": "dist/index.js",
14
- "module": "dist/esm/index.js",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/esm/index.js",
16
+ "require": "./dist/index.js",
17
+ "types": "./dist/index.d.ts"
18
+ },
19
+ "./multistep-form": {
20
+ "import": "./dist/esm/MultistepForm/index.js",
21
+ "require": "./dist/MultistepForm/index.js",
22
+ "types": "./dist/MultistepForm/index.d.ts"
23
+ }
24
+ },
15
25
  "files": [
16
26
  "dist",
17
27
  "src"
18
28
  ],
19
29
  "sideEffects": true,
20
- "typings": "dist/index.d.ts",
21
30
  "dependencies": {
22
31
  "@hookform/error-message": "^2.0.1",
23
32
  "@hookform/resolvers": "^3.3.4",
24
- "react-hook-form": "^7.50.0",
33
+ "react-hook-form": "^7.50.1",
25
34
  "react-number-format": "^5.3.1",
26
35
  "yup": "^1.3.3"
27
36
  },
28
37
  "peerDependencies": {
29
38
  "react": ">=16.8.0",
30
- "@ttoss/react-i18n": "^1.26.0",
31
- "@ttoss/ui": "^4.1.1"
39
+ "@ttoss/ui": "^4.1.2",
40
+ "@ttoss/react-i18n": "^1.26.1"
32
41
  },
33
42
  "devDependencies": {
34
- "@types/jest": "^29.5.11",
35
- "@types/react": "^18.2.48",
43
+ "@types/jest": "^29.5.12",
44
+ "@types/react": "^18.2.58",
36
45
  "jest": "^29.7.0",
37
46
  "react": "^18.2.0",
38
47
  "react-error-boundary": "^4.0.12",
39
- "react-hook-form": "^7.50.0",
40
- "theme-ui": "^0.16.1",
41
- "tsup": "^8.0.1",
48
+ "react-hook-form": "^7.50.1",
49
+ "theme-ui": "^0.16.2",
50
+ "tsup": "^8.0.2",
42
51
  "yup": "^1.3.3",
43
- "@ttoss/config": "^1.31.4",
44
- "@ttoss/react-i18n": "^1.26.0",
45
- "@ttoss/i18n-cli": "^0.7.5",
46
- "@ttoss/ui": "^4.1.1",
47
- "@ttoss/test-utils": "^2.1.0"
52
+ "@ttoss/config": "^1.31.5",
53
+ "@ttoss/react-i18n": "^1.26.1",
54
+ "@ttoss/i18n-cli": "^0.7.6",
55
+ "@ttoss/react-icons": "^0.3.1",
56
+ "@ttoss/test-utils": "^2.1.1",
57
+ "@ttoss/ui": "^4.1.2"
48
58
  },
49
59
  "publishConfig": {
50
60
  "access": "public",
@@ -1,6 +1,6 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  import {
2
3
  FieldError,
3
- FieldErrors,
4
4
  FieldName,
5
5
  FieldValues,
6
6
  useFormContext,
@@ -40,10 +40,13 @@ export const ErrorMessage = <TFieldValues extends FieldValues = FieldValues>({
40
40
  return (
41
41
  <HelpText negative disabled={disabled}>
42
42
  {(() => {
43
- if (typeof message === 'string') return message;
43
+ if (typeof message === 'string') {
44
+ return message;
45
+ }
44
46
 
45
- if (isMessageDescriptor(message))
47
+ if (isMessageDescriptor(message)) {
46
48
  return <FormattedMessage {...message} />;
49
+ }
47
50
 
48
51
  return JSON.stringify(message);
49
52
  })()}
@@ -0,0 +1,14 @@
1
+ import {
2
+ MultistepFlowMessageImageText,
3
+ MultistepFlowMessageImageTextProps,
4
+ } from './MultistepFlowMessageImageText';
5
+
6
+ export type MultistepFlowMessageProps = MultistepFlowMessageImageTextProps;
7
+
8
+ export const MultistepFlowMessage = (props: MultistepFlowMessageProps) => {
9
+ if (props.variant === 'image-text') {
10
+ return <MultistepFlowMessageImageText {...props} />;
11
+ }
12
+
13
+ return null;
14
+ };
@@ -0,0 +1,37 @@
1
+ import * as React from 'react';
2
+ import { Flex, Image, Text } from '@ttoss/ui';
3
+ import { MultistepFlowMessageBase } from './types';
4
+
5
+ export type MultistepFlowMessageImageTextProps = MultistepFlowMessageBase & {
6
+ variant: 'image-text';
7
+ src: string;
8
+ description: string | React.ReactNode;
9
+ };
10
+
11
+ export const MultistepFlowMessageImageText = ({
12
+ src,
13
+ description,
14
+ }: MultistepFlowMessageImageTextProps) => {
15
+ return (
16
+ <Flex
17
+ sx={{
18
+ flexDirection: 'column',
19
+ paddingY: 'xl',
20
+ paddingX: '2xl',
21
+ gap: 'xl',
22
+ }}
23
+ >
24
+ <Image
25
+ src={src}
26
+ sx={{
27
+ width: '184px',
28
+ height: '184px',
29
+ objectFit: 'cover',
30
+ alignSelf: 'center',
31
+ }}
32
+ />
33
+
34
+ <Text sx={{ textAlign: 'center' }}>{description}</Text>
35
+ </Flex>
36
+ );
37
+ };
@@ -0,0 +1,18 @@
1
+ import { Flex, Text } from '@ttoss/ui';
2
+
3
+ export const MultistepFooter = ({ footer }: { footer: string }) => {
4
+ return (
5
+ <Flex sx={{ display: 'flex', justifyContent: 'center' }}>
6
+ <Text
7
+ sx={{
8
+ textAlign: 'center',
9
+ marginTop: '4xl',
10
+ marginBottom: 'lg',
11
+ marginX: '2xl',
12
+ }}
13
+ >
14
+ {footer}
15
+ </Text>
16
+ </Flex>
17
+ );
18
+ };
@@ -0,0 +1,117 @@
1
+ import * as React from 'react';
2
+ import { Flex } from '@ttoss/ui';
3
+ import { MultistepFlowMessageProps } from './MultistepFlowMessage';
4
+ import { MultistepFooter } from './MultistepFooter';
5
+ import {
6
+ MultistepFormStepper,
7
+ type MultistepFormStepperProps,
8
+ } from './MultistepFormStepper';
9
+ import { MultistepHeader, type MultistepHeaderProps } from './MultistepHeader';
10
+ import { MultistepNavigation } from './MultistepNavigation';
11
+
12
+ export type MultistepStep = {
13
+ question: string;
14
+ flowMessage: MultistepFlowMessageProps;
15
+ label: string;
16
+ fields: React.ReactNode | React.ReactNode[];
17
+ schema?: MultistepFormStepperProps['schema'];
18
+ defaultValues?: MultistepFormStepperProps['defaultValues'];
19
+ };
20
+
21
+ export type MultistepFormProps<FormValues = unknown> = {
22
+ header: MultistepHeaderProps;
23
+ steps: MultistepStep[];
24
+ footer?: string;
25
+ onSubmit: (data: FormValues) => void;
26
+ nextStepButtonLabel?: string;
27
+ submitButtonLabel?: string;
28
+ };
29
+
30
+ export const MultistepForm = ({
31
+ nextStepButtonLabel = 'Next',
32
+ submitButtonLabel = 'Send',
33
+ ...props
34
+ }: MultistepFormProps) => {
35
+ const amountOfSteps = props.steps.length;
36
+ const [currentStep, setCurrentStep] = React.useState(1);
37
+
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ const [form, setForm] = React.useState<any>({});
40
+
41
+ const nextStep = () => {
42
+ if (currentStep < amountOfSteps) {
43
+ setCurrentStep((step) => {
44
+ return step + 1;
45
+ });
46
+ }
47
+ };
48
+
49
+ const backStep = () => {
50
+ if (currentStep > 1) {
51
+ setCurrentStep((step) => {
52
+ return step - 1;
53
+ });
54
+ }
55
+ };
56
+
57
+ return (
58
+ <Flex
59
+ sx={{
60
+ flexDirection: 'column',
61
+ maxWidth: '390px',
62
+ background: '#fff',
63
+ }}
64
+ >
65
+ <MultistepHeader {...props.header} />
66
+ {props.steps.map((step, stepIndex) => {
67
+ const isLastStep = stepIndex + 1 === amountOfSteps;
68
+ const isCurrentStep = stepIndex + 1 === currentStep;
69
+
70
+ return (
71
+ <Flex
72
+ sx={{
73
+ flexDirection: 'column',
74
+ display: isCurrentStep ? 'flex' : 'none',
75
+ }}
76
+ key={`form-step-${step.question}`}
77
+ aria-hidden={!isCurrentStep}
78
+ >
79
+ <MultistepFormStepper
80
+ {...step}
81
+ stepNumber={stepIndex + 1}
82
+ isLastStep={isLastStep}
83
+ // isCurrentStep={isCurrentStep}
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ onSubmit={(data: any) => {
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ const newValue = { ...form, ...data };
88
+
89
+ setForm(newValue);
90
+
91
+ if (isLastStep) {
92
+ props.onSubmit(newValue);
93
+ } else {
94
+ nextStep();
95
+ }
96
+ }}
97
+ submitLabel={isLastStep ? submitButtonLabel : nextStepButtonLabel}
98
+ />
99
+ </Flex>
100
+ );
101
+ })}
102
+
103
+ {currentStep > 1 && (
104
+ <MultistepNavigation
105
+ amountOfSteps={amountOfSteps}
106
+ currentStepNumber={currentStep}
107
+ onBack={backStep}
108
+ stepsLabel={props.steps.map((s) => {
109
+ return s.label;
110
+ })}
111
+ />
112
+ )}
113
+
114
+ {props.footer && <MultistepFooter footer={props.footer} />}
115
+ </Flex>
116
+ );
117
+ };
@@ -0,0 +1,70 @@
1
+ import * as React from 'react';
2
+ import { Button } from '@ttoss/ui';
3
+ import { Form, useForm, yup, yupResolver } from '../';
4
+ import {
5
+ MultistepFlowMessage,
6
+ MultistepFlowMessageProps,
7
+ } from './MultistepFlowMessage';
8
+ import { MultistepQuestion } from './MultistepQuestion';
9
+
10
+ export type MultistepFormStepperProps = {
11
+ flowMessage: MultistepFlowMessageProps;
12
+ onSubmit: (data: unknown) => void;
13
+ question: string;
14
+ isLastStep: boolean;
15
+ fields: React.ReactNode | React.ReactNode[];
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ schema?: yup.ObjectSchema<any>;
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ defaultValues?: any;
20
+ submitLabel: string;
21
+ stepNumber: number;
22
+ // isCurrentStep: boolean;
23
+ };
24
+
25
+ export const MultistepFormStepper = ({
26
+ flowMessage,
27
+ fields,
28
+ onSubmit,
29
+ question,
30
+ submitLabel,
31
+ schema,
32
+ isLastStep,
33
+ defaultValues,
34
+ stepNumber,
35
+ // isCurrentStep,
36
+ }: MultistepFormStepperProps) => {
37
+ const formMethods = useForm({
38
+ resolver: schema ? yupResolver(schema) : undefined,
39
+ defaultValues,
40
+ });
41
+
42
+ return (
43
+ <Form
44
+ {...formMethods}
45
+ sx={{
46
+ display: 'flex',
47
+ flexDirection: 'column',
48
+ }}
49
+ onSubmit={onSubmit}
50
+ >
51
+ <MultistepFlowMessage {...flowMessage} />
52
+
53
+ <MultistepQuestion fields={fields} question={question} />
54
+
55
+ <Button
56
+ sx={{
57
+ justifyContent: 'center',
58
+ marginTop: '2xl',
59
+ marginBottom: 'xl',
60
+ marginX: '2xl',
61
+ }}
62
+ rightIcon={isLastStep ? undefined : 'nav-right'}
63
+ aria-label={`btn-step-${stepNumber}`}
64
+ type="submit"
65
+ >
66
+ {submitLabel}
67
+ </Button>
68
+ </Form>
69
+ );
70
+ };
@@ -0,0 +1,78 @@
1
+ import { CloseButton, Flex, Image, Text } from '@ttoss/ui';
2
+ import { Icon, type IconType } from '@ttoss/react-icons';
3
+
4
+ type MultistepHeaderTitledProps = {
5
+ variant: 'titled';
6
+ title: string;
7
+ leftIcon: IconType;
8
+ rightIcon: IconType;
9
+ onLeftIconClick: () => void;
10
+ onRightIconClick: () => void;
11
+ };
12
+
13
+ const MultistepHeaderTitled = ({
14
+ title,
15
+ leftIcon,
16
+ onLeftIconClick,
17
+ rightIcon,
18
+ onRightIconClick,
19
+ }: MultistepHeaderTitledProps) => {
20
+ return (
21
+ <Flex
22
+ sx={{
23
+ display: 'flex',
24
+ justifyContent: 'space-between',
25
+ paddingX: 'xl',
26
+ paddingY: 'lg',
27
+ alignItems: 'center',
28
+ }}
29
+ >
30
+ <Icon icon={leftIcon} onClick={onLeftIconClick} />
31
+ <Text sx={{ fontWeight: 'bold', fontSize: 'lg' }}>{title}</Text>
32
+ <Icon icon={rightIcon} onClick={onRightIconClick} />
33
+ </Flex>
34
+ );
35
+ };
36
+
37
+ type MultistepHeaderLogoProps = {
38
+ variant: 'logo';
39
+ src: string;
40
+ onClose?: () => void;
41
+ };
42
+
43
+ const MultistepHeaderLogo = ({ onClose, src }: MultistepHeaderLogoProps) => {
44
+ return (
45
+ <Flex
46
+ sx={{
47
+ justifyContent: 'space-between',
48
+ alignItems: 'center',
49
+ paddingX: 'xl',
50
+ paddingY: 'lg',
51
+ }}
52
+ >
53
+ <Image
54
+ width={115}
55
+ height={32}
56
+ sx={{ objectFit: 'cover', width: 115, height: 32 }}
57
+ src={src}
58
+ />
59
+ {onClose && <CloseButton onClick={onClose} />}
60
+ </Flex>
61
+ );
62
+ };
63
+
64
+ export type MultistepHeaderProps =
65
+ | MultistepHeaderLogoProps
66
+ | MultistepHeaderTitledProps;
67
+
68
+ export const MultistepHeader = (props: MultistepHeaderProps) => {
69
+ if (props.variant === 'logo') {
70
+ return <MultistepHeaderLogo {...props} />;
71
+ }
72
+
73
+ if (props.variant === 'titled') {
74
+ return <MultistepHeaderTitled {...props} />;
75
+ }
76
+
77
+ return null;
78
+ };
@@ -0,0 +1,38 @@
1
+ import { Flex, Text } from '@ttoss/ui';
2
+ import { Icon } from '@ttoss/react-icons';
3
+
4
+ export type MultistepNavigationProps = {
5
+ amountOfSteps: number;
6
+ currentStepNumber: number;
7
+ onBack: () => void;
8
+ stepsLabel: string[];
9
+ };
10
+
11
+ export const MultistepNavigation = ({
12
+ amountOfSteps,
13
+ currentStepNumber,
14
+ onBack,
15
+ stepsLabel,
16
+ }: MultistepNavigationProps) => {
17
+ return (
18
+ <Flex
19
+ sx={{
20
+ justifyContent: 'space-between',
21
+ marginX: '2xl',
22
+ }}
23
+ >
24
+ <Flex onClick={onBack} sx={{ alignItems: 'center', cursor: 'pointer' }}>
25
+ <Text sx={{ color: '#ACADB7', display: 'flex' }}>
26
+ <Icon icon="nav-left" />
27
+ </Text>
28
+ <Text sx={{ color: '#ACADB7' }}>
29
+ {stepsLabel[currentStepNumber - 2]}
30
+ </Text>
31
+ </Flex>
32
+
33
+ <Text sx={{ alignItems: 'center', color: '#ACADB7' }}>
34
+ {currentStepNumber}/{amountOfSteps}
35
+ </Text>
36
+ </Flex>
37
+ );
38
+ };
@@ -0,0 +1,28 @@
1
+ import * as React from 'react';
2
+ import { Flex, Text } from '@ttoss/ui';
3
+
4
+ type MultistepQuestionProps = {
5
+ question: string;
6
+ fields: React.ReactNode | React.ReactNode[];
7
+ };
8
+
9
+ export const MultistepQuestion = ({
10
+ fields,
11
+ question,
12
+ }: MultistepQuestionProps) => {
13
+ return (
14
+ <Flex
15
+ sx={{
16
+ flexDirection: 'column',
17
+ paddingTop: 'xl',
18
+ paddingX: '2xl',
19
+ }}
20
+ >
21
+ <Text sx={{ textAlign: 'center', fontSize: 'lg', marginBottom: 'xl' }}>
22
+ {question}
23
+ </Text>
24
+
25
+ <Flex sx={{ flexDirection: 'column', gap: 'xl' }}>{fields}</Flex>
26
+ </Flex>
27
+ );
28
+ };
@@ -0,0 +1 @@
1
+ export { MultistepForm, type MultistepFormProps } from './MultistepForm';
@@ -0,0 +1,7 @@
1
+ export type MultistepFlowMessageVariant =
2
+ | 'image-text'
3
+ | 'heading-and-subheading';
4
+
5
+ export type MultistepFlowMessageBase = {
6
+ variant: MultistepFlowMessageVariant;
7
+ };
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ import './i18n';
2
2
 
3
3
  export { yupResolver } from '@hookform/resolvers/yup';
4
4
  export * from 'react-hook-form';
5
+ export { useForm } from 'react-hook-form';
5
6
  export * as yup from 'yup';
6
7
 
7
8
  export { Form } from './Form';