Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose snackbar timer in order to allow customization #588

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 51 additions & 27 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"enqueueSnackbar",
"snackbarprovider",
"useSnackbar",
"useSnackbarTimer",
"multiple",
"react",
"javascript",
Expand Down
109 changes: 10 additions & 99 deletions src/SnackbarItem/Snackbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
*/
import * as React from 'react';
import clsx from 'clsx';
import useEventCallback from '../utils/useEventCallback';
import { CloseReason, SharedProps, SnackbarKey } from '../types';
import useSnackbarTimer from '../useSnackbarTimer';
import { SharedProps, SnackbarKey } from '../types';
import { ComponentClasses } from '../utils/styles';
import useMergedRef from 'src/utils/useMergedRef';

interface SnackbarProps extends Required<Pick<SharedProps, 'disableWindowBlurListener' | 'onClose'>> {
export interface SnackbarProps
extends Required<Pick<SharedProps, 'disableAutoHideTimer' | 'disableWindowBlurListener' | 'onClose'>> {
open: boolean;
id: SnackbarKey;
className: string;
Expand All @@ -17,106 +19,15 @@ interface SnackbarProps extends Required<Pick<SharedProps, 'disableWindowBlurLis
}

const Snackbar = React.forwardRef<HTMLDivElement, SnackbarProps>((props, ref) => {
const {
children,
className,
autoHideDuration,
disableWindowBlurListener = false,
onClose,
id,
open,
SnackbarProps = {},
} = props;
const { children, className, SnackbarProps = {} } = props;
const internalRef = React.useRef<HTMLDivElement | null>(null);

const timerAutoHide = React.useRef<ReturnType<typeof setTimeout>>();
const mergedRef = useMergedRef(ref, internalRef);

const handleClose = useEventCallback((...args: [null, CloseReason, SnackbarKey]) => {
if (onClose) {
onClose(...args);
}
});

const setAutoHideTimer = useEventCallback((autoHideDurationParam?: number | null) => {
if (!onClose || autoHideDurationParam == null) {
return;
}

if (timerAutoHide.current) {
clearTimeout(timerAutoHide.current);
}
timerAutoHide.current = setTimeout(() => {
handleClose(null, 'timeout', id);
}, autoHideDurationParam);
});

React.useEffect(() => {
if (open) {
setAutoHideTimer(autoHideDuration);
}

return () => {
if (timerAutoHide.current) {
clearTimeout(timerAutoHide.current);
}
};
}, [open, autoHideDuration, setAutoHideTimer]);

/**
* Pause the timer when the user is interacting with the Snackbar
* or when the user hide the window.
*/
const handlePause = () => {
if (timerAutoHide.current) {
clearTimeout(timerAutoHide.current);
}
};

/**
* Restart the timer when the user is no longer interacting with the Snackbar
* or when the window is shown back.
*/
const handleResume = React.useCallback(() => {
if (autoHideDuration != null) {
setAutoHideTimer(autoHideDuration * 0.5);
}
}, [autoHideDuration, setAutoHideTimer]);

const handleMouseEnter: React.MouseEventHandler<HTMLDivElement> = (event) => {
if (SnackbarProps.onMouseEnter) {
SnackbarProps.onMouseEnter(event);
}
handlePause();
};

const handleMouseLeave: React.MouseEventHandler<HTMLDivElement> = (event) => {
if (SnackbarProps.onMouseLeave) {
SnackbarProps.onMouseLeave(event);
}
handleResume();
};

React.useEffect(() => {
if (!disableWindowBlurListener && open) {
window.addEventListener('focus', handleResume);
window.addEventListener('blur', handlePause);

return () => {
window.removeEventListener('focus', handleResume);
window.removeEventListener('blur', handlePause);
};
}

return undefined;
}, [disableWindowBlurListener, handleResume, open]);
useSnackbarTimer(props, internalRef);

return (
<div
ref={ref}
{...SnackbarProps}
className={clsx(ComponentClasses.Snackbar, className)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div ref={mergedRef} {...SnackbarProps} className={clsx(ComponentClasses.Snackbar, className)}>
{children}
</div>
);
Expand Down
2 changes: 2 additions & 0 deletions src/SnackbarItem/SnackbarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const SnackbarItem: React.FC<SnackbarItemProps> = (props) => {
TransitionComponent,
TransitionProps,
transitionDuration,
disableAutoHideTimer,
disableWindowBlurListener,
content: componentOrFunctionContent,
entered: ignoredEntered,
Expand Down Expand Up @@ -112,6 +113,7 @@ const SnackbarItem: React.FC<SnackbarItemProps> = (props) => {
<Snackbar
open={open}
id={otherSnack.id}
disableAutoHideTimer={disableAutoHideTimer}
disableWindowBlurListener={disableWindowBlurListener}
autoHideDuration={otherSnack.autoHideDuration}
className={clsx(
Expand Down
1 change: 1 addition & 0 deletions src/SnackbarProvider/SnackbarProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class SnackbarProvider extends Component<SnackbarProviderProps, State> {
content: merger('content'),
variant: merger('variant'),
anchorOrigin: merger('anchorOrigin'),
disableAutoHideTimer: merger('disableAutoHideTimer'),
disableWindowBlurListener: merger('disableWindowBlurListener'),
autoHideDuration: merger('autoHideDuration'),
hideIconVariant: merger('hideIconVariant'),
Expand Down
1 change: 1 addition & 0 deletions src/SnackbarProvider/merger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const defaults = {
maxSnack: 3,
persist: false,
hideIconVariant: false,
disableAutoHideTimer: false,
disableWindowBlurListener: false,
variant: 'default',
autoHideDuration: 5000,
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { default as SnackbarProvider, enqueueSnackbar, closeSnackbar } from './SnackbarProvider';
export { default as SnackbarContent } from './SnackbarContent';
export { default as useSnackbar } from './useSnackbar';
export { default as useSnackbarTimer } from './useSnackbarTimer';
export { default as Transition } from './transitions/Transition';
export { default as MaterialDesignContent } from './ui/MaterialDesignContent';
10 changes: 10 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,14 @@ export interface SharedProps<V extends VariantType = VariantType> extends Partia
* @default 5000
*/
autoHideDuration?: number | null;
/**
* If `true`, the `autoHideDuration` timer will be disabled. You can take charge of running
* the timer yourself in your custom component, by using the `useSnackbarTimer` hook.
* This can be useful, if you want to display a progress bar in your custom component
* in order to indicate the timers progress.
* @default false
*/
disableAutoHideTimer?: boolean;
/**
* If `true`, the `autoHideDuration` timer will expire even if the window is not focused.
* @default false
Expand Down Expand Up @@ -318,6 +326,7 @@ type NeededByInternalSnack =
| 'TransitionProps'
| 'transitionDuration'
| 'hideIconVariant'
| 'disableAutoHideTimer'
| 'disableWindowBlurListener';

/**
Expand All @@ -337,6 +346,7 @@ type NotNeededByCustomSnackbar =
| keyof TransitionHandlerProps
| 'onClose'
| 'SnackbarProps'
| 'disableAutoHideTimer'
| 'disableWindowBlurListener'
| 'TransitionComponent'
| 'transitionDuration'
Expand Down
Loading