0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-24 22:41:28 -05:00

refactor(ui): refactor toast (#1928)

refactor taost using react modal
This commit is contained in:
simeng-li 2022-09-15 15:52:36 +08:00 committed by GitHub
parent 0ae13f091b
commit fa9bf16092
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 41 additions and 40 deletions

View file

@ -54,6 +54,7 @@
input {
padding-right: calc(24px + _.unit(4));
outline: none;
}
}

View file

@ -21,14 +21,26 @@
background: var(--color-toast);
box-shadow: var(--shadow-2);
text-align: center;
opacity: 0%;
transition: opacity 0.3s ease-in-out;
word-break: break-word;
pointer-events: auto;
}
&[data-visible='true'] {
/* stylelint-disable selector-class-pattern */
:global {
.ReactModal__Content[role='toast'] {
opacity: 0%;
transition: opacity 0.3s ease-in-out;
}
.ReactModal__Content--after-open[role='toast'] {
opacity: 100%;
}
.ReactModal__Content--before-close[role='toast'] {
opacity: 0%;
}
}
/* stylelint-enable selector-class-pattern */
:global(body.desktop) {
.toast {

View file

@ -7,20 +7,20 @@ jest.useFakeTimers();
describe('Toast Component', () => {
it('showToast', () => {
const message = 'mock toast message';
const { queryByText } = render(<Toast isVisible message={message} />);
const { queryByText } = render(<Toast message={message} />);
expect(queryByText(message)).not.toBeNull();
});
it('should not render empty toast', () => {
const message = 'mock toast message';
const { queryByText } = render(<Toast message={message} />);
const { queryByText } = render(<Toast message="" />);
expect(queryByText(message)).toBeNull();
});
it('should run callback method when transition end', () => {
const callback = jest.fn();
const message = 'mock toast message';
const { container } = render(<Toast isVisible message={message} callback={callback} />);
const { container } = render(<Toast message={message} callback={callback} />);
const toast = container.querySelector('[data-visible=true]');
act(() => {

View file

@ -1,59 +1,43 @@
import { useState, useRef, useEffect, useCallback } from 'react';
import { useEffect, useState } from 'react';
import ReactModal from 'react-modal';
import * as styles from './index.module.scss';
type Props = {
message: string;
isVisible?: boolean;
duration?: number;
callback?: () => void;
};
const Toast = ({ message, isVisible = false, duration = 3000, callback }: Props) => {
const toastElement = useRef<HTMLDivElement>(null);
const [show, setShow] = useState(false);
const callbackHandler = useCallback(() => {
// Only execute on hide transitionend event
if (toastElement.current?.dataset.visible === 'true') {
return;
}
callback?.();
}, [callback]);
const Toast = ({ message, duration = 3000, callback }: Props) => {
const [text, setText] = useState('');
useEffect(() => {
if (!isVisible) {
if (!message) {
return;
}
setShow(true);
setText(message);
const timer = setTimeout(() => {
setShow(false);
callback?.();
}, duration);
return () => {
clearTimeout(timer);
setShow(false);
};
}, [callback, duration, isVisible]);
if (!isVisible) {
return null;
}
}, [callback, duration, message, text]);
return (
<div className={styles.toastContainer}>
<div
ref={toastElement}
className={styles.toast}
data-visible={show}
onTransitionEnd={callbackHandler}
>
{message}
</div>
</div>
<ReactModal
role="toast"
isOpen={Boolean(message)}
overlayClassName={styles.toastContainer}
className={styles.toast}
closeTimeoutMS={300}
>
{text}
</ReactModal>
);
};

View file

@ -41,7 +41,7 @@ const AppContent = ({ children }: Props) => {
{platform === 'web' && <div className={styles.placeHolder} />}
<main className={styles.content}>{children}</main>
{platform === 'web' && <div className={styles.placeHolder} />}
<Toast message={toast} isVisible={Boolean(toast)} callback={hideToast} />
<Toast message={toast} callback={hideToast} />
</div>
);
};

View file

@ -27,3 +27,7 @@ input::-webkit-inner-spin-button {
input[type='number'] {
-moz-appearance: textfield;
}
:focus-visible {
outline: solid 1px var(--color-primary);
}