Pular para o conteúdo principal

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 subcomponente Modal.Steps (com exceção do render e startsAt) 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 do step.

Para adicionar uma ilustração ao modal de Onboarding, use o subcomponente Modal.Banner.

Atenção

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:

Dica

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:

É possível criar formulários dentro de um Modal, utilizando o componente Form.

Opções de fechar o Modal

Atenção

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(...)
});
});
Dica

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 });

Feedback