@applica-software-guru/react-admin 1.5.282 → 1.5.284
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/dist/components/ra-forms/LongForm/Tab.d.ts.map +1 -1
- package/dist/components/ra-forms/WizardForm/Content.d.ts +15 -0
- package/dist/components/ra-forms/WizardForm/Content.d.ts.map +1 -0
- package/dist/components/ra-forms/WizardForm/Form.d.ts +38 -0
- package/dist/components/ra-forms/WizardForm/Form.d.ts.map +1 -0
- package/dist/components/ra-forms/WizardForm/Provider.d.ts +48 -0
- package/dist/components/ra-forms/WizardForm/Provider.d.ts.map +1 -0
- package/dist/components/ra-forms/WizardForm/Stepper.d.ts +18 -0
- package/dist/components/ra-forms/WizardForm/Stepper.d.ts.map +1 -0
- package/dist/components/ra-forms/WizardForm/Toolbar.d.ts +20 -0
- package/dist/components/ra-forms/WizardForm/Toolbar.d.ts.map +1 -0
- package/dist/components/ra-forms/WizardForm/index.d.ts +4 -0
- package/dist/components/ra-forms/WizardForm/index.d.ts.map +1 -0
- package/dist/components/ra-forms/index.d.ts +1 -0
- package/dist/components/ra-forms/index.d.ts.map +1 -1
- package/dist/react-admin.cjs.js +54 -54
- package/dist/react-admin.cjs.js.gz +0 -0
- package/dist/react-admin.cjs.js.map +1 -1
- package/dist/react-admin.es.js +8609 -8403
- package/dist/react-admin.es.js.gz +0 -0
- package/dist/react-admin.es.js.map +1 -1
- package/dist/react-admin.umd.js +54 -54
- package/dist/react-admin.umd.js.gz +0 -0
- package/dist/react-admin.umd.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/react.d.ts +14 -0
- package/dist/utils/react.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/ra-forms/LongForm/Tab.tsx +2 -11
- package/src/components/ra-forms/WizardForm/Content.tsx +94 -0
- package/src/components/ra-forms/WizardForm/Form.tsx +107 -0
- package/src/components/ra-forms/WizardForm/Provider.tsx +62 -0
- package/src/components/ra-forms/WizardForm/Stepper.tsx +113 -0
- package/src/components/ra-forms/WizardForm/Toolbar.tsx +58 -0
- package/src/components/ra-forms/WizardForm/index.ts +4 -0
- package/src/components/ra-forms/index.ts +1 -0
- package/src/playground/App.jsx +2 -1
- package/src/playground/components/ra-forms/TestWizardForm/AdvancedUsage.jsx +176 -0
- package/src/playground/components/ra-forms/TestWizardForm/BaseUsage.jsx +115 -0
- package/src/playground/components/ra-forms/TestWizardForm/TestWizardForm.jsx +27 -0
- package/src/playground/components/ra-forms/TestWizardForm/index.jsx +1 -0
- package/src/playground/components/ra-forms/index.jsx +1 -0
- package/src/playground/menu.jsx +8 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/react.ts +25 -0
package/dist/utils/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* This file contains utility functions that can be reused across the project.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Originally made for LongForm component to find sources.
|
|
7
|
+
* Walks the children of a React element and calls a callback function for each child.
|
|
8
|
+
*
|
|
9
|
+
* @param {ReactNode} children - The children of a React element.
|
|
10
|
+
* @param {Function} callback - The callback function to call for each child.
|
|
11
|
+
*/
|
|
12
|
+
declare function walkChildren(children: ReactNode, callback: (el: ReactElement) => void): void;
|
|
13
|
+
export { walkChildren };
|
|
14
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../../src/utils/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAkB,MAAM,OAAO,CAAC;AAGhE;;GAEG;AAEH;;;;;;GAMG;AACH,iBAAS,YAAY,CAAC,QAAQ,WAAgB,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,QAQnF;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -3,25 +3,16 @@ import { useIsActive } from '@/components/ra-forms/LongForm/hooks';
|
|
|
3
3
|
import { IItem } from '@/components/ra-forms/LongForm/types';
|
|
4
4
|
import { getId } from '@/components/ra-forms/LongForm/utils';
|
|
5
5
|
import { Optional } from '@/types';
|
|
6
|
+
import { walkChildren } from '@/utils';
|
|
6
7
|
import { Box } from '@mui/material';
|
|
7
8
|
import _ from 'lodash';
|
|
8
|
-
import React, { Children,
|
|
9
|
+
import React, { Children, cloneElement, isValidElement, useEffect, useMemo } from 'react';
|
|
9
10
|
import { useFormState } from 'react-hook-form';
|
|
10
11
|
|
|
11
12
|
type IBaseItemProps = React.PropsWithChildren<Optional<IItem, 'id' | 'index'> & { sources: Array<string> }>;
|
|
12
13
|
type ITabProps = IBaseItemProps;
|
|
13
14
|
type IGroupProps = IBaseItemProps;
|
|
14
15
|
|
|
15
|
-
function walkChildren(children: ReactNode = [], callback: (el: ReactElement) => void) {
|
|
16
|
-
const _children = _.isArray(children) ? children : [children];
|
|
17
|
-
const validChildren = _.filter(_children, (child) => isValidElement(child));
|
|
18
|
-
_.each(validChildren, (child) => {
|
|
19
|
-
callback(child);
|
|
20
|
-
// @ts-ignore @ts-expect-error Property 'children' does not exist on type '{}'.
|
|
21
|
-
walkChildren(child?.props?.children ?? [], callback);
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
16
|
function BaseItem(props: IBaseItemProps) {
|
|
26
17
|
const { errors } = useFormState();
|
|
27
18
|
const countErrors = useErrorCount();
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Children, ReactElement, ReactNode, isValidElement, useMemo } from 'react';
|
|
2
|
+
import { Box, Button, Divider, Grid, useTheme } from '@mui/material';
|
|
3
|
+
import { MainCard } from '@/components/MainCard';
|
|
4
|
+
import { Toolbar } from './Toolbar';
|
|
5
|
+
import { useTranslate } from 'react-admin';
|
|
6
|
+
import _ from 'lodash';
|
|
7
|
+
import { useWizardFormContext } from './Provider';
|
|
8
|
+
import { Stepper } from './Stepper';
|
|
9
|
+
import { walkChildren } from '@/utils';
|
|
10
|
+
|
|
11
|
+
interface ContentProps {
|
|
12
|
+
title?: ReactNode | string;
|
|
13
|
+
subheader?: ReactNode | string;
|
|
14
|
+
secondary?: ReactNode | string;
|
|
15
|
+
toolbar?: ReactElement;
|
|
16
|
+
progress?: ReactNode;
|
|
17
|
+
isSmall: boolean;
|
|
18
|
+
modal: boolean;
|
|
19
|
+
sx?: any;
|
|
20
|
+
setCurrentStep: (step: number) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function Content({ title, subheader, secondary, toolbar, progress, isSmall, modal, sx, setCurrentStep }: ContentProps) {
|
|
24
|
+
const { currentStep, steps } = useWizardFormContext();
|
|
25
|
+
const translate = useTranslate();
|
|
26
|
+
const theme = useTheme();
|
|
27
|
+
const isHorizontal = isSmall || modal;
|
|
28
|
+
|
|
29
|
+
const cancelButton = useMemo(() => {
|
|
30
|
+
if (!toolbar) return null;
|
|
31
|
+
|
|
32
|
+
return Children.toArray(toolbar.props.children).find(
|
|
33
|
+
(child) =>
|
|
34
|
+
isValidElement(child) && child.type === Button && child.props?.children === translate('ra.action.cancel')
|
|
35
|
+
);
|
|
36
|
+
}, [toolbar, translate]);
|
|
37
|
+
|
|
38
|
+
const stepFields: Array<Array<string>> = steps.map((step: ReactElement) => {
|
|
39
|
+
const fields: Array<string> = [];
|
|
40
|
+
const { sources } = step.props;
|
|
41
|
+
if (sources !== undefined) {
|
|
42
|
+
fields.push(...sources);
|
|
43
|
+
} else {
|
|
44
|
+
walkChildren(step, (el) => {
|
|
45
|
+
if (el?.props?.source !== undefined) {
|
|
46
|
+
fields.push(el.props.source);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return fields;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Grid container spacing={2} sx={sx}>
|
|
55
|
+
<Grid item xl={modal ? 12 : 2} lg={modal ? 12 : 3} md={modal ? 12 : 3} sm={modal ? 12 : 4} xs={12}>
|
|
56
|
+
{progress || <Stepper isHorizontal={isHorizontal} setCurrentStep={setCurrentStep} stepFields={stepFields} />}
|
|
57
|
+
</Grid>
|
|
58
|
+
{modal ? <Divider sx={{ width: '100%' }} /> : null}
|
|
59
|
+
<Grid
|
|
60
|
+
item
|
|
61
|
+
xl={modal ? 12 : 10}
|
|
62
|
+
lg={modal ? 12 : 9}
|
|
63
|
+
md={modal ? 12 : 9}
|
|
64
|
+
sm={modal ? 12 : 8}
|
|
65
|
+
xs={12}
|
|
66
|
+
sx={{ paddingTop: modal ? '0 !important' : null }}
|
|
67
|
+
>
|
|
68
|
+
<MainCard
|
|
69
|
+
title={title || steps[currentStep].props.label}
|
|
70
|
+
subheader={subheader}
|
|
71
|
+
secondary={secondary}
|
|
72
|
+
border={!modal}
|
|
73
|
+
divider
|
|
74
|
+
>
|
|
75
|
+
<Box>{steps[currentStep]}</Box>
|
|
76
|
+
<Box
|
|
77
|
+
sx={{
|
|
78
|
+
'& .MuiToolbar-root': {
|
|
79
|
+
paddingLeft: 0,
|
|
80
|
+
paddingRight: 0,
|
|
81
|
+
paddingBottom: 0,
|
|
82
|
+
paddingTop: theme.spacing(2)
|
|
83
|
+
}
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
{toolbar && !cancelButton ? toolbar : <Toolbar cancelButton={cancelButton} />}
|
|
87
|
+
</Box>
|
|
88
|
+
</MainCard>
|
|
89
|
+
</Grid>
|
|
90
|
+
</Grid>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { Content };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Children, ReactElement, ReactNode, useState } from 'react';
|
|
2
|
+
import { Theme, styled, useMediaQuery } from '@mui/material';
|
|
3
|
+
import { FormProps, Form as RaForm } from 'react-admin';
|
|
4
|
+
import { Provider } from './Provider';
|
|
5
|
+
import { Content } from './Content';
|
|
6
|
+
|
|
7
|
+
type WizardFormProps = FormProps & {
|
|
8
|
+
toolbar?: ReactElement;
|
|
9
|
+
progress?: ReactElement;
|
|
10
|
+
title?: ReactNode | string;
|
|
11
|
+
subheader?: ReactNode | string;
|
|
12
|
+
secondary?: ReactNode | string;
|
|
13
|
+
sx?: any;
|
|
14
|
+
modal?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const StyledForm = styled(RaForm, {
|
|
18
|
+
shouldForwardProp: (prop) => prop !== 'modal'
|
|
19
|
+
})<{ modal: boolean }>(({ theme, modal }) => ({
|
|
20
|
+
[theme.breakpoints.down('sm')]: !modal
|
|
21
|
+
? {
|
|
22
|
+
paddingBottom: `${theme.spacing(2.5)}`
|
|
23
|
+
}
|
|
24
|
+
: {}
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Form component for handling wizard-style forms with multiple steps.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} props - The properties object.
|
|
31
|
+
* @param {React.ReactNode} props.children - The child components representing each step of the wizard.
|
|
32
|
+
* @param {React.ReactNode} props.toolbar - The toolbar component to be displayed.
|
|
33
|
+
* @param {React.ReactNode} props.progress - The progress indicator component.
|
|
34
|
+
* @param {string} props.title - The title of the form.
|
|
35
|
+
* @param {string | null} [props.subheader=null] - The subheader text of the form.
|
|
36
|
+
* @param {React.ReactNode | null} [props.secondary=null] - The secondary content of the form.
|
|
37
|
+
* @param {object} props.sx - The style object for custom styling.
|
|
38
|
+
* @param {boolean} [props.modal=false] - Flag indicating if the form is displayed in a modal.
|
|
39
|
+
* @param {object} props.rest - Additional properties passed to the form.
|
|
40
|
+
*
|
|
41
|
+
* @returns {JSX.Element | null} The rendered form component.
|
|
42
|
+
*/
|
|
43
|
+
function Form({
|
|
44
|
+
children,
|
|
45
|
+
toolbar,
|
|
46
|
+
progress,
|
|
47
|
+
title,
|
|
48
|
+
subheader = null,
|
|
49
|
+
secondary = null,
|
|
50
|
+
sx,
|
|
51
|
+
modal = false,
|
|
52
|
+
...props
|
|
53
|
+
}: WizardFormProps): JSX.Element | null {
|
|
54
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
55
|
+
const steps = Children.toArray(children) as Array<ReactElement>;
|
|
56
|
+
const isSmall = useMediaQuery((theme: Theme) => theme.breakpoints.down('sm'));
|
|
57
|
+
|
|
58
|
+
function hasNextStep(): boolean {
|
|
59
|
+
return currentStep < steps.length - 1;
|
|
60
|
+
}
|
|
61
|
+
function hasPreviousStep(): boolean {
|
|
62
|
+
return currentStep > 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function goToNextStep(): void {
|
|
66
|
+
setCurrentStep((prev) => Math.min(prev + 1, steps.length - 1));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function goToPreviousStep(): void {
|
|
70
|
+
setCurrentStep((prev) => Math.max(prev - 1, 0));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const wizardFormContextValue = {
|
|
74
|
+
currentStep,
|
|
75
|
+
steps,
|
|
76
|
+
hasNextStep,
|
|
77
|
+
hasPreviousStep,
|
|
78
|
+
goToNextStep,
|
|
79
|
+
goToPreviousStep
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Provider value={wizardFormContextValue}>
|
|
84
|
+
<StyledForm {...props} modal={modal}>
|
|
85
|
+
<Content
|
|
86
|
+
title={title}
|
|
87
|
+
subheader={subheader}
|
|
88
|
+
secondary={secondary}
|
|
89
|
+
modal={modal}
|
|
90
|
+
sx={sx}
|
|
91
|
+
toolbar={toolbar}
|
|
92
|
+
progress={progress}
|
|
93
|
+
isSmall={isSmall}
|
|
94
|
+
setCurrentStep={setCurrentStep}
|
|
95
|
+
/>
|
|
96
|
+
</StyledForm>
|
|
97
|
+
</Provider>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function Step({ children }: { children: ReactNode; label: string; icon?: ReactNode; sources?: Array<string> }) {
|
|
102
|
+
return <>{children}</>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
Form.Step = Step;
|
|
106
|
+
|
|
107
|
+
export { Form };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ReactElement, ReactNode, createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
interface WizardFormContextValue {
|
|
4
|
+
currentStep: number;
|
|
5
|
+
steps: Array<ReactElement>;
|
|
6
|
+
hasNextStep: () => boolean;
|
|
7
|
+
hasPreviousStep: () => boolean;
|
|
8
|
+
goToNextStep: () => void;
|
|
9
|
+
goToPreviousStep: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const WizardFormContext = createContext<WizardFormContextValue | undefined>(undefined);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Custom hook to access the wizard form context.
|
|
16
|
+
* This hook provides access to the current step, steps, and navigation methods
|
|
17
|
+
* for a wizard form.
|
|
18
|
+
*
|
|
19
|
+
* @throws Will throw an error if used outside of a `WizardFormProvider`.
|
|
20
|
+
*
|
|
21
|
+
* @property {number} currentStep - The index of the current step in the wizard form.
|
|
22
|
+
* @property {Array<ReactElement>} steps - An array of React elements representing the steps of the wizard form.
|
|
23
|
+
*
|
|
24
|
+
* @method hasNextStep
|
|
25
|
+
* @returns {boolean} - Returns true if there is a next step available, otherwise false.
|
|
26
|
+
*
|
|
27
|
+
* @method hasPreviousStep
|
|
28
|
+
* @returns {boolean} - Returns true if there is a previous step available, otherwise false.
|
|
29
|
+
*
|
|
30
|
+
* @method goToNextStep
|
|
31
|
+
* @returns {void} - Advances to the next step in the wizard form.
|
|
32
|
+
*
|
|
33
|
+
* @method goToPreviousStep
|
|
34
|
+
* @returns {void} - Goes back to the previous step in the wizard form.
|
|
35
|
+
*
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const { hasNextStep, goToNextStep } = useWizardFormContext();
|
|
39
|
+
* return (
|
|
40
|
+
* <Button disabled={!hasNextStep} onClick={goToNextStep}>
|
|
41
|
+
* Next
|
|
42
|
+
* </Button>
|
|
43
|
+
* );
|
|
44
|
+
*/
|
|
45
|
+
function useWizardFormContext() {
|
|
46
|
+
const context = useContext(WizardFormContext);
|
|
47
|
+
if (!context) {
|
|
48
|
+
throw new Error('useWizardFormContext must be used within a WizardFormProvider');
|
|
49
|
+
}
|
|
50
|
+
return context;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface WizardFormProviderProps {
|
|
54
|
+
value: WizardFormContextValue;
|
|
55
|
+
children: ReactNode;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function Provider({ value, children }: WizardFormProviderProps) {
|
|
59
|
+
return <WizardFormContext.Provider value={value}>{children}</WizardFormContext.Provider>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { Provider, useWizardFormContext };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { Box, Stepper as MuiStepper, Step, StepIconProps, StepLabel, styled } from '@mui/material';
|
|
3
|
+
import { useWizardFormContext } from './Provider';
|
|
4
|
+
import { useFormState } from 'react-hook-form';
|
|
5
|
+
import _ from 'lodash';
|
|
6
|
+
|
|
7
|
+
interface WizardStepperProps {
|
|
8
|
+
isHorizontal: boolean;
|
|
9
|
+
setCurrentStep: (step: number) => void;
|
|
10
|
+
stepFields: Array<Array<string>>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const StyledStepper = styled(MuiStepper, {
|
|
14
|
+
shouldForwardProp: (prop) => prop !== 'isHorizontal'
|
|
15
|
+
})<{ isHorizontal: boolean }>(({ theme, isHorizontal }) => ({
|
|
16
|
+
paddingBottom: theme.spacing(2),
|
|
17
|
+
display: 'flex',
|
|
18
|
+
justifyContent: isHorizontal ? 'space-between' : 'flex-start',
|
|
19
|
+
'.MuiStep-root': {
|
|
20
|
+
flex: isHorizontal ? 1 : 'none',
|
|
21
|
+
maxWidth: isHorizontal ? 'none' : '100%',
|
|
22
|
+
display: 'flex',
|
|
23
|
+
justifyContent: isHorizontal ? 'center' : 'flex-start'
|
|
24
|
+
},
|
|
25
|
+
'.MuiStepConnector-line.MuiStepConnector-lineVertical': {
|
|
26
|
+
minHeight: 12
|
|
27
|
+
},
|
|
28
|
+
'.MuiStepConnector-vertical': {
|
|
29
|
+
marginLeft: 14.5
|
|
30
|
+
},
|
|
31
|
+
'.MuiStepConnector-horizontal': {
|
|
32
|
+
marginLeft: -10
|
|
33
|
+
}
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const StyledStepIconRoot = styled(Box)<{
|
|
37
|
+
ownerState: { completed?: boolean; active?: boolean; error?: boolean };
|
|
38
|
+
}>(({ theme, ownerState }) => ({
|
|
39
|
+
backgroundColor: ownerState.error
|
|
40
|
+
? theme.palette.error.main
|
|
41
|
+
: ownerState.completed || ownerState.active
|
|
42
|
+
? theme.palette.primary.main
|
|
43
|
+
: theme.palette.mode === 'dark'
|
|
44
|
+
? theme.palette.grey[200]
|
|
45
|
+
: theme.palette.grey[400],
|
|
46
|
+
zIndex: 1,
|
|
47
|
+
color: theme.palette.common.white,
|
|
48
|
+
width: 30,
|
|
49
|
+
height: 30,
|
|
50
|
+
display: 'flex',
|
|
51
|
+
borderRadius: '50%',
|
|
52
|
+
justifyContent: 'center',
|
|
53
|
+
alignItems: 'center'
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
function StyledStepIcon({ completed, active, error, icon, className }: StepIconProps) {
|
|
57
|
+
return (
|
|
58
|
+
<StyledStepIconRoot ownerState={{ completed, active, error }} className={className}>
|
|
59
|
+
{error ? '!' : icon}
|
|
60
|
+
</StyledStepIconRoot>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Stepper component for rendering a step-by-step navigation UI.
|
|
66
|
+
*
|
|
67
|
+
* @param {Object} props - The properties object.
|
|
68
|
+
* @param {boolean} props.isHorizontal - Determines the orientation of the stepper (horizontal or vertical).
|
|
69
|
+
* @param {function} props.setCurrentStep - Function to set the current step index.
|
|
70
|
+
* @param {Array} props.stepFields - Array of fields associated with each step.
|
|
71
|
+
*
|
|
72
|
+
* @returns {JSX.Element} The rendered Stepper component.
|
|
73
|
+
*/
|
|
74
|
+
function Stepper({ isHorizontal, setCurrentStep, stepFields }: WizardStepperProps) {
|
|
75
|
+
const { currentStep, steps } = useWizardFormContext();
|
|
76
|
+
const { errors } = useFormState();
|
|
77
|
+
|
|
78
|
+
function handleClick(index: number) {
|
|
79
|
+
if (index <= currentStep) {
|
|
80
|
+
setCurrentStep(index);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<StyledStepper
|
|
86
|
+
activeStep={currentStep}
|
|
87
|
+
orientation={isHorizontal ? 'horizontal' : 'vertical'}
|
|
88
|
+
isHorizontal={isHorizontal}
|
|
89
|
+
sx={{ p: 3, mt: 1 }}
|
|
90
|
+
>
|
|
91
|
+
{steps.map((step: ReactElement, index: number) => {
|
|
92
|
+
const stepHasError = stepFields[index].some((field) => _.get(errors, field));
|
|
93
|
+
const { label, icon } = step.props;
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<Step key={index}>
|
|
97
|
+
<StepLabel
|
|
98
|
+
StepIconComponent={StyledStepIcon}
|
|
99
|
+
error={stepHasError}
|
|
100
|
+
icon={icon}
|
|
101
|
+
onClick={() => handleClick(index)}
|
|
102
|
+
style={{ cursor: index <= currentStep ? 'pointer' : 'default' }}
|
|
103
|
+
>
|
|
104
|
+
{isHorizontal ? null : label}
|
|
105
|
+
</StepLabel>
|
|
106
|
+
</Step>
|
|
107
|
+
);
|
|
108
|
+
})}
|
|
109
|
+
</StyledStepper>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { Stepper };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ReactNode, cloneElement, useCallback } from 'react';
|
|
2
|
+
import { Box, Button } from '@mui/material';
|
|
3
|
+
import { Toolbar as RaToolbar, ToolbarProps } from '@/components/ra-forms/Toolbar';
|
|
4
|
+
import { SaveButton } from 'react-admin';
|
|
5
|
+
import { useTranslate } from 'ra-core';
|
|
6
|
+
import { useFormContext } from 'react-hook-form';
|
|
7
|
+
import { useWizardFormContext } from './Provider';
|
|
8
|
+
|
|
9
|
+
interface WizardToolbarProps extends ToolbarProps {
|
|
10
|
+
cancelButton?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Toolbar component for the WizardForm.
|
|
15
|
+
*
|
|
16
|
+
* This component renders a toolbar with navigation buttons for a multi-step form.
|
|
17
|
+
* It includes buttons to navigate to the previous and next steps, as well as a cancel button.
|
|
18
|
+
*
|
|
19
|
+
* @param {WizardToolbarProps} props - The properties for the Toolbar component.
|
|
20
|
+
* @param {React.ReactElement} props.cancelButton - An optional cancel button element.
|
|
21
|
+
* @param {object} props.toolbarProps - Additional properties to pass to the RaToolbar component.
|
|
22
|
+
*
|
|
23
|
+
* @returns {JSX.Element} The rendered Toolbar component.
|
|
24
|
+
*/
|
|
25
|
+
function Toolbar({ cancelButton, ...props }: WizardToolbarProps) {
|
|
26
|
+
const { hasNextStep, hasPreviousStep, goToNextStep, goToPreviousStep } = useWizardFormContext();
|
|
27
|
+
const translate = useTranslate();
|
|
28
|
+
const { handleSubmit } = useFormContext();
|
|
29
|
+
const onSubmit = useCallback(() => {
|
|
30
|
+
goToNextStep();
|
|
31
|
+
}, [goToNextStep]);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<RaToolbar {...props}>
|
|
35
|
+
<Box sx={{ '& .MuiButtonBase-root': { ml: 0 } }}>
|
|
36
|
+
{
|
|
37
|
+
//@ts-ignore
|
|
38
|
+
cancelButton ? cloneElement(cancelButton) : null
|
|
39
|
+
}
|
|
40
|
+
</Box>
|
|
41
|
+
<Box sx={{ flex: '1 1 auto' }} />
|
|
42
|
+
{hasPreviousStep() && (
|
|
43
|
+
<Button variant="text" color="primary" onClick={goToPreviousStep}>
|
|
44
|
+
{translate('ra.action.back')}
|
|
45
|
+
</Button>
|
|
46
|
+
)}
|
|
47
|
+
{hasNextStep() ? (
|
|
48
|
+
<Button variant="contained" color="primary" onClick={handleSubmit(onSubmit)}>
|
|
49
|
+
{translate('ra.action.next')}
|
|
50
|
+
</Button>
|
|
51
|
+
) : (
|
|
52
|
+
<SaveButton />
|
|
53
|
+
)}
|
|
54
|
+
</RaToolbar>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { Toolbar };
|
package/src/playground/App.jsx
CHANGED
|
@@ -3,7 +3,7 @@ import { menu } from './menu';
|
|
|
3
3
|
import { theme } from './theme';
|
|
4
4
|
import { ActivatePage, ApplicaAdmin, HttpError, RecoverPage, RegisterPage, Resource } from '@/';
|
|
5
5
|
import build from '@/playground/build.json';
|
|
6
|
-
import { CustomPage } from '@/playground/components';
|
|
6
|
+
import { CustomPage, TestWizardForm } from '@/playground/components';
|
|
7
7
|
import { API_URL, APP_NAME } from '@/playground/config';
|
|
8
8
|
import { ApplicaDataProvider, createAttachmentsParser } from '@applica-software-guru/crud-client';
|
|
9
9
|
import { ApplicaAuthProvider, LocalStorage } from '@applica-software-guru/iam-client';
|
|
@@ -44,6 +44,7 @@ function App() {
|
|
|
44
44
|
>
|
|
45
45
|
<CustomRoutes>
|
|
46
46
|
<Route path="/custom-page" element={<CustomPage />} />
|
|
47
|
+
<Route path="/wizard-form" element={<TestWizardForm />} />
|
|
47
48
|
</CustomRoutes>
|
|
48
49
|
<Resource name="entities/notification" {...entities.notification} />
|
|
49
50
|
<Resource name="entities/user" {...entities.user} />
|