diff --git a/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js new file mode 100644 index 00000000000000..7b20091a88ef16 --- /dev/null +++ b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js @@ -0,0 +1,65 @@ +import * as React from 'react'; +import Button from '@mui/joy/Button'; +import Snackbar from '@mui/joy/Snackbar'; +import { keyframes } from '@mui/system'; + +const inAnimation = keyframes` + 0% { + transform: scale(0); + opacity: 0; + } + 100% { + transform: scale(1); + opacity: 1; + } +`; + +const outAnimation = keyframes` + 0% { + transform: scale(1); + opacity: 1; + } + 100% { + transform: scale(0); + opacity: 0; + } +`; + +export default function CustomAnimatedSnackbar() { + const [open, setOpen] = React.useState(false); + + const animationDuration = 600; + + const handleClick = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + return ( +
The Snackbar, also commonly referred to as Toast, component informs users that an action has been or will be performed by the app.
+{{"component": "modules/components/ComponentLinkHeader.js", "design": false}} + +## Introduction + +Snackbars are designed to provide brief, non-intrusive notifications to users, informing them about processes an app has performed or will perform. + +By default, the snackbar is displayed in the lower-right corner of the screen. They are designed not to disrupt the user's workflow and can be dismissed automatically without the need of any user interaction. + +Snackbars contain a single line of text directly related to the operation performed. They can contain text actions, but no icons. + +{{"demo": "SnackbarUsage.js", "hideToolbar": true, "bg": "gradient"}} + +## Basics + +```jsx +import Snackbar from '@mui/joy/Snackbar'; +``` + +### Position + +The position of the snackbar can be controlled by specifying the `anchorOrigin` prop. + +In wider layouts, snackbars can be aligned to the left or centered, especially if they are consistently positioned in a specific location at the bottom of the screen. However, in some cases, you may need more flexible positioning. + +{{"demo": "PositionedSnackbar.js"}} + +## Customization + +### Variants + +The Snackbar component supports Joy UI's four [global variants](/joy-ui/main-features/global-variants/): `plain`, `outlined` (default), `soft`, and `solid`. + +{{"demo": "SnackbarVariants.js"}} + :::info -The Joy UI Snackbar component is still in development. -If you're in need of it, please upvote **[this GitHub issue](https://github.com/mui/material-ui/issues/36603)** to help us prioritize the next batch of new components. +To learn how to add your own variants, check out [Themed components—Extend variants](/joy-ui/customization/themed-components/#extend-variants). +Note that you lose the global variants when you add custom variants. ::: -## Integration with headless UI libraries +### Sizes + +The Snackbar component comes in three sizes: `sm`, `md` (default), and `lg`. + +{{"demo": "SnackbarSizes.js"}} + +:::info +To learn how to add custom sizes to the component, check out [Themed components—Extend sizes](/joy-ui/customization/themed-components/#extend-sizes). +::: + +### Colors + +Every palette included in the theme is available via the `color` prop. +Play around combining different colors with different variants. + +{{"demo": "SnackbarColors.js"}} + +### Hide duration + +Use `autoHideDuration` prop to control how long the Snackbar is displayed. If it is not provided, the Snackbar will be displayed until the user dismisses it. -In the meantime, you can still adopt Joy UI today for building a snackbar! +{{"demo": "SnackbarHideDuration.js"}} -This document shows how to construct it with existing Joy UI components combined with popular headless UI libraries. +### Close reason -### Parting from the Alert component +There are three reasons for the Snackbar to close: -Joy UI's [`Alert`](/joy-ui/react-alert/) component is perfect for building a snackbar (or toast) because of the default role—`alert` and support for decorators. +- `timeout`: The Snackbar is closed after the `autoHideDuration` prop timer expires. +- `clickaway`: The Snackbar is closed when the user interacts outside of the Snackbar. +- `escapeKeyDown`: The Snackbar is closed when the user presses the escape key. -### With Radix UI +You can access the value from the second argument of the `onClose` callback. -Using Joy UI's Alert component as a starting point, pass Radix UI's Toast to component prop. -Radix will enhance the functionalities by preserving the styles of Joy UI components. +```js +Snackbar
. On smaller screens, the component grows to occupy all the available width, the horizontal alignment is ignored."
+ },
+ "animationDuration": {
+ "description": "The duration of the animation in milliseconds. This value is used to control the length of time it takes for an animation to complete one cycle. It is also utilized for delaying the unmount of the component. Provide this value if you have your own animation so that we can precisely time the component's unmount to match your custom animation."
+ },
+ "autoHideDuration": {
+ "description": "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."
+ },
+ "color": {
+ "description": "The color of the component. It supports those theme colors that make sense for this component."
+ },
+ "component": {
+ "description": "The component used for the root node. Either a string to use a HTML element or a component."
+ },
+ "disableWindowBlurListener": {
+ "description": "If true
, the autoHideDuration
timer will expire even if the window is not focused."
+ },
+ "endDecorator": { "description": "Element placed after the children." },
+ "invertedColors": {
+ "description": "If true
, the children with an implicit color prop invert their colors to match the component's variant and color."
+ },
+ "key": {
+ "description": "When displaying multiple consecutive snackbars using a single parent-rendered <Snackbar/>
, add the key
prop to ensure independent treatment of each message. For instance, use <Snackbar key={message} />
. Otherwise, messages might update in place, and features like autoHideDuration
could be affected."
+ },
+ "onClose": {
+ "description": "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
.",
+ "typeDescriptions": {
+ "event": "The event source of the callback.",
+ "reason": "Can be: "timeout"
(autoHideDuration
expired), "clickaway"
, or "escapeKeyDown"
."
+ }
+ },
+ "onUnmount": { "description": "A callback fired when the component is about to be unmounted." },
+ "open": { "description": "If true
, the component is shown." },
+ "resumeHideDuration": {
+ "description": "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."
+ },
+ "size": { "description": "The size of the component." },
+ "slotProps": { "description": "The props used for each slot inside." },
+ "slots": { "description": "The components used for each slot inside." },
+ "startDecorator": { "description": "Element placed before the children." },
+ "sx": {
+ "description": "The system prop that allows defining system overrides as well as additional CSS styles."
+ },
+ "variant": {
+ "description": "The global variant to use."
+ }
+ },
+ "classDescriptions": {
+ "root": { "description": "Class name applied to the root element." },
+ "anchorOriginTopCenter": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "anchorOrigin={{ 'top', 'center' }}
"
+ },
+ "anchorOriginBottomCenter": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "anchorOrigin={{ 'bottom', 'center' }}
"
+ },
+ "anchorOriginTopRight": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "anchorOrigin={{ 'top', 'right' }}
"
+ },
+ "anchorOriginBottomRight": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "anchorOrigin={{ 'bottom', 'right' }}
"
+ },
+ "anchorOriginTopLeft": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "anchorOrigin={{ 'top', 'left' }}
"
+ },
+ "anchorOriginBottomLeft": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "anchorOrigin={{ 'bottom', 'left' }}
"
+ },
+ "colorPrimary": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"primary\"
"
+ },
+ "colorDanger": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"danger\"
"
+ },
+ "colorNeutral": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"neutral\"
"
+ },
+ "colorSuccess": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"success\"
"
+ },
+ "colorWarning": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"warning\"
"
+ },
+ "endDecorator": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the endDecorator element",
+ "conditions": "supplied"
+ },
+ "sizeSm": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"sm\"
"
+ },
+ "sizeMd": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"md\"
"
+ },
+ "sizeLg": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"lg\"
"
+ },
+ "startDecorator": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the startDecorator element",
+ "conditions": "supplied"
+ },
+ "variantPlain": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"plain\"
"
+ },
+ "variantOutlined": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
"
+ },
+ "variantSoft": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"soft\"
"
+ },
+ "variantSolid": {
+ "description": "Class name applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"solid\"
"
+ }
+ },
+ "slotDescriptions": {
+ "root": "The component that renders the root.",
+ "startDecorator": "The component that renders the start decorator.",
+ "endDecorator": "The component that renders the end decorator.",
+ "clickAway": "The component that renders the click away."
+ }
+}
diff --git a/packages/mui-base/src/Snackbar/Snackbar.tsx b/packages/mui-base/src/Snackbar/Snackbar.tsx
index 8593516d21a247..1ba54db99b2045 100644
--- a/packages/mui-base/src/Snackbar/Snackbar.tsx
+++ b/packages/mui-base/src/Snackbar/Snackbar.tsx
@@ -27,6 +27,7 @@ const useUtilityClasses = () => {
* Demos:
*
* - [Snackbar](https://mui.com/base-ui/react-snackbar/)
+ * - [Snackbar](https://mui.com/joy-ui/react-snackbar/)
* - [Snackbar](https://mui.com/material-ui/react-snackbar/)
*
* API:
diff --git a/packages/mui-joy/src/Snackbar/Snackbar.test.tsx b/packages/mui-joy/src/Snackbar/Snackbar.test.tsx
new file mode 100644
index 00000000000000..b8c12d8535e237
--- /dev/null
+++ b/packages/mui-joy/src/Snackbar/Snackbar.test.tsx
@@ -0,0 +1,452 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import { spy } from 'sinon';
+import { describeConformance, createRenderer, fireEvent, act } from '@mui-internal/test-utils';
+import Snackbar, { snackbarClasses as classes } from '@mui/joy/Snackbar';
+import { ThemeProvider } from '@mui/joy/styles';
+
+describe('Joy {
+ props: P &
+ Omit