Modal
Baseβ
Use casesβ
Transactionalβ
Used to get additional information from a given flow or as addition to an action or a task.
Onboardingβ
In the use case Modal
with Onboarding, you should use the subcomponent Modal.Steps
, which renders the component that was passed in the render
property (MySteps
, in the example). In the startsAt
property, you can inform the index of the first step
that will be rendered (0, by default).
The render
property provides the following properties to the rendered component:
current
: Indicates the index of the current step.next
: Function that triggers the rendering of the next step.prev
: Function that triggers the rendering of the previous step....rest
: Any property passed to the subcomponentModal.Steps
(except forrender
andstartsAt
) will be sent to the component to be rendered (in the example,closeModal
). This is useful for the information to go from the father component to thestep
render.
To add an illustration to the Onboarding Modal, use the subcomponent Modal.Banner
.
In the Onboarding use case, the Modal
is only closed by internal actions, in other words, with a click on the close button or on the button that finishes the flow.
See below a complete example of this use case, using all mentioned resources:
import React, { useState, useRef } from 'react';
import {
Modal,
Button,
ButtonGroup
} from '@resultadosdigitais/tangram-components';
import Illustration from './Illustration';
const FirstStep = ({ closeModal, next }) => {
return (
<>
<Modal.Banner caption="Text describing the elements of the illustration for accessibility">
<Illustration step="1" />
</Modal.Banner>
<Modal.Header id="modalTitle">Step 1</Modal.Header>
<Modal.Content id="modalDescription">Content from step 1</Modal.Content>
<Modal.Actions>
<ButtonGroup>
<Button kind={Button.kinds.tertiary} onClick={closeModal}>
Skip
</Button>
<Button onClick={next}>Next</Button>
</ButtonGroup>
</Modal.Actions>
</>
);
};
const SecondStep = ({ closeModal, prev, next }) => {
return (
<>
<Modal.Banner caption="Text describing the elements of the illustration for accessibility">
<Illustration step="2" />
</Modal.Banner>
<Modal.Header id="modalTitle">Step 2</Modal.Header>
<Modal.Content id="modalDescription">Content from step 2</Modal.Content>
<Modal.Actions>
<ButtonGroup>
<Button kind={Button.kinds.tertiary} onClick={closeModal}>
Skip
</Button>
<Button kind={Button.kinds.secondary} onClick={prev}>
Previous
</Button>
<Button onClick={next}>Next</Button>
</ButtonGroup>
</Modal.Actions>
</>
);
};
const ThirdStep = ({ closeModal, prev }) => {
return (
<>
<Modal.Banner caption="Text describing the elements of the illustration for accessibility">
<Illustration step="3" />
</Modal.Banner>
<Modal.Header id="modalTitle">Step 3</Modal.Header>
<Modal.Content id="modalDescription">Content from step 3</Modal.Content>
<Modal.Actions>
<ButtonGroup>
<Button kind={Button.kinds.secondary} onClick={prev}>
Previous
</Button>
<Button onClick={closeModal}>Finish</Button>
</ButtonGroup>
</Modal.Actions>
</>
);
};
const MySteps = ({ current, ...rest }) => {
const STEPS = [FirstStep, SecondStep, ThirdStep];
const Step = STEPS[current];
return <Step {...rest} />;
};
const ExampleOnboarding = () => {
const [open, setOpen] = useState(false);
const triggerRef = useRef();
const closeModal = () => {
setOpen(false);
triggerRef.current && triggerRef.current.focus();
};
const openModal = () => {
setOpen(true);
};
return (
<>
<Button
kind={Button.kinds.secondary}
onClick={openModal}
ref={triggerRef}
>
Open Modal
</Button>
<Modal
open={open}
aria-describedby="modalDescription"
aria-labelledby="modalTitle"
>
<Modal.Steps total={3} render={MySteps} closeModal={closeModal} />
</Modal>
</>
);
};
export default ExampleOnboarding;
Example:
In our examples, the Modal.Banner
illustration is an SVG as a React component. You can turn a SVG file into a component using the React SVGR Playground tool.
Saving the last step seenβ
You can use the LocalStorage to save and check the last seen step's index. This is useful for when it's necessary to resume the flow from where the user stopped, in case he has broken the Onboard before finishing. See the example below.
import React, { useState, useRef, useEffect } from 'react';
import {
Modal,
Button,
ButtonGroup
} from '@resultadosdigitais/tangram-components';
import Illustration from './Illustration';
const CURRENT_STEP_KEY = 'modal-onboarding-current-step';
const FirstStep = ({ closeModal, next }) => {
return (
<>
<Modal.Banner caption="Text describing the elements of the illustration for accessibility">
<Illustration step="1" />
</Modal.Banner>
<Modal.Header id="modalTitle">Step 1</Modal.Header>
<Modal.Content id="modalDescription">Content from step 1</Modal.Content>
<Modal.Actions>
<ButtonGroup>
<Button kind={Button.kinds.tertiary} onClick={closeModal}>
Skip
</Button>
<Button onClick={next}>Next</Button>
</ButtonGroup>
</Modal.Actions>
</>
);
};
const SecondStep = ({ closeModal, prev, next }) => {
return (
<>
<Modal.Banner caption="Text describing the elements of the illustration for accessibility">
<Illustration step="2" />
</Modal.Banner>
<Modal.Header id="modalTitle">Step 2</Modal.Header>
<Modal.Content id="modalDescription">Content from step 2</Modal.Content>
<Modal.Actions>
<ButtonGroup>
<Button kind={Button.kinds.tertiary} onClick={closeModal}>
Skip
</Button>
<Button kind={Button.kinds.secondary} onClick={prev}>
Previous
</Button>
<Button onClick={next}>Next</Button>
</ButtonGroup>
</Modal.Actions>
</>
);
};
const ThirdStep = ({ closeModal, prev }) => {
return (
<>
<Modal.Banner caption="Text describing the elements of the illustration for accessibility">
<Illustration step="3" />
</Modal.Banner>
<Modal.Header id="modalTitle">Step 3</Modal.Header>
<Modal.Content id="modalDescription">Content from step 3</Modal.Content>
<Modal.Actions>
<ButtonGroup>
<Button kind={Button.kinds.secondary} onClick={prev}>
Previous
</Button>
<Button onClick={closeModal}>Finish</Button>
</ButtonGroup>
</Modal.Actions>
</>
);
};
const MySteps = ({ current, ...rest }) => {
const STEPS = [FirstStep, SecondStep, ThirdStep];
const Step = STEPS[current];
useEffect(() => {
localStorage.setItem(CURRENT_STEP_KEY, current);
}, [current]);
return <Step {...rest} />;
};
const ModalOnboardingLocalStorageExample = () => {
const [open, setOpen] = useState(false);
const triggerRef = useRef();
const closeModal = () => {
setOpen(false);
triggerRef.current && triggerRef.current.focus();
};
const openModal = () => {
setOpen(true);
};
const getCurrentStep = () => {
const currentStep = localStorage.getItem(CURRENT_STEP_KEY) || 0;
return parseInt(currentStep);
};
return (
<>
<Button
kind={Button.kinds.secondary}
onClick={openModal}
ref={triggerRef}
>
Open Modal
</Button>
<Modal
open={open}
aria-describedby="modalDescription"
aria-labelledby="modalTitle"
>
<Modal.Steps
total={3}
startsAt={getCurrentStep()}
render={MySteps}
closeModal={closeModal}
/>
</Modal>
</>
);
};
export default ModalOnboardingLocalStorageExample;
Example:
With Form
β
It is possible to create forms inside a Modal
, by using the component Form
.
Modal
close optionsβ
Remember to always leave a close option active, so as not to block the person's workflow.
Clicking in internal actionβ
Disabling close optionsβ
By default, Modal
can be closed when the person presses the "Esc" key or clicks outside the Modal
area.
If you need to block the closing of Modal
with the "Esc" key, use the disableEscapeKeyDown
property.
If you need to block the action of clicking outside to close Modal
use the disableOutsideClick
property.
Testingβ
The Modal
component uses the React Portals to be rendered in a node that exists outside the DOM hierarchy of the parent component. Because of this, the Theme
context is accessed to be encapsulate for him.
This way, when testing any component that renders the Modal
, is necessary to encapsulate it with the Theme
. It avoids the error The 'useTheme' hook needs Theme to get the context
to be thrown. See below:
import MyComponentUsingModal from './MyComponentUsingModal'
import { render } from '@testing-library/react'
import { Theme } from '@resultadosdigitais/tangram-components'
describe('<MyComponentUsingModal />', () => {
it('renders the component', () => {
render(
<Theme>
<MyComponentUsingModal />
</Theme>
);
expect(...)
});
});
If using the render
function from @testing-library/react
, you can use the wrapper
option. The final result will be the same.
render(<MyComponentUsingModal />, { wrapper: Theme });