From 80ae697f68a708a80b8c713f524f617f4f7ff450 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 9 Jun 2022 09:08:11 +0530 Subject: [PATCH 01/71] SnackbarUnstyled initial commit --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 12 ++++++++++++ .../SnackbarUnstyled/SnackbarUnstyled.types.ts | 18 ++++++++++++++++++ .../mui-base/src/SnackbarUnstyled/index.ts | 3 +++ packages/mui-base/src/index.d.ts | 3 +++ packages/mui-base/src/index.js | 3 +++ 5 files changed, 39 insertions(+) create mode 100644 packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx create mode 100644 packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts create mode 100644 packages/mui-base/src/SnackbarUnstyled/index.ts diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx new file mode 100644 index 00000000000000..034dbb3ffccfcd --- /dev/null +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +import { SnackbarUnstyledProps } from './SnackbarUnstyled.types'; + +const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { + const { component, components = {} } = props; + + const Root = component || components.Root || 'div'; + + return abc; +}; + +export default SnackbarUnstyled; diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts new file mode 100644 index 00000000000000..518be383e539c6 --- /dev/null +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -0,0 +1,18 @@ +import React from 'react'; + +export interface SnackbarUnstyledProps { + /** + * The component used for the Root slot. + * Either a string to use a HTML element or a component. + * This is equivalent to `components.Root`. If both are provided, the `component` is used. + */ + component?: React.ElementType; + /** + * The components used for each slot inside the Badge. + * Either a string to use a HTML element or a component. + * @default {} + */ + components?: { + Root?: React.ElementType; + }; +} diff --git a/packages/mui-base/src/SnackbarUnstyled/index.ts b/packages/mui-base/src/SnackbarUnstyled/index.ts new file mode 100644 index 00000000000000..08ae195d828efd --- /dev/null +++ b/packages/mui-base/src/SnackbarUnstyled/index.ts @@ -0,0 +1,3 @@ +export { default } from './SnackbarUnstyled'; + +export { SnackbarUnstyledProps } from './SnackbarUnstyled.types'; diff --git a/packages/mui-base/src/index.d.ts b/packages/mui-base/src/index.d.ts index 88573a74f9763c..fc849c2e460f33 100644 --- a/packages/mui-base/src/index.d.ts +++ b/packages/mui-base/src/index.d.ts @@ -60,6 +60,9 @@ export * from './SelectUnstyled'; export { default as SliderUnstyled } from './SliderUnstyled'; export * from './SliderUnstyled'; +export { default as SnackbarUnstyled } from './SnackbarUnstyled'; +export * from './SnackbarUnstyled'; + export { default as SwitchUnstyled } from './SwitchUnstyled'; export * from './SwitchUnstyled'; diff --git a/packages/mui-base/src/index.js b/packages/mui-base/src/index.js index 8d926af0470ac2..e85aaa6e222729 100644 --- a/packages/mui-base/src/index.js +++ b/packages/mui-base/src/index.js @@ -55,6 +55,9 @@ export * from './SelectUnstyled'; export { default as SliderUnstyled } from './SliderUnstyled'; export * from './SliderUnstyled'; +export { default as SnackbarUnstyled } from './SnackbarUnstyled'; +export * from './SnackbarUnstyled'; + export { default as SwitchUnstyled } from './SwitchUnstyled'; export * from './SwitchUnstyled'; From 4a723e6ddc3cc5a0827ef5f6b6677348a970566a Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 9 Jun 2022 11:51:08 +0530 Subject: [PATCH 02/71] fix api description --- .../mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 518be383e539c6..3f83c94c4fcebc 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -8,7 +8,7 @@ export interface SnackbarUnstyledProps { */ component?: React.ElementType; /** - * The components used for each slot inside the Badge. + * The components used for each slot inside the Snackbar. * Either a string to use a HTML element or a component. * @default {} */ From 47a69a47b0d61799665bbbaa514aaddfcd7acdf9 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 9 Jun 2022 16:46:18 +0530 Subject: [PATCH 03/71] add some more props --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 30 +++++++++++++++-- .../SnackbarUnstyled.types.ts | 33 ++++++++++++++++++- .../mui-base/src/SnackbarUnstyled/index.ts | 2 +- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 034dbb3ffccfcd..cc9bc75f8a6aa9 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -1,12 +1,38 @@ import * as React from 'react'; +import ClickAwayListener from '../ClickAwayListener'; import { SnackbarUnstyledProps } from './SnackbarUnstyled.types'; const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { - const { component, components = {} } = props; + const { + children, + ClickAwayListenerProps, + component, + components = {}, + componentsProps = {}, + onClose, + } = props; const Root = component || components.Root || 'div'; - return abc; + const TransitionComponent = components.Transition; + + const handleClickAway = (event: React.SyntheticEvent | Event) => { + if (onClose) { + onClose(event, 'clickaway'); + } + }; + + return ( + + + {TransitionComponent ? ( + {children} + ) : ( + children + )} + + + ); }; export default SnackbarUnstyled; diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 3f83c94c4fcebc..71a991cda7b57d 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -1,6 +1,17 @@ import React from 'react'; +import { ClickAwayListenerProps } from '../ClickAwayListener'; -export interface SnackbarUnstyledProps { +export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; + +export interface SnackbarUnstyledProps { + /** + * The Snackbar content if any. + */ + children?: React.ReactElement; + /** + * Props applied to the `ClickAwayListener` element. + */ + ClickAwayListenerProps?: Partial; /** * The component used for the Root slot. * Either a string to use a HTML element or a component. @@ -14,5 +25,25 @@ export interface SnackbarUnstyledProps { */ components?: { Root?: React.ElementType; + Transition?: React.ElementType; }; + /** + * The props used for each slot inside the Snackbar. + * @default {} + */ + componentsProps?: { + root?: React.HTMLAttributes; + transition?: TransitionType; + }; + /** + * Callback fired when the component requests to be closed. + * Typically `onClose` is used to set state in the parent component, + * which is used to control the `Snackbar` `open` prop. + * The `reason` parameter can optionally be used to control the response to `onClose`, + * for example ignoring `clickaway`. + * + * @param {React.SyntheticEvent | Event} event The event source of the callback. + * @param {string} reason Can be: `"timeout"` (`autoHideDuration` expired), `"clickaway"`, or `"escapeKeyDown"`. + */ + onClose?: (event: React.SyntheticEvent | Event, reason: SnackbarCloseReason) => void; } diff --git a/packages/mui-base/src/SnackbarUnstyled/index.ts b/packages/mui-base/src/SnackbarUnstyled/index.ts index 08ae195d828efd..56b7bd98df5357 100644 --- a/packages/mui-base/src/SnackbarUnstyled/index.ts +++ b/packages/mui-base/src/SnackbarUnstyled/index.ts @@ -1,3 +1,3 @@ export { default } from './SnackbarUnstyled'; -export { SnackbarUnstyledProps } from './SnackbarUnstyled.types'; +export * from './SnackbarUnstyled.types'; From 9aa17ff29be818e24c19c73e94e5ed6cbbb48c35 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 10 Jun 2022 11:18:56 +0530 Subject: [PATCH 04/71] remove children prop description --- .../mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 71a991cda7b57d..209cd5e9ee8ed8 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -4,9 +4,6 @@ import { ClickAwayListenerProps } from '../ClickAwayListener'; export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; export interface SnackbarUnstyledProps { - /** - * The Snackbar content if any. - */ children?: React.ReactElement; /** * Props applied to the `ClickAwayListener` element. From f1d12be52d533bd75300835238ee0aa9aa8730ff Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 10 Jun 2022 12:08:56 +0530 Subject: [PATCH 05/71] add className in root props --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 11 ++++++++++- .../src/SnackbarUnstyled/SnackbarUnstyled.types.ts | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index cc9bc75f8a6aa9..d45fb55b7d894c 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import clsx from 'clsx'; import ClickAwayListener from '../ClickAwayListener'; import { SnackbarUnstyledProps } from './SnackbarUnstyled.types'; @@ -10,12 +11,20 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { components = {}, componentsProps = {}, onClose, + className, + ...other } = props; const Root = component || components.Root || 'div'; const TransitionComponent = components.Transition; + const rootProps = { + ...other, + ...componentsProps.root, + className: clsx(className, componentsProps.root?.className), + }; + const handleClickAway = (event: React.SyntheticEvent | Event) => { if (onClose) { onClose(event, 'clickaway'); @@ -24,7 +33,7 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { return ( - + {TransitionComponent ? ( {children} ) : ( diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 209cd5e9ee8ed8..62ec33c2998438 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -4,6 +4,7 @@ import { ClickAwayListenerProps } from '../ClickAwayListener'; export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; export interface SnackbarUnstyledProps { + className?: string; children?: React.ReactElement; /** * Props applied to the `ClickAwayListener` element. From d579e804ef9de677a5b671354d1631c3792ba15b Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 10 Jun 2022 14:30:55 +0530 Subject: [PATCH 06/71] add some props --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 55 ++++++++++++++++--- .../SnackbarUnstyled.types.ts | 18 +++++- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index d45fb55b7d894c..6c0bf6e2659c1a 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -1,29 +1,48 @@ import * as React from 'react'; import clsx from 'clsx'; +import { unstable_useEventCallback as useEventCallback } from '@mui/utils'; import ClickAwayListener from '../ClickAwayListener'; -import { SnackbarUnstyledProps } from './SnackbarUnstyled.types'; +import { SnackbarCloseReason, SnackbarUnstyledProps } from './SnackbarUnstyled.types'; const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { const { + autoHideDuration = null, children, + className, ClickAwayListenerProps, component, components = {}, componentsProps = {}, + onBlur, onClose, - className, + resumeHideDuration, ...other } = props; + const timerAutoHide = React.useRef>(); + const Root = component || components.Root || 'div'; const TransitionComponent = components.Transition; - const rootProps = { - ...other, - ...componentsProps.root, - className: clsx(className, componentsProps.root?.className), - }; + const handleClose = useEventCallback( + (event: Event | React.SyntheticEvent | null, reason: SnackbarCloseReason) => { + if (onClose) { + onClose(event, reason); + } + }, + ); + + const setAutoHideTimer = useEventCallback((autoHideDurationParam: number) => { + if (!onClose || autoHideDurationParam == null) { + return; + } + + clearTimeout(timerAutoHide.current); + timerAutoHide.current = setTimeout(() => { + handleClose(null, 'timeout'); + }, autoHideDurationParam); + }); const handleClickAway = (event: React.SyntheticEvent | Event) => { if (onClose) { @@ -31,6 +50,28 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { } }; + // 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(resumeHideDuration != null ? resumeHideDuration : autoHideDuration * 0.5); + } + }, [autoHideDuration, resumeHideDuration, setAutoHideTimer]); + + const handleBlur = (event: React.FocusEvent) => { + if (onBlur) { + onBlur(event); + } + handleResume(); + }; + + const rootProps = { + onBlur: handleBlur, + ...other, + ...componentsProps.root, + className: clsx(className, componentsProps.root?.className), + }; + return ( diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 62ec33c2998438..3fad9d0bcac7fb 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -4,6 +4,14 @@ import { ClickAwayListenerProps } from '../ClickAwayListener'; export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; export interface SnackbarUnstyledProps { + /** + * The number of milliseconds to wait before automatically calling the + * `onClose` function. `onClose` should then set the state of the `open` + * prop to hide the Snackbar. This behavior is disabled by default with + * the `null` value. + * @default null + */ + autoHideDuration?: number | null; className?: string; children?: React.ReactElement; /** @@ -33,6 +41,7 @@ export interface SnackbarUnstyledProps { root?: React.HTMLAttributes; transition?: TransitionType; }; + onBlur?: React.FocusEventHandler; /** * Callback fired when the component requests to be closed. * Typically `onClose` is used to set state in the parent component, @@ -43,5 +52,12 @@ export interface SnackbarUnstyledProps { * @param {React.SyntheticEvent | Event} event The event source of the callback. * @param {string} reason Can be: `"timeout"` (`autoHideDuration` expired), `"clickaway"`, or `"escapeKeyDown"`. */ - onClose?: (event: React.SyntheticEvent | Event, reason: SnackbarCloseReason) => void; + onClose?: (event: React.SyntheticEvent | Event | null, reason: SnackbarCloseReason) => void; + /** + * The number of milliseconds to wait before dismissing after user interaction. + * If `autoHideDuration` prop isn't specified, it does nothing. + * If `autoHideDuration` prop is specified but `resumeHideDuration` isn't, + * we default to `autoHideDuration / 2` ms. + */ + resumeHideDuration?: number; } From c38b6ee313b1cbe5da0d430dfefb39179fa5ee5f Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 10 Jun 2022 17:04:53 +0530 Subject: [PATCH 07/71] add other event handlers --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 38 ++++++++++++++++++- .../SnackbarUnstyled.types.ts | 3 ++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 6c0bf6e2659c1a..80fdcbbe385e7b 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -15,6 +15,9 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { componentsProps = {}, onBlur, onClose, + onFocus, + onMouseEnter, + onMouseLeave, resumeHideDuration, ...other } = props; @@ -50,6 +53,12 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { } }; + // Pause the timer when the user is interacting with the Snackbar + // or when the user hide the window. + const handlePause = () => { + 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(() => { @@ -59,14 +68,39 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { }, [autoHideDuration, resumeHideDuration, setAutoHideTimer]); const handleBlur = (event: React.FocusEvent) => { - if (onBlur) { - onBlur(event); + const onBlurMethod = onBlur || componentsProps.root?.onBlur; + if (onBlurMethod) { + onBlurMethod(event); + } + handleResume(); + }; + + const handleFocus = (event: React.FocusEvent) => { + if (onFocus) { + onFocus(event); + } + handlePause(); + }; + + const handleMouseEnter = (event: React.MouseEvent) => { + if (onMouseEnter) { + onMouseEnter(event); + } + handlePause(); + }; + + const handleMouseLeave = (event: React.MouseEvent) => { + if (onMouseLeave) { + onMouseLeave(event); } handleResume(); }; const rootProps = { onBlur: handleBlur, + onFocus: handleFocus, + onMouseEnter: handleMouseEnter, + onMouseLeave: handleMouseLeave, ...other, ...componentsProps.root, className: clsx(className, componentsProps.root?.className), diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 3fad9d0bcac7fb..4be3392ff7246c 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -53,6 +53,9 @@ export interface SnackbarUnstyledProps { * @param {string} reason Can be: `"timeout"` (`autoHideDuration` expired), `"clickaway"`, or `"escapeKeyDown"`. */ onClose?: (event: React.SyntheticEvent | Event | null, reason: SnackbarCloseReason) => void; + onFocus?: React.FocusEventHandler; + onMouseEnter?: React.MouseEventHandler; + onMouseLeave?: React.MouseEventHandler; /** * The number of milliseconds to wait before dismissing after user interaction. * If `autoHideDuration` prop isn't specified, it does nothing. From 1a78db1f724bb5b43b470e0478060baf74249b37 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 13 Jun 2022 19:04:09 +0530 Subject: [PATCH 08/71] support event handler callbacks from componentsProps.root --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 80fdcbbe385e7b..56ca93fe791791 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -68,30 +68,33 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { }, [autoHideDuration, resumeHideDuration, setAutoHideTimer]); const handleBlur = (event: React.FocusEvent) => { - const onBlurMethod = onBlur || componentsProps.root?.onBlur; - if (onBlurMethod) { - onBlurMethod(event); + const onBlurCallback = componentsProps.root?.onBlur || onBlur; + if (onBlurCallback) { + onBlurCallback(event); } handleResume(); }; const handleFocus = (event: React.FocusEvent) => { - if (onFocus) { - onFocus(event); + const onFocusCallback = componentsProps.root?.onFocus || onFocus; + if (onFocusCallback) { + onFocusCallback(event); } handlePause(); }; const handleMouseEnter = (event: React.MouseEvent) => { - if (onMouseEnter) { - onMouseEnter(event); + const onMouseEnterCallback = componentsProps.root?.onMouseEnter || onMouseEnter; + if (onMouseEnterCallback) { + onMouseEnterCallback(event); } handlePause(); }; const handleMouseLeave = (event: React.MouseEvent) => { - if (onMouseLeave) { - onMouseLeave(event); + const onMouseLeaveCallback = componentsProps.root?.onMouseLeave || onMouseLeave; + if (onMouseLeaveCallback) { + onMouseLeaveCallback(event); } handleResume(); }; From 22700a03cc65f8a352cc0f86541daeaab8a4c0bf Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 13 Jun 2022 20:27:24 +0530 Subject: [PATCH 09/71] add other logic --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 64 ++++++++++++++++++- .../SnackbarUnstyled.types.ts | 9 +++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 56ca93fe791791..d0f2d445611c7a 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -13,11 +13,13 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { component, components = {}, componentsProps = {}, + disableWindowBlurListener = false, onBlur, onClose, onFocus, onMouseEnter, onMouseLeave, + open, resumeHideDuration, ...other } = props; @@ -28,6 +30,33 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { const TransitionComponent = components.Transition; + React.useEffect(() => { + if (!open) { + return undefined; + } + + /** + * @param {KeyboardEvent} nativeEvent + */ + function handleKeyDown(nativeEvent: KeyboardEvent) { + if (!nativeEvent.defaultPrevented) { + // IE11, Edge (prior to using Bink?) use 'Esc' + if (nativeEvent.key === 'Escape' || nativeEvent.key === 'Esc') { + // not calling `preventDefault` since we don't know if people may ignore this event e.g. a permanently open snackbar + if (onClose) { + onClose(nativeEvent, 'escapeKeyDown'); + } + } + } + } + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [open, onClose]); + const handleClose = useEventCallback( (event: Event | React.SyntheticEvent | null, reason: SnackbarCloseReason) => { if (onClose) { @@ -36,7 +65,7 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { }, ); - const setAutoHideTimer = useEventCallback((autoHideDurationParam: number) => { + const setAutoHideTimer = useEventCallback((autoHideDurationParam: number | null) => { if (!onClose || autoHideDurationParam == null) { return; } @@ -47,6 +76,16 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { }, autoHideDurationParam); }); + React.useEffect(() => { + if (open) { + setAutoHideTimer(autoHideDuration); + } + + return () => { + clearTimeout(timerAutoHide.current); + }; + }, [open, autoHideDuration, setAutoHideTimer]); + const handleClickAway = (event: React.SyntheticEvent | Event) => { if (onClose) { onClose(event, 'clickaway'); @@ -99,6 +138,26 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { handleResume(); }; + React.useEffect(() => { + // TODO: window global should be refactored here + 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]); + + // So we only render active snackbars. + if (!open) { + return null; + } + const rootProps = { onBlur: handleBlur, onFocus: handleFocus, @@ -107,6 +166,9 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { ...other, ...componentsProps.root, className: clsx(className, componentsProps.root?.className), + // ClickAwayListener adds an `onClick` prop which results in the alert not being announced. + // See https://github.com/mui/material-ui/issues/29080 + role: 'presentation', }; return ( diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 4be3392ff7246c..776e490684f36e 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -41,6 +41,11 @@ export interface SnackbarUnstyledProps { root?: React.HTMLAttributes; transition?: TransitionType; }; + /** + * If `true`, the `autoHideDuration` timer will expire even if the window is not focused. + * @default false + */ + disableWindowBlurListener?: boolean; onBlur?: React.FocusEventHandler; /** * Callback fired when the component requests to be closed. @@ -56,6 +61,10 @@ export interface SnackbarUnstyledProps { onFocus?: React.FocusEventHandler; onMouseEnter?: React.MouseEventHandler; onMouseLeave?: React.MouseEventHandler; + /** + * If `true`, the component is shown. + */ + open?: boolean; /** * The number of milliseconds to wait before dismissing after user interaction. * If `autoHideDuration` prop isn't specified, it does nothing. From 0946e492824507b56b6daddb2f7a0606b1e5e950 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 13 Jun 2022 20:31:30 +0530 Subject: [PATCH 10/71] add ref --- .../mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index d0f2d445611c7a..120356358a6425 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -4,7 +4,10 @@ import { unstable_useEventCallback as useEventCallback } from '@mui/utils'; import ClickAwayListener from '../ClickAwayListener'; import { SnackbarCloseReason, SnackbarUnstyledProps } from './SnackbarUnstyled.types'; -const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { +const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( + props: SnackbarUnstyledProps, + ref: React.ForwardedRef, +) { const { autoHideDuration = null, children, @@ -166,6 +169,7 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { ...other, ...componentsProps.root, className: clsx(className, componentsProps.root?.className), + ref, // ClickAwayListener adds an `onClick` prop which results in the alert not being announced. // See https://github.com/mui/material-ui/issues/29080 role: 'presentation', @@ -182,6 +186,6 @@ const SnackbarUnstyled = (props: SnackbarUnstyledProps) => { ); -}; +}); export default SnackbarUnstyled; From adac7386cc69942d7a750b4803600d1d5cd86b31 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 15 Jun 2022 10:29:04 +0530 Subject: [PATCH 11/71] add snackbarUnstyled classes --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 14 +++++++++++++- packages/mui-base/src/SnackbarUnstyled/index.ts | 3 +++ .../SnackbarUnstyled/snackbarUnstyledClasses.ts | 17 +++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 packages/mui-base/src/SnackbarUnstyled/snackbarUnstyledClasses.ts diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 120356358a6425..ae90b96b41e65c 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -3,6 +3,16 @@ import clsx from 'clsx'; import { unstable_useEventCallback as useEventCallback } from '@mui/utils'; import ClickAwayListener from '../ClickAwayListener'; import { SnackbarCloseReason, SnackbarUnstyledProps } from './SnackbarUnstyled.types'; +import composeClasses from '../composeClasses'; +import { getSnackbarUnstyledUtilityClass } from './snackbarUnstyledClasses'; + +const useUtilityClasses = () => { + const slots = { + root: ['root'], + }; + + return composeClasses(slots, getSnackbarUnstyledUtilityClass, undefined); +}; const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( props: SnackbarUnstyledProps, @@ -27,6 +37,8 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( ...other } = props; + const classes = useUtilityClasses(); + const timerAutoHide = React.useRef>(); const Root = component || components.Root || 'div'; @@ -168,7 +180,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( onMouseLeave: handleMouseLeave, ...other, ...componentsProps.root, - className: clsx(className, componentsProps.root?.className), + className: clsx(classes.root, className, componentsProps.root?.className), ref, // ClickAwayListener adds an `onClick` prop which results in the alert not being announced. // See https://github.com/mui/material-ui/issues/29080 diff --git a/packages/mui-base/src/SnackbarUnstyled/index.ts b/packages/mui-base/src/SnackbarUnstyled/index.ts index 56b7bd98df5357..db0cad8ac68ef8 100644 --- a/packages/mui-base/src/SnackbarUnstyled/index.ts +++ b/packages/mui-base/src/SnackbarUnstyled/index.ts @@ -1,3 +1,6 @@ export { default } from './SnackbarUnstyled'; export * from './SnackbarUnstyled.types'; + +export { default as snackbarUnstyledClasses } from './snackbarUnstyledClasses'; +export * from './snackbarUnstyledClasses'; diff --git a/packages/mui-base/src/SnackbarUnstyled/snackbarUnstyledClasses.ts b/packages/mui-base/src/SnackbarUnstyled/snackbarUnstyledClasses.ts new file mode 100644 index 00000000000000..ebaef86c2bd6cd --- /dev/null +++ b/packages/mui-base/src/SnackbarUnstyled/snackbarUnstyledClasses.ts @@ -0,0 +1,17 @@ +import generateUtilityClass from '../generateUtilityClass'; +import generateUtilityClasses from '../generateUtilityClasses'; + +export interface SnackbarUnstyledClasses { + /** Class name applied to the root element. */ + root: string; +} + +export function getSnackbarUnstyledUtilityClass(slot: string): string { + return generateUtilityClass('BaseSnackbar', slot); +} + +const snackbarUnstyledClasses: SnackbarUnstyledClasses = generateUtilityClasses('BaseSnackbar', [ + 'root', +]); + +export default snackbarUnstyledClasses; From 06a8e065f4bce82947bd9f827b8c3e982a80c0ea Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 15 Jun 2022 11:53:10 +0530 Subject: [PATCH 12/71] add SnackbarUnstyled describeConformanceUnstyled --- .../SnackbarUnstyled.test.tsx | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.test.tsx diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.test.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.test.tsx new file mode 100644 index 00000000000000..9bf30eafe7b537 --- /dev/null +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.test.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { createRenderer, createMount, describeConformanceUnstyled } from 'test/utils'; +import SnackbarUnstyled, { snackbarUnstyledClasses as classes } from '@mui/base/SnackbarUnstyled'; + +describe('', () => { + const { render } = createRenderer(); + const mount = createMount(); + + describeConformanceUnstyled( + +
+ , + () => ({ + classes, + inheritComponent: 'div', + render, + mount, + refInstanceof: window.HTMLDivElement, + muiName: 'BaseSnackbar', + slots: { + root: { + expectedClassName: classes.root, + }, + }, + skip: ['componentsPropsCallbacks', 'ownerStatePropagation'], // not implemented yet + }), + ); +}); From d002a6f63fff8e74292f157e97b3ce32b73d843c Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 15 Jun 2022 12:02:39 +0530 Subject: [PATCH 13/71] yarn proptypes --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index ae90b96b41e65c..94507b963f5b09 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import PropTypes from 'prop-types'; import clsx from 'clsx'; import { unstable_useEventCallback as useEventCallback } from '@mui/utils'; import ClickAwayListener from '../ClickAwayListener'; @@ -200,4 +201,97 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( ); }); +SnackbarUnstyled.propTypes /* remove-proptypes */ = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * The number of milliseconds to wait before automatically calling the + * `onClose` function. `onClose` should then set the state of the `open` + * prop to hide the Snackbar. This behavior is disabled by default with + * the `null` value. + * @default null + */ + autoHideDuration: PropTypes.number, + /** + * @ignore + */ + children: PropTypes.element, + /** + * @ignore + */ + className: PropTypes.string, + /** + * Props applied to the `ClickAwayListener` element. + */ + ClickAwayListenerProps: PropTypes.object, + /** + * The component used for the Root slot. + * Either a string to use a HTML element or a component. + * This is equivalent to `components.Root`. If both are provided, the `component` is used. + */ + component: PropTypes.elementType, + /** + * The components used for each slot inside the Snackbar. + * Either a string to use a HTML element or a component. + * @default {} + */ + components: PropTypes.shape({ + Root: PropTypes.elementType, + Transition: PropTypes.elementType, + }), + /** + * The props used for each slot inside the Snackbar. + * @default {} + */ + componentsProps: PropTypes.shape({ + root: PropTypes.object, + transition: PropTypes.object, + }), + /** + * If `true`, the `autoHideDuration` timer will expire even if the window is not focused. + * @default false + */ + disableWindowBlurListener: PropTypes.bool, + /** + * @ignore + */ + onBlur: PropTypes.func, + /** + * Callback fired when the component requests to be closed. + * Typically `onClose` is used to set state in the parent component, + * which is used to control the `Snackbar` `open` prop. + * The `reason` parameter can optionally be used to control the response to `onClose`, + * for example ignoring `clickaway`. + * + * @param {React.SyntheticEvent | Event} event The event source of the callback. + * @param {string} reason Can be: `"timeout"` (`autoHideDuration` expired), `"clickaway"`, or `"escapeKeyDown"`. + */ + onClose: PropTypes.func, + /** + * @ignore + */ + onFocus: PropTypes.func, + /** + * @ignore + */ + onMouseEnter: PropTypes.func, + /** + * @ignore + */ + onMouseLeave: PropTypes.func, + /** + * If `true`, the component is shown. + */ + open: PropTypes.bool, + /** + * The number of milliseconds to wait before dismissing after user interaction. + * If `autoHideDuration` prop isn't specified, it does nothing. + * If `autoHideDuration` prop is specified but `resumeHideDuration` isn't, + * we default to `autoHideDuration / 2` ms. + */ + resumeHideDuration: PropTypes.number, +} as any; + export default SnackbarUnstyled; From 6a18a218699ab40990a7d157e55c60f67674af05 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 15 Jun 2022 12:48:13 +0530 Subject: [PATCH 14/71] setup docs --- .../data/base/components/snackbar/snackbar.md | 11 +++++++ docs/data/base/pages.ts | 10 +++++++ docs/data/base/pagesApi.js | 1 + docs/pages/base/api/snackbar-unstyled.js | 23 ++++++++++++++ docs/pages/base/api/snackbar-unstyled.json | 30 +++++++++++++++++++ docs/pages/base/react-snackbar.js | 11 +++++++ .../snackbar-unstyled-pt.json | 15 ++++++++++ .../snackbar-unstyled-zh.json | 15 ++++++++++ .../snackbar-unstyled/snackbar-unstyled.json | 15 ++++++++++ docs/translations/translations.json | 1 + .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 11 ++++++- 11 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 docs/data/base/components/snackbar/snackbar.md create mode 100644 docs/pages/base/api/snackbar-unstyled.js create mode 100644 docs/pages/base/api/snackbar-unstyled.json create mode 100644 docs/pages/base/react-snackbar.js create mode 100644 docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled-pt.json create mode 100644 docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled-zh.json create mode 100644 docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md new file mode 100644 index 00000000000000..3221106380696b --- /dev/null +++ b/docs/data/base/components/snackbar/snackbar.md @@ -0,0 +1,11 @@ +--- +product: base +title: Unstyled React Snackbar component +components: SnackbarUnstyled +githubLabel: 'component: snackbar' +packageName: '@mui/base' +--- + +# Unstyled snackbar + +

The SnackbarUnstyled component provides brief notifications.

diff --git a/docs/data/base/pages.ts b/docs/data/base/pages.ts index 47131fd01fed15..7b0322305524ff 100644 --- a/docs/data/base/pages.ts +++ b/docs/data/base/pages.ts @@ -28,6 +28,16 @@ const pages = [ { pathname: '/base/react-switch', title: 'Switch' }, ], }, + { + pathname: '/base/components/feedback', + subheader: 'feedback', + children: [ + { + pathname: '/base/react-snackbar', + title: 'Snackbar', + }, + ], + }, { pathname: '/base/components/navigation', subheader: 'navigation', diff --git a/docs/data/base/pagesApi.js b/docs/data/base/pagesApi.js index 45a794ad6d2d5e..6eb57ac63dc3b6 100644 --- a/docs/data/base/pagesApi.js +++ b/docs/data/base/pagesApi.js @@ -15,6 +15,7 @@ module.exports = [ { pathname: '/base/api/portal' }, { pathname: '/base/api/select-unstyled' }, { pathname: '/base/api/slider-unstyled' }, + { pathname: '/base/api/snackbar-unstyled' }, { pathname: '/base/api/switch-unstyled' }, { pathname: '/base/api/table-pagination-unstyled' }, { pathname: '/base/api/tab-panel-unstyled' }, diff --git a/docs/pages/base/api/snackbar-unstyled.js b/docs/pages/base/api/snackbar-unstyled.js new file mode 100644 index 00000000000000..55d836f296d78d --- /dev/null +++ b/docs/pages/base/api/snackbar-unstyled.js @@ -0,0 +1,23 @@ +import * as React from 'react'; +import ApiPage from 'docs/src/modules/components/ApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './snackbar-unstyled.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docs/translations/api-docs/snackbar-unstyled', + false, + /snackbar-unstyled.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/snackbar-unstyled.json b/docs/pages/base/api/snackbar-unstyled.json new file mode 100644 index 00000000000000..20b7c6f6a88db4 --- /dev/null +++ b/docs/pages/base/api/snackbar-unstyled.json @@ -0,0 +1,30 @@ +{ + "props": { + "autoHideDuration": { "type": { "name": "number" }, "default": "null" }, + "ClickAwayListenerProps": { "type": { "name": "object" } }, + "component": { "type": { "name": "elementType" } }, + "components": { + "type": { + "name": "shape", + "description": "{ Root?: elementType, Transition?: elementType }" + }, + "default": "{}" + }, + "componentsProps": { + "type": { "name": "shape", "description": "{ root?: object, transition?: object }" }, + "default": "{}" + }, + "disableWindowBlurListener": { "type": { "name": "bool" } }, + "onClose": { "type": { "name": "func" } }, + "open": { "type": { "name": "bool" } }, + "resumeHideDuration": { "type": { "name": "number" } } + }, + "name": "SnackbarUnstyled", + "styles": { "classes": [], "globalClasses": {}, "name": null }, + "spread": true, + "forwardsRefTo": "HTMLDivElement", + "filename": "/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/pages/base/react-snackbar.js b/docs/pages/base/react-snackbar.js new file mode 100644 index 00000000000000..79fbb93579e627 --- /dev/null +++ b/docs/pages/base/react-snackbar.js @@ -0,0 +1,11 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import { + demos, + docs, + demoComponents, +} from 'docs/data/base/components/snackbar/snackbar.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled-pt.json b/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled-pt.json new file mode 100644 index 00000000000000..8cdfb6b8f156b8 --- /dev/null +++ b/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled-pt.json @@ -0,0 +1,15 @@ +{ + "componentDescription": "", + "propDescriptions": { + "autoHideDuration": "The number of milliseconds to wait before automatically calling the onClose function. onClose should then set the state of the open prop to hide the Snackbar. This behavior is disabled by default with the null value.", + "ClickAwayListenerProps": "Props applied to the ClickAwayListener element.", + "component": "The component used for the Root slot. Either a string to use a HTML element or a component. This is equivalent to components.Root. If both are provided, the component is used.", + "components": "The components used for each slot inside the Snackbar. Either a string to use a HTML element or a component.", + "componentsProps": "The props used for each slot inside the Snackbar.", + "disableWindowBlurListener": "If true, the autoHideDuration timer will expire even if the window is not focused.", + "onClose": "Callback fired when the component requests to be closed. Typically onClose is used to set state in the parent component, which is used to control the Snackbar open prop. The reason parameter can optionally be used to control the response to onClose, for example ignoring clickaway.

Signature:
function(event: React.SyntheticEvent<any> | Event, reason: string) => void
event: The event source of the callback.
reason: Can be: "timeout" (autoHideDuration expired), "clickaway", or "escapeKeyDown".", + "open": "If true, the component is shown.", + "resumeHideDuration": "The number of milliseconds to wait before dismissing after user interaction. If autoHideDuration prop isn't specified, it does nothing. If autoHideDuration prop is specified but resumeHideDuration isn't, we default to autoHideDuration / 2 ms." + }, + "classDescriptions": {} +} diff --git a/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled-zh.json b/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled-zh.json new file mode 100644 index 00000000000000..8cdfb6b8f156b8 --- /dev/null +++ b/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled-zh.json @@ -0,0 +1,15 @@ +{ + "componentDescription": "", + "propDescriptions": { + "autoHideDuration": "The number of milliseconds to wait before automatically calling the onClose function. onClose should then set the state of the open prop to hide the Snackbar. This behavior is disabled by default with the null value.", + "ClickAwayListenerProps": "Props applied to the ClickAwayListener element.", + "component": "The component used for the Root slot. Either a string to use a HTML element or a component. This is equivalent to components.Root. If both are provided, the component is used.", + "components": "The components used for each slot inside the Snackbar. Either a string to use a HTML element or a component.", + "componentsProps": "The props used for each slot inside the Snackbar.", + "disableWindowBlurListener": "If true, the autoHideDuration timer will expire even if the window is not focused.", + "onClose": "Callback fired when the component requests to be closed. Typically onClose is used to set state in the parent component, which is used to control the Snackbar open prop. The reason parameter can optionally be used to control the response to onClose, for example ignoring clickaway.

Signature:
function(event: React.SyntheticEvent<any> | Event, reason: string) => void
event: The event source of the callback.
reason: Can be: "timeout" (autoHideDuration expired), "clickaway", or "escapeKeyDown".", + "open": "If true, the component is shown.", + "resumeHideDuration": "The number of milliseconds to wait before dismissing after user interaction. If autoHideDuration prop isn't specified, it does nothing. If autoHideDuration prop is specified but resumeHideDuration isn't, we default to autoHideDuration / 2 ms." + }, + "classDescriptions": {} +} diff --git a/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json b/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json new file mode 100644 index 00000000000000..8cdfb6b8f156b8 --- /dev/null +++ b/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json @@ -0,0 +1,15 @@ +{ + "componentDescription": "", + "propDescriptions": { + "autoHideDuration": "The number of milliseconds to wait before automatically calling the onClose function. onClose should then set the state of the open prop to hide the Snackbar. This behavior is disabled by default with the null value.", + "ClickAwayListenerProps": "Props applied to the ClickAwayListener element.", + "component": "The component used for the Root slot. Either a string to use a HTML element or a component. This is equivalent to components.Root. If both are provided, the component is used.", + "components": "The components used for each slot inside the Snackbar. Either a string to use a HTML element or a component.", + "componentsProps": "The props used for each slot inside the Snackbar.", + "disableWindowBlurListener": "If true, the autoHideDuration timer will expire even if the window is not focused.", + "onClose": "Callback fired when the component requests to be closed. Typically onClose is used to set state in the parent component, which is used to control the Snackbar open prop. The reason parameter can optionally be used to control the response to onClose, for example ignoring clickaway.

Signature:
function(event: React.SyntheticEvent<any> | Event, reason: string) => void
event: The event source of the callback.
reason: Can be: "timeout" (autoHideDuration expired), "clickaway", or "escapeKeyDown".", + "open": "If true, the component is shown.", + "resumeHideDuration": "The number of milliseconds to wait before dismissing after user interaction. If autoHideDuration prop isn't specified, it does nothing. If autoHideDuration prop is specified but resumeHideDuration isn't, we default to autoHideDuration / 2 ms." + }, + "classDescriptions": {} +} diff --git a/docs/translations/translations.json b/docs/translations/translations.json index 7a25709baba9e9..b6766fdf560d86 100644 --- a/docs/translations/translations.json +++ b/docs/translations/translations.json @@ -207,6 +207,7 @@ "/base/react-no-ssr": "No SSR", "/base/react-popper": "Popper", "/base/react-portal": "Portal", + "/base/react-snackbar": "Snackbar", "/base/react-textarea-autosize": "Textarea autosize", "/base/react-trap-focus": "Trap focus", "/material-ui/getting-started": "Getting started", diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 94507b963f5b09..34007de1979cf1 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -14,7 +14,16 @@ const useUtilityClasses = () => { return composeClasses(slots, getSnackbarUnstyledUtilityClass, undefined); }; - +/** + * + * Demos: + * + * - [Snackbar](https://mui.com/base/react-snackbar/) + * + * API: + * + * - [SnackbarUnstyled API](https://mui.com/base/api/snackbar-unstyled/) + */ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( props: SnackbarUnstyledProps, ref: React.ForwardedRef, From 7c35ec208acd4cb75d16952088ec3c91602ae731 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 16 Jun 2022 18:42:52 +0530 Subject: [PATCH 15/71] add Basic usage docs --- .../components/snackbar/UnstyledSnackbar.js | 59 +++++++++++++++++++ .../components/snackbar/UnstyledSnackbar.tsx | 59 +++++++++++++++++++ .../snackbar/UnstyledSnackbar.tsx.preview | 8 +++ .../data/base/components/snackbar/snackbar.md | 10 ++++ 4 files changed, 136 insertions(+) create mode 100644 docs/data/base/components/snackbar/UnstyledSnackbar.js create mode 100644 docs/data/base/components/snackbar/UnstyledSnackbar.tsx create mode 100644 docs/data/base/components/snackbar/UnstyledSnackbar.tsx.preview diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.js b/docs/data/base/components/snackbar/UnstyledSnackbar.js new file mode 100644 index 00000000000000..083eba1b84c2ca --- /dev/null +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.js @@ -0,0 +1,59 @@ +import * as React from 'react'; +import { styled, keyframes } from '@mui/system'; +import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; + +const snackbarInLeft = keyframes` + from { + transform: translateX(-100%); + + } + to { + transform: translateX(0); + } +`; + +const StyledSnackbar = styled(SnackbarUnstyled)` + position: fixed; + z-index: 5500; + display: flex; + left: 10px; + bottom: 10px; + right: auto; + justify-content: center; + align-items: center; + max-width: 560px; + min-width: 300px; + background-color: #3182ce; + padding: 0.75rem; + color: white; + font-family: IBM Plex Sans, sans-serif; + animation: ${snackbarInLeft} 500ms; + transition: transform 0.3s ease-out; +`; + +export default function UnstyledSnackbar() { + const [open, setOpen] = React.useState(false); + + const handleClose = (_, reason) => { + if (reason === 'clickaway') { + return; + } + + setOpen(false); + }; + + const handleClick = () => { + setOpen(true); + }; + + return ( + + + +
Hello World
+
+
+ ); +} diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx new file mode 100644 index 00000000000000..f80197fdb6468c --- /dev/null +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx @@ -0,0 +1,59 @@ +import * as React from 'react'; +import { styled, keyframes } from '@mui/system'; +import SnackbarUnstyled, { SnackbarCloseReason } from '@mui/base/SnackbarUnstyled'; + +const snackbarInLeft = keyframes` + from { + transform: translateX(-100%); + + } + to { + transform: translateX(0); + } +`; + +const StyledSnackbar = styled(SnackbarUnstyled)` + position: fixed; + z-index: 5500; + display: flex; + left: 10px; + bottom: 10px; + right: auto; + justify-content: center; + align-items: center; + max-width: 560px; + min-width: 300px; + background-color: #3182ce; + padding: 0.75rem; + color: white; + font-family: IBM Plex Sans, sans-serif; + animation: ${snackbarInLeft} 500ms; + transition: transform 0.3s ease-out; +`; + +export default function UnstyledSnackbar() { + const [open, setOpen] = React.useState(false); + + const handleClose = (_: any, reason: SnackbarCloseReason) => { + if (reason === 'clickaway') { + return; + } + + setOpen(false); + }; + + const handleClick = () => { + setOpen(true); + }; + + return ( + + + +
Hello World
+
+
+ ); +} diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx.preview b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx.preview new file mode 100644 index 00000000000000..d123b1e813e2bd --- /dev/null +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx.preview @@ -0,0 +1,8 @@ + + + +
Hello World
+
+
\ No newline at end of file diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index 3221106380696b..5dc2655834f8d9 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -9,3 +9,13 @@ packageName: '@mui/base' # Unstyled snackbar

The SnackbarUnstyled component provides brief notifications.

+ +```js +import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; +``` + +{{"component": "modules/components/ComponentLinkHeader.js", "design": false}} + +## Basic usage + +{{"demo": "UnstyledSnackbar.js", "defaultCodeOpen": false}} From 28e45ab4fa1bade21337c85efdcc8304c5ccf5b7 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 17 Jun 2022 10:59:05 +0530 Subject: [PATCH 16/71] children prop should accept string --- docs/data/base/components/snackbar/UnstyledSnackbar.js | 2 +- docs/data/base/components/snackbar/UnstyledSnackbar.tsx | 2 +- docs/data/base/components/snackbar/UnstyledSnackbar.tsx.preview | 2 +- .../mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.js b/docs/data/base/components/snackbar/UnstyledSnackbar.js index 083eba1b84c2ca..c6f484918e2dc4 100644 --- a/docs/data/base/components/snackbar/UnstyledSnackbar.js +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.js @@ -52,7 +52,7 @@ export default function UnstyledSnackbar() { Open snackbar -
Hello World
+ Hello World
); diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx index f80197fdb6468c..10b69ac665b442 100644 --- a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx @@ -52,7 +52,7 @@ export default function UnstyledSnackbar() { Open snackbar -
Hello World
+ Hello World
); diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx.preview b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx.preview index d123b1e813e2bd..eed6c6ff410eb2 100644 --- a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx.preview +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx.preview @@ -3,6 +3,6 @@ Open snackbar -
Hello World
+ Hello World
\ No newline at end of file diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 776e490684f36e..91d3b16b79e57b 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -13,7 +13,7 @@ export interface SnackbarUnstyledProps { */ autoHideDuration?: number | null; className?: string; - children?: React.ReactElement; + children?: React.ReactNode; /** * Props applied to the `ClickAwayListener` element. */ From 0df128b404d8dabc75e1165c94bd15ffd4e130d7 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 17 Jun 2022 18:03:21 +0530 Subject: [PATCH 17/71] yarn proptypes --- packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 34007de1979cf1..33209833742ba9 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -226,7 +226,7 @@ SnackbarUnstyled.propTypes /* remove-proptypes */ = { /** * @ignore */ - children: PropTypes.element, + children: PropTypes.node, /** * @ignore */ From dd3714d7544e779771c81d6f5d2039d6c56a25fd Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 20 Jun 2022 15:53:38 +0530 Subject: [PATCH 18/71] add Transition component demo --- .../snackbar/TransitionComponentSnackbar.js | 198 ++++++++++++++++++ .../snackbar/TransitionComponentSnackbar.tsx | 193 +++++++++++++++++ .../data/base/components/snackbar/snackbar.md | 6 + .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 21 +- 4 files changed, 415 insertions(+), 3 deletions(-) create mode 100644 docs/data/base/components/snackbar/TransitionComponentSnackbar.js create mode 100644 docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx diff --git a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js new file mode 100644 index 00000000000000..56b46d064cc1d7 --- /dev/null +++ b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js @@ -0,0 +1,198 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { styled } from '@mui/system'; +import { Transition } from 'react-transition-group'; + +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import CloseIcon from '@mui/icons-material/Close'; +import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; + +const StyledSnackbar = styled(SnackbarUnstyled)` + position: fixed; + z-index: 5500; + display: flex; + top: 15px; + right: 15px; + max-width: 560px; + min-width: 300px; +`; + +const SnackbarContent = styled('div')` + display: flex; + overflow: hidden; + padding: 0.75rem 2rem 0.75rem 1rem; + background-color: #9ae6b4; + color: #171923; + border-radius: 0.375rem; + text-align: start; + position: relative; + + & .snackbar-message { + flex: 1 1 0%; + max-width: 100%; + } + + & .snackbar-title { + font-weight: 700; + line-height: 1.5rem; + margin-right: 0.5rem; + } + + & .snackbar-description { + line-height: 1.5rem; + } + + & .snackbar-close-icon { + cursor: pointer; + font-size: 10px; + position: absolute; + top: 0.25rem; + right: 0.25rem; + width: 1.25rem; + height: 1.5rem; + display: flex; + align-items: center; + justify-content: center; + } +`; + +const positioningStyles = { + entering: 'translateX(200px)', + entered: 'translateX(0)', + exiting: 'translateX(200px)', + exited: 'translateX(200px)', + unmounted: 'translateX(200px)', +}; + +const Transform = ({ open, children, onEnter, onExit }) => { + return ( + + {(status) => { + return ( +
+ {children} +
+ ); + }} +
+ ); +}; + +Transform.propTypes = { + children: PropTypes.node, + onEnter: PropTypes.func, + onExit: PropTypes.func, + open: PropTypes.bool, +}; + +const statusStyles = { + entered: { + opacity: 1, + }, + entering: { + opacity: 0, + }, + exited: { + opacity: 0, + }, + exiting: { + opacity: 0, + }, + unmounted: { + opacity: 0, + }, +}; + +const FadeTransform = (props) => { + const { open, children, onEnter, onExit } = props; + + return ( + + {(status) => ( +
+ + {children} + +
+ )} +
+ ); +}; + +FadeTransform.propTypes = { + children: PropTypes.node, + onEnter: PropTypes.func, + onExit: PropTypes.func, + open: PropTypes.bool, +}; + +export default function TransitionComponentSnackbar() { + const [open, setOpen] = React.useState(false); + + const handleClose = (_, reason) => { + if (reason === 'clickaway') { + return; + } + + setOpen(false); + }; + + const handleClick = () => { + setOpen(true); + }; + + return ( + + + + + +
+
Account Created
+
+ We've created your account for you +
+
+ +
+
+
+ ); +} diff --git a/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx b/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx new file mode 100644 index 00000000000000..8b3d95b02b1d2b --- /dev/null +++ b/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx @@ -0,0 +1,193 @@ +import * as React from 'react'; +import { styled } from '@mui/system'; +import { Transition } from 'react-transition-group'; +import { TransitionProps } from 'react-transition-group/Transition'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import CloseIcon from '@mui/icons-material/Close'; +import SnackbarUnstyled, { + SnackbarCloseReason, + SnackbarUnstyledProps, +} from '@mui/base/SnackbarUnstyled'; + +const StyledSnackbar = styled(SnackbarUnstyled)` + position: fixed; + z-index: 5500; + display: flex; + top: 15px; + right: 15px; + max-width: 560px; + min-width: 300px; +`; + +const SnackbarContent = styled('div')` + display: flex; + overflow: hidden; + padding: 0.75rem 2rem 0.75rem 1rem; + background-color: #9ae6b4; + color: #171923; + border-radius: 0.375rem; + text-align: start; + position: relative; + + & .snackbar-message { + flex: 1 1 0%; + max-width: 100%; + } + + & .snackbar-title { + font-weight: 700; + line-height: 1.5rem; + margin-right: 0.5rem; + } + + & .snackbar-description { + line-height: 1.5rem; + } + + & .snackbar-close-icon { + cursor: pointer; + font-size: 10px; + position: absolute; + top: 0.25rem; + right: 0.25rem; + width: 1.25rem; + height: 1.5rem; + display: flex; + align-items: center; + justify-content: center; + } +`; + +const positioningStyles = { + entering: 'translateX(200px)', + entered: 'translateX(0)', + exiting: 'translateX(200px)', + exited: 'translateX(200px)', + unmounted: 'translateX(200px)', +}; + +interface TransformProps { + children: SnackbarUnstyledProps['children']; + open: SnackbarUnstyledProps['open']; + onEnter: TransitionProps['onEnter']; + onExit: TransitionProps['onExit']; +} + +const Transform = ({ open, children, onEnter, onExit }: TransformProps) => { + return ( + + {(status) => { + return ( +
+ {children} +
+ ); + }} +
+ ); +}; + +const statusStyles = { + entered: { + opacity: 1, + }, + entering: { + opacity: 0, + }, + exited: { + opacity: 0, + }, + exiting: { + opacity: 0, + }, + unmounted: { + opacity: 0, + }, +}; + +const FadeTransform = (props: TransformProps) => { + const { open, children, onEnter, onExit } = props; + + return ( + + {(status) => ( +
+ + {children} + +
+ )} +
+ ); +}; + +export default function TransitionComponentSnackbar() { + const [open, setOpen] = React.useState(false); + + const handleClose = (_: any, reason: SnackbarCloseReason) => { + if (reason === 'clickaway') { + return; + } + + setOpen(false); + }; + + const handleClick = () => { + setOpen(true); + }; + + return ( + + + + + +
+
Account Created
+
+ We've created your account for you +
+
+ +
+
+
+ ); +} diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index 5dc2655834f8d9..74feeea975c8ad 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -19,3 +19,9 @@ import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; ## Basic usage {{"demo": "UnstyledSnackbar.js", "defaultCodeOpen": false}} + +## Providing `Transition` component + +You can also provide a Transition component in `components.Transition` prop if you want to apply animations to your snackbar. + +{{"demo": "TransitionComponentSnackbar.js", "defaultCodeOpen": false}} diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 33209833742ba9..c339be90220c74 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -50,6 +50,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( const classes = useUtilityClasses(); const timerAutoHide = React.useRef>(); + const [exited, setExited] = React.useState(true); const Root = component || components.Root || 'div'; @@ -80,7 +81,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( return () => { document.removeEventListener('keydown', handleKeyDown); }; - }, [open, onClose]); + }, [exited, open, onClose]); const handleClose = useEventCallback( (event: Event | React.SyntheticEvent | null, reason: SnackbarCloseReason) => { @@ -178,8 +179,16 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( return undefined; }, [disableWindowBlurListener, handleResume, open]); + const handleExited = () => { + setExited(true); + }; + + const handleEnter = () => { + setExited(false); + }; + // So we only render active snackbars. - if (!open) { + if (!open && exited) { return null; } @@ -201,7 +210,13 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( {TransitionComponent ? ( - {children} + + {children} + ) : ( children )} From e53ba677349081cd7e99685d7773c6e835ac1acb Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 20 Jun 2022 16:15:38 +0530 Subject: [PATCH 19/71] improve SnackbarUnstyled types --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 8 ++++---- .../SnackbarUnstyled/SnackbarUnstyled.types.ts | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index c339be90220c74..984c71b7e99442 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -132,7 +132,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( } }, [autoHideDuration, resumeHideDuration, setAutoHideTimer]); - const handleBlur = (event: React.FocusEvent) => { + const handleBlur = (event: React.FocusEvent) => { const onBlurCallback = componentsProps.root?.onBlur || onBlur; if (onBlurCallback) { onBlurCallback(event); @@ -140,7 +140,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( handleResume(); }; - const handleFocus = (event: React.FocusEvent) => { + const handleFocus = (event: React.FocusEvent) => { const onFocusCallback = componentsProps.root?.onFocus || onFocus; if (onFocusCallback) { onFocusCallback(event); @@ -148,7 +148,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( handlePause(); }; - const handleMouseEnter = (event: React.MouseEvent) => { + const handleMouseEnter = (event: React.MouseEvent) => { const onMouseEnterCallback = componentsProps.root?.onMouseEnter || onMouseEnter; if (onMouseEnterCallback) { onMouseEnterCallback(event); @@ -156,7 +156,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( handlePause(); }; - const handleMouseLeave = (event: React.MouseEvent) => { + const handleMouseLeave = (event: React.MouseEvent) => { const onMouseLeaveCallback = componentsProps.root?.onMouseLeave || onMouseLeave; if (onMouseLeaveCallback) { onMouseLeaveCallback(event); diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 91d3b16b79e57b..e3676a8e7c3d36 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -1,9 +1,10 @@ import React from 'react'; +import { OverrideProps } from '@mui/types'; import { ClickAwayListenerProps } from '../ClickAwayListener'; export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; -export interface SnackbarUnstyledProps { +interface SnackbarUnstyledOwnProps { /** * The number of milliseconds to wait before automatically calling the * `onClose` function. `onClose` should then set the state of the `open` @@ -12,7 +13,6 @@ export interface SnackbarUnstyledProps { * @default null */ autoHideDuration?: number | null; - className?: string; children?: React.ReactNode; /** * Props applied to the `ClickAwayListener` element. @@ -46,7 +46,6 @@ export interface SnackbarUnstyledProps { * @default false */ disableWindowBlurListener?: boolean; - onBlur?: React.FocusEventHandler; /** * Callback fired when the component requests to be closed. * Typically `onClose` is used to set state in the parent component, @@ -58,9 +57,6 @@ export interface SnackbarUnstyledProps { * @param {string} reason Can be: `"timeout"` (`autoHideDuration` expired), `"clickaway"`, or `"escapeKeyDown"`. */ onClose?: (event: React.SyntheticEvent | Event | null, reason: SnackbarCloseReason) => void; - onFocus?: React.FocusEventHandler; - onMouseEnter?: React.MouseEventHandler; - onMouseLeave?: React.MouseEventHandler; /** * If `true`, the component is shown. */ @@ -73,3 +69,13 @@ export interface SnackbarUnstyledProps { */ resumeHideDuration?: number; } + +export interface SnackbarUnstyledTypeMap

{ + props: P & SnackbarUnstyledOwnProps; + defaultComponent: D; +} + +export type SnackbarUnstyledProps< + D extends React.ElementType = SnackbarUnstyledTypeMap['defaultComponent'], + P = {}, +> = OverrideProps, D>; From 8d3a9f7edcf471b2102d10ca50124cf5caa0c270 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 20 Jun 2022 16:42:16 +0530 Subject: [PATCH 20/71] refactor MUI Snackbar types to use types from SnackbarUnstyled --- .../snackbars/ConsecutiveSnackbars.tsx | 5 +- .../snackbars/CustomizedSnackbars.tsx | 5 +- .../components/snackbars/SimpleSnackbar.tsx | 5 +- .../mui-material/src/Snackbar/Snackbar.d.ts | 55 ++++--------------- 4 files changed, 24 insertions(+), 46 deletions(-) diff --git a/docs/data/material/components/snackbars/ConsecutiveSnackbars.tsx b/docs/data/material/components/snackbars/ConsecutiveSnackbars.tsx index 1e577154f46519..40421b68b57e46 100644 --- a/docs/data/material/components/snackbars/ConsecutiveSnackbars.tsx +++ b/docs/data/material/components/snackbars/ConsecutiveSnackbars.tsx @@ -38,7 +38,10 @@ export default function ConsecutiveSnackbars() { setSnackPack((prev) => [...prev, { message, key: new Date().getTime() }]); }; - const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => { + const handleClose = ( + event: React.SyntheticEvent | Event | null, + reason?: string, + ) => { if (reason === 'clickaway') { return; } diff --git a/docs/data/material/components/snackbars/CustomizedSnackbars.tsx b/docs/data/material/components/snackbars/CustomizedSnackbars.tsx index c0d670082e8892..ca92ff8152ad61 100644 --- a/docs/data/material/components/snackbars/CustomizedSnackbars.tsx +++ b/docs/data/material/components/snackbars/CustomizedSnackbars.tsx @@ -18,7 +18,10 @@ export default function CustomizedSnackbars() { setOpen(true); }; - const handleClose = (event?: React.SyntheticEvent | Event, reason?: string) => { + const handleClose = ( + event?: React.SyntheticEvent | Event | null, + reason?: string, + ) => { if (reason === 'clickaway') { return; } diff --git a/docs/data/material/components/snackbars/SimpleSnackbar.tsx b/docs/data/material/components/snackbars/SimpleSnackbar.tsx index 0380665fe5d8b1..036f6cba3e1e0f 100644 --- a/docs/data/material/components/snackbars/SimpleSnackbar.tsx +++ b/docs/data/material/components/snackbars/SimpleSnackbar.tsx @@ -11,7 +11,10 @@ export default function SimpleSnackbar() { setOpen(true); }; - const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => { + const handleClose = ( + event: React.SyntheticEvent | Event | null, + reason?: string, + ) => { if (reason === 'clickaway') { return; } diff --git a/packages/mui-material/src/Snackbar/Snackbar.d.ts b/packages/mui-material/src/Snackbar/Snackbar.d.ts index 0e065badef2fd0..a86338b49b66e5 100644 --- a/packages/mui-material/src/Snackbar/Snackbar.d.ts +++ b/packages/mui-material/src/Snackbar/Snackbar.d.ts @@ -1,8 +1,7 @@ import * as React from 'react'; import { SxProps } from '@mui/system'; -import { ClickAwayListenerProps } from '@mui/base/ClickAwayListener'; +import { SnackbarUnstyledProps } from '@mui/base/SnackbarUnstyled'; import { Theme } from '../styles'; -import { InternalStandardProps as StandardProps } from '..'; import { SnackbarContentProps } from '../SnackbarContent'; import { TransitionProps } from '../transitions/transition'; import { SnackbarClasses } from './snackbarClasses'; @@ -12,9 +11,18 @@ export interface SnackbarOrigin { horizontal: 'left' | 'center' | 'right'; } -export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; +export { SnackbarCloseReason } from '@mui/base/SnackbarUnstyled'; -export interface SnackbarProps extends StandardProps> { +export interface SnackbarProps + extends Pick< + SnackbarUnstyledProps, + | 'autoHideDuration' + | 'ClickAwayListenerProps' + | 'disableWindowBlurListener' + | 'onClose' + | 'open' + | 'resumeHideDuration' + > { /** * The action to display. It renders after the message, at the end of the snackbar. */ @@ -26,14 +34,6 @@ export interface SnackbarProps extends StandardProps; - /** - * Props applied to the `ClickAwayListener` element. - */ - ClickAwayListenerProps?: Partial; /** * Props applied to the [`SnackbarContent`](/material-ui/api/snackbar-content/) element. */ ContentProps?: Partial; - /** - * If `true`, the `autoHideDuration` timer will expire even if the window is not focused. - * @default false - */ - disableWindowBlurListener?: boolean; /** * When displaying multiple consecutive Snackbars from a parent rendering a single * , add the key prop to ensure independent treatment of each message. @@ -66,28 +57,6 @@ export interface SnackbarProps extends StandardProps | Event} event The event source of the callback. - * @param {string} reason Can be: `"timeout"` (`autoHideDuration` expired), `"clickaway"`, or `"escapeKeyDown"`. - */ - onClose?: (event: React.SyntheticEvent | Event, reason: SnackbarCloseReason) => void; - /** - * If `true`, the component is shown. - */ - open?: boolean; - /** - * The number of milliseconds to wait before dismissing after user interaction. - * If `autoHideDuration` prop isn't specified, it does nothing. - * If `autoHideDuration` prop is specified but `resumeHideDuration` isn't, - * we default to `autoHideDuration / 2` ms. - */ - resumeHideDuration?: number; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ From 873ee49969b70a530e70214125e1b4b14b2d7c99 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 20 Jun 2022 18:43:39 +0530 Subject: [PATCH 21/71] migrate MUI Core Snackbar component to use SnackbarUnstyled --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 26 ++- .../SnackbarUnstyled.types.ts | 6 +- .../mui-material/src/Snackbar/Snackbar.js | 201 +++--------------- 3 files changed, 56 insertions(+), 177 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 984c71b7e99442..7d7508517703c2 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -179,12 +179,24 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( return undefined; }, [disableWindowBlurListener, handleResume, open]); - const handleExited = () => { + const handleExited = (node: HTMLElement) => { setExited(true); + + const onExited = componentsProps.transition?.onExited; + + if (onExited) { + onExited(node); + } }; - const handleEnter = () => { + const handleEnter = (node: HTMLElement, isAppearing: boolean) => { setExited(false); + + const onEnter = componentsProps.transition?.onEnter; + + if (onEnter) { + onEnter(node, isAppearing); + } }; // So we only render active snackbars. @@ -193,10 +205,6 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( } const rootProps = { - onBlur: handleBlur, - onFocus: handleFocus, - onMouseEnter: handleMouseEnter, - onMouseLeave: handleMouseLeave, ...other, ...componentsProps.root, className: clsx(classes.root, className, componentsProps.root?.className), @@ -204,6 +212,10 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( // ClickAwayListener adds an `onClick` prop which results in the alert not being announced. // See https://github.com/mui/material-ui/issues/29080 role: 'presentation', + onBlur: handleBlur, + onFocus: handleFocus, + onMouseEnter: handleMouseEnter, + onMouseLeave: handleMouseLeave, }; return ( @@ -211,9 +223,9 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( {TransitionComponent ? ( {children} diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index e3676a8e7c3d36..311d234f218b53 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -4,7 +4,7 @@ import { ClickAwayListenerProps } from '../ClickAwayListener'; export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; -interface SnackbarUnstyledOwnProps { +interface SnackbarUnstyledOwnProps { /** * The number of milliseconds to wait before automatically calling the * `onClose` function. `onClose` should then set the state of the `open` @@ -39,7 +39,9 @@ interface SnackbarUnstyledOwnProps { */ componentsProps?: { root?: React.HTMLAttributes; - transition?: TransitionType; + transition?: { + [key: string]: any; + }; }; /** * If `true`, the `autoHideDuration` timer will expire even if the window is not focused. diff --git a/packages/mui-material/src/Snackbar/Snackbar.js b/packages/mui-material/src/Snackbar/Snackbar.js index 822fda844739df..e49b321528a758 100644 --- a/packages/mui-material/src/Snackbar/Snackbar.js +++ b/packages/mui-material/src/Snackbar/Snackbar.js @@ -2,11 +2,10 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; import { unstable_composeClasses as composeClasses } from '@mui/base'; -import ClickAwayListener from '@mui/base/ClickAwayListener'; +import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; import styled from '../styles/styled'; import useTheme from '../styles/useTheme'; import useThemeProps from '../styles/useThemeProps'; -import useEventCallback from '../utils/useEventCallback'; import capitalize from '../utils/capitalize'; import Grow from '../Grow'; import SnackbarContent from '../SnackbarContent'; @@ -107,173 +106,39 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { const ownerState = { ...props, anchorOrigin: { vertical, horizontal } }; const classes = useUtilityClasses(ownerState); - const timerAutoHide = React.useRef(); - const [exited, setExited] = React.useState(true); - - const handleClose = useEventCallback((...args) => { - if (onClose) { - onClose(...args); - } - }); - - const setAutoHideTimer = useEventCallback((autoHideDurationParam) => { - if (!onClose || autoHideDurationParam == null) { - return; - } - - clearTimeout(timerAutoHide.current); - timerAutoHide.current = setTimeout(() => { - handleClose(null, 'timeout'); - }, autoHideDurationParam); - }); - - React.useEffect(() => { - if (open) { - setAutoHideTimer(autoHideDuration); - } - - return () => { - 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 = () => { - 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(resumeHideDuration != null ? resumeHideDuration : autoHideDuration * 0.5); - } - }, [autoHideDuration, resumeHideDuration, setAutoHideTimer]); - - const handleFocus = (event) => { - if (onFocus) { - onFocus(event); - } - handlePause(); - }; - const handleMouseEnter = (event) => { - if (onMouseEnter) { - onMouseEnter(event); - } - handlePause(); - }; - - const handleBlur = (event) => { - if (onBlur) { - onBlur(event); - } - handleResume(); - }; - const handleMouseLeave = (event) => { - if (onMouseLeave) { - onMouseLeave(event); - } - handleResume(); - }; - - const handleClickAway = (event) => { - if (onClose) { - onClose(event, 'clickaway'); - } - }; - - const handleExited = (node) => { - setExited(true); - - if (onExited) { - onExited(node); - } - }; - - const handleEnter = (node, isAppearing) => { - setExited(false); - - if (onEnter) { - onEnter(node, isAppearing); - } - }; - - React.useEffect(() => { - // TODO: window global should be refactored here - 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]); - - React.useEffect(() => { - if (!open) { - return undefined; - } - - /** - * @param {KeyboardEvent} nativeEvent - */ - function handleKeyDown(nativeEvent) { - if (!nativeEvent.defaultPrevented) { - // IE11, Edge (prior to using Bink?) use 'Esc' - if (nativeEvent.key === 'Escape' || nativeEvent.key === 'Esc') { - // not calling `preventDefault` since we don't know if people may ignore this event e.g. a permanently open snackbar - if (onClose) { - onClose(nativeEvent, 'escapeKeyDown'); - } - } - } - } - - document.addEventListener('keydown', handleKeyDown); - - return () => { - document.removeEventListener('keydown', handleKeyDown); - }; - }, [exited, open, onClose]); - - // So we only render active snackbars. - if (!open && exited) { - return null; - } - return ( - - - - {children || } - - - + + {children || } + ); }); From d3154913c09673ca53ba10b4dfa66c5377781ed1 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 20 Jun 2022 18:54:52 +0530 Subject: [PATCH 22/71] fix types --- .../mui-material/src/Snackbar/Snackbar.d.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/mui-material/src/Snackbar/Snackbar.d.ts b/packages/mui-material/src/Snackbar/Snackbar.d.ts index a86338b49b66e5..a474e8b3bfbf13 100644 --- a/packages/mui-material/src/Snackbar/Snackbar.d.ts +++ b/packages/mui-material/src/Snackbar/Snackbar.d.ts @@ -2,6 +2,7 @@ import * as React from 'react'; import { SxProps } from '@mui/system'; import { SnackbarUnstyledProps } from '@mui/base/SnackbarUnstyled'; import { Theme } from '../styles'; +import { InternalStandardProps as StandardProps } from '..'; import { SnackbarContentProps } from '../SnackbarContent'; import { TransitionProps } from '../transitions/transition'; import { SnackbarClasses } from './snackbarClasses'; @@ -15,14 +16,15 @@ export { SnackbarCloseReason } from '@mui/base/SnackbarUnstyled'; export interface SnackbarProps extends Pick< - SnackbarUnstyledProps, - | 'autoHideDuration' - | 'ClickAwayListenerProps' - | 'disableWindowBlurListener' - | 'onClose' - | 'open' - | 'resumeHideDuration' - > { + SnackbarUnstyledProps, + | 'autoHideDuration' + | 'ClickAwayListenerProps' + | 'disableWindowBlurListener' + | 'onClose' + | 'open' + | 'resumeHideDuration' + >, + StandardProps> { /** * The action to display. It renders after the message, at the end of the snackbar. */ From 93b12608cbb48c957a21d21eab0624bc0cfa808c Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 20 Jun 2022 18:57:31 +0530 Subject: [PATCH 23/71] yarn docs:i18n --- docs/translations/translations.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/translations/translations.json b/docs/translations/translations.json index b6766fdf560d86..7263282358bcac 100644 --- a/docs/translations/translations.json +++ b/docs/translations/translations.json @@ -196,6 +196,8 @@ "/base/react-select": "Select", "/base/react-slider": "Slider", "/base/react-switch": "Switch", + "feedback": "Feedback", + "/base/react-snackbar": "Snackbar", "navigation": "Navigation", "/base/react-menu": "Menu", "/base/react-table-pagination": "Table pagination", @@ -207,7 +209,6 @@ "/base/react-no-ssr": "No SSR", "/base/react-popper": "Popper", "/base/react-portal": "Portal", - "/base/react-snackbar": "Snackbar", "/base/react-textarea-autosize": "Textarea autosize", "/base/react-trap-focus": "Trap focus", "/material-ui/getting-started": "Getting started", @@ -246,7 +247,6 @@ "/material-ui/react-table": "Table", "/material-ui/react-tooltip": "Tooltip", "/material-ui/react-typography": "Typography", - "feedback": "Feedback", "/material-ui/react-alert": "Alert", "/material-ui/react-backdrop": "Backdrop", "/material-ui/react-dialog": "Dialog", From 5626d6e0ab3d9a4a587589a5674fdaf5f43fddfe Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 20 Jun 2022 19:08:20 +0530 Subject: [PATCH 24/71] fix warning and add docs --- .../components/snackbar/TransitionComponentSnackbar.tsx | 5 +++++ docs/data/base/components/snackbar/snackbar.md | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx b/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx index 8b3d95b02b1d2b..280eacd939dbd5 100644 --- a/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx +++ b/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx @@ -74,6 +74,8 @@ interface TransformProps { } const Transform = ({ open, children, onEnter, onExit }: TransformProps) => { + const nodeRef = React.useRef(null); + return ( { appear onEnter={onEnter} onExit={onExit} + nodeRef={nodeRef} > {(status) => { return ( @@ -118,6 +121,7 @@ const statusStyles = { const FadeTransform = (props: TransformProps) => { const { open, children, onEnter, onExit } = props; + const nodeRef = React.useRef(null); return ( { appear onEnter={onEnter} onExit={onExit} + nodeRef={nodeRef} > {(status) => (

Date: Mon, 20 Jun 2022 20:30:41 +0530 Subject: [PATCH 25/71] yarn docs:typescript:formatted --- .../base/components/snackbar/TransitionComponentSnackbar.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js index 56b46d064cc1d7..0fa5ebd97c5d99 100644 --- a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js +++ b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js @@ -65,6 +65,8 @@ const positioningStyles = { }; const Transform = ({ open, children, onEnter, onExit }) => { + const nodeRef = React.useRef(null); + return ( { appear onEnter={onEnter} onExit={onExit} + nodeRef={nodeRef} > {(status) => { return ( @@ -116,6 +119,7 @@ const statusStyles = { const FadeTransform = (props) => { const { open, children, onEnter, onExit } = props; + const nodeRef = React.useRef(null); return ( { appear onEnter={onEnter} onExit={onExit} + nodeRef={nodeRef} > {(status) => (
Date: Mon, 20 Jun 2022 21:31:54 +0530 Subject: [PATCH 26/71] add OverrideableComponent --- .../mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 7d7508517703c2..8e57aaaba20075 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -1,9 +1,14 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; +import { OverridableComponent } from '@mui/types'; import { unstable_useEventCallback as useEventCallback } from '@mui/utils'; import ClickAwayListener from '../ClickAwayListener'; -import { SnackbarCloseReason, SnackbarUnstyledProps } from './SnackbarUnstyled.types'; +import { + SnackbarCloseReason, + SnackbarUnstyledProps, + SnackbarUnstyledTypeMap, +} from './SnackbarUnstyled.types'; import composeClasses from '../composeClasses'; import { getSnackbarUnstyledUtilityClass } from './snackbarUnstyledClasses'; @@ -235,7 +240,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( ); -}); +}) as OverridableComponent; SnackbarUnstyled.propTypes /* remove-proptypes */ = { // ----------------------------- Warning -------------------------------- From 73404d6d6e5ce14da0c3d3de87cf6e427183cc53 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 20 Jun 2022 22:15:24 +0530 Subject: [PATCH 27/71] fix types --- docs/pages/experiments/material-ui/snackbar.tsx | 2 +- .../api-docs/snackbar-unstyled/snackbar-unstyled.json | 2 +- packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx | 5 ++--- .../mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts | 6 ------ 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/docs/pages/experiments/material-ui/snackbar.tsx b/docs/pages/experiments/material-ui/snackbar.tsx index d67f71597ef864..df22b200f7c847 100644 --- a/docs/pages/experiments/material-ui/snackbar.tsx +++ b/docs/pages/experiments/material-ui/snackbar.tsx @@ -48,7 +48,7 @@ export default function SnackbarCssVars() { setOpen(true); }; - const handleClose = (event?: React.SyntheticEvent | Event, reason?: string) => { + const handleClose = (event?: React.SyntheticEvent | Event | null, reason?: string) => { if (reason === 'clickaway') { return; } diff --git a/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json b/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json index 8cdfb6b8f156b8..b3a6c23491ca15 100644 --- a/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json +++ b/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json @@ -3,7 +3,7 @@ "propDescriptions": { "autoHideDuration": "The number of milliseconds to wait before automatically calling the onClose function. onClose should then set the state of the open prop to hide the Snackbar. This behavior is disabled by default with the null value.", "ClickAwayListenerProps": "Props applied to the ClickAwayListener element.", - "component": "The component used for the Root slot. Either a string to use a HTML element or a component. This is equivalent to components.Root. If both are provided, the component is used.", + "component": "The component used for the root node. Either a string to use a HTML element or a component.", "components": "The components used for each slot inside the Snackbar. Either a string to use a HTML element or a component.", "componentsProps": "The props used for each slot inside the Snackbar.", "disableWindowBlurListener": "If true, the autoHideDuration timer will expire even if the window is not focused.", diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 8e57aaaba20075..b09fea24f7fb72 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -30,7 +30,7 @@ const useUtilityClasses = () => { * - [SnackbarUnstyled API](https://mui.com/base/api/snackbar-unstyled/) */ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( - props: SnackbarUnstyledProps, + props: SnackbarUnstyledProps & { component?: React.ElementType }, ref: React.ForwardedRef, ) { const { @@ -268,9 +268,8 @@ SnackbarUnstyled.propTypes /* remove-proptypes */ = { */ ClickAwayListenerProps: PropTypes.object, /** - * The component used for the Root slot. + * The component used for the root node. * Either a string to use a HTML element or a component. - * This is equivalent to `components.Root`. If both are provided, the `component` is used. */ component: PropTypes.elementType, /** diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 311d234f218b53..f4d8988a0c88b6 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -18,12 +18,6 @@ interface SnackbarUnstyledOwnProps { * Props applied to the `ClickAwayListener` element. */ ClickAwayListenerProps?: Partial; - /** - * The component used for the Root slot. - * Either a string to use a HTML element or a component. - * This is equivalent to `components.Root`. If both are provided, the `component` is used. - */ - component?: React.ElementType; /** * The components used for each slot inside the Snackbar. * Either a string to use a HTML element or a component. From 31d7bb943835964bade8da792e8a4e6775afbdb3 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 20 Jun 2022 22:35:07 +0530 Subject: [PATCH 28/71] fix style identation --- .../base/components/snackbar/UnstyledSnackbar.js | 12 ++++++------ .../base/components/snackbar/UnstyledSnackbar.tsx | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.js b/docs/data/base/components/snackbar/UnstyledSnackbar.js index c6f484918e2dc4..469be4192ee9d4 100644 --- a/docs/data/base/components/snackbar/UnstyledSnackbar.js +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.js @@ -4,12 +4,12 @@ import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; const snackbarInLeft = keyframes` from { - transform: translateX(-100%); - - } - to { - transform: translateX(0); - } + transform: translateX(-100%); + } + + to { + transform: translateX(0); + } `; const StyledSnackbar = styled(SnackbarUnstyled)` diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx index 10b69ac665b442..27a080ce50cb9e 100644 --- a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx @@ -4,12 +4,12 @@ import SnackbarUnstyled, { SnackbarCloseReason } from '@mui/base/SnackbarUnstyle const snackbarInLeft = keyframes` from { - transform: translateX(-100%); - - } - to { - transform: translateX(0); - } + transform: translateX(-100%); + } + + to { + transform: translateX(0); + } `; const StyledSnackbar = styled(SnackbarUnstyled)` From 79dbfb3dd766c7c76deded388dda21e6bb1f3964 Mon Sep 17 00:00:00 2001 From: danilo leal <67129314+danilo-leal@users.noreply.github.com> Date: Wed, 22 Jun 2022 23:22:23 -0300 Subject: [PATCH 29/71] customize simple snackbar --- .../components/snackbar/UnstyledSnackbar.js | 44 ++++++++++++++----- .../components/snackbar/UnstyledSnackbar.tsx | 44 ++++++++++++++----- 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.js b/docs/data/base/components/snackbar/UnstyledSnackbar.js index 469be4192ee9d4..e2fa166db6196d 100644 --- a/docs/data/base/components/snackbar/UnstyledSnackbar.js +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.js @@ -2,7 +2,18 @@ import * as React from 'react'; import { styled, keyframes } from '@mui/system'; import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; -const snackbarInLeft = keyframes` +const blue = { + 100: '#DAECFF', + 400: '#3399FF', + 600: '#0072E5', + 900: '#003A75', +}; + +const grey = { + 200: '#E0E3E7', +}; + +const snackbarInRight = keyframes` from { transform: translateX(-100%); } @@ -12,24 +23,33 @@ const snackbarInLeft = keyframes` } `; -const StyledSnackbar = styled(SnackbarUnstyled)` +const StyledSnackbar = styled(SnackbarUnstyled)( + ({ theme }) => ` position: fixed; z-index: 5500; display: flex; - left: 10px; - bottom: 10px; - right: auto; - justify-content: center; - align-items: center; + right: 16px; + bottom: 16px; + left: auto; + justify-content: start; max-width: 560px; min-width: 300px; - background-color: #3182ce; + background-color: ${theme.palette.mode === 'dark' ? blue[600] : blue[100]}; + border-radius: 8px; + border: 1px solid ${blue[400]}; + box-shadow: ${ + theme.palette.mode === 'dark' + ? `0 5px 13px -3px rgba(0,0,0,0.4)` + : `0 5px 13px -3px ${grey[200]}` + }; padding: 0.75rem; - color: white; + color: ${theme.palette.mode === 'dark' ? blue[600] : blue[900]}; font-family: IBM Plex Sans, sans-serif; - animation: ${snackbarInLeft} 500ms; - transition: transform 0.3s ease-out; -`; + font-weight: 600; + animation: ${snackbarInRight} 500ms; + transition: transform 0.2s ease-out; + `, +); export default function UnstyledSnackbar() { const [open, setOpen] = React.useState(false); diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx index 27a080ce50cb9e..6c516fd8d06572 100644 --- a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx @@ -2,7 +2,18 @@ import * as React from 'react'; import { styled, keyframes } from '@mui/system'; import SnackbarUnstyled, { SnackbarCloseReason } from '@mui/base/SnackbarUnstyled'; -const snackbarInLeft = keyframes` +const blue = { + 100: '#DAECFF', + 400: '#3399FF', + 600: '#0072E5', + 900: '#003A75', +}; + +const grey = { + 200: '#E0E3E7', +}; + +const snackbarInRight = keyframes` from { transform: translateX(-100%); } @@ -12,24 +23,33 @@ const snackbarInLeft = keyframes` } `; -const StyledSnackbar = styled(SnackbarUnstyled)` +const StyledSnackbar = styled(SnackbarUnstyled)( + ({ theme }) => ` position: fixed; z-index: 5500; display: flex; - left: 10px; - bottom: 10px; - right: auto; - justify-content: center; - align-items: center; + right: 16px; + bottom: 16px; + left: auto; + justify-content: start; max-width: 560px; min-width: 300px; - background-color: #3182ce; + background-color: ${theme.palette.mode === 'dark' ? blue[600] : blue[100]}; + border-radius: 8px; + border: 1px solid ${blue[400]}; + box-shadow: ${ + theme.palette.mode === 'dark' + ? `0 5px 13px -3px rgba(0,0,0,0.4)` + : `0 5px 13px -3px ${grey[200]}` + }; padding: 0.75rem; - color: white; + color: ${theme.palette.mode === 'dark' ? blue[600] : blue[900]}; font-family: IBM Plex Sans, sans-serif; - animation: ${snackbarInLeft} 500ms; - transition: transform 0.3s ease-out; -`; + font-weight: 600; + animation: ${snackbarInRight} 500ms; + transition: transform 0.2s ease-out; + `, +); export default function UnstyledSnackbar() { const [open, setOpen] = React.useState(false); From 4274c96546c39b5dde367444b4233be9f789e140 Mon Sep 17 00:00:00 2001 From: danilo leal <67129314+danilo-leal@users.noreply.github.com> Date: Wed, 22 Jun 2022 23:32:36 -0300 Subject: [PATCH 30/71] customizing the second demo --- .../snackbar/TransitionComponentSnackbar.js | 46 ++++++++++++---- .../snackbar/TransitionComponentSnackbar.tsx | 54 +++++++++++++------ .../components/snackbar/UnstyledSnackbar.js | 8 +-- .../components/snackbar/UnstyledSnackbar.tsx | 8 +-- 4 files changed, 82 insertions(+), 34 deletions(-) diff --git a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js index 0fa5ebd97c5d99..527ed31a7f5ffa 100644 --- a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js +++ b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js @@ -7,23 +7,45 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import CloseIcon from '@mui/icons-material/Close'; import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; +const blue = { + 50: '#F0F7FF', + 100: '#DAECFF', + 400: '#3399FF', + 600: '#0072E5', + 900: '#003A75', +}; + +const grey = { + 200: '#E0E3E7', + 800: '#2D3843', +}; + const StyledSnackbar = styled(SnackbarUnstyled)` position: fixed; z-index: 5500; display: flex; - top: 15px; - right: 15px; + bottom: 16px; + right: 16px; max-width: 560px; min-width: 300px; `; -const SnackbarContent = styled('div')` +const SnackbarContent = styled('div')( + ({ theme }) => ` display: flex; overflow: hidden; - padding: 0.75rem 2rem 0.75rem 1rem; - background-color: #9ae6b4; - color: #171923; - border-radius: 0.375rem; + background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]}; + border-radius: 8px; + border: 1px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[400]}; + box-shadow: ${ + theme.palette.mode === 'dark' + ? `0 5px 13px -3px rgba(0,0,0,0.4)` + : `0 5px 13px -3px ${grey[200]}` + }; + padding: 0.875rem; + color: ${theme.palette.mode === 'dark' ? '#fff' : blue[900]}; + font-family: IBM Plex Sans, sans-serif; + font-weight: 500; text-align: start; position: relative; @@ -33,28 +55,30 @@ const SnackbarContent = styled('div')` } & .snackbar-title { - font-weight: 700; line-height: 1.5rem; margin-right: 0.5rem; } & .snackbar-description { line-height: 1.5rem; + font-weight: 400; + color: ${theme.palette.mode === 'dark' ? blue[100] : grey[800]}; } & .snackbar-close-icon { cursor: pointer; font-size: 10px; position: absolute; - top: 0.25rem; - right: 0.25rem; + top: 0.725rem; + right: 0.725rem; width: 1.25rem; height: 1.5rem; display: flex; align-items: center; justify-content: center; } -`; + `, +); const positioningStyles = { entering: 'translateX(200px)', diff --git a/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx b/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx index 280eacd939dbd5..cc85e928cfae71 100644 --- a/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx +++ b/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx @@ -2,30 +2,52 @@ import * as React from 'react'; import { styled } from '@mui/system'; import { Transition } from 'react-transition-group'; import { TransitionProps } from 'react-transition-group/Transition'; -import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; import CloseIcon from '@mui/icons-material/Close'; import SnackbarUnstyled, { SnackbarCloseReason, SnackbarUnstyledProps, } from '@mui/base/SnackbarUnstyled'; +const blue = { + 50: '#F0F7FF', + 100: '#DAECFF', + 400: '#3399FF', + 600: '#0072E5', + 900: '#003A75', +}; + +const grey = { + 200: '#E0E3E7', + 800: '#2D3843', +}; + const StyledSnackbar = styled(SnackbarUnstyled)` position: fixed; z-index: 5500; display: flex; - top: 15px; - right: 15px; + bottom: 16px; + right: 16px; max-width: 560px; min-width: 300px; `; -const SnackbarContent = styled('div')` +const SnackbarContent = styled('div')( + ({ theme }) => ` display: flex; overflow: hidden; - padding: 0.75rem 2rem 0.75rem 1rem; - background-color: #9ae6b4; - color: #171923; - border-radius: 0.375rem; + background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]}; + border-radius: 8px; + border: 1px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[400]}; + box-shadow: ${ + theme.palette.mode === 'dark' + ? `0 5px 13px -3px rgba(0,0,0,0.4)` + : `0 5px 13px -3px ${grey[200]}` + }; + padding: 0.875rem; + color: ${theme.palette.mode === 'dark' ? '#fff' : blue[900]}; + font-family: IBM Plex Sans, sans-serif; + font-weight: 500; text-align: start; position: relative; @@ -35,28 +57,30 @@ const SnackbarContent = styled('div')` } & .snackbar-title { - font-weight: 700; line-height: 1.5rem; margin-right: 0.5rem; } & .snackbar-description { line-height: 1.5rem; + font-weight: 400; + color: ${theme.palette.mode === 'dark' ? blue[100] : grey[800]}; } & .snackbar-close-icon { cursor: pointer; font-size: 10px; position: absolute; - top: 0.25rem; - right: 0.25rem; + top: 0.725rem; + right: 0.725rem; width: 1.25rem; height: 1.5rem; display: flex; align-items: center; justify-content: center; } -`; + `, +); const positioningStyles = { entering: 'translateX(200px)', @@ -176,7 +200,7 @@ export default function TransitionComponentSnackbar() { componentsProps={{ transition: { open } }} > -
-
Account Created
+
Notifications sent
- We've created your account for you + All your notifications were sent to the desired adress.
diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.js b/docs/data/base/components/snackbar/UnstyledSnackbar.js index e2fa166db6196d..2c6c97b6e95c26 100644 --- a/docs/data/base/components/snackbar/UnstyledSnackbar.js +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.js @@ -3,7 +3,7 @@ import { styled, keyframes } from '@mui/system'; import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; const blue = { - 100: '#DAECFF', + 50: '#F0F7FF', 400: '#3399FF', 600: '#0072E5', 900: '#003A75', @@ -34,16 +34,16 @@ const StyledSnackbar = styled(SnackbarUnstyled)( justify-content: start; max-width: 560px; min-width: 300px; - background-color: ${theme.palette.mode === 'dark' ? blue[600] : blue[100]}; + background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]}; border-radius: 8px; - border: 1px solid ${blue[400]}; + border: 1px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[400]}; box-shadow: ${ theme.palette.mode === 'dark' ? `0 5px 13px -3px rgba(0,0,0,0.4)` : `0 5px 13px -3px ${grey[200]}` }; padding: 0.75rem; - color: ${theme.palette.mode === 'dark' ? blue[600] : blue[900]}; + color: ${theme.palette.mode === 'dark' ? '#fff' : blue[900]}; font-family: IBM Plex Sans, sans-serif; font-weight: 600; animation: ${snackbarInRight} 500ms; diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx index 6c516fd8d06572..f59aadcdef42d3 100644 --- a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx @@ -3,7 +3,7 @@ import { styled, keyframes } from '@mui/system'; import SnackbarUnstyled, { SnackbarCloseReason } from '@mui/base/SnackbarUnstyled'; const blue = { - 100: '#DAECFF', + 50: '#F0F7FF', 400: '#3399FF', 600: '#0072E5', 900: '#003A75', @@ -34,16 +34,16 @@ const StyledSnackbar = styled(SnackbarUnstyled)( justify-content: start; max-width: 560px; min-width: 300px; - background-color: ${theme.palette.mode === 'dark' ? blue[600] : blue[100]}; + background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]}; border-radius: 8px; - border: 1px solid ${blue[400]}; + border: 1px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[400]}; box-shadow: ${ theme.palette.mode === 'dark' ? `0 5px 13px -3px rgba(0,0,0,0.4)` : `0 5px 13px -3px ${grey[200]}` }; padding: 0.75rem; - color: ${theme.palette.mode === 'dark' ? blue[600] : blue[900]}; + color: ${theme.palette.mode === 'dark' ? '#fff' : blue[900]}; font-family: IBM Plex Sans, sans-serif; font-weight: 600; animation: ${snackbarInRight} 500ms; From 74b9fe56677082c439c72c8d88bf3e31df1a4b7d Mon Sep 17 00:00:00 2001 From: danilo leal <67129314+danilo-leal@users.noreply.github.com> Date: Wed, 22 Jun 2022 23:33:32 -0300 Subject: [PATCH 31/71] typescript:formatted --- .../components/snackbar/TransitionComponentSnackbar.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js index 527ed31a7f5ffa..69df18293b5486 100644 --- a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js +++ b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { styled } from '@mui/system'; import { Transition } from 'react-transition-group'; -import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; import CloseIcon from '@mui/icons-material/Close'; import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; @@ -205,7 +205,7 @@ export default function TransitionComponentSnackbar() { componentsProps={{ transition: { open } }} > -
-
Account Created
+
Notifications sent
- We've created your account for you + All your notifications were sent to the desired adress.
From a9d6dba15bd08e1ce34c2b5758db7598f4cff0d6 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 23 Jun 2022 18:43:31 +0530 Subject: [PATCH 32/71] fix transition animation in demo --- .../components/snackbar/UnstyledSnackbar.js | 48 +++++++++---------- .../components/snackbar/UnstyledSnackbar.tsx | 48 +++++++++---------- 2 files changed, 46 insertions(+), 50 deletions(-) diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.js b/docs/data/base/components/snackbar/UnstyledSnackbar.js index 2c6c97b6e95c26..f357e355a394b5 100644 --- a/docs/data/base/components/snackbar/UnstyledSnackbar.js +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import { styled, keyframes } from '@mui/system'; +import { styled, keyframes, css } from '@mui/system'; import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; const blue = { @@ -15,7 +15,7 @@ const grey = { const snackbarInRight = keyframes` from { - transform: translateX(-100%); + transform: translateX(100%); } to { @@ -24,30 +24,28 @@ const snackbarInRight = keyframes` `; const StyledSnackbar = styled(SnackbarUnstyled)( - ({ theme }) => ` - position: fixed; - z-index: 5500; - display: flex; - right: 16px; - bottom: 16px; - left: auto; - justify-content: start; - max-width: 560px; - min-width: 300px; - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]}; - border-radius: 8px; - border: 1px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[400]}; - box-shadow: ${ - theme.palette.mode === 'dark' + ({ theme }) => css` + position: fixed; + z-index: 5500; + display: flex; + right: 16px; + bottom: 16px; + left: auto; + justify-content: start; + max-width: 560px; + min-width: 300px; + background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]}; + border-radius: 8px; + border: 1px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[400]}; + box-shadow: ${theme.palette.mode === 'dark' ? `0 5px 13px -3px rgba(0,0,0,0.4)` - : `0 5px 13px -3px ${grey[200]}` - }; - padding: 0.75rem; - color: ${theme.palette.mode === 'dark' ? '#fff' : blue[900]}; - font-family: IBM Plex Sans, sans-serif; - font-weight: 600; - animation: ${snackbarInRight} 500ms; - transition: transform 0.2s ease-out; + : `0 5px 13px -3px ${grey[200]}`}; + padding: 0.75rem; + color: ${theme.palette.mode === 'dark' ? '#fff' : blue[900]}; + font-family: IBM Plex Sans, sans-serif; + font-weight: 600; + animation: ${snackbarInRight} 500ms; + transition: transform 0.2s ease-out; `, ); diff --git a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx index f59aadcdef42d3..2e3b2fa448cce2 100644 --- a/docs/data/base/components/snackbar/UnstyledSnackbar.tsx +++ b/docs/data/base/components/snackbar/UnstyledSnackbar.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { styled, keyframes } from '@mui/system'; +import { styled, keyframes, css } from '@mui/system'; import SnackbarUnstyled, { SnackbarCloseReason } from '@mui/base/SnackbarUnstyled'; const blue = { @@ -15,7 +15,7 @@ const grey = { const snackbarInRight = keyframes` from { - transform: translateX(-100%); + transform: translateX(100%); } to { @@ -24,30 +24,28 @@ const snackbarInRight = keyframes` `; const StyledSnackbar = styled(SnackbarUnstyled)( - ({ theme }) => ` - position: fixed; - z-index: 5500; - display: flex; - right: 16px; - bottom: 16px; - left: auto; - justify-content: start; - max-width: 560px; - min-width: 300px; - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]}; - border-radius: 8px; - border: 1px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[400]}; - box-shadow: ${ - theme.palette.mode === 'dark' + ({ theme }) => css` + position: fixed; + z-index: 5500; + display: flex; + right: 16px; + bottom: 16px; + left: auto; + justify-content: start; + max-width: 560px; + min-width: 300px; + background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]}; + border-radius: 8px; + border: 1px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[400]}; + box-shadow: ${theme.palette.mode === 'dark' ? `0 5px 13px -3px rgba(0,0,0,0.4)` - : `0 5px 13px -3px ${grey[200]}` - }; - padding: 0.75rem; - color: ${theme.palette.mode === 'dark' ? '#fff' : blue[900]}; - font-family: IBM Plex Sans, sans-serif; - font-weight: 600; - animation: ${snackbarInRight} 500ms; - transition: transform 0.2s ease-out; + : `0 5px 13px -3px ${grey[200]}`}; + padding: 0.75rem; + color: ${theme.palette.mode === 'dark' ? '#fff' : blue[900]}; + font-family: IBM Plex Sans, sans-serif; + font-weight: 600; + animation: ${snackbarInRight} 500ms; + transition: transform 0.2s ease-out; `, ); From db26c453ea134247fe4b9a07b2b715b5c0ca35aa Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 24 Jun 2022 09:16:10 +0530 Subject: [PATCH 33/71] revert changes in Material UI Snackbar component --- .../snackbars/ConsecutiveSnackbars.tsx | 5 +- .../snackbars/CustomizedSnackbars.tsx | 5 +- .../components/snackbars/SimpleSnackbar.tsx | 5 +- .../experiments/material-ui/snackbar.tsx | 2 +- .../mui-material/src/Snackbar/Snackbar.d.ts | 55 +++-- .../mui-material/src/Snackbar/Snackbar.js | 201 +++++++++++++++--- 6 files changed, 214 insertions(+), 59 deletions(-) diff --git a/docs/data/material/components/snackbars/ConsecutiveSnackbars.tsx b/docs/data/material/components/snackbars/ConsecutiveSnackbars.tsx index 40421b68b57e46..1e577154f46519 100644 --- a/docs/data/material/components/snackbars/ConsecutiveSnackbars.tsx +++ b/docs/data/material/components/snackbars/ConsecutiveSnackbars.tsx @@ -38,10 +38,7 @@ export default function ConsecutiveSnackbars() { setSnackPack((prev) => [...prev, { message, key: new Date().getTime() }]); }; - const handleClose = ( - event: React.SyntheticEvent | Event | null, - reason?: string, - ) => { + const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => { if (reason === 'clickaway') { return; } diff --git a/docs/data/material/components/snackbars/CustomizedSnackbars.tsx b/docs/data/material/components/snackbars/CustomizedSnackbars.tsx index ca92ff8152ad61..c0d670082e8892 100644 --- a/docs/data/material/components/snackbars/CustomizedSnackbars.tsx +++ b/docs/data/material/components/snackbars/CustomizedSnackbars.tsx @@ -18,10 +18,7 @@ export default function CustomizedSnackbars() { setOpen(true); }; - const handleClose = ( - event?: React.SyntheticEvent | Event | null, - reason?: string, - ) => { + const handleClose = (event?: React.SyntheticEvent | Event, reason?: string) => { if (reason === 'clickaway') { return; } diff --git a/docs/data/material/components/snackbars/SimpleSnackbar.tsx b/docs/data/material/components/snackbars/SimpleSnackbar.tsx index 036f6cba3e1e0f..0380665fe5d8b1 100644 --- a/docs/data/material/components/snackbars/SimpleSnackbar.tsx +++ b/docs/data/material/components/snackbars/SimpleSnackbar.tsx @@ -11,10 +11,7 @@ export default function SimpleSnackbar() { setOpen(true); }; - const handleClose = ( - event: React.SyntheticEvent | Event | null, - reason?: string, - ) => { + const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => { if (reason === 'clickaway') { return; } diff --git a/docs/pages/experiments/material-ui/snackbar.tsx b/docs/pages/experiments/material-ui/snackbar.tsx index df22b200f7c847..d67f71597ef864 100644 --- a/docs/pages/experiments/material-ui/snackbar.tsx +++ b/docs/pages/experiments/material-ui/snackbar.tsx @@ -48,7 +48,7 @@ export default function SnackbarCssVars() { setOpen(true); }; - const handleClose = (event?: React.SyntheticEvent | Event | null, reason?: string) => { + const handleClose = (event?: React.SyntheticEvent | Event, reason?: string) => { if (reason === 'clickaway') { return; } diff --git a/packages/mui-material/src/Snackbar/Snackbar.d.ts b/packages/mui-material/src/Snackbar/Snackbar.d.ts index a474e8b3bfbf13..0e065badef2fd0 100644 --- a/packages/mui-material/src/Snackbar/Snackbar.d.ts +++ b/packages/mui-material/src/Snackbar/Snackbar.d.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { SxProps } from '@mui/system'; -import { SnackbarUnstyledProps } from '@mui/base/SnackbarUnstyled'; +import { ClickAwayListenerProps } from '@mui/base/ClickAwayListener'; import { Theme } from '../styles'; import { InternalStandardProps as StandardProps } from '..'; import { SnackbarContentProps } from '../SnackbarContent'; @@ -12,19 +12,9 @@ export interface SnackbarOrigin { horizontal: 'left' | 'center' | 'right'; } -export { SnackbarCloseReason } from '@mui/base/SnackbarUnstyled'; +export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; -export interface SnackbarProps - extends Pick< - SnackbarUnstyledProps, - | 'autoHideDuration' - | 'ClickAwayListenerProps' - | 'disableWindowBlurListener' - | 'onClose' - | 'open' - | 'resumeHideDuration' - >, - StandardProps> { +export interface SnackbarProps extends StandardProps> { /** * The action to display. It renders after the message, at the end of the snackbar. */ @@ -36,6 +26,14 @@ export interface SnackbarProps * @default { vertical: 'bottom', horizontal: 'left' } */ anchorOrigin?: SnackbarOrigin; + /** + * The number of milliseconds to wait before automatically calling the + * `onClose` function. `onClose` should then set the state of the `open` + * prop to hide the Snackbar. This behavior is disabled by default with + * the `null` value. + * @default null + */ + autoHideDuration?: number | null; /** * Replace the `SnackbarContent` component. */ @@ -44,10 +42,19 @@ export interface SnackbarProps * Override or extend the styles applied to the component. */ classes?: Partial; + /** + * Props applied to the `ClickAwayListener` element. + */ + ClickAwayListenerProps?: Partial; /** * Props applied to the [`SnackbarContent`](/material-ui/api/snackbar-content/) element. */ ContentProps?: Partial; + /** + * If `true`, the `autoHideDuration` timer will expire even if the window is not focused. + * @default false + */ + disableWindowBlurListener?: boolean; /** * When displaying multiple consecutive Snackbars from a parent rendering a single * , add the key prop to ensure independent treatment of each message. @@ -59,6 +66,28 @@ export interface SnackbarProps * The message to display. */ message?: SnackbarContentProps['message']; + /** + * Callback fired when the component requests to be closed. + * Typically `onClose` is used to set state in the parent component, + * which is used to control the `Snackbar` `open` prop. + * The `reason` parameter can optionally be used to control the response to `onClose`, + * for example ignoring `clickaway`. + * + * @param {React.SyntheticEvent | Event} event The event source of the callback. + * @param {string} reason Can be: `"timeout"` (`autoHideDuration` expired), `"clickaway"`, or `"escapeKeyDown"`. + */ + onClose?: (event: React.SyntheticEvent | Event, reason: SnackbarCloseReason) => void; + /** + * If `true`, the component is shown. + */ + open?: boolean; + /** + * The number of milliseconds to wait before dismissing after user interaction. + * If `autoHideDuration` prop isn't specified, it does nothing. + * If `autoHideDuration` prop is specified but `resumeHideDuration` isn't, + * we default to `autoHideDuration / 2` ms. + */ + resumeHideDuration?: number; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-material/src/Snackbar/Snackbar.js b/packages/mui-material/src/Snackbar/Snackbar.js index e49b321528a758..822fda844739df 100644 --- a/packages/mui-material/src/Snackbar/Snackbar.js +++ b/packages/mui-material/src/Snackbar/Snackbar.js @@ -2,10 +2,11 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; import { unstable_composeClasses as composeClasses } from '@mui/base'; -import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; +import ClickAwayListener from '@mui/base/ClickAwayListener'; import styled from '../styles/styled'; import useTheme from '../styles/useTheme'; import useThemeProps from '../styles/useThemeProps'; +import useEventCallback from '../utils/useEventCallback'; import capitalize from '../utils/capitalize'; import Grow from '../Grow'; import SnackbarContent from '../SnackbarContent'; @@ -106,39 +107,173 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { const ownerState = { ...props, anchorOrigin: { vertical, horizontal } }; const classes = useUtilityClasses(ownerState); + const timerAutoHide = React.useRef(); + const [exited, setExited] = React.useState(true); + + const handleClose = useEventCallback((...args) => { + if (onClose) { + onClose(...args); + } + }); + + const setAutoHideTimer = useEventCallback((autoHideDurationParam) => { + if (!onClose || autoHideDurationParam == null) { + return; + } + + clearTimeout(timerAutoHide.current); + timerAutoHide.current = setTimeout(() => { + handleClose(null, 'timeout'); + }, autoHideDurationParam); + }); + + React.useEffect(() => { + if (open) { + setAutoHideTimer(autoHideDuration); + } + + return () => { + 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 = () => { + 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(resumeHideDuration != null ? resumeHideDuration : autoHideDuration * 0.5); + } + }, [autoHideDuration, resumeHideDuration, setAutoHideTimer]); + + const handleFocus = (event) => { + if (onFocus) { + onFocus(event); + } + handlePause(); + }; + const handleMouseEnter = (event) => { + if (onMouseEnter) { + onMouseEnter(event); + } + handlePause(); + }; + + const handleBlur = (event) => { + if (onBlur) { + onBlur(event); + } + handleResume(); + }; + const handleMouseLeave = (event) => { + if (onMouseLeave) { + onMouseLeave(event); + } + handleResume(); + }; + + const handleClickAway = (event) => { + if (onClose) { + onClose(event, 'clickaway'); + } + }; + + const handleExited = (node) => { + setExited(true); + + if (onExited) { + onExited(node); + } + }; + + const handleEnter = (node, isAppearing) => { + setExited(false); + + if (onEnter) { + onEnter(node, isAppearing); + } + }; + + React.useEffect(() => { + // TODO: window global should be refactored here + 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]); + + React.useEffect(() => { + if (!open) { + return undefined; + } + + /** + * @param {KeyboardEvent} nativeEvent + */ + function handleKeyDown(nativeEvent) { + if (!nativeEvent.defaultPrevented) { + // IE11, Edge (prior to using Bink?) use 'Esc' + if (nativeEvent.key === 'Escape' || nativeEvent.key === 'Esc') { + // not calling `preventDefault` since we don't know if people may ignore this event e.g. a permanently open snackbar + if (onClose) { + onClose(nativeEvent, 'escapeKeyDown'); + } + } + } + } + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [exited, open, onClose]); + + // So we only render active snackbars. + if (!open && exited) { + return null; + } + return ( - - {children || } - + + + + {children || } + + + ); }); From b183f155278745c074f7c91370e46fa152d42525 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 24 Jun 2022 18:30:44 +0530 Subject: [PATCH 34/71] add useSnackbar hook --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 185 ++-------------- .../SnackbarUnstyled.types.ts | 40 +--- .../mui-base/src/SnackbarUnstyled/index.ts | 3 + .../src/SnackbarUnstyled/useSnackbar.ts | 197 ++++++++++++++++++ .../src/SnackbarUnstyled/useSnackbar.types.ts | 40 ++++ 5 files changed, 264 insertions(+), 201 deletions(-) create mode 100644 packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts create mode 100644 packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index b09fea24f7fb72..3cd10b401d4591 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -2,15 +2,11 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; import { OverridableComponent } from '@mui/types'; -import { unstable_useEventCallback as useEventCallback } from '@mui/utils'; import ClickAwayListener from '../ClickAwayListener'; -import { - SnackbarCloseReason, - SnackbarUnstyledProps, - SnackbarUnstyledTypeMap, -} from './SnackbarUnstyled.types'; +import { SnackbarUnstyledProps, SnackbarUnstyledTypeMap } from './SnackbarUnstyled.types'; import composeClasses from '../composeClasses'; import { getSnackbarUnstyledUtilityClass } from './snackbarUnstyledClasses'; +import useSnackbar from './useSnackbar'; const useUtilityClasses = () => { const slots = { @@ -54,157 +50,20 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( const classes = useUtilityClasses(); - const timerAutoHide = React.useRef>(); - const [exited, setExited] = React.useState(true); + const { getRootProps, getTransitionProps, exited, onClickAway } = useSnackbar({ + autoHideDuration, + disableWindowBlurListener, + onClose, + open, + resumeHideDuration, + ref, + }); const Root = component || components.Root || 'div'; const TransitionComponent = components.Transition; - React.useEffect(() => { - if (!open) { - return undefined; - } - - /** - * @param {KeyboardEvent} nativeEvent - */ - function handleKeyDown(nativeEvent: KeyboardEvent) { - if (!nativeEvent.defaultPrevented) { - // IE11, Edge (prior to using Bink?) use 'Esc' - if (nativeEvent.key === 'Escape' || nativeEvent.key === 'Esc') { - // not calling `preventDefault` since we don't know if people may ignore this event e.g. a permanently open snackbar - if (onClose) { - onClose(nativeEvent, 'escapeKeyDown'); - } - } - } - } - - document.addEventListener('keydown', handleKeyDown); - - return () => { - document.removeEventListener('keydown', handleKeyDown); - }; - }, [exited, open, onClose]); - - const handleClose = useEventCallback( - (event: Event | React.SyntheticEvent | null, reason: SnackbarCloseReason) => { - if (onClose) { - onClose(event, reason); - } - }, - ); - - const setAutoHideTimer = useEventCallback((autoHideDurationParam: number | null) => { - if (!onClose || autoHideDurationParam == null) { - return; - } - - clearTimeout(timerAutoHide.current); - timerAutoHide.current = setTimeout(() => { - handleClose(null, 'timeout'); - }, autoHideDurationParam); - }); - - React.useEffect(() => { - if (open) { - setAutoHideTimer(autoHideDuration); - } - - return () => { - clearTimeout(timerAutoHide.current); - }; - }, [open, autoHideDuration, setAutoHideTimer]); - - const handleClickAway = (event: React.SyntheticEvent | Event) => { - if (onClose) { - onClose(event, 'clickaway'); - } - }; - - // Pause the timer when the user is interacting with the Snackbar - // or when the user hide the window. - const handlePause = () => { - 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(resumeHideDuration != null ? resumeHideDuration : autoHideDuration * 0.5); - } - }, [autoHideDuration, resumeHideDuration, setAutoHideTimer]); - - const handleBlur = (event: React.FocusEvent) => { - const onBlurCallback = componentsProps.root?.onBlur || onBlur; - if (onBlurCallback) { - onBlurCallback(event); - } - handleResume(); - }; - - const handleFocus = (event: React.FocusEvent) => { - const onFocusCallback = componentsProps.root?.onFocus || onFocus; - if (onFocusCallback) { - onFocusCallback(event); - } - handlePause(); - }; - - const handleMouseEnter = (event: React.MouseEvent) => { - const onMouseEnterCallback = componentsProps.root?.onMouseEnter || onMouseEnter; - if (onMouseEnterCallback) { - onMouseEnterCallback(event); - } - handlePause(); - }; - - const handleMouseLeave = (event: React.MouseEvent) => { - const onMouseLeaveCallback = componentsProps.root?.onMouseLeave || onMouseLeave; - if (onMouseLeaveCallback) { - onMouseLeaveCallback(event); - } - handleResume(); - }; - - React.useEffect(() => { - // TODO: window global should be refactored here - 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]); - - const handleExited = (node: HTMLElement) => { - setExited(true); - - const onExited = componentsProps.transition?.onExited; - - if (onExited) { - onExited(node); - } - }; - - const handleEnter = (node: HTMLElement, isAppearing: boolean) => { - setExited(false); - - const onEnter = componentsProps.transition?.onEnter; - - if (onEnter) { - onEnter(node, isAppearing); - } - }; - - // So we only render active snackbars. + // So that we only render active snackbars. if (!open && exited) { return null; } @@ -213,24 +72,24 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( ...other, ...componentsProps.root, className: clsx(classes.root, className, componentsProps.root?.className), - ref, - // ClickAwayListener adds an `onClick` prop which results in the alert not being announced. - // See https://github.com/mui/material-ui/issues/29080 - role: 'presentation', - onBlur: handleBlur, - onFocus: handleFocus, - onMouseEnter: handleMouseEnter, - onMouseLeave: handleMouseLeave, + ...getRootProps({ + onBlur: componentsProps.root?.onBlur || onBlur, + onFocus: componentsProps.root?.onFocus || onFocus, + onMouseEnter: componentsProps.root?.onMouseEnter || onMouseEnter, + onMouseLeave: componentsProps.root?.onMouseLeave || onMouseLeave, + }), }; return ( - + {TransitionComponent ? ( {children} diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index f4d8988a0c88b6..d64e7ca7f1093c 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -1,18 +1,9 @@ import React from 'react'; import { OverrideProps } from '@mui/types'; import { ClickAwayListenerProps } from '../ClickAwayListener'; +import { UseSnackbarParameters } from './useSnackbar.types'; -export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; - -interface SnackbarUnstyledOwnProps { - /** - * The number of milliseconds to wait before automatically calling the - * `onClose` function. `onClose` should then set the state of the `open` - * prop to hide the Snackbar. This behavior is disabled by default with - * the `null` value. - * @default null - */ - autoHideDuration?: number | null; +interface SnackbarUnstyledOwnProps extends Omit { children?: React.ReactNode; /** * Props applied to the `ClickAwayListener` element. @@ -37,33 +28,6 @@ interface SnackbarUnstyledOwnProps { [key: string]: any; }; }; - /** - * If `true`, the `autoHideDuration` timer will expire even if the window is not focused. - * @default false - */ - disableWindowBlurListener?: boolean; - /** - * Callback fired when the component requests to be closed. - * Typically `onClose` is used to set state in the parent component, - * which is used to control the `Snackbar` `open` prop. - * The `reason` parameter can optionally be used to control the response to `onClose`, - * for example ignoring `clickaway`. - * - * @param {React.SyntheticEvent | Event} event The event source of the callback. - * @param {string} reason Can be: `"timeout"` (`autoHideDuration` expired), `"clickaway"`, or `"escapeKeyDown"`. - */ - onClose?: (event: React.SyntheticEvent | Event | null, reason: SnackbarCloseReason) => void; - /** - * If `true`, the component is shown. - */ - open?: boolean; - /** - * The number of milliseconds to wait before dismissing after user interaction. - * If `autoHideDuration` prop isn't specified, it does nothing. - * If `autoHideDuration` prop is specified but `resumeHideDuration` isn't, - * we default to `autoHideDuration / 2` ms. - */ - resumeHideDuration?: number; } export interface SnackbarUnstyledTypeMap

{ diff --git a/packages/mui-base/src/SnackbarUnstyled/index.ts b/packages/mui-base/src/SnackbarUnstyled/index.ts index db0cad8ac68ef8..deb18dfe9bb202 100644 --- a/packages/mui-base/src/SnackbarUnstyled/index.ts +++ b/packages/mui-base/src/SnackbarUnstyled/index.ts @@ -4,3 +4,6 @@ export * from './SnackbarUnstyled.types'; export { default as snackbarUnstyledClasses } from './snackbarUnstyledClasses'; export * from './snackbarUnstyledClasses'; + +export { default as useSnackbar } from './useSnackbar'; +export * from './useSnackbar.types'; diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts new file mode 100644 index 00000000000000..dda5d902bbb962 --- /dev/null +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts @@ -0,0 +1,197 @@ +import * as React from 'react'; +import { unstable_useEventCallback as useEventCallback } from '@mui/utils'; +import { TransitionProps } from '@mui/material/transitions'; +import { UseSnackbarParameters, SnackbarCloseReason } from './useSnackbar.types'; + +/** + * The basic building block for creating custom snackbar. + * + * Demos: + * + * - [Snackbar](https://mui.com/base/react-snackbar/) + */ +export default function useSnackbar(parameters: UseSnackbarParameters) { + const { + autoHideDuration = null, + disableWindowBlurListener = false, + onClose, + open, + ref, + resumeHideDuration, + } = parameters; + + const timerAutoHide = React.useRef>(); + const [exited, setExited] = React.useState(true); + + React.useEffect(() => { + if (!open) { + return undefined; + } + + /** + * @param {KeyboardEvent} nativeEvent + */ + function handleKeyDown(nativeEvent: KeyboardEvent) { + if (!nativeEvent.defaultPrevented) { + // IE11, Edge (prior to using Bink?) use 'Esc' + if (nativeEvent.key === 'Escape' || nativeEvent.key === 'Esc') { + // not calling `preventDefault` since we don't know if people may ignore this event e.g. a permanently open snackbar + if (onClose) { + onClose(nativeEvent, 'escapeKeyDown'); + } + } + } + } + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [exited, open, onClose]); + + const handleClose = useEventCallback( + (event: Event | React.SyntheticEvent | null, reason: SnackbarCloseReason) => { + if (onClose) { + onClose(event, reason); + } + }, + ); + + const setAutoHideTimer = useEventCallback((autoHideDurationParam: number | null) => { + if (!onClose || autoHideDurationParam == null) { + return; + } + + clearTimeout(timerAutoHide.current); + timerAutoHide.current = setTimeout(() => { + handleClose(null, 'timeout'); + }, autoHideDurationParam); + }); + + React.useEffect(() => { + if (open) { + setAutoHideTimer(autoHideDuration); + } + + return () => { + clearTimeout(timerAutoHide.current); + }; + }, [open, autoHideDuration, setAutoHideTimer]); + + const handleClickAway = (event: React.SyntheticEvent | Event) => { + if (onClose) { + onClose(event, 'clickaway'); + } + }; + + // Pause the timer when the user is interacting with the Snackbar + // or when the user hide the window. + const handlePause = () => { + 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(resumeHideDuration != null ? resumeHideDuration : autoHideDuration * 0.5); + } + }, [autoHideDuration, resumeHideDuration, setAutoHideTimer]); + + const createHandleBlur = + (otherHandlers: Record | undefined>) => + (event: React.FocusEvent) => { + const onBlurCallback = otherHandlers.onBlur; + if (onBlurCallback) { + onBlurCallback(event); + } + handleResume(); + }; + + const createHandleFocus = + (otherHandlers: Record | undefined>) => + (event: React.FocusEvent) => { + const onFocusCallback = otherHandlers.onFocus; + if (onFocusCallback) { + onFocusCallback(event); + } + handlePause(); + }; + + const createMouseEnter = + (otherHandlers: Record | undefined>) => + (event: React.MouseEvent) => { + const onMouseEnterCallback = otherHandlers.onMouseEnter; + if (onMouseEnterCallback) { + onMouseEnterCallback(event); + } + handlePause(); + }; + + const createMouseLeave = + (otherHandlers: Record | undefined>) => + (event: React.MouseEvent) => { + const onMouseLeaveCallback = otherHandlers.onMouseLeave; + if (onMouseLeaveCallback) { + onMouseLeaveCallback(event); + } + handleResume(); + }; + + React.useEffect(() => { + // TODO: window global should be refactored here + 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]); + + const createHandleExited = (otherHandlers?: TransitionProps) => (node: HTMLElement) => { + setExited(true); + + const onExited = otherHandlers?.onExited; + + if (onExited) { + onExited(node); + } + }; + + const createHandleEnter = + (otherHandlers?: TransitionProps) => (node: HTMLElement, isAppearing: boolean) => { + setExited(false); + + const onEnter = otherHandlers?.onEnter; + + if (onEnter) { + onEnter(node, isAppearing); + } + }; + + const getRootProps = | undefined> = {}>( + otherProps: TOther = {} as TOther, + ) => ({ + ref, + // ClickAwayListener adds an `onClick` prop which results in the alert not being announced. + // See https://github.com/mui/material-ui/issues/29080 + role: 'presentation', + onBlur: createHandleBlur(otherProps), + onFocus: createHandleFocus(otherProps), + onMouseEnter: createMouseEnter(otherProps), + onMouseLeave: createMouseLeave(otherProps), + }); + + const getTransitionProps = (otherProps?: TransitionProps) => ({ + onEnter: createHandleEnter(otherProps), + onExited: createHandleExited(otherProps), + }); + + return { getRootProps, getTransitionProps, exited, onClickAway: handleClickAway }; +} diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts new file mode 100644 index 00000000000000..36d391dfc82ad0 --- /dev/null +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts @@ -0,0 +1,40 @@ +export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; + +export interface UseSnackbarParameters { + /** + * The number of milliseconds to wait before automatically calling the + * `onClose` function. `onClose` should then set the state of the `open` + * prop to hide the Snackbar. This behavior is disabled by default with + * the `null` value. + * @default null + */ + autoHideDuration?: number | null; + /** + * If `true`, the `autoHideDuration` timer will expire even if the window is not focused. + * @default false + */ + disableWindowBlurListener?: boolean; + /** + * Callback fired when the component requests to be closed. + * Typically `onClose` is used to set state in the parent component, + * which is used to control the `Snackbar` `open` prop. + * The `reason` parameter can optionally be used to control the response to `onClose`, + * for example ignoring `clickaway`. + * + * @param {React.SyntheticEvent | Event} event The event source of the callback. + * @param {string} reason Can be: `"timeout"` (`autoHideDuration` expired), `"clickaway"`, or `"escapeKeyDown"`. + */ + onClose?: (event: React.SyntheticEvent | Event | null, reason: SnackbarCloseReason) => void; + /** + * If `true`, the component is shown. + */ + open?: boolean; + ref?: React.Ref; + /** + * The number of milliseconds to wait before dismissing after user interaction. + * If `autoHideDuration` prop isn't specified, it does nothing. + * If `autoHideDuration` prop is specified but `resumeHideDuration` isn't, + * we default to `autoHideDuration / 2` ms. + */ + resumeHideDuration?: number; +} From 6b48905e6ac230a897751d6780c2f0b5cb90b25d Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 24 Jun 2022 18:31:16 +0530 Subject: [PATCH 35/71] add useSnackbar demo --- .../snackbar/TransitionComponentSnackbar.js | 2 +- .../snackbar/TransitionComponentSnackbar.tsx | 2 +- .../base/components/snackbar/UseSnackbar.js | 79 +++++++++++++++++++ .../base/components/snackbar/UseSnackbar.tsx | 79 +++++++++++++++++++ .../snackbar/UseSnackbar.tsx.preview | 8 ++ .../data/base/components/snackbar/snackbar.md | 14 ++++ 6 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 docs/data/base/components/snackbar/UseSnackbar.js create mode 100644 docs/data/base/components/snackbar/UseSnackbar.tsx create mode 100644 docs/data/base/components/snackbar/UseSnackbar.tsx.preview diff --git a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js index 69df18293b5486..43a8ca68029713 100644 --- a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js +++ b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js @@ -216,7 +216,7 @@ export default function TransitionComponentSnackbar() {

Notifications sent
- All your notifications were sent to the desired adress. + All your notifications were sent to the desired address.
diff --git a/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx b/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx index cc85e928cfae71..6fc9dbf9699766 100644 --- a/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx +++ b/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx @@ -211,7 +211,7 @@ export default function TransitionComponentSnackbar() {
Notifications sent
- All your notifications were sent to the desired adress. + All your notifications were sent to the desired address.
diff --git a/docs/data/base/components/snackbar/UseSnackbar.js b/docs/data/base/components/snackbar/UseSnackbar.js new file mode 100644 index 00000000000000..7e5ba235965aa3 --- /dev/null +++ b/docs/data/base/components/snackbar/UseSnackbar.js @@ -0,0 +1,79 @@ +import * as React from 'react'; +import { useSnackbar } from '@mui/base/SnackbarUnstyled'; +import { css, keyframes, styled } from '@mui/system'; + +const blue = { + 50: '#F0F7FF', + 400: '#3399FF', + 600: '#0072E5', + 900: '#003A75', +}; + +const grey = { + 200: '#E0E3E7', +}; + +const snackbarInRight = keyframes` + from { + transform: translateX(100%); + } + + to { + transform: translateX(0); + } +`; + +const CustomSnackbar = styled('div')( + ({ theme }) => css` + position: fixed; + z-index: 5500; + display: flex; + right: 16px; + top: 16px; + left: auto; + justify-content: start; + max-width: 560px; + min-width: 300px; + background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]}; + border-radius: 8px; + border: 1px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[400]}; + box-shadow: ${theme.palette.mode === 'dark' + ? `0 5px 13px -3px rgba(0,0,0,0.4)` + : `0 5px 13px -3px ${grey[200]}`}; + padding: 0.75rem; + color: ${theme.palette.mode === 'dark' ? '#fff' : blue[900]}; + font-family: IBM Plex Sans, sans-serif; + font-weight: 600; + animation: ${snackbarInRight} 500ms; + transition: transform 0.2s ease-out; + `, +); + +export default function UseSnackbar() { + const [open, setOpen] = React.useState(false); + + const handleClose = () => { + setOpen(false); + }; + + const { getRootProps } = useSnackbar({ + onClose: handleClose, + open, + autoHideDuration: 5000, + }); + + const handleOpen = () => { + setOpen(true); + }; + + return ( + + + {open ? ( + Hello World + ) : null} + + ); +} diff --git a/docs/data/base/components/snackbar/UseSnackbar.tsx b/docs/data/base/components/snackbar/UseSnackbar.tsx new file mode 100644 index 00000000000000..7e5ba235965aa3 --- /dev/null +++ b/docs/data/base/components/snackbar/UseSnackbar.tsx @@ -0,0 +1,79 @@ +import * as React from 'react'; +import { useSnackbar } from '@mui/base/SnackbarUnstyled'; +import { css, keyframes, styled } from '@mui/system'; + +const blue = { + 50: '#F0F7FF', + 400: '#3399FF', + 600: '#0072E5', + 900: '#003A75', +}; + +const grey = { + 200: '#E0E3E7', +}; + +const snackbarInRight = keyframes` + from { + transform: translateX(100%); + } + + to { + transform: translateX(0); + } +`; + +const CustomSnackbar = styled('div')( + ({ theme }) => css` + position: fixed; + z-index: 5500; + display: flex; + right: 16px; + top: 16px; + left: auto; + justify-content: start; + max-width: 560px; + min-width: 300px; + background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]}; + border-radius: 8px; + border: 1px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[400]}; + box-shadow: ${theme.palette.mode === 'dark' + ? `0 5px 13px -3px rgba(0,0,0,0.4)` + : `0 5px 13px -3px ${grey[200]}`}; + padding: 0.75rem; + color: ${theme.palette.mode === 'dark' ? '#fff' : blue[900]}; + font-family: IBM Plex Sans, sans-serif; + font-weight: 600; + animation: ${snackbarInRight} 500ms; + transition: transform 0.2s ease-out; + `, +); + +export default function UseSnackbar() { + const [open, setOpen] = React.useState(false); + + const handleClose = () => { + setOpen(false); + }; + + const { getRootProps } = useSnackbar({ + onClose: handleClose, + open, + autoHideDuration: 5000, + }); + + const handleOpen = () => { + setOpen(true); + }; + + return ( + + + {open ? ( + Hello World + ) : null} + + ); +} diff --git a/docs/data/base/components/snackbar/UseSnackbar.tsx.preview b/docs/data/base/components/snackbar/UseSnackbar.tsx.preview new file mode 100644 index 00000000000000..9e96bb111c1d29 --- /dev/null +++ b/docs/data/base/components/snackbar/UseSnackbar.tsx.preview @@ -0,0 +1,8 @@ + + + {open ? ( + Hello World + ) : null} + \ No newline at end of file diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index d213333d43610e..b0c788f46295cb 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -32,3 +32,17 @@ You can animate the open and close states of the snackbar with a render prop chi These two callbacks allow the snackbar to unmount the child content when closed and fully transitioned. {{"demo": "TransitionComponentSnackbar.js", "defaultCodeOpen": false}} + +## The useSnackbar hook + +```js +import { useSnackbar } from '@mui/base/SnackbarUnstyled'; +``` + +The `useSnackbar` hook lets you use the functionality of `SnackbarUnstyled` in other components. + +It returns props to be placed on the root element and the transition element. If you are using `ClickAwayListener` to close the snackbar by clicking outside of it, make sure to pass `onClickAway` handler returned by this hook to the `ClickAwayListener` component. + +Make sure to pass the `open` state to the hook and use it to show/hide the snackbar. + +{{"demo": "UseSnackbar.js", "defaultCodeOpen": false}} From f8fc88f37318f5a2551b640c30015c8f491511e0 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 24 Jun 2022 20:34:29 +0530 Subject: [PATCH 36/71] fix transition type --- .../src/SnackbarUnstyled/useSnackbar.ts | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts index dda5d902bbb962..16d56ed12621eb 100644 --- a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { unstable_useEventCallback as useEventCallback } from '@mui/utils'; -import { TransitionProps } from '@mui/material/transitions'; +import { TransitionProps } from 'react-transition-group/Transition'; import { UseSnackbarParameters, SnackbarCloseReason } from './useSnackbar.types'; /** @@ -154,18 +154,20 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { return undefined; }, [disableWindowBlurListener, handleResume, open]); - const createHandleExited = (otherHandlers?: TransitionProps) => (node: HTMLElement) => { - setExited(true); + const createHandleExited = + (otherHandlers?: Omit) => (node: HTMLElement) => { + setExited(true); - const onExited = otherHandlers?.onExited; + const onExited = otherHandlers?.onExited; - if (onExited) { - onExited(node); - } - }; + if (onExited) { + onExited(node); + } + }; const createHandleEnter = - (otherHandlers?: TransitionProps) => (node: HTMLElement, isAppearing: boolean) => { + (otherHandlers?: Omit) => + (node: HTMLElement, isAppearing: boolean) => { setExited(false); const onEnter = otherHandlers?.onEnter; @@ -182,13 +184,15 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { // ClickAwayListener adds an `onClick` prop which results in the alert not being announced. // See https://github.com/mui/material-ui/issues/29080 role: 'presentation', + ...otherProps, onBlur: createHandleBlur(otherProps), onFocus: createHandleFocus(otherProps), onMouseEnter: createMouseEnter(otherProps), onMouseLeave: createMouseLeave(otherProps), }); - const getTransitionProps = (otherProps?: TransitionProps) => ({ + const getTransitionProps = (otherProps?: Omit) => ({ + ...otherProps, onEnter: createHandleEnter(otherProps), onExited: createHandleExited(otherProps), }); From c079bd77c2045cd69981b5a890f52444fbb044d0 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 24 Jun 2022 21:12:00 +0530 Subject: [PATCH 37/71] use Partial type instead --- .../src/SnackbarUnstyled/useSnackbar.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts index 16d56ed12621eb..6a1b6f1458d970 100644 --- a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts @@ -154,20 +154,18 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { return undefined; }, [disableWindowBlurListener, handleResume, open]); - const createHandleExited = - (otherHandlers?: Omit) => (node: HTMLElement) => { - setExited(true); + const createHandleExited = (otherHandlers?: Partial) => (node: HTMLElement) => { + setExited(true); - const onExited = otherHandlers?.onExited; + const onExited = otherHandlers?.onExited; - if (onExited) { - onExited(node); - } - }; + if (onExited) { + onExited(node); + } + }; const createHandleEnter = - (otherHandlers?: Omit) => - (node: HTMLElement, isAppearing: boolean) => { + (otherHandlers?: Partial) => (node: HTMLElement, isAppearing: boolean) => { setExited(false); const onEnter = otherHandlers?.onEnter; @@ -191,7 +189,7 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { onMouseLeave: createMouseLeave(otherProps), }); - const getTransitionProps = (otherProps?: Omit) => ({ + const getTransitionProps = (otherProps?: Partial) => ({ ...otherProps, onEnter: createHandleEnter(otherProps), onExited: createHandleExited(otherProps), From 9b0b01d83b5e614cf5d5bd04c1aab45e7c57e655 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 27 Jun 2022 18:03:26 +0530 Subject: [PATCH 38/71] allow componentsProps callback and ownerState propagation --- docs/pages/base/api/snackbar-unstyled.json | 5 +- .../SnackbarUnstyled.spec.tsx | 20 +++++ .../SnackbarUnstyled.test.tsx | 1 - .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 79 +++++++++++++------ .../SnackbarUnstyled.types.ts | 31 +++++++- 5 files changed, 107 insertions(+), 29 deletions(-) create mode 100644 packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.spec.tsx diff --git a/docs/pages/base/api/snackbar-unstyled.json b/docs/pages/base/api/snackbar-unstyled.json index 20b7c6f6a88db4..e6918f150d48b3 100644 --- a/docs/pages/base/api/snackbar-unstyled.json +++ b/docs/pages/base/api/snackbar-unstyled.json @@ -11,7 +11,10 @@ "default": "{}" }, "componentsProps": { - "type": { "name": "shape", "description": "{ root?: object, transition?: object }" }, + "type": { + "name": "shape", + "description": "{ root?: func
| object, transition?: func
| object }" + }, "default": "{}" }, "disableWindowBlurListener": { "type": { "name": "bool" } }, diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.spec.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.spec.tsx new file mode 100644 index 00000000000000..4bdc28d063f4bc --- /dev/null +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.spec.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import SnackbarUnstyled, { + SnackbarUnstyledProps, + SnackbarUnstyledRootSlotProps, +} from '@mui/base/SnackbarUnstyled'; + +const Root = React.forwardRef( + (props: SnackbarUnstyledRootSlotProps, ref: React.ForwardedRef) => { + const { ownerState, ...other } = props; + return !ownerState.exited && ownerState.open ? ( +
+ Hello World +
+ ) : null; + }, +); + +const SnackbarUnstyledWithCustomRoot = (props: SnackbarUnstyledProps) => { + return ; +}; diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.test.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.test.tsx index 9bf30eafe7b537..19423ae029492b 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.test.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.test.tsx @@ -22,7 +22,6 @@ describe('', () => { expectedClassName: classes.root, }, }, - skip: ['componentsPropsCallbacks', 'ownerStatePropagation'], // not implemented yet }), ); }); diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 3cd10b401d4591..af4ba1e38d6fa2 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -1,12 +1,17 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import clsx from 'clsx'; import { OverridableComponent } from '@mui/types'; import ClickAwayListener from '../ClickAwayListener'; -import { SnackbarUnstyledProps, SnackbarUnstyledTypeMap } from './SnackbarUnstyled.types'; +import { + SnackbarUnstyledOwnerState, + SnackbarUnstyledProps, + SnackbarUnstyledRootSlotProps, + SnackbarUnstyledTypeMap, +} from './SnackbarUnstyled.types'; import composeClasses from '../composeClasses'; import { getSnackbarUnstyledUtilityClass } from './snackbarUnstyledClasses'; import useSnackbar from './useSnackbar'; +import { useSlotProps, WithOptionalOwnerState } from '../utils'; const useUtilityClasses = () => { const slots = { @@ -32,7 +37,6 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( const { autoHideDuration = null, children, - className, ClickAwayListenerProps, component, components = {}, @@ -59,27 +63,54 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( ref, }); + const ownerState: SnackbarUnstyledOwnerState = { + ...props, + autoHideDuration, + disableWindowBlurListener, + exited, + }; + const Root = component || components.Root || 'div'; const TransitionComponent = components.Transition; + const componentsPropsRootSlotEventCallback = ( + eventHandler: keyof React.HTMLAttributes, + ) => { + if (typeof componentsProps.root === 'object') { + return componentsProps.root?.[eventHandler]; + } + + if (typeof componentsProps.root === 'function') { + return componentsProps.root?.(ownerState)?.[eventHandler]; + } + return null; + }; + + const rootProps: WithOptionalOwnerState = useSlotProps({ + elementType: Root, + getSlotProps: (eventHandlers) => + getRootProps({ + ...eventHandlers, + onBlur: componentsPropsRootSlotEventCallback('onBlur') || onBlur, + onFocus: componentsPropsRootSlotEventCallback('onFocus') || onFocus, + onMouseEnter: componentsPropsRootSlotEventCallback('onMouseEnter') || onMouseEnter, + onMouseLeave: componentsPropsRootSlotEventCallback('onMouseLeave') || onMouseLeave, + }), + externalForwardedProps: other, + externalSlotProps: componentsProps.root, + additionalProps: { + ref, + }, + ownerState, + className: classes.root, + }); + // So that we only render active snackbars. if (!open && exited) { return null; } - const rootProps = { - ...other, - ...componentsProps.root, - className: clsx(classes.root, className, componentsProps.root?.className), - ...getRootProps({ - onBlur: componentsProps.root?.onBlur || onBlur, - onFocus: componentsProps.root?.onFocus || onFocus, - onMouseEnter: componentsProps.root?.onMouseEnter || onMouseEnter, - onMouseLeave: componentsProps.root?.onMouseLeave || onMouseLeave, - }), - }; - return ( @@ -87,8 +118,14 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( {children} @@ -118,10 +155,6 @@ SnackbarUnstyled.propTypes /* remove-proptypes */ = { * @ignore */ children: PropTypes.node, - /** - * @ignore - */ - className: PropTypes.string, /** * Props applied to the `ClickAwayListener` element. */ @@ -145,8 +178,8 @@ SnackbarUnstyled.propTypes /* remove-proptypes */ = { * @default {} */ componentsProps: PropTypes.shape({ - root: PropTypes.object, - transition: PropTypes.object, + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + transition: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }), /** * If `true`, the `autoHideDuration` timer will expire even if the window is not focused. diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index d64e7ca7f1093c..716dea591bdca2 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -2,6 +2,9 @@ import React from 'react'; import { OverrideProps } from '@mui/types'; import { ClickAwayListenerProps } from '../ClickAwayListener'; import { UseSnackbarParameters } from './useSnackbar.types'; +import { SlotComponentProps } from '../utils'; + +export interface SnackbarUnstyledComponentsPropsOverrides {} interface SnackbarUnstyledOwnProps extends Omit { children?: React.ReactNode; @@ -23,10 +26,19 @@ interface SnackbarUnstyledOwnProps extends Omit { * @default {} */ componentsProps?: { - root?: React.HTMLAttributes; - transition?: { - [key: string]: any; - }; + root?: SlotComponentProps< + 'div', + SnackbarUnstyledComponentsPropsOverrides, + SnackbarUnstyledOwnerState + >; + transition?: + | { + [key: string]: any; + } + | (( + ownerState: SnackbarUnstyledOwnerState, + ) => Partial> & + SnackbarUnstyledComponentsPropsOverrides); }; } @@ -39,3 +51,14 @@ export type SnackbarUnstyledProps< D extends React.ElementType = SnackbarUnstyledTypeMap['defaultComponent'], P = {}, > = OverrideProps, D>; + +export type SnackbarUnstyledOwnerState = SnackbarUnstyledProps & { + exited: boolean; +}; + +export type SnackbarUnstyledRootSlotProps = { + ownerState: SnackbarUnstyledOwnerState; + className?: string; + children?: React.ReactNode; + ref: React.Ref; +}; From f808f109965af7862fe234dc721eeef32799e9cd Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 27 Jun 2022 18:22:03 +0530 Subject: [PATCH 39/71] fix type --- .../src/SnackbarUnstyled/SnackbarUnstyled.types.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 716dea591bdca2..efd89a13267c85 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -35,10 +35,9 @@ interface SnackbarUnstyledOwnProps extends Omit { | { [key: string]: any; } - | (( - ownerState: SnackbarUnstyledOwnerState, - ) => Partial> & - SnackbarUnstyledComponentsPropsOverrides); + | ((ownerState: SnackbarUnstyledOwnerState) => { + [key: string]: any; + }); }; } From 703581e275d17eda0eea83f8c23d227258fe10bb Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 14 Jul 2022 15:36:14 +0530 Subject: [PATCH 40/71] useSlotProps for Transition component as well --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 11 ++++++++++- .../src/SnackbarUnstyled/SnackbarUnstyled.types.ts | 6 ++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index af4ba1e38d6fa2..396f95695af16c 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -6,6 +6,7 @@ import { SnackbarUnstyledOwnerState, SnackbarUnstyledProps, SnackbarUnstyledRootSlotProps, + SnackbarUnstyledTransitionSlotProps, SnackbarUnstyledTypeMap, } from './SnackbarUnstyled.types'; import composeClasses from '../composeClasses'; @@ -106,6 +107,14 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( className: classes.root, }); + const transitionProps: WithOptionalOwnerState = useSlotProps( + { + elementType: TransitionComponent as React.ElementType, + externalSlotProps: componentsProps.transition, + ownerState, + }, + ); + // So that we only render active snackbars. if (!open && exited) { return null; @@ -116,7 +125,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( {TransitionComponent ? ( ; }; + +export type SnackbarUnstyledTransitionSlotProps = { + ownerState: SnackbarUnstyledOwnerState; + className?: string; + children?: React.ReactNode; +}; From 11ba396a705c43e310faaa59a5870dbaa5042566 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 14 Jul 2022 18:38:34 +0530 Subject: [PATCH 41/71] pass getTransitionProps to getSlotProps --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 28 +++++++++---------- .../SnackbarUnstyled.types.ts | 3 +- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 396f95695af16c..7695559e0163db 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -110,6 +110,18 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( const transitionProps: WithOptionalOwnerState = useSlotProps( { elementType: TransitionComponent as React.ElementType, + getSlotProps: (otherHandlers) => + getTransitionProps({ + ...otherHandlers, + onEnter: + typeof componentsProps.transition === 'function' + ? componentsProps.transition?.(ownerState)?.onEnter + : componentsProps.transition?.onEnter, + onExited: + typeof componentsProps.transition === 'function' + ? componentsProps.transition?.(ownerState)?.onExited + : componentsProps.transition?.onExited, + }), externalSlotProps: componentsProps.transition, ownerState, }, @@ -124,21 +136,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( {TransitionComponent ? ( - - {children} - + {children} ) : ( children )} diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index ca3ed0a27f46d7..431b2139c120fc 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -1,4 +1,5 @@ import React from 'react'; +import { TransitionChildren } from 'react-transition-group/Transition'; import { OverrideProps } from '@mui/types'; import { ClickAwayListenerProps } from '../ClickAwayListener'; import { UseSnackbarParameters } from './useSnackbar.types'; @@ -65,5 +66,5 @@ export type SnackbarUnstyledRootSlotProps = { export type SnackbarUnstyledTransitionSlotProps = { ownerState: SnackbarUnstyledOwnerState; className?: string; - children?: React.ReactNode; + children?: TransitionChildren; }; From a83e744eda82e56d2b97392765b04fe44ca71249 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 14 Jul 2022 18:55:27 +0530 Subject: [PATCH 42/71] remove exited from dependency array --- packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts index 6a1b6f1458d970..aac1d21c277fcf 100644 --- a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts @@ -48,7 +48,7 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { return () => { document.removeEventListener('keydown', handleKeyDown); }; - }, [exited, open, onClose]); + }, [open, onClose]); const handleClose = useEventCallback( (event: Event | React.SyntheticEvent | null, reason: SnackbarCloseReason) => { From 1166248ee8229081b2e043db3b0de4b4937ce3fd Mon Sep 17 00:00:00 2001 From: Zeeshan Tamboli Date: Fri, 15 Jul 2022 09:54:52 +0530 Subject: [PATCH 43/71] Update packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Dudak --- packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts index aac1d21c277fcf..e7265104bfdb2a 100644 --- a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts @@ -33,7 +33,7 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { */ function handleKeyDown(nativeEvent: KeyboardEvent) { if (!nativeEvent.defaultPrevented) { - // IE11, Edge (prior to using Bink?) use 'Esc' + // IE11, Edge (prior to using Blink?) use 'Esc' if (nativeEvent.key === 'Escape' || nativeEvent.key === 'Esc') { // not calling `preventDefault` since we don't know if people may ignore this event e.g. a permanently open snackbar if (onClose) { From fec623079fb3e368afec83ad64da03eb8b25716d Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 15 Jul 2022 10:09:59 +0530 Subject: [PATCH 44/71] resolve PR review changes --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 40 +++++-------------- .../SnackbarUnstyled.types.ts | 11 +++-- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 7695559e0163db..1639d723617bc9 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -12,7 +12,7 @@ import { import composeClasses from '../composeClasses'; import { getSnackbarUnstyledUtilityClass } from './snackbarUnstyledClasses'; import useSnackbar from './useSnackbar'; -import { useSlotProps, WithOptionalOwnerState } from '../utils'; +import { resolveComponentProps, useSlotProps, WithOptionalOwnerState } from '../utils'; const useUtilityClasses = () => { const slots = { @@ -32,13 +32,12 @@ const useUtilityClasses = () => { * - [SnackbarUnstyled API](https://mui.com/base/api/snackbar-unstyled/) */ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( - props: SnackbarUnstyledProps & { component?: React.ElementType }, + props: SnackbarUnstyledProps, ref: React.ForwardedRef, ) { const { autoHideDuration = null, children, - ClickAwayListenerProps, component, components = {}, componentsProps = {}, @@ -75,28 +74,17 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( const TransitionComponent = components.Transition; - const componentsPropsRootSlotEventCallback = ( - eventHandler: keyof React.HTMLAttributes, - ) => { - if (typeof componentsProps.root === 'object') { - return componentsProps.root?.[eventHandler]; - } - - if (typeof componentsProps.root === 'function') { - return componentsProps.root?.(ownerState)?.[eventHandler]; - } - return null; - }; - const rootProps: WithOptionalOwnerState = useSlotProps({ elementType: Root, getSlotProps: (eventHandlers) => getRootProps({ ...eventHandlers, - onBlur: componentsPropsRootSlotEventCallback('onBlur') || onBlur, - onFocus: componentsPropsRootSlotEventCallback('onFocus') || onFocus, - onMouseEnter: componentsPropsRootSlotEventCallback('onMouseEnter') || onMouseEnter, - onMouseLeave: componentsPropsRootSlotEventCallback('onMouseLeave') || onMouseLeave, + onBlur: resolveComponentProps(componentsProps.root, ownerState)?.onBlur || onBlur, + onFocus: resolveComponentProps(componentsProps.root, ownerState)?.onFocus || onFocus, + onMouseEnter: + resolveComponentProps(componentsProps.root, ownerState)?.onMouseEnter || onMouseEnter, + onMouseLeave: + resolveComponentProps(componentsProps.root, ownerState)?.onMouseLeave || onMouseLeave, }), externalForwardedProps: other, externalSlotProps: componentsProps.root, @@ -113,14 +101,8 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( getSlotProps: (otherHandlers) => getTransitionProps({ ...otherHandlers, - onEnter: - typeof componentsProps.transition === 'function' - ? componentsProps.transition?.(ownerState)?.onEnter - : componentsProps.transition?.onEnter, - onExited: - typeof componentsProps.transition === 'function' - ? componentsProps.transition?.(ownerState)?.onExited - : componentsProps.transition?.onExited, + onEnter: resolveComponentProps(componentsProps.transition, ownerState)?.onEnter, + onExited: resolveComponentProps(componentsProps.transition, ownerState)?.onExited, }), externalSlotProps: componentsProps.transition, ownerState, @@ -133,7 +115,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( } return ( - + {TransitionComponent ? ( {children} diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 431b2139c120fc..35b9dea5130677 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -7,12 +7,8 @@ import { SlotComponentProps } from '../utils'; export interface SnackbarUnstyledComponentsPropsOverrides {} -interface SnackbarUnstyledOwnProps extends Omit { +export interface SnackbarUnstyledOwnProps extends Omit { children?: React.ReactNode; - /** - * Props applied to the `ClickAwayListener` element. - */ - ClickAwayListenerProps?: Partial; /** * The components used for each slot inside the Snackbar. * Either a string to use a HTML element or a component. @@ -27,6 +23,7 @@ interface SnackbarUnstyledOwnProps extends Omit { * @default {} */ componentsProps?: { + clickAwayListener?: Partial; root?: SlotComponentProps< 'div', SnackbarUnstyledComponentsPropsOverrides, @@ -50,7 +47,9 @@ export interface SnackbarUnstyledTypeMap

= OverrideProps, D>; +> = OverrideProps, D> & { + component?: D; +}; export type SnackbarUnstyledOwnerState = SnackbarUnstyledProps & { exited: boolean; From 14f01fc8ed838ae5d586525c3790b22f5b024cad Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 15 Jul 2022 10:24:31 +0530 Subject: [PATCH 45/71] yarn proptypes && yarn docs:api --- docs/pages/base/api/snackbar-unstyled.json | 3 +-- .../snackbar-unstyled/snackbar-unstyled.json | 1 - .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 18 ++++++++++++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/pages/base/api/snackbar-unstyled.json b/docs/pages/base/api/snackbar-unstyled.json index e6918f150d48b3..5df98faa31e007 100644 --- a/docs/pages/base/api/snackbar-unstyled.json +++ b/docs/pages/base/api/snackbar-unstyled.json @@ -1,7 +1,6 @@ { "props": { "autoHideDuration": { "type": { "name": "number" }, "default": "null" }, - "ClickAwayListenerProps": { "type": { "name": "object" } }, "component": { "type": { "name": "elementType" } }, "components": { "type": { @@ -13,7 +12,7 @@ "componentsProps": { "type": { "name": "shape", - "description": "{ root?: func
| object, transition?: func
| object }" + "description": "{ clickAwayListener?: { children: element, disableReactTree?: bool, mouseEvent?: 'onClick'
| 'onMouseDown'
| 'onMouseUp'
| 'onPointerDown'
| 'onPointerUp'
| false, onClickAway?: func, touchEvent?: 'onTouchEnd'
| 'onTouchStart'
| false }, root?: func
| object, transition?: func
| object }" }, "default": "{}" }, diff --git a/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json b/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json index b3a6c23491ca15..83efad9029bbf1 100644 --- a/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json +++ b/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json @@ -2,7 +2,6 @@ "componentDescription": "", "propDescriptions": { "autoHideDuration": "The number of milliseconds to wait before automatically calling the onClose function. onClose should then set the state of the open prop to hide the Snackbar. This behavior is disabled by default with the null value.", - "ClickAwayListenerProps": "Props applied to the ClickAwayListener element.", "component": "The component used for the root node. Either a string to use a HTML element or a component.", "components": "The components used for each slot inside the Snackbar. Either a string to use a HTML element or a component.", "componentsProps": "The props used for each slot inside the Snackbar.", diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 1639d723617bc9..9abeaab45c8b39 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -144,10 +144,6 @@ SnackbarUnstyled.propTypes /* remove-proptypes */ = { * @ignore */ children: PropTypes.node, - /** - * Props applied to the `ClickAwayListener` element. - */ - ClickAwayListenerProps: PropTypes.object, /** * The component used for the root node. * Either a string to use a HTML element or a component. @@ -167,6 +163,20 @@ SnackbarUnstyled.propTypes /* remove-proptypes */ = { * @default {} */ componentsProps: PropTypes.shape({ + clickAwayListener: PropTypes.shape({ + children: PropTypes.element.isRequired, + disableReactTree: PropTypes.bool, + mouseEvent: PropTypes.oneOf([ + 'onClick', + 'onMouseDown', + 'onMouseUp', + 'onPointerDown', + 'onPointerUp', + false, + ]), + onClickAway: PropTypes.func, + touchEvent: PropTypes.oneOf(['onTouchEnd', 'onTouchStart', false]), + }), root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), transition: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }), From 6a69771eaa6d2d34191767302be8d4a41b2cdaa1 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 15 Jul 2022 15:57:41 +0530 Subject: [PATCH 46/71] remove Transition component and update demos --- .../snackbar/TransitionComponentSnackbar.js | 155 +++++------------- .../snackbar/TransitionComponentSnackbar.tsx | 152 +++++------------ .../base/components/snackbar/UseSnackbar.js | 7 +- .../base/components/snackbar/UseSnackbar.tsx | 7 +- .../snackbar/UseSnackbar.tsx.preview | 4 +- .../data/base/components/snackbar/snackbar.md | 15 +- docs/pages/base/api/snackbar-unstyled.json | 8 +- .../snackbar-unstyled/snackbar-unstyled.json | 1 + .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 42 +---- .../SnackbarUnstyled.types.ts | 24 +-- .../src/SnackbarUnstyled/useSnackbar.ts | 31 +--- 11 files changed, 116 insertions(+), 330 deletions(-) diff --git a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js index 43a8ca68029713..6695ec076c6f88 100644 --- a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js +++ b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js @@ -1,11 +1,9 @@ import * as React from 'react'; -import PropTypes from 'prop-types'; import { styled } from '@mui/system'; -import { Transition } from 'react-transition-group'; - import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; import CloseIcon from '@mui/icons-material/Close'; import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; +import Slide from '@mui/material/Slide'; const blue = { 50: '#F0F7FF', @@ -80,105 +78,9 @@ const SnackbarContent = styled('div')( `, ); -const positioningStyles = { - entering: 'translateX(200px)', - entered: 'translateX(0)', - exiting: 'translateX(200px)', - exited: 'translateX(200px)', - unmounted: 'translateX(200px)', -}; - -const Transform = ({ open, children, onEnter, onExit }) => { - const nodeRef = React.useRef(null); - - return ( - - {(status) => { - return ( -

- {children} -
- ); - }} - - ); -}; - -Transform.propTypes = { - children: PropTypes.node, - onEnter: PropTypes.func, - onExit: PropTypes.func, - open: PropTypes.bool, -}; - -const statusStyles = { - entered: { - opacity: 1, - }, - entering: { - opacity: 0, - }, - exited: { - opacity: 0, - }, - exiting: { - opacity: 0, - }, - unmounted: { - opacity: 0, - }, -}; - -const FadeTransform = (props) => { - const { open, children, onEnter, onExit } = props; - const nodeRef = React.useRef(null); - - return ( - - {(status) => ( -
- - {children} - -
- )} -
- ); -}; - -FadeTransform.propTypes = { - children: PropTypes.node, - onEnter: PropTypes.func, - onExit: PropTypes.func, - open: PropTypes.bool, -}; - export default function TransitionComponentSnackbar() { const [open, setOpen] = React.useState(false); + const [exited, setExited] = React.useState(true); const handleClose = (_, reason) => { if (reason === 'clickaway') { @@ -192,6 +94,14 @@ export default function TransitionComponentSnackbar() { setOpen(true); }; + const handleOnEnter = () => { + setExited(false); + }; + + const handleOnExited = () => { + setExited(true); + }; + return ( {open ? ( - Hello World + + Hello World + ) : null} ); diff --git a/docs/data/base/components/snackbar/UseSnackbar.tsx b/docs/data/base/components/snackbar/UseSnackbar.tsx index 7e5ba235965aa3..73872ef31fce07 100644 --- a/docs/data/base/components/snackbar/UseSnackbar.tsx +++ b/docs/data/base/components/snackbar/UseSnackbar.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { useSnackbar } from '@mui/base/SnackbarUnstyled'; +import ClickAwayListener from '@mui/base/ClickAwayListener'; import { css, keyframes, styled } from '@mui/system'; const blue = { @@ -56,7 +57,7 @@ export default function UseSnackbar() { setOpen(false); }; - const { getRootProps } = useSnackbar({ + const { getRootProps, onClickAway } = useSnackbar({ onClose: handleClose, open, autoHideDuration: 5000, @@ -72,7 +73,9 @@ export default function UseSnackbar() { Open Snackbar {open ? ( - Hello World + + Hello World + ) : null} ); diff --git a/docs/data/base/components/snackbar/UseSnackbar.tsx.preview b/docs/data/base/components/snackbar/UseSnackbar.tsx.preview index 9e96bb111c1d29..51f73579c11833 100644 --- a/docs/data/base/components/snackbar/UseSnackbar.tsx.preview +++ b/docs/data/base/components/snackbar/UseSnackbar.tsx.preview @@ -3,6 +3,8 @@ Open Snackbar {open ? ( - Hello World + + Hello World + ) : null} \ No newline at end of file diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index b0c788f46295cb..c37a2c7857c811 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -20,16 +20,17 @@ import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; {{"demo": "UnstyledSnackbar.js", "defaultCodeOpen": false}} -## Providing `Transition` component - -You can also provide a Transition component in `components.Transition` prop if you want to apply animations to your snackbar. +## Transitions You can animate the open and close states of the snackbar with a render prop child and a transition component as shown below, as long as the component meets these conditions: -- Calls the `onEnter` callback prop when the enter transition starts -- Calls the `onExited` callback prop when the exit transition is completed +- Is a direct child descendent of the snackbar +- Has an `in` prop—this corresponds to the open state +- Passes the `exited` prop to `SnackbarUnstyled` +- Calls the `onEnter` callback prop when the enter transition starts - sets `exited` to false +- Calls the `onExited` callback prop when the exit transition is completed - sets `exited` to true -These two callbacks allow the snackbar to unmount the child content when closed and fully transitioned. +These two callbacks allow the snackbar to unmount the child content when closed and keep it fully transitioned. This is only applicable if you are using transition components using [react-transition-group](https://github.com/reactjs/react-transition-group) library internally. {{"demo": "TransitionComponentSnackbar.js", "defaultCodeOpen": false}} @@ -41,7 +42,7 @@ import { useSnackbar } from '@mui/base/SnackbarUnstyled'; The `useSnackbar` hook lets you use the functionality of `SnackbarUnstyled` in other components. -It returns props to be placed on the root element and the transition element. If you are using `ClickAwayListener` to close the snackbar by clicking outside of it, make sure to pass `onClickAway` handler returned by this hook to the `ClickAwayListener` component. +It returns props to be placed on the root element. If you are using `ClickAwayListener` to close the snackbar by clicking outside of it, make sure to pass `onClickAway` handler returned by this hook to the `ClickAwayListener` component. Make sure to pass the `open` state to the hook and use it to show/hide the snackbar. diff --git a/docs/pages/base/api/snackbar-unstyled.json b/docs/pages/base/api/snackbar-unstyled.json index 5df98faa31e007..d42a410a66c939 100644 --- a/docs/pages/base/api/snackbar-unstyled.json +++ b/docs/pages/base/api/snackbar-unstyled.json @@ -3,20 +3,18 @@ "autoHideDuration": { "type": { "name": "number" }, "default": "null" }, "component": { "type": { "name": "elementType" } }, "components": { - "type": { - "name": "shape", - "description": "{ Root?: elementType, Transition?: elementType }" - }, + "type": { "name": "shape", "description": "{ Root?: elementType }" }, "default": "{}" }, "componentsProps": { "type": { "name": "shape", - "description": "{ clickAwayListener?: { children: element, disableReactTree?: bool, mouseEvent?: 'onClick'
| 'onMouseDown'
| 'onMouseUp'
| 'onPointerDown'
| 'onPointerUp'
| false, onClickAway?: func, touchEvent?: 'onTouchEnd'
| 'onTouchStart'
| false }, root?: func
| object, transition?: func
| object }" + "description": "{ clickAwayListener?: { children: element, disableReactTree?: bool, mouseEvent?: 'onClick'
| 'onMouseDown'
| 'onMouseUp'
| 'onPointerDown'
| 'onPointerUp'
| false, onClickAway?: func, touchEvent?: 'onTouchEnd'
| 'onTouchStart'
| false }, root?: func
| object }" }, "default": "{}" }, "disableWindowBlurListener": { "type": { "name": "bool" } }, + "exited": { "type": { "name": "bool" }, "default": "true" }, "onClose": { "type": { "name": "func" } }, "open": { "type": { "name": "bool" } }, "resumeHideDuration": { "type": { "name": "number" } } diff --git a/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json b/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json index 83efad9029bbf1..bc66a2650a8e0b 100644 --- a/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json +++ b/docs/translations/api-docs/snackbar-unstyled/snackbar-unstyled.json @@ -6,6 +6,7 @@ "components": "The components used for each slot inside the Snackbar. Either a string to use a HTML element or a component.", "componentsProps": "The props used for each slot inside the Snackbar.", "disableWindowBlurListener": "If true, the autoHideDuration timer will expire even if the window is not focused.", + "exited": "The prop used to handle exited transition and unmount the component.", "onClose": "Callback fired when the component requests to be closed. Typically onClose is used to set state in the parent component, which is used to control the Snackbar open prop. The reason parameter can optionally be used to control the response to onClose, for example ignoring clickaway.

Signature:
function(event: React.SyntheticEvent<any> | Event, reason: string) => void
event: The event source of the callback.
reason: Can be: "timeout" (autoHideDuration expired), "clickaway", or "escapeKeyDown".", "open": "If true, the component is shown.", "resumeHideDuration": "The number of milliseconds to wait before dismissing after user interaction. If autoHideDuration prop isn't specified, it does nothing. If autoHideDuration prop is specified but resumeHideDuration isn't, we default to autoHideDuration / 2 ms." diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 9abeaab45c8b39..dee2fbf8733f12 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -6,7 +6,6 @@ import { SnackbarUnstyledOwnerState, SnackbarUnstyledProps, SnackbarUnstyledRootSlotProps, - SnackbarUnstyledTransitionSlotProps, SnackbarUnstyledTypeMap, } from './SnackbarUnstyled.types'; import composeClasses from '../composeClasses'; @@ -42,6 +41,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( components = {}, componentsProps = {}, disableWindowBlurListener = false, + exited = true, onBlur, onClose, onFocus, @@ -54,7 +54,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( const classes = useUtilityClasses(); - const { getRootProps, getTransitionProps, exited, onClickAway } = useSnackbar({ + const { getRootProps, onClickAway } = useSnackbar({ autoHideDuration, disableWindowBlurListener, onClose, @@ -63,17 +63,10 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( ref, }); - const ownerState: SnackbarUnstyledOwnerState = { - ...props, - autoHideDuration, - disableWindowBlurListener, - exited, - }; + const ownerState: SnackbarUnstyledOwnerState = props; const Root = component || components.Root || 'div'; - const TransitionComponent = components.Transition; - const rootProps: WithOptionalOwnerState = useSlotProps({ elementType: Root, getSlotProps: (eventHandlers) => @@ -95,20 +88,6 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( className: classes.root, }); - const transitionProps: WithOptionalOwnerState = useSlotProps( - { - elementType: TransitionComponent as React.ElementType, - getSlotProps: (otherHandlers) => - getTransitionProps({ - ...otherHandlers, - onEnter: resolveComponentProps(componentsProps.transition, ownerState)?.onEnter, - onExited: resolveComponentProps(componentsProps.transition, ownerState)?.onExited, - }), - externalSlotProps: componentsProps.transition, - ownerState, - }, - ); - // So that we only render active snackbars. if (!open && exited) { return null; @@ -116,13 +95,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( return ( - - {TransitionComponent ? ( - {children} - ) : ( - children - )} - + {children} ); }) as OverridableComponent; @@ -156,7 +129,6 @@ SnackbarUnstyled.propTypes /* remove-proptypes */ = { */ components: PropTypes.shape({ Root: PropTypes.elementType, - Transition: PropTypes.elementType, }), /** * The props used for each slot inside the Snackbar. @@ -178,13 +150,17 @@ SnackbarUnstyled.propTypes /* remove-proptypes */ = { touchEvent: PropTypes.oneOf(['onTouchEnd', 'onTouchStart', false]), }), root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - transition: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }), /** * If `true`, the `autoHideDuration` timer will expire even if the window is not focused. * @default false */ disableWindowBlurListener: PropTypes.bool, + /** + * The prop used to handle exited transition and unmount the component. + * @default true + */ + exited: PropTypes.bool, /** * @ignore */ diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 35b9dea5130677..52bdf950485890 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -1,5 +1,4 @@ import React from 'react'; -import { TransitionChildren } from 'react-transition-group/Transition'; import { OverrideProps } from '@mui/types'; import { ClickAwayListenerProps } from '../ClickAwayListener'; import { UseSnackbarParameters } from './useSnackbar.types'; @@ -16,7 +15,6 @@ export interface SnackbarUnstyledOwnProps extends Omit; - transition?: - | { - [key: string]: any; - } - | ((ownerState: SnackbarUnstyledOwnerState) => { - [key: string]: any; - }); }; + /** + * The prop used to handle exited transition and unmount the component. + * @default true + */ + exited?: boolean; } export interface SnackbarUnstyledTypeMap

{ @@ -51,9 +47,7 @@ export type SnackbarUnstyledProps< component?: D; }; -export type SnackbarUnstyledOwnerState = SnackbarUnstyledProps & { - exited: boolean; -}; +export type SnackbarUnstyledOwnerState = SnackbarUnstyledProps; export type SnackbarUnstyledRootSlotProps = { ownerState: SnackbarUnstyledOwnerState; @@ -61,9 +55,3 @@ export type SnackbarUnstyledRootSlotProps = { children?: React.ReactNode; ref: React.Ref; }; - -export type SnackbarUnstyledTransitionSlotProps = { - ownerState: SnackbarUnstyledOwnerState; - className?: string; - children?: TransitionChildren; -}; diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts index e7265104bfdb2a..48369270b26b0b 100644 --- a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts @@ -1,6 +1,5 @@ import * as React from 'react'; import { unstable_useEventCallback as useEventCallback } from '@mui/utils'; -import { TransitionProps } from 'react-transition-group/Transition'; import { UseSnackbarParameters, SnackbarCloseReason } from './useSnackbar.types'; /** @@ -21,7 +20,6 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { } = parameters; const timerAutoHide = React.useRef>(); - const [exited, setExited] = React.useState(true); React.useEffect(() => { if (!open) { @@ -154,27 +152,6 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { return undefined; }, [disableWindowBlurListener, handleResume, open]); - const createHandleExited = (otherHandlers?: Partial) => (node: HTMLElement) => { - setExited(true); - - const onExited = otherHandlers?.onExited; - - if (onExited) { - onExited(node); - } - }; - - const createHandleEnter = - (otherHandlers?: Partial) => (node: HTMLElement, isAppearing: boolean) => { - setExited(false); - - const onEnter = otherHandlers?.onEnter; - - if (onEnter) { - onEnter(node, isAppearing); - } - }; - const getRootProps = | undefined> = {}>( otherProps: TOther = {} as TOther, ) => ({ @@ -189,11 +166,5 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { onMouseLeave: createMouseLeave(otherProps), }); - const getTransitionProps = (otherProps?: Partial) => ({ - ...otherProps, - onEnter: createHandleEnter(otherProps), - onExited: createHandleExited(otherProps), - }); - - return { getRootProps, getTransitionProps, exited, onClickAway: handleClickAway }; + return { getRootProps, onClickAway: handleClickAway }; } From 8139c92c5584ffd91f754842cdd568eeb2def91e Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 15 Jul 2022 16:06:03 +0530 Subject: [PATCH 47/71] add return type of getRootProps --- packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts | 8 ++++++-- .../src/SnackbarUnstyled/useSnackbar.types.ts | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts index 48369270b26b0b..ad9e22affe66c5 100644 --- a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts @@ -1,6 +1,10 @@ import * as React from 'react'; import { unstable_useEventCallback as useEventCallback } from '@mui/utils'; -import { UseSnackbarParameters, SnackbarCloseReason } from './useSnackbar.types'; +import { + UseSnackbarParameters, + SnackbarCloseReason, + UseSnackbarRootSlotProps, +} from './useSnackbar.types'; /** * The basic building block for creating custom snackbar. @@ -154,7 +158,7 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { const getRootProps = | undefined> = {}>( otherProps: TOther = {} as TOther, - ) => ({ + ): UseSnackbarRootSlotProps => ({ ref, // ClickAwayListener adds an `onClick` prop which results in the alert not being announced. // See https://github.com/mui/material-ui/issues/29080 diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts index 36d391dfc82ad0..c939bfac689e82 100644 --- a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts @@ -38,3 +38,14 @@ export interface UseSnackbarParameters { */ resumeHideDuration?: number; } + +export type UseSnackbarRootSlotProps = TOther & UseSnackbarRootSlotOwnProps; + +interface UseSnackbarRootSlotOwnProps { + onBlur: React.FocusEventHandler; + onFocus: React.FocusEventHandler; + onMouseEnter: React.MouseEventHandler; + onMouseLeave: React.MouseEventHandler; + ref?: React.Ref; + role: React.AriaRole; +} From eb36dcf63db82f82b139efdf3c887cbb569528c1 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 15 Jul 2022 18:06:02 +0530 Subject: [PATCH 48/71] add SnackbarUnstyled tests --- .../SnackbarUnstyled.test.tsx | 444 +++++++++++++++++- 1 file changed, 442 insertions(+), 2 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.test.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.test.tsx index 19423ae029492b..e52e0862c1efd1 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.test.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.test.tsx @@ -1,9 +1,33 @@ import * as React from 'react'; -import { createRenderer, createMount, describeConformanceUnstyled } from 'test/utils'; +import { expect } from 'chai'; +import { spy } from 'sinon'; +import { + act, + createRenderer, + createMount, + describeConformanceUnstyled, + fireEvent, +} from 'test/utils'; import SnackbarUnstyled, { snackbarUnstyledClasses as classes } from '@mui/base/SnackbarUnstyled'; describe('', () => { - const { render } = createRenderer(); + const { clock, render: clientRender } = createRenderer({ clock: 'fake' }); + /** + * @type {typeof plainRender extends (...args: infer T) => any ? T : enver} args + * + * @remarks + * This is for all intents and purposes the same as our client render method. + * `plainRender` is already wrapped in act(). + * However, React has a bug that flushes effects in a portal synchronously. + * We have to defer the effect manually like `useEffect` would so we have to flush the effect manually instead of relying on `act()`. + * React bug: https://github.com/facebook/react/issues/20074 + */ + function render(...args: [React.ReactElement]) { + const result = clientRender(...args); + clock.tick(0); + return result; + } + const mount = createMount(); describeConformanceUnstyled( @@ -24,4 +48,420 @@ describe('', () => { }, }), ); + + describe('prop: onClose', () => { + it('should be called when clicking away', () => { + const handleClose = spy(); + render( + + message + , + ); + + const event = new window.Event('click', { bubbles: true, cancelable: true }); + document.body.dispatchEvent(event); + + expect(handleClose.callCount).to.equal(1); + expect(handleClose.args[0]).to.deep.equal([event, 'clickaway']); + }); + + it('should be called when pressing Escape', () => { + const handleClose = spy(); + render( + + message + , + ); + + expect(fireEvent.keyDown(document.body, { key: 'Escape' })).to.equal(true); + expect(handleClose.callCount).to.equal(1); + expect(handleClose.args[0][1]).to.deep.equal('escapeKeyDown'); + }); + + it('can limit which Snackbars are closed when pressing Escape', () => { + const handleCloseA = spy((event) => event.preventDefault()); + const handleCloseB = spy(); + render( + + + messageA + + , + + messageB + + , + , + ); + + fireEvent.keyDown(document.body, { key: 'Escape' }); + + expect(handleCloseA.callCount).to.equal(1); + expect(handleCloseB.callCount).to.equal(0); + }); + }); + + describe('prop: autoHideDuration', () => { + it('should call onClose when the timer is done', () => { + const handleClose = spy(); + const autoHideDuration = 2e3; + const { setProps } = render( + + Hello World + , + ); + + setProps({ open: true }); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(autoHideDuration); + + expect(handleClose.callCount).to.equal(1); + expect(handleClose.args[0]).to.deep.equal([null, 'timeout']); + }); + + it('calls onClose at timeout even if the prop changes', () => { + const handleClose1 = spy(); + const handleClose2 = spy(); + const autoHideDuration = 2e3; + const { setProps } = render( + + Hello World + , + ); + + setProps({ open: true }); + clock.tick(autoHideDuration / 2); + setProps({ open: true, onClose: handleClose2 }); + clock.tick(autoHideDuration / 2); + + expect(handleClose1.callCount).to.equal(0); + expect(handleClose2.callCount).to.equal(1); + }); + + it('should not call onClose when the autoHideDuration is reset', () => { + const handleClose = spy(); + const autoHideDuration = 2e3; + const { setProps } = render( + + Hello World + , + ); + + setProps({ open: true }); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(autoHideDuration / 2); + setProps({ autoHideDuration: undefined }); + clock.tick(autoHideDuration / 2); + + expect(handleClose.callCount).to.equal(0); + }); + + it('should not call onClose if autoHideDuration is undefined', () => { + const handleClose = spy(); + const autoHideDuration = 2e3; + render( + + Hello World + , + ); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(autoHideDuration); + + expect(handleClose.callCount).to.equal(0); + }); + + it('should not call onClose if autoHideDuration is null', () => { + const handleClose = spy(); + const autoHideDuration = 2e3; + + render( + + Hello World + , + ); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(autoHideDuration); + + expect(handleClose.callCount).to.equal(0); + }); + + it('should not call onClose when closed', () => { + const handleClose = spy(); + const autoHideDuration = 2e3; + + const { setProps } = render( + + Hello World + , + ); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(autoHideDuration / 2); + setProps({ open: false }); + clock.tick(autoHideDuration / 2); + + expect(handleClose.callCount).to.equal(0); + }); + }); + + [ + { + type: 'mouse', + enter: (container: HTMLElement) => fireEvent.mouseEnter(container.querySelector('button')!), + leave: (container: HTMLElement) => fireEvent.mouseLeave(container.querySelector('button')!), + }, + { + type: 'keyboard', + enter: (container: HTMLElement) => act(() => container.querySelector('button')!.focus()), + leave: (container: HTMLElement) => act(() => container.querySelector('button')!.blur()), + }, + ].forEach((userInteraction) => { + describe(`interacting with ${userInteraction.type}`, () => { + it('should be able to interrupt the timer', () => { + const handleMouseEnter = spy(); + const handleMouseLeave = spy(); + const handleBlur = spy(); + const handleFocus = spy(); + const handleClose = spy(); + const autoHideDuration = 2e3; + + const { container } = render( + + message + {} + , + ); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(autoHideDuration / 2); + userInteraction.enter(container.querySelector('div')!); + + if (userInteraction.type === 'keyboard') { + expect(handleFocus.callCount).to.equal(1); + } else { + expect(handleMouseEnter.callCount).to.equal(1); + } + + clock.tick(autoHideDuration / 2); + userInteraction.leave(container.querySelector('div')!); + + if (userInteraction.type === 'keyboard') { + expect(handleBlur.callCount).to.equal(1); + } else { + expect(handleMouseLeave.callCount).to.equal(1); + } + expect(handleClose.callCount).to.equal(0); + + clock.tick(2e3); + + expect(handleClose.callCount).to.equal(1); + expect(handleClose.args[0]).to.deep.equal([null, 'timeout']); + }); + + it('should not call onClose with not timeout after user interaction', () => { + const handleClose = spy(); + const autoHideDuration = 2e3; + const resumeHideDuration = 3e3; + + const { container } = render( + + message{} + , + ); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(autoHideDuration / 2); + userInteraction.enter(container.querySelector('div')!); + clock.tick(autoHideDuration / 2); + userInteraction.leave(container.querySelector('div')!); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(2e3); + + expect(handleClose.callCount).to.equal(0); + }); + + it('should call onClose when timer done after user interaction', () => { + const handleClose = spy(); + const autoHideDuration = 2e3; + const resumeHideDuration = 3e3; + + const { container } = render( + + message{} + , + ); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(autoHideDuration / 2); + userInteraction.enter(container.querySelector('div')!); + clock.tick(autoHideDuration / 2); + userInteraction.leave(container.querySelector('div')!); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(resumeHideDuration); + + expect(handleClose.callCount).to.equal(1); + expect(handleClose.args[0]).to.deep.equal([null, 'timeout']); + }); + + it('should call onClose immediately after user interaction when 0', () => { + const handleClose = spy(); + const autoHideDuration = 6e3; + const resumeHideDuration = 0; + const { setProps, container } = render( + + message{} + , + ); + + setProps({ open: true }); + + expect(handleClose.callCount).to.equal(0); + + userInteraction.enter(container.querySelector('div')!); + clock.tick(100); + userInteraction.leave(container.querySelector('div')!); + clock.tick(resumeHideDuration); + + expect(handleClose.callCount).to.equal(1); + expect(handleClose.args[0]).to.deep.equal([null, 'timeout']); + }); + }); + }); + + describe('prop: disableWindowBlurListener', () => { + it('should pause auto hide when not disabled and window lost focus', () => { + const handleClose = spy(); + const autoHideDuration = 2e3; + render( + + message + , + ); + + act(() => { + const bEvent = new window.Event('blur', { + bubbles: false, + cancelable: false, + }); + window.dispatchEvent(bEvent); + }); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(autoHideDuration); + + expect(handleClose.callCount).to.equal(0); + + act(() => { + const fEvent = new window.Event('focus', { + bubbles: false, + cancelable: false, + }); + window.dispatchEvent(fEvent); + }); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(autoHideDuration); + + expect(handleClose.callCount).to.equal(1); + expect(handleClose.args[0]).to.deep.equal([null, 'timeout']); + }); + + it('should not pause auto hide when disabled and window lost focus', () => { + const handleClose = spy(); + const autoHideDuration = 2e3; + render( + + message + , + ); + + act(() => { + const event = new window.Event('blur', { bubbles: false, cancelable: false }); + window.dispatchEvent(event); + }); + + expect(handleClose.callCount).to.equal(0); + + clock.tick(autoHideDuration); + + expect(handleClose.callCount).to.equal(1); + expect(handleClose.args[0]).to.deep.equal([null, 'timeout']); + }); + }); + + describe('prop: open', () => { + it('should not render anything when closed', () => { + const { container } = render(Hello, World!); + expect(container).to.have.text(''); + }); + + it('should be able show it after mounted', () => { + const { container, setProps } = render( + Hello, World!, + ); + expect(container).to.have.text(''); + setProps({ open: true }); + expect(container).to.have.text('Hello, World!'); + }); + }); + + describe('prop: children', () => { + it('should render the children', () => { + const nodeRef = React.createRef(); + const children =

; + const { container } = render({children}); + expect(container).to.contain(nodeRef.current); + }); + }); }); From 8ef2dd8c0af01077cf07891eec64609db72c6f36 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 15 Jul 2022 23:10:55 +0530 Subject: [PATCH 49/71] export UseSnackbarRootSlotOwnProps --- packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts index c939bfac689e82..9fe3d008a6103c 100644 --- a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.types.ts @@ -41,7 +41,7 @@ export interface UseSnackbarParameters { export type UseSnackbarRootSlotProps = TOther & UseSnackbarRootSlotOwnProps; -interface UseSnackbarRootSlotOwnProps { +export interface UseSnackbarRootSlotOwnProps { onBlur: React.FocusEventHandler; onFocus: React.FocusEventHandler; onMouseEnter: React.MouseEventHandler; From 263024c4203b273b597be1170a4291a0eba39ff7 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Thu, 11 Aug 2022 11:06:57 +0200 Subject: [PATCH 50/71] Improve event handlers, resolve clickAwayListenerProps --- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 30 ++++++++++------- .../SnackbarUnstyled.types.ts | 12 +++++-- .../src/SnackbarUnstyled/useSnackbar.ts | 33 ++++++++++++------- 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index dee2fbf8733f12..fa49f69476e5bc 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -7,11 +7,12 @@ import { SnackbarUnstyledProps, SnackbarUnstyledRootSlotProps, SnackbarUnstyledTypeMap, + SnackbarUnstyledClickAwayListenerSlotProps, } from './SnackbarUnstyled.types'; import composeClasses from '../composeClasses'; import { getSnackbarUnstyledUtilityClass } from './snackbarUnstyledClasses'; import useSnackbar from './useSnackbar'; -import { resolveComponentProps, useSlotProps, WithOptionalOwnerState } from '../utils'; +import { useSlotProps, WithOptionalOwnerState } from '../utils'; const useUtilityClasses = () => { const slots = { @@ -69,16 +70,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( const rootProps: WithOptionalOwnerState = useSlotProps({ elementType: Root, - getSlotProps: (eventHandlers) => - getRootProps({ - ...eventHandlers, - onBlur: resolveComponentProps(componentsProps.root, ownerState)?.onBlur || onBlur, - onFocus: resolveComponentProps(componentsProps.root, ownerState)?.onFocus || onFocus, - onMouseEnter: - resolveComponentProps(componentsProps.root, ownerState)?.onMouseEnter || onMouseEnter, - onMouseLeave: - resolveComponentProps(componentsProps.root, ownerState)?.onMouseLeave || onMouseLeave, - }), + getSlotProps: getRootProps, externalForwardedProps: other, externalSlotProps: componentsProps.root, additionalProps: { @@ -88,13 +80,27 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( className: classes.root, }); + const clickAwayListenerProps: WithOptionalOwnerState< + Omit + > = useSlotProps({ + elementType: ClickAwayListener, + externalSlotProps: componentsProps.clickAwayListener, + additionalProps: { + onClickAway, + }, + ownerState, + }); + + // ClickAwayListener doesn't support ownerState + delete clickAwayListenerProps.ownerState; + // So that we only render active snackbars. if (!open && exited) { return null; } return ( - + {children} ); diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts index 52bdf950485890..ad866690b61522 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.types.ts @@ -1,6 +1,6 @@ import React from 'react'; import { OverrideProps } from '@mui/types'; -import { ClickAwayListenerProps } from '../ClickAwayListener'; +import ClickAwayListener, { ClickAwayListenerProps } from '../ClickAwayListener'; import { UseSnackbarParameters } from './useSnackbar.types'; import { SlotComponentProps } from '../utils'; @@ -21,7 +21,11 @@ export interface SnackbarUnstyledOwnProps extends Omit; + clickAwayListener?: SlotComponentProps< + typeof ClickAwayListener, + SnackbarUnstyledComponentsPropsOverrides, + SnackbarUnstyledOwnerState + >; root?: SlotComponentProps< 'div', SnackbarUnstyledComponentsPropsOverrides, @@ -55,3 +59,7 @@ export type SnackbarUnstyledRootSlotProps = { children?: React.ReactNode; ref: React.Ref; }; + +export interface SnackbarUnstyledClickAwayListenerSlotProps extends ClickAwayListenerProps { + ownerState: SnackbarUnstyledOwnerState; +} diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts index ad9e22affe66c5..64b77c7a5249d9 100644 --- a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts @@ -5,6 +5,7 @@ import { SnackbarCloseReason, UseSnackbarRootSlotProps, } from './useSnackbar.types'; +import extractEventHandlers from '../utils/extractEventHandlers'; /** * The basic building block for creating custom snackbar. @@ -157,18 +158,26 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { }, [disableWindowBlurListener, handleResume, open]); const getRootProps = | undefined> = {}>( - otherProps: TOther = {} as TOther, - ): UseSnackbarRootSlotProps => ({ - ref, - // ClickAwayListener adds an `onClick` prop which results in the alert not being announced. - // See https://github.com/mui/material-ui/issues/29080 - role: 'presentation', - ...otherProps, - onBlur: createHandleBlur(otherProps), - onFocus: createHandleFocus(otherProps), - onMouseEnter: createMouseEnter(otherProps), - onMouseLeave: createMouseLeave(otherProps), - }); + otherHandlers: TOther = {} as TOther, + ): UseSnackbarRootSlotProps => { + const propsEventHandlers = extractEventHandlers(parameters) as Partial; + const externalEventHandlers = { + ...propsEventHandlers, + ...otherHandlers, + }; + + return { + ref, + // ClickAwayListener adds an `onClick` prop which results in the alert not being announced. + // See https://github.com/mui/material-ui/issues/29080 + role: 'presentation', + ...externalEventHandlers, + onBlur: createHandleBlur(externalEventHandlers), + onFocus: createHandleFocus(externalEventHandlers), + onMouseEnter: createMouseEnter(externalEventHandlers), + onMouseLeave: createMouseLeave(externalEventHandlers), + }; + }; return { getRootProps, onClickAway: handleClickAway }; } From 7004d42d7be0afbefbb7733446c28a9f00a2987a Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 12 Aug 2022 18:59:58 +0530 Subject: [PATCH 51/71] yarn proptypes && yarn docs:api --- docs/pages/base/api/snackbar-unstyled.js | 10 +++--- docs/pages/base/api/snackbar-unstyled.json | 2 +- .../src/SnackbarUnstyled/SnackbarUnstyled.tsx | 31 ++++++++++--------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/docs/pages/base/api/snackbar-unstyled.js b/docs/pages/base/api/snackbar-unstyled.js index 55d836f296d78d..fa1e59c794181a 100644 --- a/docs/pages/base/api/snackbar-unstyled.js +++ b/docs/pages/base/api/snackbar-unstyled.js @@ -8,7 +8,7 @@ export default function Page(props) { return ; } -Page.getInitialProps = () => { +export function getStaticProps() { const req = require.context( 'docs/translations/api-docs/snackbar-unstyled', false, @@ -17,7 +17,9 @@ Page.getInitialProps = () => { const descriptions = mapApiPageTranslations(req); return { - descriptions, - pageContent: jsonPageContent, + props: { + descriptions, + pageContent: jsonPageContent, + }, }; -}; +} diff --git a/docs/pages/base/api/snackbar-unstyled.json b/docs/pages/base/api/snackbar-unstyled.json index d42a410a66c939..d54fdcd97a6dfd 100644 --- a/docs/pages/base/api/snackbar-unstyled.json +++ b/docs/pages/base/api/snackbar-unstyled.json @@ -9,7 +9,7 @@ "componentsProps": { "type": { "name": "shape", - "description": "{ clickAwayListener?: { children: element, disableReactTree?: bool, mouseEvent?: 'onClick'
| 'onMouseDown'
| 'onMouseUp'
| 'onPointerDown'
| 'onPointerUp'
| false, onClickAway?: func, touchEvent?: 'onTouchEnd'
| 'onTouchStart'
| false }, root?: func
| object }" + "description": "{ clickAwayListener?: func
| { children: element, disableReactTree?: bool, mouseEvent?: 'onClick'
| 'onMouseDown'
| 'onMouseUp'
| 'onPointerDown'
| 'onPointerUp'
| false, onClickAway?: func, touchEvent?: 'onTouchEnd'
| 'onTouchStart'
| false }, root?: func
| object }" }, "default": "{}" }, diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index fa49f69476e5bc..21f45f14af40e7 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -141,20 +141,23 @@ SnackbarUnstyled.propTypes /* remove-proptypes */ = { * @default {} */ componentsProps: PropTypes.shape({ - clickAwayListener: PropTypes.shape({ - children: PropTypes.element.isRequired, - disableReactTree: PropTypes.bool, - mouseEvent: PropTypes.oneOf([ - 'onClick', - 'onMouseDown', - 'onMouseUp', - 'onPointerDown', - 'onPointerUp', - false, - ]), - onClickAway: PropTypes.func, - touchEvent: PropTypes.oneOf(['onTouchEnd', 'onTouchStart', false]), - }), + clickAwayListener: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.shape({ + children: PropTypes.element.isRequired, + disableReactTree: PropTypes.bool, + mouseEvent: PropTypes.oneOf([ + 'onClick', + 'onMouseDown', + 'onMouseUp', + 'onPointerDown', + 'onPointerUp', + false, + ]), + onClickAway: PropTypes.func, + touchEvent: PropTypes.oneOf(['onTouchEnd', 'onTouchStart', false]), + }), + ]), root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }), /** From 4c4716f65c85e5c44e26133009b28dce836c9219 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 15 Aug 2022 13:09:57 +0530 Subject: [PATCH 52/71] make the tests pass by passing props for event handlers --- packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index 21f45f14af40e7..c15fd42fb43daf 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -56,6 +56,7 @@ const SnackbarUnstyled = React.forwardRef(function SnackbarUnstyled( const classes = useUtilityClasses(); const { getRootProps, onClickAway } = useSnackbar({ + ...props, autoHideDuration, disableWindowBlurListener, onClose, From a23b4083721c783196310eaa6dc54d97199a4d20 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 15 Aug 2022 20:05:57 +0530 Subject: [PATCH 53/71] docs: implement slide transition with react-transition-grou --- .../snackbar/TransitionComponentSnackbar.js | 57 ++++++++++++------- .../snackbar/TransitionComponentSnackbar.tsx | 57 ++++++++++++------- 2 files changed, 74 insertions(+), 40 deletions(-) diff --git a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js index 6695ec076c6f88..bd91e54a0c445e 100644 --- a/docs/data/base/components/snackbar/TransitionComponentSnackbar.js +++ b/docs/data/base/components/snackbar/TransitionComponentSnackbar.js @@ -1,9 +1,9 @@ import * as React from 'react'; +import { Transition } from 'react-transition-group'; import { styled } from '@mui/system'; import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; import CloseIcon from '@mui/icons-material/Close'; import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; -import Slide from '@mui/material/Slide'; const blue = { 50: '#F0F7FF', @@ -78,9 +78,18 @@ const SnackbarContent = styled('div')( `, ); +const positioningStyles = { + entering: 'translateX(0)', + entered: 'translateX(0)', + exiting: 'translateX(500px)', + exited: 'translateX(500px)', + unmounted: 'translateX(500px)', +}; + export default function TransitionComponentSnackbar() { const [open, setOpen] = React.useState(false); const [exited, setExited] = React.useState(true); + const nodeRef = React.useRef(null); const handleClose = (_, reason) => { if (reason === 'clickaway') { @@ -113,33 +122,41 @@ export default function TransitionComponentSnackbar() { onClose={handleClose} exited={exited} > - - - ( + -
-
Notifications sent
-
- All your notifications were sent to the desired address. + ref={nodeRef} + > + +
+
Notifications sent
+
+ All your notifications were sent to the desired address. +
-
- - - + + + )} + ); diff --git a/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx b/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx index f4671e34d2da68..71e9fe94ad278b 100644 --- a/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx +++ b/docs/data/base/components/snackbar/TransitionComponentSnackbar.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; +import { Transition } from 'react-transition-group'; import { styled } from '@mui/system'; import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; import CloseIcon from '@mui/icons-material/Close'; import SnackbarUnstyled, { SnackbarCloseReason } from '@mui/base/SnackbarUnstyled'; -import Slide from '@mui/material/Slide'; const blue = { 50: '#F0F7FF', @@ -78,9 +78,18 @@ const SnackbarContent = styled('div')( `, ); +const positioningStyles = { + entering: 'translateX(0)', + entered: 'translateX(0)', + exiting: 'translateX(500px)', + exited: 'translateX(500px)', + unmounted: 'translateX(500px)', +}; + export default function TransitionComponentSnackbar() { const [open, setOpen] = React.useState(false); const [exited, setExited] = React.useState(true); + const nodeRef = React.useRef(null); const handleClose = (_: any, reason: SnackbarCloseReason) => { if (reason === 'clickaway') { @@ -113,33 +122,41 @@ export default function TransitionComponentSnackbar() { onClose={handleClose} exited={exited} > - - - ( + -
-
Notifications sent
-
- All your notifications were sent to the desired address. + ref={nodeRef} + > + +
+
Notifications sent
+
+ All your notifications were sent to the desired address. +
-
- - - + + + )} + ); From c31567904806776a9c2565655de34f867e760d08 Mon Sep 17 00:00:00 2001 From: Sam Sycamore <71297412+samuelsycamore@users.noreply.github.com> Date: Tue, 16 Aug 2022 15:40:31 -0500 Subject: [PATCH 54/71] snackbar doc structure draft --- .../data/base/components/snackbar/snackbar.md | 108 +++++++++++++++--- 1 file changed, 92 insertions(+), 16 deletions(-) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index c37a2c7857c811..4711d5c917070d 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -8,42 +8,118 @@ packageName: '@mui/base' # Unstyled snackbar -

The SnackbarUnstyled component provides brief notifications.

+

The SnackbarUnstyled component provides brief notifications.

-```js -import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; -``` +## Introduction + +The SnackbarUnstyled component provides brief notifications. {{"component": "modules/components/ComponentLinkHeader.js", "design": false}} -## Basic usage +## Component + +### Usage + +After [installation](/base/getting-started/installation/), you can start building with this component using the following basic elements: + +```jsx +import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; + +export default function MyApp() { + return ; +} +``` + +### Basics {{"demo": "UnstyledSnackbar.js", "defaultCodeOpen": false}} -## Transitions +```jsx + +``` -You can animate the open and close states of the snackbar with a render prop child and a transition component as shown below, as long as the component meets these conditions: +### Anatomy -- Is a direct child descendent of the snackbar -- Has an `in` prop—this corresponds to the open state -- Passes the `exited` prop to `SnackbarUnstyled` -- Calls the `onEnter` callback prop when the enter transition starts - sets `exited` to false -- Calls the `onExited` callback prop when the exit transition is completed - sets `exited` to true +The `SnackbarUnstyled` component is composed of -These two callbacks allow the snackbar to unmount the child content when closed and keep it fully transitioned. This is only applicable if you are using transition components using [react-transition-group](https://github.com/reactjs/react-transition-group) library internally. +```html -{{"demo": "TransitionComponentSnackbar.js", "defaultCodeOpen": false}} +``` + +### Slot props + +:::info +The following props are available on all non-utility Base components. +See [Usage](/base/getting-started/usage/) for full details. +::: + +Use the `component` prop to override the root slot with a custom element: + +```jsx + +``` -## The useSnackbar hook +Use the `components` prop to override any interior slots in addition to the root: + +```jsx + +``` + +:::warning +If the root element is customized with both the `component` and `components` props, then `component` will take precedence. +::: + +Use the `componentsProps` prop to pass custom props to internal slots. +The following code snippet applies a CSS class called `my-badge` to the badge slot: + +```jsx + +``` + +:::warning +Note that `componentsProps` slot names are written in lowercase (`root`) while `components` slot names are capitalized (`Root`). +::: + +## Hook ```js import { useSnackbar } from '@mui/base/SnackbarUnstyled'; ``` -The `useSnackbar` hook lets you use the functionality of `SnackbarUnstyled` in other components. +The `useSnackbar` hook lets you apply the functionality of `SnackbarUnstyled` to a fully custom component. It returns props to be placed on the root element. If you are using `ClickAwayListener` to close the snackbar by clicking outside of it, make sure to pass `onClickAway` handler returned by this hook to the `ClickAwayListener` component. Make sure to pass the `open` state to the hook and use it to show/hide the snackbar. {{"demo": "UseSnackbar.js", "defaultCodeOpen": false}} + +Hooks _do not_ support [slot props](#slot-props), but they do support [customization props](#customization). + +:::info +Hooks give you the most room for customization, but require more work to implement. +With hooks, you can take full control over how your component is rendered, and define all the custom props and CSS classes you need. + +You may not need to use hooks unless you find that you're limited by the customization options of their component counterparts—for instance, if your component requires significantly different [structure](#anatomy). +::: + +## Customization + +:::info +The following features can be used with both components and hooks. +For the sake of simplicity, demos and code snippets primarily feature components. +::: + +### Transitions + +You can animate the open and close states of the snackbar with a render prop child and a transition component as shown below, as long as the component meets these conditions: + +- Is a direct child descendent of the snackbar +- Has an `in` prop—this corresponds to the open state +- Passes the `exited` prop to `SnackbarUnstyled` +- Calls the `onEnter` callback prop when the enter transition starts - sets `exited` to false +- Calls the `onExited` callback prop when the exit transition is completed - sets `exited` to true + +These two callbacks allow the snackbar to unmount the child content when closed and keep it fully transitioned. This is only applicable if you are using transition components using [react-transition-group](https://github.com/reactjs/react-transition-group) library internally. + +{{"demo": "TransitionComponentSnackbar.js", "defaultCodeOpen": false}} From f2352c8a0f0ea525f43adf3b64ce0a1bea9b732e Mon Sep 17 00:00:00 2001 From: Sam Sycamore <71297412+samuelsycamore@users.noreply.github.com> Date: Tue, 16 Aug 2022 15:54:15 -0500 Subject: [PATCH 55/71] snackbar content first pass --- docs/data/base/components/snackbar/snackbar.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index 4711d5c917070d..351c5991ac164e 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -40,10 +40,10 @@ export default function MyApp() { ### Anatomy -The `SnackbarUnstyled` component is composed of +The `SnackbarUnstyled` component is composed of a single root `
` element that is designed to be used as a notification popover: ```html - + ``` ### Slot props @@ -56,13 +56,13 @@ See [Usage](/base/getting-started/usage/) for full details. Use the `component` prop to override the root slot with a custom element: ```jsx - + ``` Use the `components` prop to override any interior slots in addition to the root: ```jsx - + ``` :::warning @@ -73,7 +73,7 @@ Use the `componentsProps` prop to pass custom props to internal slots. The following code snippet applies a CSS class called `my-badge` to the badge slot: ```jsx - + ``` :::warning @@ -88,9 +88,13 @@ import { useSnackbar } from '@mui/base/SnackbarUnstyled'; The `useSnackbar` hook lets you apply the functionality of `SnackbarUnstyled` to a fully custom component. -It returns props to be placed on the root element. If you are using `ClickAwayListener` to close the snackbar by clicking outside of it, make sure to pass `onClickAway` handler returned by this hook to the `ClickAwayListener` component. +It returns props to be placed on the custom component, along with fields representing the component's internal state. + +Hooks _do not_ support [slot props](#slot-props), but they do support [customization props](#customization). + +If you are using a [`ClickAwayListener`](/react-click-away-listener/) to close the snackbar by clicking outside of it, make sure to pass the `onClickAway` handler returned by this hook to the `ClickAwayListener`. -Make sure to pass the `open` state to the hook and use it to show/hide the snackbar. +Pass the `open` state to the hook and use it to show and hide the snackbar. {{"demo": "UseSnackbar.js", "defaultCodeOpen": false}} From 4e99697b84b5f18a70997671d2db5828e45d6a41 Mon Sep 17 00:00:00 2001 From: Sam Sycamore <71297412+samuelsycamore@users.noreply.github.com> Date: Tue, 16 Aug 2022 21:27:25 -0500 Subject: [PATCH 56/71] content second pass --- .../data/base/components/snackbar/snackbar.md | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index 351c5991ac164e..b8485d471f8c08 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -8,11 +8,14 @@ packageName: '@mui/base' # Unstyled snackbar -

The SnackbarUnstyled component provides brief notifications.

+

The SnackbarUnstyled component informs users that an action has been or will be performed by the app.

## Introduction -The SnackbarUnstyled component provides brief notifications. +A snackbar provides users with a brief message about app processes without interrupting their activity or experience. + +The `SnackbarUnstyled` component is built to appear temporarily in the corner of the screen to inform users of an action that the app is taking. +After a set amount of time, it disappears on its own without requiring user interaction to dismiss it. {{"component": "modules/components/ComponentLinkHeader.js", "design": false}} @@ -26,24 +29,23 @@ After [installation](/base/getting-started/installation/), you can start buildin import SnackbarUnstyled from '@mui/base/SnackbarUnstyled'; export default function MyApp() { - return ; + return {/* snackbar text */}; } ``` ### Basics -{{"demo": "UnstyledSnackbar.js", "defaultCodeOpen": false}} +The following demo illustrates the basic usage of the `SnackbarUnstyled` component. +Click **Open snackbar** to see how it behaves: -```jsx - -``` +{{"demo": "UnstyledSnackbar.js", "defaultCodeOpen": false}} ### Anatomy -The `SnackbarUnstyled` component is composed of a single root `
` element that is designed to be used as a notification popover: +The `SnackbarUnstyled` component is composed of a single root `
` slot with no interior slots: ```html - + ``` ### Slot props @@ -116,14 +118,17 @@ For the sake of simplicity, demos and code snippets primarily feature components ### Transitions -You can animate the open and close states of the snackbar with a render prop child and a transition component as shown below, as long as the component meets these conditions: +You can animate the open and close states of the snackbar with a render prop child and a transition component, as long as the component meets these conditions: - Is a direct child descendent of the snackbar - Has an `in` prop—this corresponds to the open state - Passes the `exited` prop to `SnackbarUnstyled` -- Calls the `onEnter` callback prop when the enter transition starts - sets `exited` to false -- Calls the `onExited` callback prop when the exit transition is completed - sets `exited` to true +- Calls the `onEnter` callback prop when the enter transition starts—sets `exited` to false +- Calls the `onExited` callback prop when the exit transition is completed—sets `exited` to true + +These two callbacks allow the snackbar to unmount the child content when closed and keep it fully transitioned. +This is only applicable if you are using transition components using [react-transition-group](https://github.com/reactjs/react-transition-group) library internally. -These two callbacks allow the snackbar to unmount the child content when closed and keep it fully transitioned. This is only applicable if you are using transition components using [react-transition-group](https://github.com/reactjs/react-transition-group) library internally. +The demo below shows how to create a snackbar with custom transitions: {{"demo": "TransitionComponentSnackbar.js", "defaultCodeOpen": false}} From 3026d2759ff27bce0469d5047c541e42e955eedf Mon Sep 17 00:00:00 2001 From: Sam Sycamore <71297412+samuelsycamore@users.noreply.github.com> Date: Tue, 16 Aug 2022 21:32:19 -0500 Subject: [PATCH 57/71] fix copypasta --- docs/data/base/components/snackbar/snackbar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index b8485d471f8c08..61eee7b0bcbb34 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -72,7 +72,7 @@ If the root element is customized with both the `component` and `components` pro ::: Use the `componentsProps` prop to pass custom props to internal slots. -The following code snippet applies a CSS class called `my-badge` to the badge slot: +The following code snippet applies a CSS class called `my-snackbar` to the root slot: ```jsx From 07512b210302f85c6f21f3a89146e9f4ff46b932 Mon Sep 17 00:00:00 2001 From: Sam Sycamore <71297412+samuelsycamore@users.noreply.github.com> Date: Tue, 16 Aug 2022 21:39:09 -0500 Subject: [PATCH 58/71] tweak frontmatter and tiny tense change --- docs/data/base/components/snackbar/snackbar.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index 61eee7b0bcbb34..114196988fe530 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -1,9 +1,8 @@ --- product: base -title: Unstyled React Snackbar component +title: Unstyled React Snackbar component and hook components: SnackbarUnstyled githubLabel: 'component: snackbar' -packageName: '@mui/base' --- # Unstyled snackbar @@ -94,7 +93,7 @@ It returns props to be placed on the custom component, along with fields represe Hooks _do not_ support [slot props](#slot-props), but they do support [customization props](#customization). -If you are using a [`ClickAwayListener`](/react-click-away-listener/) to close the snackbar by clicking outside of it, make sure to pass the `onClickAway` handler returned by this hook to the `ClickAwayListener`. +If you use a [`ClickAwayListener`](/react-click-away-listener/) to let the user close the snackbar by clicking outside of it, make sure to pass the `onClickAway` handler returned by this hook to the `ClickAwayListener`. Pass the `open` state to the hook and use it to show and hide the snackbar. From d4585a0ea6d58c733c93327010242b32ab6d64cf Mon Sep 17 00:00:00 2001 From: Sam Sycamore <71297412+samuelsycamore@users.noreply.github.com> Date: Tue, 16 Aug 2022 21:41:59 -0500 Subject: [PATCH 59/71] introduce hook demo --- docs/data/base/components/snackbar/snackbar.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index 114196988fe530..06504898a501fd 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -97,6 +97,8 @@ If you use a [`ClickAwayListener`](/react-click-away-listener/) to let the user Pass the `open` state to the hook and use it to show and hide the snackbar. +The demo below shows how to build a fully custom component with the `useSnackbar` hook that also incorporates the `ClickAwayListener` component: + {{"demo": "UseSnackbar.js", "defaultCodeOpen": false}} Hooks _do not_ support [slot props](#slot-props), but they do support [customization props](#customization). From b4a091e22c503e69d72d9ed26106a45d0644f0ab Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 17 Aug 2022 11:03:21 +0530 Subject: [PATCH 60/71] docs: class -> className --- docs/data/base/components/snackbar/snackbar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index 06504898a501fd..4413ec2bf553a6 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -44,7 +44,7 @@ Click **Open snackbar** to see how it behaves: The `SnackbarUnstyled` component is composed of a single root `
` slot with no interior slots: ```html - +
snackbar content
``` ### Slot props From 62645e9966ce4868069c10bc69609a0b2886162e Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 17 Aug 2022 11:04:38 +0530 Subject: [PATCH 61/71] fix URL to ClickAwayListener component --- docs/data/base/components/snackbar/snackbar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index 4413ec2bf553a6..00a4f1d299d418 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -93,7 +93,7 @@ It returns props to be placed on the custom component, along with fields represe Hooks _do not_ support [slot props](#slot-props), but they do support [customization props](#customization). -If you use a [`ClickAwayListener`](/react-click-away-listener/) to let the user close the snackbar by clicking outside of it, make sure to pass the `onClickAway` handler returned by this hook to the `ClickAwayListener`. +If you use a [`ClickAwayListener`](/base/react-click-away-listener/) to let the user close the snackbar by clicking outside of it, make sure to pass the `onClickAway` handler returned by this hook to the `ClickAwayListener`. Pass the `open` state to the hook and use it to show and hide the snackbar. From 735c41ea2a05199b713082d10f46a4077af801af Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 17 Aug 2022 11:05:12 +0530 Subject: [PATCH 62/71] docs: remove redundant line --- docs/data/base/components/snackbar/snackbar.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index 00a4f1d299d418..d33835c675f1cd 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -101,8 +101,6 @@ The demo below shows how to build a fully custom component with the `useSnackbar {{"demo": "UseSnackbar.js", "defaultCodeOpen": false}} -Hooks _do not_ support [slot props](#slot-props), but they do support [customization props](#customization). - :::info Hooks give you the most room for customization, but require more work to implement. With hooks, you can take full control over how your component is rendered, and define all the custom props and CSS classes you need. From a89fedca0511b1369ca6ce4f262a0f6d1ed26d99 Mon Sep 17 00:00:00 2001 From: Sam Sycamore <71297412+samuelsycamore@users.noreply.github.com> Date: Wed, 17 Aug 2022 13:16:30 -0500 Subject: [PATCH 63/71] expand basics section --- .../data/base/components/snackbar/snackbar.md | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index d33835c675f1cd..076caf562a008e 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -11,10 +11,9 @@ githubLabel: 'component: snackbar' ## Introduction -A snackbar provides users with a brief message about app processes without interrupting their activity or experience. +A snackbar provides users with a brief, temporary message about app processes without interrupting their activity or experience. -The `SnackbarUnstyled` component is built to appear temporarily in the corner of the screen to inform users of an action that the app is taking. -After a set amount of time, it disappears on its own without requiring user interaction to dismiss it. +The `SnackbarUnstyled` component is built to appear in the corner or center of the screen to inform users about an action that the app is taking. {{"component": "modules/components/ComponentLinkHeader.js", "design": false}} @@ -34,7 +33,20 @@ export default function MyApp() { ### Basics -The following demo illustrates the basic usage of the `SnackbarUnstyled` component. +:::info +Snackbars are differentiated from other kinds of popups and alerts in that they are _informational, temporary, and contextual_ (Source: [Material Design—Snackbars](https://material.io/components/snackbars)). +::: + +`SnackbarUnstyled` doesn't impose any restrictions on its implementation—it's up to you to design it so that it doesn't interrupt the user experience, and disappears after a set amount of time without the requiring the user to take action. + +Use the `autoHideDuration` prop to set the time (in milliseconds) that the snackbar remains on the screen. + +:::info +You may want to implement `SnackbarUnstyled` with [`ClickAwayListener`](/base/react-click-away-listener/), so that the user can choose to dismiss the snackbar before its time runs out. +But this behavior is optional for a snackbar. +::: + +The following demo illustrates the basic usage of `SnackbarUnstyled`. Click **Open snackbar** to see how it behaves: {{"demo": "UnstyledSnackbar.js", "defaultCodeOpen": false}} From 5104048a822ba09ee374fa10ac77307d1a5291d3 Mon Sep 17 00:00:00 2001 From: Sam Sycamore <71297412+samuelsycamore@users.noreply.github.com> Date: Wed, 17 Aug 2022 13:22:00 -0500 Subject: [PATCH 64/71] typo --- docs/data/base/components/snackbar/snackbar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index 076caf562a008e..58f0b4d91794d0 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -37,7 +37,7 @@ export default function MyApp() { Snackbars are differentiated from other kinds of popups and alerts in that they are _informational, temporary, and contextual_ (Source: [Material Design—Snackbars](https://material.io/components/snackbars)). ::: -`SnackbarUnstyled` doesn't impose any restrictions on its implementation—it's up to you to design it so that it doesn't interrupt the user experience, and disappears after a set amount of time without the requiring the user to take action. +`SnackbarUnstyled` doesn't impose any restrictions on its implementation—it's up to you to design it so that it doesn't interrupt the user experience, and disappears after a set amount of time without requiring the user to take action. Use the `autoHideDuration` prop to set the time (in milliseconds) that the snackbar remains on the screen. From 7cf394a2f807ddfa729574c189d75037fd482a44 Mon Sep 17 00:00:00 2001 From: Sam Sycamore <71297412+samuelsycamore@users.noreply.github.com> Date: Wed, 17 Aug 2022 13:28:01 -0500 Subject: [PATCH 65/71] clarify what clickawaylistener does --- docs/data/base/components/snackbar/snackbar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index 58f0b4d91794d0..c797b6a1f14d96 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -42,7 +42,7 @@ Snackbars are differentiated from other kinds of popups and alerts in that they Use the `autoHideDuration` prop to set the time (in milliseconds) that the snackbar remains on the screen. :::info -You may want to implement `SnackbarUnstyled` with [`ClickAwayListener`](/base/react-click-away-listener/), so that the user can choose to dismiss the snackbar before its time runs out. +You may want to implement `SnackbarUnstyled` with [`ClickAwayListener`](/base/react-click-away-listener/), so that the user can choose to dismiss the snackbar before its time is up by clicking anywhere outside of it. But this behavior is optional for a snackbar. ::: From 6b57a107aa915ea4a57ac13fe5990471edc2a74d Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 25 Aug 2022 13:57:34 +0530 Subject: [PATCH 66/71] yarn docs:api --- docs/pages/base/api/snackbar-unstyled.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/pages/base/api/snackbar-unstyled.js b/docs/pages/base/api/snackbar-unstyled.js index fa1e59c794181a..55d836f296d78d 100644 --- a/docs/pages/base/api/snackbar-unstyled.js +++ b/docs/pages/base/api/snackbar-unstyled.js @@ -8,7 +8,7 @@ export default function Page(props) { return ; } -export function getStaticProps() { +Page.getInitialProps = () => { const req = require.context( 'docs/translations/api-docs/snackbar-unstyled', false, @@ -17,9 +17,7 @@ export function getStaticProps() { const descriptions = mapApiPageTranslations(req); return { - props: { - descriptions, - pageContent: jsonPageContent, - }, + descriptions, + pageContent: jsonPageContent, }; -} +}; From e782ff313f48249b6a43ede18dc15d54c718c054 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 22 Sep 2022 17:44:02 +0530 Subject: [PATCH 67/71] change className signature pattern --- .../mui-base/src/SnackbarUnstyled/snackbarUnstyledClasses.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/snackbarUnstyledClasses.ts b/packages/mui-base/src/SnackbarUnstyled/snackbarUnstyledClasses.ts index ebaef86c2bd6cd..0aaca0c4067f01 100644 --- a/packages/mui-base/src/SnackbarUnstyled/snackbarUnstyledClasses.ts +++ b/packages/mui-base/src/SnackbarUnstyled/snackbarUnstyledClasses.ts @@ -7,10 +7,10 @@ export interface SnackbarUnstyledClasses { } export function getSnackbarUnstyledUtilityClass(slot: string): string { - return generateUtilityClass('BaseSnackbar', slot); + return generateUtilityClass('MuiSnackbar', slot); } -const snackbarUnstyledClasses: SnackbarUnstyledClasses = generateUtilityClasses('BaseSnackbar', [ +const snackbarUnstyledClasses: SnackbarUnstyledClasses = generateUtilityClasses('MuiSnackbar', [ 'root', ]); From f12673bc46ac36da3e36d8f00f668df620eac848 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 22 Sep 2022 17:44:48 +0530 Subject: [PATCH 68/71] use optional chaining operator --- .../src/SnackbarUnstyled/useSnackbar.ts | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts index 64b77c7a5249d9..9c4f0a41324a2a 100644 --- a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts @@ -39,9 +39,7 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { // IE11, Edge (prior to using Blink?) use 'Esc' if (nativeEvent.key === 'Escape' || nativeEvent.key === 'Esc') { // not calling `preventDefault` since we don't know if people may ignore this event e.g. a permanently open snackbar - if (onClose) { - onClose(nativeEvent, 'escapeKeyDown'); - } + onClose?.(nativeEvent, 'escapeKeyDown'); } } } @@ -55,9 +53,7 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { const handleClose = useEventCallback( (event: Event | React.SyntheticEvent | null, reason: SnackbarCloseReason) => { - if (onClose) { - onClose(event, reason); - } + onClose?.(event, reason); }, ); @@ -83,9 +79,7 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { }, [open, autoHideDuration, setAutoHideTimer]); const handleClickAway = (event: React.SyntheticEvent | Event) => { - if (onClose) { - onClose(event, 'clickaway'); - } + onClose?.(event, 'clickaway'); }; // Pause the timer when the user is interacting with the Snackbar @@ -106,9 +100,7 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { (otherHandlers: Record | undefined>) => (event: React.FocusEvent) => { const onBlurCallback = otherHandlers.onBlur; - if (onBlurCallback) { - onBlurCallback(event); - } + onBlurCallback?.(event); handleResume(); }; @@ -116,9 +108,7 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { (otherHandlers: Record | undefined>) => (event: React.FocusEvent) => { const onFocusCallback = otherHandlers.onFocus; - if (onFocusCallback) { - onFocusCallback(event); - } + onFocusCallback?.(event); handlePause(); }; @@ -126,9 +116,7 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { (otherHandlers: Record | undefined>) => (event: React.MouseEvent) => { const onMouseEnterCallback = otherHandlers.onMouseEnter; - if (onMouseEnterCallback) { - onMouseEnterCallback(event); - } + onMouseEnterCallback?.(event); handlePause(); }; @@ -136,9 +124,7 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { (otherHandlers: Record | undefined>) => (event: React.MouseEvent) => { const onMouseLeaveCallback = otherHandlers.onMouseLeave; - if (onMouseLeaveCallback) { - onMouseLeaveCallback(event); - } + onMouseLeaveCallback?.(event); handleResume(); }; From 28e75538a45aecd13032ac3b3c0f0ff78abdc0ab Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 22 Sep 2022 17:50:02 +0530 Subject: [PATCH 69/71] remove unnecessary type --- packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts index 9c4f0a41324a2a..801885686c4165 100644 --- a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts @@ -51,11 +51,9 @@ export default function useSnackbar(parameters: UseSnackbarParameters) { }; }, [open, onClose]); - const handleClose = useEventCallback( - (event: Event | React.SyntheticEvent | null, reason: SnackbarCloseReason) => { - onClose?.(event, reason); - }, - ); + const handleClose = useEventCallback((event: null, reason: SnackbarCloseReason) => { + onClose?.(event, reason); + }); const setAutoHideTimer = useEventCallback((autoHideDurationParam: number | null) => { if (!onClose || autoHideDurationParam == null) { From 0a1e8efd4a725f5076ff90ace2ce39326813e5ec Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 22 Sep 2022 17:59:56 +0530 Subject: [PATCH 70/71] yarn docs:api --- docs/pages/base/api/snackbar-unstyled.json | 2 +- packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/base/api/snackbar-unstyled.json b/docs/pages/base/api/snackbar-unstyled.json index d54fdcd97a6dfd..24cb9c021f6890 100644 --- a/docs/pages/base/api/snackbar-unstyled.json +++ b/docs/pages/base/api/snackbar-unstyled.json @@ -25,6 +25,6 @@ "forwardsRefTo": "HTMLDivElement", "filename": "/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx", "inheritance": null, - "demos": "", + "demos": "", "cssComponent": false } diff --git a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx index c15fd42fb43daf..9999fa651710e2 100644 --- a/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx +++ b/packages/mui-base/src/SnackbarUnstyled/SnackbarUnstyled.tsx @@ -25,7 +25,7 @@ const useUtilityClasses = () => { * * Demos: * - * - [Snackbar](https://mui.com/base/react-snackbar/) + * - [Unstyled snackbar](https://mui.com/base/react-snackbar/) * * API: * From 6cb3dad3c854fd7b15b91fbb778994994f591905 Mon Sep 17 00:00:00 2001 From: Sam Sycamore <71297412+samuelsycamore@users.noreply.github.com> Date: Fri, 30 Sep 2022 19:02:58 -0500 Subject: [PATCH 71/71] remove md specs and mention of position --- docs/data/base/components/snackbar/snackbar.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index c797b6a1f14d96..c8ed37ffb3e7e6 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -13,7 +13,7 @@ githubLabel: 'component: snackbar' A snackbar provides users with a brief, temporary message about app processes without interrupting their activity or experience. -The `SnackbarUnstyled` component is built to appear in the corner or center of the screen to inform users about an action that the app is taking. +The `SnackbarUnstyled` component is built to appear on-screen to inform users about an action that the app is taking. {{"component": "modules/components/ComponentLinkHeader.js", "design": false}} @@ -33,10 +33,6 @@ export default function MyApp() { ### Basics -:::info -Snackbars are differentiated from other kinds of popups and alerts in that they are _informational, temporary, and contextual_ (Source: [Material Design—Snackbars](https://material.io/components/snackbars)). -::: - `SnackbarUnstyled` doesn't impose any restrictions on its implementation—it's up to you to design it so that it doesn't interrupt the user experience, and disappears after a set amount of time without requiring the user to take action. Use the `autoHideDuration` prop to set the time (in milliseconds) that the snackbar remains on the screen.