mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
parent
0ae13f091b
commit
fa9bf16092
6 changed files with 41 additions and 40 deletions
|
@ -54,6 +54,7 @@
|
|||
|
||||
input {
|
||||
padding-right: calc(24px + _.unit(4));
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -27,3 +27,7 @@ input::-webkit-inner-spin-button {
|
|||
input[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: solid 1px var(--color-primary);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue