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 {
|
input {
|
||||||
padding-right: calc(24px + _.unit(4));
|
padding-right: calc(24px + _.unit(4));
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,14 +21,26 @@
|
||||||
background: var(--color-toast);
|
background: var(--color-toast);
|
||||||
box-shadow: var(--shadow-2);
|
box-shadow: var(--shadow-2);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
opacity: 0%;
|
|
||||||
transition: opacity 0.3s ease-in-out;
|
|
||||||
word-break: break-word;
|
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%;
|
opacity: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ReactModal__Content--before-close[role='toast'] {
|
||||||
|
opacity: 0%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
:global(body.desktop) {
|
:global(body.desktop) {
|
||||||
.toast {
|
.toast {
|
||||||
|
|
|
@ -7,20 +7,20 @@ jest.useFakeTimers();
|
||||||
describe('Toast Component', () => {
|
describe('Toast Component', () => {
|
||||||
it('showToast', () => {
|
it('showToast', () => {
|
||||||
const message = 'mock toast message';
|
const message = 'mock toast message';
|
||||||
const { queryByText } = render(<Toast isVisible message={message} />);
|
const { queryByText } = render(<Toast message={message} />);
|
||||||
expect(queryByText(message)).not.toBeNull();
|
expect(queryByText(message)).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not render empty toast', () => {
|
it('should not render empty toast', () => {
|
||||||
const message = 'mock toast message';
|
const message = 'mock toast message';
|
||||||
const { queryByText } = render(<Toast message={message} />);
|
const { queryByText } = render(<Toast message="" />);
|
||||||
expect(queryByText(message)).toBeNull();
|
expect(queryByText(message)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run callback method when transition end', () => {
|
it('should run callback method when transition end', () => {
|
||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
const message = 'mock toast message';
|
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]');
|
const toast = container.querySelector('[data-visible=true]');
|
||||||
|
|
||||||
act(() => {
|
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';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
message: string;
|
message: string;
|
||||||
isVisible?: boolean;
|
|
||||||
duration?: number;
|
duration?: number;
|
||||||
callback?: () => void;
|
callback?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Toast = ({ message, isVisible = false, duration = 3000, callback }: Props) => {
|
const Toast = ({ message, duration = 3000, callback }: Props) => {
|
||||||
const toastElement = useRef<HTMLDivElement>(null);
|
const [text, setText] = useState('');
|
||||||
const [show, setShow] = useState(false);
|
|
||||||
|
|
||||||
const callbackHandler = useCallback(() => {
|
|
||||||
// Only execute on hide transitionend event
|
|
||||||
if (toastElement.current?.dataset.visible === 'true') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback?.();
|
|
||||||
}, [callback]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isVisible) {
|
if (!message) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setShow(true);
|
setText(message);
|
||||||
|
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
setShow(false);
|
callback?.();
|
||||||
}, duration);
|
}, duration);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
setShow(false);
|
|
||||||
};
|
};
|
||||||
}, [callback, duration, isVisible]);
|
}, [callback, duration, message, text]);
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.toastContainer}>
|
<ReactModal
|
||||||
<div
|
role="toast"
|
||||||
ref={toastElement}
|
isOpen={Boolean(message)}
|
||||||
className={styles.toast}
|
overlayClassName={styles.toastContainer}
|
||||||
data-visible={show}
|
className={styles.toast}
|
||||||
onTransitionEnd={callbackHandler}
|
closeTimeoutMS={300}
|
||||||
>
|
>
|
||||||
{message}
|
{text}
|
||||||
</div>
|
</ReactModal>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ const AppContent = ({ children }: Props) => {
|
||||||
{platform === 'web' && <div className={styles.placeHolder} />}
|
{platform === 'web' && <div className={styles.placeHolder} />}
|
||||||
<main className={styles.content}>{children}</main>
|
<main className={styles.content}>{children}</main>
|
||||||
{platform === 'web' && <div className={styles.placeHolder} />}
|
{platform === 'web' && <div className={styles.placeHolder} />}
|
||||||
<Toast message={toast} isVisible={Boolean(toast)} callback={hideToast} />
|
<Toast message={toast} callback={hideToast} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,3 +27,7 @@ input::-webkit-inner-spin-button {
|
||||||
input[type='number'] {
|
input[type='number'] {
|
||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: solid 1px var(--color-primary);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue