Modal
Base
Casos de Uso
Transacional
Utilizada para obtenção de informações adicionais em um determinado fluxo ou como complemento a uma ação ou tarefa.
Onboarding
No caso de uso de Onboarding com Modal
, você deve usar o subcomponente Modal.Steps
, que renderiza o componente passado na propriedade render
(MySteps
, no exemplo). Na propriedade startsAt
poderá ser informado o índice do primeiro step
a ser renderizado (que, por padrão, é 0).
A propriedade render
disponibiliza as seguintes propriedades para o componente a ser renderizado:
current
: Indica o índice do passo atual.next
: Função que dispara a renderização do próximo passo.prev
: Função que dispara a renderização do passo anterior....rest
: Qualquer propriedade passada para o subcomponenteModal.Steps
(com exceção dorender
estartsAt
) será reencaminhada para o componente a ser renderizado (no exemplo,closeModal
). Isso é útil para que informações possam transitar do componente pai para renderização dostep
.
Para adicionar uma ilustração ao modal de Onboarding, use o subcomponente Modal.Banner
.
No caso de uso de Onboarding, o Modal
é fechado apenas por ação interna, ou seja, com um clique no botão de fechar ou de finalizar o fluxo.
Veja abaixo um exemplo completo desse caso de uso, usando todos os recursos citados:
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;
Exemplo:
Nos nossos exemplos, a ilustração do Modal.Banner
é um SVG como componente React. Você pode transformar um arquivo SVG em componente por meio da ferramenta React SVGR Playground.
Salvando o último step visualizado
Você pode usar o LocalStorage para salvar e consultar o índice do último step visualizado. Isso é útil nos casos em que seja necessário retomar o fluxo de onde o usuário parou, caso ele tenha interrompido o Onboarding antes de finalizá-lo. Veja o exemplo abaixo.
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;
Exemplo:
Com Form
É possível criar formulários dentro de um Modal
, utilizando o componente Form
.
Opções de fechar o Modal
Lembre-se de sempre deixar uma opção de fechar ativa, para não travar o fluxo de trabalho da pessoa.
Clicando em uma ação interna
Desabilitando opções de fechar
Por padrão, o Modal
pode ser fechado quando a pessoa pressiona a tecla "Esc" ou clica fora da área do Modal
.
Se for necessário realizar o bloqueio do fechamento do Modal
com a tecla "Esc" use a propriedade disableEscapeKeyDown
.
Se for necessário realizar o bloqueio da ação de clicar fora para fechar o Modal
use a propriedade disableOutsideClick
.
Testando
O componente Modal
utiliza a funcionalidade Portal do React para ser renderizado em um nó do DOM fora da hierarquia do componente pai. Por conta disso, ele acessa o contexto do Theme
para manter-se encapsulado por ele.
Dessa forma, quando for testar algum componente que renderiza o Modal
é necessário definir o Theme
como componente pai para evitar que o erro The 'useTheme' hook needs Theme to get the context
seja disparado. Veja abaixo:
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(...)
});
});
Caso esteja utilizando a função render
do @testing-library/react
, você pode usar a opção wrapper
. O resultado final será o mesmo.
render(<MyComponentUsingModal />, { wrapper: Theme });