Skip to content

Commit

Permalink
Implement option to pause when document is hidden (#304)
Browse files Browse the repository at this point in the history
* Implement option to pause when document is hidden

* Add option to documentation

* Rename pauseWhenDocumentHidden -> pauseWhenPageIsHidden for conciseness; improve documentation with examples

---------

Co-authored-by: etcd <etcd>
  • Loading branch information
etcd authored Jan 29, 2024
1 parent 0f4de3c commit 5bb5868
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 17 deletions.
15 changes: 15 additions & 0 deletions src/hooks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

export const useIsDocumentHidden = () => {
const [isDocumentHidden, setIsDocumentHidden] = React.useState(false);

React.useEffect(() => {
const callback = () => {
setIsDocumentHidden(document.hidden);
};
document.addEventListener('visibilitychange', callback);
return () => window.removeEventListener('visibilitychange', callback);
}, []);

return isDocumentHidden;
};
22 changes: 20 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ReactDOM from 'react-dom';

import './styles.css';
import { getAsset, Loader } from './assets';
import { useIsDocumentHidden } from './hooks';
import type { HeightT, ToastT, ToastToDismiss, ExternalToast, ToasterProps, ToastProps } from './types';
import { ToastState, toast } from './state';

Expand Down Expand Up @@ -57,6 +58,7 @@ const Toast = (props: ToastProps) => {
expandByDefault,
classNames,
closeButtonAriaLabel = 'Close toast',
pauseWhenPageIsHidden,
} = props;
const [mounted, setMounted] = React.useState(false);
const [removed, setRemoved] = React.useState(false);
Expand Down Expand Up @@ -100,6 +102,8 @@ const Toast = (props: ToastProps) => {
return prev + curr.height;
}, 0);
}, [heights, heightIndex]);
const isDocumentHidden = useIsDocumentHidden();

const invert = toast.invert || ToasterInvert;
const disabled = toastType === 'loading';

Expand Down Expand Up @@ -145,6 +149,7 @@ const Toast = (props: ToastProps) => {
if ((toast.promise && toastType === 'loading') || toast.duration === Infinity || toast.type === 'loading') return;
let timeoutId: NodeJS.Timeout;
let remainingTime = duration;

// Pause the timer on each hover
const pauseTimer = () => {
if (lastCloseTimerStartTimeRef.current < closeTimerStartTimeRef.current) {
Expand All @@ -167,14 +172,25 @@ const Toast = (props: ToastProps) => {
}, remainingTime);
};

if (expanded || interacting) {
if (expanded || interacting || (pauseWhenPageIsHidden && isDocumentHidden)) {
pauseTimer();
} else {
startTimer();
}

return () => clearTimeout(timeoutId);
}, [expanded, interacting, expandByDefault, toast, duration, deleteToast, toast.promise, toastType]);
}, [
expanded,
interacting,
expandByDefault,
toast,
duration,
deleteToast,
toast.promise,
toastType,
pauseWhenPageIsHidden,
isDocumentHidden,
]);

React.useEffect(() => {
const toastNode = toastRef.current;
Expand Down Expand Up @@ -428,6 +444,7 @@ const Toaster = (props: ToasterProps) => {
gap,
loadingIcon,
containerAriaLabel = 'Notifications',
pauseWhenPageIsHidden,
} = props;
const [toasts, setToasts] = React.useState<ToastT[]>([]);
const possiblePositions = React.useMemo(() => {
Expand Down Expand Up @@ -648,6 +665,7 @@ const Toaster = (props: ToasterProps) => {
gap={gap}
loadingIcon={loadingIcon}
expanded={expanded}
pauseWhenPageIsHidden={pauseWhenPageIsHidden}
/>
))}
</ol>
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export interface ToasterProps {
dir?: 'rtl' | 'ltr' | 'auto';
loadingIcon?: React.ReactNode;
containerAriaLabel?: string;
pauseWhenPageIsHidden?: boolean;
}

export interface ToastProps {
Expand Down Expand Up @@ -128,6 +129,7 @@ export interface ToastProps {
loadingIcon?: React.ReactNode;
classNames?: ToastClassnames;
closeButtonAriaLabel?: string;
pauseWhenPageIsHidden: boolean;
}

export enum SwipeStateTypes {
Expand Down
31 changes: 16 additions & 15 deletions website/src/pages/toaster.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,19 @@ Changes the directionality of the toast's text.

## API Reference

| Property | Description | Default |
| :------------ | :------------------------------------------------------------------------------------------------: | -------------: |
| theme | Toast's theme, either `light`, `dark`, or `system` | `light` |
| richColors | Makes error and success state more colorful | `false` |
| expand | Toasts will be expanded by default | `false` |
| visibleToasts | Amount of visible toasts | `3` |
| position | Place where the toasts will be rendered | `bottom-right` |
| closeButton | Adds a close button to all toasts | `false` |
| offset | Offset from the edges of the screen. | `32px` |
| dir | Directionality of toast's text | `ltr` |
| hotkey | Keyboard shortcut that will move focus to the toaster area. | `⌥/alt + T` |
| invert | Dark toasts in light mode and vice versa. | `false` |
| toastOptions | These will act as default options for all toasts. See [toast()](/toast) for all available options. | `4000` |
| gap | Gap between toasts when expanded | `14` |
| loadingIcon | Changes the default loading icon | `-` |
| Property | Description | Default |
| :-------------------- | :-----------------------------------------------------------------------------------------------------------------------------: | -------------: |
| theme | Toast's theme, either `light`, `dark`, or `system` | `light` |
| richColors | Makes error and success state more colorful | `false` |
| expand | Toasts will be expanded by default | `false` |
| visibleToasts | Amount of visible toasts | `3` |
| position | Place where the toasts will be rendered | `bottom-right` |
| closeButton | Adds a close button to all toasts | `false` |
| offset | Offset from the edges of the screen. | `32px` |
| dir | Directionality of toast's text | `ltr` |
| hotkey | Keyboard shortcut that will move focus to the toaster area. | `⌥/alt + T` |
| invert | Dark toasts in light mode and vice versa. | `false` |
| toastOptions | These will act as default options for all toasts. See [toast()](/toast) for all available options. | `4000` |
| gap | Gap between toasts when expanded | `14` |
| loadingIcon | Changes the default loading icon | `-` |
| pauseWhenPageIsHidden | Pauses toast timers when the page is hidden, e.g., when the tab is backgrounded, the browser is minimized, or the OS is locked. | `false` |

1 comment on commit 5bb5868

@vercel
Copy link

@vercel vercel bot commented on 5bb5868 Jan 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.