Skip to content

Commit

Permalink
feat(packages/components): export TotalLayout + add logic for the tot…
Browse files Browse the repository at this point in the history
…al layout to know when the form is dirty
  • Loading branch information
paola-p committed Dec 12, 2023
1 parent cb578b7 commit d1d1b88
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 96 deletions.
47 changes: 9 additions & 38 deletions packages/components/src/layout/TotalLayout/TotalLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,20 @@ import Footer from './TotalLayoutFooter';
import Body from './TotalLayoutBody/TotalLayoutBody';
import { TotalLayoutStyles } from './TotalLayout.styles';
import { TOTAL_LAYOUT_DEFAULT_PROPS, TOTAL_LAYOUT_PROP_TYPES } from './TotalLayout.constants';
import { Modal } from '../../overlay';
import { Button } from '../../form';
import { Paragraph } from '../../typography';

const useTotalLayout = () => {
const [activeStep, setActiveStep] = React.useState(0);
const [completedSteps, setCompletedSteps] = React.useState([]);
const [stepsInfo, setStepsInfo] = React.useState([]);
const [openCancelModal, setOpenCancelModal] = React.useState(false);
const formIsDirty = React.useRef(false);
return {
activeStep,
setActiveStep,
completedSteps,
setCompletedSteps,
stepsInfo,
setStepsInfo,
openCancelModal,
setOpenCancelModal,
formIsDirty,
};
};

Expand All @@ -41,9 +37,8 @@ const TotalLayout = ({
footerFinalActions,
stepsInfo,
setStepsInfo,
openCancelModal,
setOpenCancelModal,
onCancel,
formIsDirty,
isLoading,
}) => {
const form = useFormContext();
const [topScroll, setTopScroll] = React.useState(false);
Expand Down Expand Up @@ -124,11 +119,15 @@ const TotalLayout = ({
};

const handleNext = async () => {
formIsDirty.current =
formIsDirty.current || Object.keys(form.formState.touchedFields).length > 0;
setCompletedSteps((prevCompletedSteps) => [...prevCompletedSteps, activeStep]);
setActiveStep((prevActiveStep) => getNextValidStep(prevActiveStep + 1));
window.scrollTo(0, 0, { behavior: 'smooth' });
};
const handlePrev = async () => {
formIsDirty.current =
formIsDirty.current || Object.keys(form.formState.touchedFields).length > 0;
const isValidStep = await form.trigger();
if (!isValidStep && completedSteps.includes(activeStep)) {
setCompletedSteps((prevCompletedSteps) =>
Expand Down Expand Up @@ -156,26 +155,6 @@ const TotalLayout = ({

return (
<Box id="TotalLayout" style={{ height: '100vh' }}>
{/* Cancel Modal */}
<Modal title="Cancelando" opened={openCancelModal} onClose={() => setOpenCancelModal(false)}>
<Box>
<Paragraph>{'Seguro que desea salir? Todos sus cambios serán descartados.'}</Paragraph>
</Box>
<Stack fullWidth justifyContent="space-between" style={{ marginTop: 16 }}>
<Button variant="light" onClick={() => setOpenCancelModal(false)}>
Cancel
</Button>
<Button
onClick={() => {
onCancel();
setOpenCancelModal(false);
}}
>
Confirm
</Button>
</Stack>
</Modal>

<Stack fullWidth fullHeight direction="column">
<Box className={classes.header} noFlex>
<Header style={{ position: 'fixed', top: 0, height: '72px' }} />
Expand Down Expand Up @@ -204,6 +183,7 @@ const TotalLayout = ({
minStepNumberForDraftSave={minStepNumberForDraftSave}
onSave={() => validateAndAct(onSave)}
isLastStep={lastValidStep === activeStep}
isLoading={isLoading}
/>
</Box>
</Stack>
Expand All @@ -217,16 +197,7 @@ TotalLayout.propTypes = TOTAL_LAYOUT_PROP_TYPES;
export { TotalLayout, useTotalLayout };

/*
DONE:
- arreglar footer en pequeñito
- save draft es dinámico. Pasar el stepNumberForDraftSave
- Pasar labels dinámicamente -> para los botones de footer
- Vertical Stepper! Crear un TotalLayoutStepper a partir del VerticalStepper
TODO:
- Steps se muestran dinámicamente en el stepper
- onCancel para el header y Modal. Genérica, reutilizable.
- Toast de confirmación al guardar borrador.
- aplicar efecto a las sombras del header
- Tidy: una carpeta para cada componente con sus constantes y estilos, etc.
- Al final: P r o p s d e t o d o
*/
85 changes: 37 additions & 48 deletions packages/components/src/layout/TotalLayout/TotalLayout.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ import mdx from './TotalLayout.mdx';
import BasicDataForm from './mock/BasicDataForm';
import ContentForm from './mock/ContentForm';
import OptionalForm from './mock/OptionalForm';
import { Modal } from '../../overlay';
import { Paragraph } from '../../typography';
import { Stack } from '../Stack';
import { Button } from '../../form';

export default {
title: 'Molecules/Layout/TotalLayout',
Expand All @@ -37,8 +33,6 @@ export default {
// The page must take care of wrapping the TotalLayout within a FormProvider
const Template = () => {
const totalLayoutProps = useTotalLayout();
const [openModal, setOpenModal] = React.useState(false);
const [modalContent, setModalContent] = React.useState({ title: '', body: '', action: null });

const initialStepsInfo = [
{
Expand All @@ -53,6 +47,13 @@ const Template = () => {
tags: z.string().optional(),
color: z.string().optional(),
}),
initialValues: {
title: '',
description: '',
program: '',
tags: '',
color: '',
},
// Steps can use the any form methods with the useFormContext hook (from react-hook-form)
stepComponent: <BasicDataForm key={'basicDataForm'} />,
},
Expand All @@ -65,6 +66,10 @@ const Template = () => {
instructions: z.string({ required_error: 'Instructions are required' }).min(1),
deliverables: z.boolean().optional(),
}),
initialValues: {
instructions: '',
deliverables: false,
},
stepComponent: <ContentForm key={'contentForm'} />,
},
{
Expand All @@ -88,6 +93,7 @@ const Template = () => {

// Prepare Form
const formMethods = useForm({
defaultValues: initialStepsInfo[totalLayoutProps.activeStep]?.initialValues,
resolver: zodResolver(initialStepsInfo[totalLayoutProps.activeStep]?.validationSchema),
});
const formValues = formMethods.watch();
Expand All @@ -102,39 +108,25 @@ const Template = () => {
}
}, [formValues.addOptionalStep]);

// Prepare dynamic modal. Total Layout only handles the "global" cancel modal
const renderActionModal = ({ title, body, action }) => (
<Modal title={title} opened={openModal} onClose={() => setOpenModal(false)}>
<Box>
<Paragraph>{body}</Paragraph>
</Box>
<Stack fullWidth justifyContent="space-between" style={{ marginTop: 16 }}>
<Button variant="light" onClick={() => setOpenModal(false)}>
Cancel
</Button>
<Button
onClick={() => {
if (action) action();
setOpenModal(false);
}}
>
Confirm
</Button>
</Stack>
</Modal>
);

// Prepare Header. It is necesary to pass the setOpenCancelModal function to the header
const onCancel = () => {
console.log('Redirecting after cancel');
};

const handleOnCancel = () => {
if (totalLayoutProps.formIsDirty) {
// Usar openConfirmationModal del plugin Layout(leemons)
return;
}
onCancel();
};

const buildHeader = () => (
<TotalLayoutHeader
title={'Nueva Tarea'}
icon={<PluginAssignmentsIcon />}
formTitlePlaceholder={'Título de la tarea'}
setOpenCancelModal={totalLayoutProps.setOpenCancelModal}
onCancel={handleOnCancel}
/>
);

Expand Down Expand Up @@ -186,25 +178,22 @@ const Template = () => {
// const footerFinalAction = [{ label: 'Finalize with single action', action: handleFinalize }];

return (
<>
{/* Modal - Implemented at the page level */}
{renderActionModal(modalContent)}
<FormProvider {...formMethods}>
<Box style={{ margin: '-16px' }}>
<TotalLayout
{...totalLayoutProps}
Header={() => buildHeader()}
showStepper
footerActionsLabels={footerActionsLabels}
footerFinalActions={footerFinalActions}
minStepNumberForDraftSave={1}
onSave={handleOnSave}
initialStepsInfo={initialStepsInfo}
onCancel={onCancel}
/>
</Box>
</FormProvider>
</>
<FormProvider {...formMethods}>
<Box style={{ margin: '-16px' }}>
<TotalLayout
{...totalLayoutProps}
Header={() => buildHeader()}
showStepper
footerActionsLabels={footerActionsLabels}
footerFinalActions={footerFinalActions}
minStepNumberForDraftSave={1}
onSave={handleOnSave}
initialStepsInfo={initialStepsInfo}
onCancel={onCancel}
isLoading={false}
/>
</Box>
</FormProvider>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const TotalLayoutFooter = ({
minStepNumberForDraftSave,
onSave,
isLastStep,
isLoading,
}) => {
const { classes } = TotalLayoutFooterStyles(
{ showFooterShadow, leftOffset },
Expand All @@ -30,7 +31,7 @@ const TotalLayoutFooter = ({
// It's the final step and there's more than one final action
if (finalActions?.length > 1) {
return (
<DropdownButton data={finalActions}>
<DropdownButton data={finalActions} loading={isLoading} disabled={isLoading}>
{footerActionsLabels.dropdownLabel || 'Finalizar'}
</DropdownButton>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import PropTypes from 'prop-types';
import { PluginAssignmentsIcon as Icon } from '@bubbles-ui/icons/solid';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { useFormContext } from 'react-hook-form';
import { Stack } from '../../Stack';
import { Text } from '../../../typography';
import { Button } from '../../../form';
import { Button } from '../../../form/Button';
import { Box } from '../../Box';
import {
TOTAL_LAYOUT_HEADER_PROP_TYPES,
TOTAL_LAYOUT_HEADER_DEFAULT_PROPS,
} from './TotalLayoutHeader.constants';
import { TotalLayoutHeaderStyles } from './TotalLayoutHeader.styles';
import CrossIcon from './crossIcon';
import { useTotalLayout } from '../TotalLayout';

const TotalLayoutHeader = ({
title,
icon,
formTitlePlaceholder,
children,
setOpenCancelModal,
onCancel,
compact = false,
}) => {
const { watch } = useFormContext();
Expand Down Expand Up @@ -54,12 +53,7 @@ const TotalLayoutHeader = ({
</Stack>
{/* CANCEL BUTTON */}
<Stack alingItems="center">
<Button
variant="link"
type="button"
leftIcon={<CrossIcon />}
onClick={() => setOpenCancelModal(true)}
>
<Button variant="link" type="button" leftIcon={<CrossIcon />} onClick={onCancel}>
Cancelar
</Button>
</Stack>
Expand Down

0 comments on commit d1d1b88

Please sign in to comment.