From a29a4b28a1a447994d4e94bbdd56304ba27c88a6 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 4 Sep 2023 20:07:31 +0530 Subject: [PATCH 01/58] Joy UI Snackbar initial start --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 89 +++++++++++++++++++ .../mui-joy/src/Snackbar/SnackbarProps.ts | 51 +++++++++++ packages/mui-joy/src/Snackbar/index.ts | 5 ++ .../mui-joy/src/Snackbar/snackbarClasses.ts | 24 +++++ 4 files changed, 169 insertions(+) create mode 100644 packages/mui-joy/src/Snackbar/Snackbar.tsx create mode 100644 packages/mui-joy/src/Snackbar/SnackbarProps.ts create mode 100644 packages/mui-joy/src/Snackbar/index.ts create mode 100644 packages/mui-joy/src/Snackbar/snackbarClasses.ts diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx new file mode 100644 index 00000000000000..5f6fa1e668e45e --- /dev/null +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -0,0 +1,89 @@ +'use client'; +import * as React from 'react'; +import clsx from 'clsx'; +import { unstable_composeClasses as composeClasses } from '@mui/base'; +import { useSnackbar } from '@mui/base/useSnackbar'; +import useSlot from '../utils/useSlot'; +import styled from '../styles/styled'; +import { useThemeProps } from '../styles'; +import { SnackbarProps } from './SnackbarProps'; +import { getSnackbarUtilityClass } from './snackbarClasses'; + +const useUtilityClasses = () => { + const slots = { + root: ['root'], + startDecorator: ['startDecorator'], + endDecorator: ['endDecorator'], + }; + + return composeClasses(slots, getSnackbarUtilityClass, {}); +}; + +const SnackbarRoot = styled('div')({}); + +const SnackbarStartDecorator = styled('span')({}); + +const SnackbarEndDecorator = styled('span')({}); + +const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { + const props = useThemeProps({ + props: inProps, + name: 'JoySnackbar', + }); + + const { children, className, component, slots, slotProps, open, ...other } = props; + + const ownerState = { ...props }; + + const classes = useUtilityClasses(); + + const { getRootProps } = useSnackbar({ ...ownerState }); + + const externalForwardedProps = { ...other, component, slots, slotProps }; + + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: SnackbarRoot, + externalForwardedProps, + getSlotProps: getRootProps, + ownerState, + }); + + const [SlotStartDecorator, startDecoratorProps] = useSlot('startDecorator', { + className: classes.startDecorator, + elementType: SnackbarStartDecorator, + externalForwardedProps, + ownerState, + }); + + const [SlotEndDecorator, endDecoratorProps] = useSlot('endDecorator', { + className: classes.endDecorator, + elementType: SnackbarEndDecorator, + externalForwardedProps, + ownerState, + }); + + return ( + + { + + {slots?.startDecorator && ( + + {slots?.startDecorator as React.ReactNode} + + )} + + {children} + {slots?.endDecorator && ( + + {slots?.endDecorator as React.ReactNode} + + )} + + } + + ); +}); + +export default Snackbar; diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts new file mode 100644 index 00000000000000..dc0e83dfcd4a98 --- /dev/null +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -0,0 +1,51 @@ +import { OverrideProps } from '@mui/types'; +import { ClickAwayListenerProps } from '@mui/base/ClickAwayListener'; +import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types'; + +export interface SnackbarSlots { + /** + * The component that renders the root. + * @default 'div' + */ + root?: React.ElementType; + /** + * The component that renders the start decorator. + * @default 'span' + */ + startDecorator?: React.ElementType; + /** + * The component that renders the end decorator. + * @default 'span' + */ + endDecorator?: React.ElementType; +} + +export type SnackbarSlotsAndSlotProps = CreateSlotsAndSlotProps< + SnackbarSlots, + { + root: SlotProps<'div', {}, SnackbarOwnerState>; + startDecorator: SlotProps<'span', {}, SnackbarOwnerState>; + endDecorator: SlotProps<'span', {}, SnackbarOwnerState>; + } +>; + +export interface SnackbarTypeMap

{ + props: P & { + /** + * Props applied to the `ClickAwayListener` element. + */ + ClickAwayListenerProps?: Partial; + /** + * If `true`, the component is shown. + */ + open?: boolean; + } & SnackbarSlotsAndSlotProps; + defaultComponent: D; +} + +export type SnackbarProps< + D extends React.ElementType = SnackbarTypeMap['defaultComponent'], + P = { component?: React.ElementType }, +> = OverrideProps, D>; + +export interface SnackbarOwnerState extends SnackbarProps {} diff --git a/packages/mui-joy/src/Snackbar/index.ts b/packages/mui-joy/src/Snackbar/index.ts new file mode 100644 index 00000000000000..9a1de25f1c8376 --- /dev/null +++ b/packages/mui-joy/src/Snackbar/index.ts @@ -0,0 +1,5 @@ +'use client'; +export { default } from './Snackbar'; +export * from './snackbarClasses'; +export { default as snackbarClasses } from './snackbarClasses'; +export * from './SnackbarProps'; diff --git a/packages/mui-joy/src/Snackbar/snackbarClasses.ts b/packages/mui-joy/src/Snackbar/snackbarClasses.ts new file mode 100644 index 00000000000000..f5cf07d40db0e7 --- /dev/null +++ b/packages/mui-joy/src/Snackbar/snackbarClasses.ts @@ -0,0 +1,24 @@ +import { generateUtilityClass, generateUtilityClasses } from '../className'; + +export interface SnackbarClasses { + /** Class name applied to the endDecorator element if supplied. */ + endDecorator: string; + /** Class name applied to the root element. */ + root: string; + /** Class name applied to the startDecorator element if supplied. */ + startDecorator: string; +} + +export type SnackbarClassKey = keyof SnackbarClasses; + +export function getSnackbarUtilityClass(slot: string): string { + return generateUtilityClass('MuiSnackbar', slot); +} + +const snackbarClasses: SnackbarClasses = generateUtilityClasses('MuiSnackbar', [ + 'root', + 'endDecorator', + 'startDecorator', +]); + +export default snackbarClasses; From c9565fa31ecfaf697b3da3e25b630ec9fb523ae3 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 10:06:25 +0530 Subject: [PATCH 02/58] add color, size and variant props --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 35 ++++++++++++---- .../mui-joy/src/Snackbar/SnackbarProps.ts | 23 +++++++++-- .../mui-joy/src/Snackbar/snackbarClasses.ts | 40 ++++++++++++++++++- 3 files changed, 84 insertions(+), 14 deletions(-) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 5f6fa1e668e45e..088a4020006315 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -3,15 +3,23 @@ import * as React from 'react'; import clsx from 'clsx'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { useSnackbar } from '@mui/base/useSnackbar'; +import { unstable_capitalize as capitalize } from '@mui/utils'; import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { useThemeProps } from '../styles'; -import { SnackbarProps } from './SnackbarProps'; +import { SnackbarProps, SnackbarOwnerState } from './SnackbarProps'; import { getSnackbarUtilityClass } from './snackbarClasses'; -const useUtilityClasses = () => { +const useUtilityClasses = (ownerState: SnackbarOwnerState) => { + const { variant, color, size } = ownerState; + const slots = { - root: ['root'], + root: [ + 'root', + size && `size${capitalize(size)}`, + color && `color${capitalize(color)}`, + variant && `variant${capitalize(variant)}`, + ], startDecorator: ['startDecorator'], endDecorator: ['endDecorator'], }; @@ -31,11 +39,22 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { name: 'JoySnackbar', }); - const { children, className, component, slots, slotProps, open, ...other } = props; - - const ownerState = { ...props }; - - const classes = useUtilityClasses(); + const { + color = 'neutral', + children, + className, + component, + size = 'md', + slots, + slotProps, + open, + variant = 'outlined', + ...other + } = props; + + const ownerState = { ...props, color, size, variant }; + + const classes = useUtilityClasses(ownerState); const { getRootProps } = useSnackbar({ ...ownerState }); diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index dc0e83dfcd4a98..e23126fbc6772e 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -1,5 +1,5 @@ -import { OverrideProps } from '@mui/types'; -import { ClickAwayListenerProps } from '@mui/base/ClickAwayListener'; +import { OverrideProps, OverridableStringUnion } from '@mui/types'; +import { ColorPaletteProp, VariantProp } from '../styles/types'; import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types'; export interface SnackbarSlots { @@ -29,16 +29,31 @@ export type SnackbarSlotsAndSlotProps = CreateSlotsAndSlotProps< } >; +export interface SnackbarPropsColorOverrides {} +export interface SnackbarPropsSizeOverrides {} +export interface SnackbarPropsVariantOverrides {} + export interface SnackbarTypeMap

{ props: P & { /** - * Props applied to the `ClickAwayListener` element. + * The color of the component. It supports those theme colors that make sense for this component. + * @default 'neutral' */ - ClickAwayListenerProps?: Partial; + color?: OverridableStringUnion; /** * If `true`, the component is shown. */ open?: boolean; + /** + * The size of the component. + * @default 'md' + */ + size?: OverridableStringUnion<'sm' | 'md' | 'lg', SnackbarPropsSizeOverrides>; + /** + * The [global variant](https://mui.com/joy-ui/main-features/global-variants/) to use. + * @default 'outlined' + */ + variant?: OverridableStringUnion; } & SnackbarSlotsAndSlotProps; defaultComponent: D; } diff --git a/packages/mui-joy/src/Snackbar/snackbarClasses.ts b/packages/mui-joy/src/Snackbar/snackbarClasses.ts index f5cf07d40db0e7..0bb90a049d93b4 100644 --- a/packages/mui-joy/src/Snackbar/snackbarClasses.ts +++ b/packages/mui-joy/src/Snackbar/snackbarClasses.ts @@ -1,12 +1,36 @@ import { generateUtilityClass, generateUtilityClasses } from '../className'; export interface SnackbarClasses { - /** Class name applied to the endDecorator element if supplied. */ - endDecorator: string; /** Class name applied to the root element. */ root: string; + /** Class name applied to the root element if `color="primary"`. */ + colorPrimary: string; + /** Class name applied to the root element if `color="danger"`. */ + colorDanger: string; + /** Class name applied to the root element if `color="neutral"`. */ + colorNeutral: string; + /** Class name applied to the root element if `color="success"`. */ + colorSuccess: string; + /** Class name applied to the root element if `color="warning"`. */ + colorWarning: string; + /** Class name applied to the endDecorator element if supplied. */ + endDecorator: string; + /** Class name applied to the root element if `size="sm"`. */ + sizeSm: string; + /** Class name applied to the root element if `size="md"`. */ + sizeMd: string; + /** Class name applied to the root element if `size="lg"`. */ + sizeLg: string; /** Class name applied to the startDecorator element if supplied. */ startDecorator: string; + /** Class name applied to the root element if `variant="plain"`. */ + variantPlain: string; + /** Class name applied to the root element if `variant="outlined"`. */ + variantOutlined: string; + /** Class name applied to the root element if `variant="soft"`. */ + variantSoft: string; + /** Class name applied to the root element if `variant="solid"`. */ + variantSolid: string; } export type SnackbarClassKey = keyof SnackbarClasses; @@ -17,8 +41,20 @@ export function getSnackbarUtilityClass(slot: string): string { const snackbarClasses: SnackbarClasses = generateUtilityClasses('MuiSnackbar', [ 'root', + 'colorPrimary', + 'colorDanger', + 'colorNeutral', + 'colorSuccess', + 'colorWarning', 'endDecorator', + 'sizeSm', + 'sizeMd', + 'sizeLg', 'startDecorator', + 'variantPlain', + 'variantOutlined', + 'variantSoft', + 'variantSolid', ]); export default snackbarClasses; From 78b8fe5e941245a6800632c51d893581bff82da4 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 11:09:23 +0530 Subject: [PATCH 03/58] add other props --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 19 ++++++++- .../mui-joy/src/Snackbar/SnackbarProps.ts | 42 +++++++++---------- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 088a4020006315..94093a87ee472c 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -40,19 +40,30 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { }); const { + autoHideDuration = null, color = 'neutral', children, className, component, + disableWindowBlurListener = false, + onClose, + open, + resumeHideDuration, size = 'md', slots, slotProps, - open, variant = 'outlined', ...other } = props; - const ownerState = { ...props, color, size, variant }; + const ownerState = { + ...props, + autoHideDuration, + color, + disableWindowBlurListener, + size, + variant, + }; const classes = useUtilityClasses(ownerState); @@ -83,6 +94,10 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { ownerState, }); + if (!open) { + return null; + } + return ( { diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index e23126fbc6772e..068f7f9dabb634 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -1,4 +1,5 @@ import { OverrideProps, OverridableStringUnion } from '@mui/types'; +import { UseSnackbarParameters } from '@mui/base/useSnackbar'; import { ColorPaletteProp, VariantProp } from '../styles/types'; import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types'; @@ -33,28 +34,27 @@ export interface SnackbarPropsColorOverrides {} export interface SnackbarPropsSizeOverrides {} export interface SnackbarPropsVariantOverrides {} +export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; + export interface SnackbarTypeMap

{ - props: P & { - /** - * The color of the component. It supports those theme colors that make sense for this component. - * @default 'neutral' - */ - color?: OverridableStringUnion; - /** - * If `true`, the component is shown. - */ - open?: boolean; - /** - * The size of the component. - * @default 'md' - */ - size?: OverridableStringUnion<'sm' | 'md' | 'lg', SnackbarPropsSizeOverrides>; - /** - * The [global variant](https://mui.com/joy-ui/main-features/global-variants/) to use. - * @default 'outlined' - */ - variant?: OverridableStringUnion; - } & SnackbarSlotsAndSlotProps; + props: P & + UseSnackbarParameters & { + /** + * The color of the component. It supports those theme colors that make sense for this component. + * @default 'neutral' + */ + color?: OverridableStringUnion; + /** + * The size of the component. + * @default 'md' + */ + size?: OverridableStringUnion<'sm' | 'md' | 'lg', SnackbarPropsSizeOverrides>; + /** + * The [global variant](https://mui.com/joy-ui/main-features/global-variants/) to use. + * @default 'outlined' + */ + variant?: OverridableStringUnion; + } & SnackbarSlotsAndSlotProps; defaultComponent: D; } From 45190209f109846bb17990d8da512bdaac854218 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 11:49:29 +0530 Subject: [PATCH 04/58] add anchorOrigin and other props --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 25 +++++++++++++++++-- .../mui-joy/src/Snackbar/SnackbarProps.ts | 14 ++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 94093a87ee472c..2cd10203017c65 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -29,9 +29,24 @@ const useUtilityClasses = (ownerState: SnackbarOwnerState) => { const SnackbarRoot = styled('div')({}); -const SnackbarStartDecorator = styled('span')({}); +const SnackbarStartDecorator = styled('span', { + name: 'JoySnackbar', + slot: 'StartDecorator', + overridesResolver: (props, styles) => styles.startDecorator, +})({ + display: 'inherit', + flex: 'none', +}); -const SnackbarEndDecorator = styled('span')({}); +const SnackbarEndDecorator = styled('span', { + name: 'JoySnackbar', + slot: 'EndDecorator', + overridesResolver: (props, styles) => styles.endDecorator, +})({ + display: 'inherit', + flex: 'none', + marginLeft: 'auto', +}); const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { const props = useThemeProps({ @@ -40,13 +55,18 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { }); const { + anchorOrigin: { vertical, horizontal } = { vertical: 'bottom', horizontal: 'left' }, autoHideDuration = null, color = 'neutral', children, className, component, disableWindowBlurListener = false, + onBlur, onClose, + onFocus, + onMouseEnter, + onMouseLeave, open, resumeHideDuration, size = 'md', @@ -58,6 +78,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { const ownerState = { ...props, + anchorOrigin: { vertical, horizontal }, autoHideDuration, color, disableWindowBlurListener, diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index 068f7f9dabb634..09b5148c38f78e 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -34,11 +34,23 @@ export interface SnackbarPropsColorOverrides {} export interface SnackbarPropsSizeOverrides {} export interface SnackbarPropsVariantOverrides {} -export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; +export interface SnackbarOrigin { + vertical: 'top' | 'bottom'; + horizontal: 'left' | 'center' | 'right'; +} + +export type { SnackbarCloseReason } from '@mui/base/useSnackbar'; export interface SnackbarTypeMap

{ props: P & UseSnackbarParameters & { + /** + * The anchor of the `Snackbar`. + * On smaller screens, the component grows to occupy all the available width, + * the horizontal alignment is ignored. + * @default { vertical: 'bottom', horizontal: 'left' } + */ + anchorOrigin?: SnackbarOrigin; /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' From 30e081819c80c4c6df96458f7043b4d9cf8a46a0 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 11:53:21 +0530 Subject: [PATCH 05/58] anchorOrigin class names --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 9 +++++++-- .../mui-joy/src/Snackbar/snackbarClasses.ts | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 2cd10203017c65..3e70399b2ad70e 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -11,7 +11,7 @@ import { SnackbarProps, SnackbarOwnerState } from './SnackbarProps'; import { getSnackbarUtilityClass } from './snackbarClasses'; const useUtilityClasses = (ownerState: SnackbarOwnerState) => { - const { variant, color, size } = ownerState; + const { variant, color, size, anchorOrigin } = ownerState; const slots = { root: [ @@ -19,6 +19,7 @@ const useUtilityClasses = (ownerState: SnackbarOwnerState) => { size && `size${capitalize(size)}`, color && `color${capitalize(color)}`, variant && `variant${capitalize(variant)}`, + `anchorOrigin${capitalize(anchorOrigin!.vertical)}${capitalize(anchorOrigin!.horizontal)}`, ], startDecorator: ['startDecorator'], endDecorator: ['endDecorator'], @@ -27,7 +28,11 @@ const useUtilityClasses = (ownerState: SnackbarOwnerState) => { return composeClasses(slots, getSnackbarUtilityClass, {}); }; -const SnackbarRoot = styled('div')({}); +const SnackbarRoot = styled('div', { + name: 'SnackbarAlert', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}); const SnackbarStartDecorator = styled('span', { name: 'JoySnackbar', diff --git a/packages/mui-joy/src/Snackbar/snackbarClasses.ts b/packages/mui-joy/src/Snackbar/snackbarClasses.ts index 0bb90a049d93b4..9a294d04a6b06b 100644 --- a/packages/mui-joy/src/Snackbar/snackbarClasses.ts +++ b/packages/mui-joy/src/Snackbar/snackbarClasses.ts @@ -3,6 +3,18 @@ import { generateUtilityClass, generateUtilityClasses } from '../className'; export interface SnackbarClasses { /** Class name applied to the root element. */ root: string; + /** Styles applied to the root element if `anchorOrigin={{ 'top', 'center' }}`. */ + anchorOriginTopCenter: string; + /** Styles applied to the root element if `anchorOrigin={{ 'bottom', 'center' }}`. */ + anchorOriginBottomCenter: string; + /** Styles applied to the root element if `anchorOrigin={{ 'top', 'right' }}`. */ + anchorOriginTopRight: string; + /** Styles applied to the root element if `anchorOrigin={{ 'bottom', 'right' }}`. */ + anchorOriginBottomRight: string; + /** Styles applied to the root element if `anchorOrigin={{ 'top', 'left' }}`. */ + anchorOriginTopLeft: string; + /** Styles applied to the root element if `anchorOrigin={{ 'bottom', 'left' }}`. */ + anchorOriginBottomLeft: string; /** Class name applied to the root element if `color="primary"`. */ colorPrimary: string; /** Class name applied to the root element if `color="danger"`. */ @@ -41,6 +53,12 @@ export function getSnackbarUtilityClass(slot: string): string { const snackbarClasses: SnackbarClasses = generateUtilityClasses('MuiSnackbar', [ 'root', + 'anchorOriginTopCenter', + 'anchorOriginBottomCenter', + 'anchorOriginTopRight', + 'anchorOriginBottomRight', + 'anchorOriginTopLeft', + 'anchorOriginBottomLeft', 'colorPrimary', 'colorDanger', 'colorNeutral', From 332f1f3cfd5434949c9ae63fd365a3d04d630f1c Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 13:04:26 +0530 Subject: [PATCH 06/58] add styles --- .../joy/components/snackbar/SnackbarUsage.js | 53 +++++++++ docs/data/joy/components/snackbar/snackbar.md | 69 ++---------- packages/mui-joy/src/Snackbar/Snackbar.tsx | 105 ++++++++++++++---- .../mui-joy/src/Snackbar/SnackbarProps.ts | 8 +- packages/mui-joy/src/styles/extendTheme.ts | 1 + packages/mui-joy/src/styles/types/zIndex.ts | 1 + 6 files changed, 153 insertions(+), 84 deletions(-) create mode 100644 docs/data/joy/components/snackbar/SnackbarUsage.js diff --git a/docs/data/joy/components/snackbar/SnackbarUsage.js b/docs/data/joy/components/snackbar/SnackbarUsage.js new file mode 100644 index 00000000000000..7c0862a0792eb0 --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarUsage.js @@ -0,0 +1,53 @@ +import * as React from 'react'; +import Snackbar from '@mui/joy/Snackbar'; +import Button from '@mui/joy/Button'; +import JoyUsageDemo from 'docs/src/modules/components/JoyUsageDemo'; + +function SimpleSnackbar(props) { + const [open, setOpen] = React.useState(false); + + const handleClick = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + return ( + + + + Hello World + + + ); +} + +export default function SnackbarUsage() { + return ( + } + /> + ); +} diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index a7b8df8e5a1a0a..1ab59c837fe340 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -1,76 +1,21 @@ --- productId: joy-ui title: React Snackbar component +components: Snackbar githubLabel: 'component: snackbar' +waiAria: https://www.w3.org/TR/wai-aria-1.1/#alert --- # Snackbar

The Snackbar, also commonly referred to as Toast, component informs users that an action has been or will be performed by the app.

-:::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. -::: +{{"component": "modules/components/ComponentLinkHeader.js", "design": false}} -## Integration with headless UI libraries +## Introduction -In the meantime, you can still adopt Joy UI today for building a snackbar! +Snackbars inform users of a process that an app has performed or will perform. They appear temporarily, towards the bottom of the screen. They shouldn't interrupt the user experience, and they don't require user input to disappear. -This document shows how to construct it with existing Joy UI components combined with popular headless UI libraries. +Snackbars contain a single line of text directly related to the operation performed. They may contain a text action, but no icons. You can use them to display notifications. -### Parting from the Alert component - -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. - -### With Radix UI - -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. - -Animation is created by targeting `data-*` attributes injected by Radix UI's `Toast.Root` component. -In this demo, it uses `@mui/system` keyframes API, same as emotion's keyframes, to build the animation stylesheet. - -- [Install Radix UI's Toast](https://www.radix-ui.com/primitives/docs/components/toast#installation) -- [Toast component documentation](https://www.radix-ui.com/primitives/docs/components/toast) - - - -### With React Aria - -React Aria provides the `useToast` hook that can be used with Joy UI's `Alert` component. - -- [Install React Aria's Toast](https://react-spectrum.adobe.com/react-aria/useToast.html) -- [Toast component documentation](https://react-spectrum.adobe.com/react-aria/useToast.html#features) - - - -### With Sonner - -[Sonner](https://sonner.emilkowal.ski/), an opinionated toast component for React, comes with features like stackable toasts and swipe-to-dismiss animation. - -To use Sonner with Joy UI, override Sonner's CSS variables with Joy UI color tokens by targeting `[data-sonner-toaster][data-theme]` selector. - -You can also pass Joy UI's Alert component to [`toast.custom()`](https://github.com/emilkowalski/sonner#headless) to fully control the structure of the notification. - -Lastly, it is also possible to enhance Sonner's `toast` function with a new method -that closely resemble Joy UI's semantics (i.e.: adding a `warning` toast). - -- [Sonner documentation](https://github.com/emilkowalski/sonner#introduction) - - +{{"demo": "SnackbarUsage.js", "hideToolbar": true, "bg": "gradient"}} diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 3e70399b2ad70e..ffa7346f4e7f75 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -4,10 +4,12 @@ import clsx from 'clsx'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { useSnackbar } from '@mui/base/useSnackbar'; import { unstable_capitalize as capitalize } from '@mui/utils'; +import { OverridableComponent } from '@mui/types'; import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { useThemeProps } from '../styles'; -import { SnackbarProps, SnackbarOwnerState } from './SnackbarProps'; +import { resolveSxValue } from '../styles/styleUtils'; +import { SnackbarProps, SnackbarOwnerState, SnackbarTypeMap } from './SnackbarProps'; import { getSnackbarUtilityClass } from './snackbarClasses'; const useUtilityClasses = (ownerState: SnackbarOwnerState) => { @@ -29,10 +31,78 @@ const useUtilityClasses = (ownerState: SnackbarOwnerState) => { }; const SnackbarRoot = styled('div', { - name: 'SnackbarAlert', + name: 'JoySnackbar', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})({}); +})<{ ownerState: SnackbarOwnerState }>(({ theme, ownerState }) => { + const { p, padding, borderRadius } = resolveSxValue({ theme, ownerState }, [ + 'p', + 'padding', + 'borderRadius', + ]); + + return [ + { + zIndex: theme.vars.zIndex.snackbar, + position: 'fixed', + display: 'flex', + left: 8, + right: 8, + justifyContent: 'center', + alignItems: 'center', + maxWidth: 300, + ...(ownerState.anchorOrigin!.vertical === 'top' ? { top: 8 } : { bottom: 8 }), + ...(ownerState.anchorOrigin!.horizontal === 'left' && { justifyContent: 'flex-start' }), + ...(ownerState.anchorOrigin!.horizontal === 'right' && { justifyContent: 'flex-end' }), + [theme.breakpoints.up('sm')]: { + ...(ownerState.anchorOrigin!.vertical === 'top' ? { top: 24 } : { bottom: 24 }), + ...(ownerState.anchorOrigin!.horizontal === 'center' && { + left: '50%', + right: 'auto', + transform: 'translateX(-50%)', + }), + ...(ownerState.anchorOrigin!.horizontal === 'left' && { + left: 24, + right: 'auto', + }), + ...(ownerState.anchorOrigin!.horizontal === 'right' && { + right: 24, + left: 'auto', + }), + }, + '--Snackbar-radius': theme.vars.radius.sm, + '--Snackbar-decoratorChildRadius': + 'max((var(--Snackbar-radius) - var(--variant-borderWidth, 0px)) - var(--Snackbar-padding), min(var(--Snackbar-padding) + var(--variant-borderWidth, 0px), var(--Snackbar-radius) / 2))', + ...(ownerState.size === 'sm' && { + '--Snackbar-padding': '0.5rem', + '--Snackbar-decoratorChildHeight': '1.5rem', + '--Icon-fontSize': theme.vars.fontSize.xl, + gap: '0.5rem', + }), + ...(ownerState.size === 'md' && { + '--Snackbar-padding': '0.75rem', + '--Snackbar-decoratorChildHeight': '2rem', + '--Icon-fontSize': theme.vars.fontSize.xl, + gap: '0.625rem', + }), + ...(ownerState.size === 'lg' && { + '--Snackbar-padding': '1rem', + '--Snackbar-decoratorChildHeight': '2.375rem', + '--Icon-fontSize': theme.vars.fontSize.xl2, + gap: '0.875rem', + }), + backgroundColor: theme.vars.palette.background.surface, + padding: `var(--Snackbar-padding)`, + borderRadius: 'var(--Snackbar-radius)', + ...theme.typography[`body-${({ sm: 'xs', md: 'sm', lg: 'md' } as const)[ownerState.size!]}`], + fontWeight: theme.vars.fontWeight.md, + ...theme.variants[ownerState.variant!]?.[ownerState.color!], + } as const, + p !== undefined && { '--Snackbar-padding': p }, + padding !== undefined && { '--Snackbar-padding': padding }, + borderRadius !== undefined && { '--Snackbar-radius': borderRadius }, + ]; +}); const SnackbarStartDecorator = styled('span', { name: 'JoySnackbar', @@ -126,24 +196,19 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { return ( - { - - {slots?.startDecorator && ( - - {slots?.startDecorator as React.ReactNode} - - )} - - {children} - {slots?.endDecorator && ( - - {slots?.endDecorator as React.ReactNode} - - )} - - } + {slots?.startDecorator && ( + + {slots?.startDecorator as React.ReactNode} + + )} + {children} + {slots?.endDecorator && ( + + {slots?.endDecorator as React.ReactNode} + + )} ); -}); +}) as OverridableComponent; export default Snackbar; diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index 09b5148c38f78e..3e2cd2e5c18bbd 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -1,6 +1,6 @@ import { OverrideProps, OverridableStringUnion } from '@mui/types'; import { UseSnackbarParameters } from '@mui/base/useSnackbar'; -import { ColorPaletteProp, VariantProp } from '../styles/types'; +import { ColorPaletteProp, VariantProp, ApplyColorInversion, SxProps } from '../styles/types'; import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types'; export interface SnackbarSlots { @@ -61,6 +61,10 @@ export interface SnackbarTypeMap

{ * @default 'md' */ size?: OverridableStringUnion<'sm' | 'md' | 'lg', SnackbarPropsSizeOverrides>; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; /** * The [global variant](https://mui.com/joy-ui/main-features/global-variants/) to use. * @default 'outlined' @@ -75,4 +79,4 @@ export type SnackbarProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface SnackbarOwnerState extends SnackbarProps {} +export interface SnackbarOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/styles/extendTheme.ts b/packages/mui-joy/src/styles/extendTheme.ts index d7d6eed4aabe89..f732bbabf877fb 100644 --- a/packages/mui-joy/src/styles/extendTheme.ts +++ b/packages/mui-joy/src/styles/extendTheme.ts @@ -421,6 +421,7 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme { table: 10, popup: 1000, modal: 1300, + snackbar: 1400, tooltip: 1500, }, diff --git a/packages/mui-joy/src/styles/types/zIndex.ts b/packages/mui-joy/src/styles/types/zIndex.ts index f91fb675cb09b9..ab91eab0913581 100644 --- a/packages/mui-joy/src/styles/types/zIndex.ts +++ b/packages/mui-joy/src/styles/types/zIndex.ts @@ -15,6 +15,7 @@ export interface DefaultZIndex { popup: number; modal: number; tooltip: number; + snackbar: number; } export interface ZIndexOverrides {} export interface ZIndex extends OverridableRecord {} From 6b86512b9ae8b7903fbe4383083de8784eced8ab Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 13:13:35 +0530 Subject: [PATCH 07/58] add click away listener --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 32 +++++++++++-------- .../mui-joy/src/Snackbar/SnackbarProps.ts | 5 +++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index ffa7346f4e7f75..560224a942753e 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import clsx from 'clsx'; import { unstable_composeClasses as composeClasses } from '@mui/base'; +import { ClickAwayListener } from '@mui/base/ClickAwayListener'; import { useSnackbar } from '@mui/base/useSnackbar'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { OverridableComponent } from '@mui/types'; @@ -135,6 +136,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { color = 'neutral', children, className, + ClickAwayListenerProps, component, disableWindowBlurListener = false, onBlur, @@ -163,7 +165,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { const classes = useUtilityClasses(ownerState); - const { getRootProps } = useSnackbar({ ...ownerState }); + const { getRootProps, onClickAway } = useSnackbar({ ...ownerState }); const externalForwardedProps = { ...other, component, slots, slotProps }; @@ -195,19 +197,21 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { } return ( - - {slots?.startDecorator && ( - - {slots?.startDecorator as React.ReactNode} - - )} - {children} - {slots?.endDecorator && ( - - {slots?.endDecorator as React.ReactNode} - - )} - + + + {slots?.startDecorator && ( + + {slots?.startDecorator as React.ReactNode} + + )} + {children} + {slots?.endDecorator && ( + + {slots?.endDecorator as React.ReactNode} + + )} + + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index 3e2cd2e5c18bbd..b05c0b700d3b2f 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -1,4 +1,5 @@ import { OverrideProps, OverridableStringUnion } from '@mui/types'; +import { ClickAwayListenerProps } from '@mui/base/ClickAwayListener'; import { UseSnackbarParameters } from '@mui/base/useSnackbar'; import { ColorPaletteProp, VariantProp, ApplyColorInversion, SxProps } from '../styles/types'; import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types'; @@ -51,6 +52,10 @@ export interface SnackbarTypeMap

{ * @default { vertical: 'bottom', horizontal: 'left' } */ anchorOrigin?: SnackbarOrigin; + /** + * Props applied to the `ClickAwayListener` element. + */ + ClickAwayListenerProps?: Partial; /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' From 601fbc52463abb06a5b1268ba7ded786b777617e Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 13:19:49 +0530 Subject: [PATCH 08/58] do not close on blur --- docs/data/joy/components/snackbar/SnackbarUsage.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/data/joy/components/snackbar/SnackbarUsage.js b/docs/data/joy/components/snackbar/SnackbarUsage.js index 7c0862a0792eb0..1230acef4cbd00 100644 --- a/docs/data/joy/components/snackbar/SnackbarUsage.js +++ b/docs/data/joy/components/snackbar/SnackbarUsage.js @@ -10,7 +10,11 @@ function SimpleSnackbar(props) { setOpen(true); }; - const handleClose = () => { + const handleClose = (event, reason) => { + if (reason === 'clickaway') { + return; + } + setOpen(false); }; From be4da8d576614f024490dd9281e7da1541614b7c Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 14:20:32 +0530 Subject: [PATCH 09/58] remove unneeded styles --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 560224a942753e..58e2825e147dad 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -72,23 +72,18 @@ const SnackbarRoot = styled('div', { }), }, '--Snackbar-radius': theme.vars.radius.sm, - '--Snackbar-decoratorChildRadius': - 'max((var(--Snackbar-radius) - var(--variant-borderWidth, 0px)) - var(--Snackbar-padding), min(var(--Snackbar-padding) + var(--variant-borderWidth, 0px), var(--Snackbar-radius) / 2))', ...(ownerState.size === 'sm' && { '--Snackbar-padding': '0.5rem', - '--Snackbar-decoratorChildHeight': '1.5rem', '--Icon-fontSize': theme.vars.fontSize.xl, gap: '0.5rem', }), ...(ownerState.size === 'md' && { '--Snackbar-padding': '0.75rem', - '--Snackbar-decoratorChildHeight': '2rem', '--Icon-fontSize': theme.vars.fontSize.xl, gap: '0.625rem', }), ...(ownerState.size === 'lg' && { '--Snackbar-padding': '1rem', - '--Snackbar-decoratorChildHeight': '2.375rem', '--Icon-fontSize': theme.vars.fontSize.xl2, gap: '0.875rem', }), From 732c646f1c331ce66b263df28ff407bd860f3b5f Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 15:22:42 +0530 Subject: [PATCH 10/58] add positioned snackbar demo --- .../components/snackbar/PositionedSnackbar.js | 93 ++++++++++++++++++ .../snackbar/PositionedSnackbar.tsx | 97 +++++++++++++++++++ .../snackbar/PositionedSnackbar.tsx.preview | 11 +++ docs/data/joy/components/snackbar/snackbar.md | 7 ++ 4 files changed, 208 insertions(+) create mode 100644 docs/data/joy/components/snackbar/PositionedSnackbar.js create mode 100644 docs/data/joy/components/snackbar/PositionedSnackbar.tsx create mode 100644 docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview diff --git a/docs/data/joy/components/snackbar/PositionedSnackbar.js b/docs/data/joy/components/snackbar/PositionedSnackbar.js new file mode 100644 index 00000000000000..71a3ede563dc9c --- /dev/null +++ b/docs/data/joy/components/snackbar/PositionedSnackbar.js @@ -0,0 +1,93 @@ +import * as React from 'react'; +import Grid from '@mui/joy/Grid'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Snackbar from '@mui/joy/Snackbar'; + +export default function PositionedSnackbar() { + const [state, setState] = React.useState({ + open: false, + vertical: 'top', + horizontal: 'center', + }); + const { vertical, horizontal, open } = state; + + const handleClick = (newState) => () => { + setState({ ...newState, open: true }); + }; + + const handleClose = () => { + setState({ ...state, open: false }); + }; + + const buttons = ( + + + + + + + + + + + + + + + + + + + + + + + ); + + return ( + + {buttons} + + I love snacks + + + ); +} diff --git a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx new file mode 100644 index 00000000000000..6a7e8fb6d95bec --- /dev/null +++ b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx @@ -0,0 +1,97 @@ +import * as React from 'react'; +import Grid from '@mui/joy/Grid'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Snackbar, { SnackbarOrigin } from '@mui/joy/Snackbar'; + +interface State extends SnackbarOrigin { + open: boolean; +} + +export default function PositionedSnackbar() { + const [state, setState] = React.useState({ + open: false, + vertical: 'top', + horizontal: 'center', + }); + const { vertical, horizontal, open } = state; + + const handleClick = (newState: SnackbarOrigin) => () => { + setState({ ...newState, open: true }); + }; + + const handleClose = () => { + setState({ ...state, open: false }); + }; + + const buttons = ( + + + + + + + + + + + + + + + + + + + + + + + ); + + return ( + + {buttons} + + I love snacks + + + ); +} diff --git a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview new file mode 100644 index 00000000000000..f57a0dd32b64fe --- /dev/null +++ b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview @@ -0,0 +1,11 @@ +{buttons} + + I love snacks + \ No newline at end of file diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index 1ab59c837fe340..0e3f64134cff82 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -19,3 +19,10 @@ Snackbars inform users of a process that an app has performed or will perform. T Snackbars contain a single line of text directly related to the operation performed. They may contain a text action, but no icons. You can use them to display notifications. {{"demo": "SnackbarUsage.js", "hideToolbar": true, "bg": "gradient"}} + +## Positioned snackbars + +In wide layouts, snackbars can be left-aligned or center-aligned if they are consistently placed on the same spot at the bottom of the screen, however there may be circumstances where the placement of the snackbar needs to be more flexible. +You can control the position of the snackbar by specifying the `anchorOrigin` prop. + +{{"demo": "PositionedSnackbar.js"}} From f59072cab7bbdce7bc79c7b5defe84c8d45199bb Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 15:36:48 +0530 Subject: [PATCH 11/58] generate proptypes --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 128 +++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 58e2825e147dad..917891fffe4803 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -1,5 +1,6 @@ 'use client'; 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'; @@ -199,6 +200,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { {slots?.startDecorator as React.ReactNode} )} + {children} {slots?.endDecorator && ( @@ -210,4 +212,130 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { ); }) as OverridableComponent; +Snackbar.propTypes /* remove-proptypes */ = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * The anchor of the `Snackbar`. + * On smaller screens, the component grows to occupy all the available width, + * the horizontal alignment is ignored. + * @default { vertical: 'bottom', horizontal: 'left' } + */ + anchorOrigin: PropTypes.shape({ + horizontal: PropTypes.oneOf(['center', 'left', 'right']).isRequired, + vertical: PropTypes.oneOf(['bottom', 'top']).isRequired, + }), + /** + * 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.node, + /** + * @ignore + */ + className: PropTypes.string, + /** + * Props applied to the `ClickAwayListener` element. + */ + ClickAwayListenerProps: PropTypes.object, + /** + * The color of the component. It supports those theme colors that make sense for this component. + * @default 'neutral' + */ + color: PropTypes.oneOf(['danger', 'neutral', 'primary', 'success', 'warning']), + /** + * The component used for the root node. + * Either a string to use a HTML element or a component. + */ + component: PropTypes.elementType, + /** + * 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, + /** + * The size of the component. + * @default 'md' + */ + size: PropTypes.oneOf(['sm', 'md', 'lg']), + /** + * The props used for each slot inside. + * @default {} + */ + slotProps: PropTypes.shape({ + endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + * @default {} + */ + slots: PropTypes.shape({ + endDecorator: PropTypes.elementType, + root: PropTypes.elementType, + startDecorator: PropTypes.elementType, + }), + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), + /** + * The [global variant](https://mui.com/joy-ui/main-features/global-variants/) to use. + * @default 'outlined' + */ + variant: PropTypes.oneOf(['outlined', 'plain', 'soft', 'solid']), +} as any; + export default Snackbar; From 82ac9d74bb91c4ae0ba6961b39da96cdbd203517 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 15:49:41 +0530 Subject: [PATCH 12/58] change w3 aria link --- docs/data/joy/components/snackbar/snackbar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index 0e3f64134cff82..d81e516e0fdb0a 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -3,7 +3,7 @@ productId: joy-ui title: React Snackbar component components: Snackbar githubLabel: 'component: snackbar' -waiAria: https://www.w3.org/TR/wai-aria-1.1/#alert +waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/alert/ --- # Snackbar From 1dc715496dbc910b39fcde673d02881195b00bef Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 19:43:41 +0530 Subject: [PATCH 13/58] import React --- packages/mui-joy/src/Snackbar/SnackbarProps.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index b05c0b700d3b2f..af03247aa686f0 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -1,3 +1,4 @@ +import * as React from 'react'; import { OverrideProps, OverridableStringUnion } from '@mui/types'; import { ClickAwayListenerProps } from '@mui/base/ClickAwayListener'; import { UseSnackbarParameters } from '@mui/base/useSnackbar'; From 3d89466f108d5135fa71822424e10b90e3c4c307 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 19:49:26 +0530 Subject: [PATCH 14/58] yarn docs:api --- docs/data/joy/pagesApi.js | 1 + docs/pages/base-ui/api/snackbar.json | 2 +- docs/pages/joy-ui/api/snackbar.js | 19 +++ docs/pages/joy-ui/api/snackbar.json | 120 ++++++++++++++ .../api-docs-joy/snackbar/snackbar.json | 151 ++++++++++++++++++ packages/mui-base/src/Snackbar/Snackbar.tsx | 1 + .../mui-joy/src/Snackbar/Snackbar.test.tsx | 0 packages/mui-joy/src/Snackbar/Snackbar.tsx | 11 +- packages/mui-joy/src/index.ts | 3 + 9 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 docs/pages/joy-ui/api/snackbar.js create mode 100644 docs/pages/joy-ui/api/snackbar.json create mode 100644 docs/translations/api-docs-joy/snackbar/snackbar.json create mode 100644 packages/mui-joy/src/Snackbar/Snackbar.test.tsx diff --git a/docs/data/joy/pagesApi.js b/docs/data/joy/pagesApi.js index a8c57ef7c41a54..ef946d38a21cbc 100644 --- a/docs/data/joy/pagesApi.js +++ b/docs/data/joy/pagesApi.js @@ -55,6 +55,7 @@ module.exports = [ { pathname: '/joy-ui/api/sheet' }, { pathname: '/joy-ui/api/skeleton' }, { pathname: '/joy-ui/api/slider' }, + { pathname: '/joy-ui/api/snackbar' }, { pathname: '/joy-ui/api/stack' }, { pathname: '/joy-ui/api/svg-icon' }, { pathname: '/joy-ui/api/switch' }, diff --git a/docs/pages/base-ui/api/snackbar.json b/docs/pages/base-ui/api/snackbar.json index 7fbded8b892229..709e8a25b5dde2 100644 --- a/docs/pages/base-ui/api/snackbar.json +++ b/docs/pages/base-ui/api/snackbar.json @@ -44,6 +44,6 @@ "forwardsRefTo": "HTMLDivElement", "filename": "/packages/mui-base/src/Snackbar/Snackbar.tsx", "inheritance": null, - "demos": "

", + "demos": "", "cssComponent": false } diff --git a/docs/pages/joy-ui/api/snackbar.js b/docs/pages/joy-ui/api/snackbar.js new file mode 100644 index 00000000000000..025d0c4d798970 --- /dev/null +++ b/docs/pages/joy-ui/api/snackbar.js @@ -0,0 +1,19 @@ +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.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context('docs/translations/api-docs-joy/snackbar', false, /snackbar.*.json$/); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/joy-ui/api/snackbar.json b/docs/pages/joy-ui/api/snackbar.json new file mode 100644 index 00000000000000..fb1038dca08992 --- /dev/null +++ b/docs/pages/joy-ui/api/snackbar.json @@ -0,0 +1,120 @@ +{ + "props": { + "anchorOrigin": { + "type": { + "name": "shape", + "description": "{ horizontal: 'center'
| 'left'
| 'right', vertical: 'bottom'
| 'top' }" + }, + "default": "{ vertical: 'bottom', horizontal: 'left' }" + }, + "autoHideDuration": { "type": { "name": "number" }, "default": "null" }, + "ClickAwayListenerProps": { "type": { "name": "object" } }, + "color": { + "type": { + "name": "enum", + "description": "'danger'
| 'neutral'
| 'primary'
| 'success'
| 'warning'" + }, + "default": "'neutral'", + "additionalInfo": { "joy-color": true } + }, + "component": { "type": { "name": "elementType" } }, + "disableWindowBlurListener": { "type": { "name": "bool" }, "default": "false" }, + "onClose": { + "type": { "name": "func" }, + "signature": { + "type": "function(event: React.SyntheticEvent | Event, reason: string) => void", + "describedArgs": ["event", "reason"] + } + }, + "open": { "type": { "name": "bool" } }, + "resumeHideDuration": { "type": { "name": "number" } }, + "size": { + "type": { "name": "enum", "description": "'sm'
| 'md'
| 'lg'" }, + "default": "'md'", + "additionalInfo": { "joy-size": true } + }, + "slotProps": { + "type": { + "name": "shape", + "description": "{ endDecorator?: func
| object, root?: func
| object, startDecorator?: func
| object }" + }, + "default": "{}" + }, + "slots": { + "type": { + "name": "shape", + "description": "{ endDecorator?: elementType, root?: elementType, startDecorator?: elementType }" + }, + "default": "{}", + "additionalInfo": { "slotsApi": true } + }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + }, + "variant": { + "type": { + "name": "enum", + "description": "'outlined'
| 'plain'
| 'soft'
| 'solid'" + }, + "default": "'outlined'", + "additionalInfo": { "joy-variant": true } + } + }, + "name": "Snackbar", + "imports": ["import Snackbar from '@mui/joy/Snackbar';", "import { Snackbar } from '@mui/joy';"], + "styles": { "classes": [], "globalClasses": {}, "name": "MuiSnackbar" }, + "slots": [ + { + "name": "root", + "description": "The component that renders the root.", + "default": "'div'", + "class": ".MuiSnackbar-root" + }, + { + "name": "startDecorator", + "description": "The component that renders the start decorator.", + "default": "'span'", + "class": ".MuiSnackbar-startDecorator" + }, + { + "name": "endDecorator", + "description": "The component that renders the end decorator.", + "default": "'span'", + "class": ".MuiSnackbar-endDecorator" + } + ], + "classes": { + "classes": [ + "anchorOriginBottomCenter", + "anchorOriginBottomLeft", + "anchorOriginBottomRight", + "anchorOriginTopCenter", + "anchorOriginTopLeft", + "anchorOriginTopRight", + "colorDanger", + "colorNeutral", + "colorPrimary", + "colorSuccess", + "colorWarning", + "sizeLg", + "sizeMd", + "sizeSm", + "variantOutlined", + "variantPlain", + "variantSoft", + "variantSolid" + ], + "globalClasses": {} + }, + "spread": true, + "themeDefaultProps": null, + "muiName": "JoySnackbar", + "filename": "/packages/mui-joy/src/Snackbar/Snackbar.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/translations/api-docs-joy/snackbar/snackbar.json b/docs/translations/api-docs-joy/snackbar/snackbar.json new file mode 100644 index 00000000000000..a9c6b1c03bf326 --- /dev/null +++ b/docs/translations/api-docs-joy/snackbar/snackbar.json @@ -0,0 +1,151 @@ +{ + "componentDescription": "", + "propDescriptions": { + "anchorOrigin": { + "description": "The anchor of the Snackbar. On smaller screens, the component grows to occupy all the available width, the horizontal alignment is ignored." + }, + "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." + }, + "ClickAwayListenerProps": { + "description": "Props applied to the ClickAwayListener element." + }, + "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." + }, + "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"." + } + }, + "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." }, + "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." + } +} 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..e69de29bb2d1d6 diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 917891fffe4803..75bb8c75688b3e 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -119,7 +119,16 @@ const SnackbarEndDecorator = styled('span', { flex: 'none', marginLeft: 'auto', }); - +/** + * + * Demos: + * + * - [Snackbar](https://mui.com/joy-ui/react-snackbar/) + * + * API: + * + * - [Snackbar API](https://mui.com/joy-ui/api/snackbar/) + */ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { const props = useThemeProps({ props: inProps, diff --git a/packages/mui-joy/src/index.ts b/packages/mui-joy/src/index.ts index ecca349eeb3148..89cfb85cac88f8 100644 --- a/packages/mui-joy/src/index.ts +++ b/packages/mui-joy/src/index.ts @@ -184,6 +184,9 @@ export * from './Skeleton'; export { default as Slider } from './Slider'; export * from './Slider'; +export { default as Snackbar } from './Snackbar'; +export * from './Snackbar'; + export { default as Stack } from './Stack'; export * from './Stack'; From 5137c6b7bac7e2d9937bfcbaf4ee2de6ff7bd4d6 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 19:56:29 +0530 Subject: [PATCH 15/58] remove planned tag from Snackbar --- docs/data/joy/pages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/joy/pages.ts b/docs/data/joy/pages.ts index dd12a9c6d097b8..5a9213ebf8e893 100644 --- a/docs/data/joy/pages.ts +++ b/docs/data/joy/pages.ts @@ -73,7 +73,7 @@ const pages: readonly MuiPage[] = [ { pathname: '/joy-ui/react-linear-progress', title: 'Linear Progress' }, { pathname: '/joy-ui/react-modal' }, { pathname: '/joy-ui/react-skeleton', newFeature: true }, - { pathname: '/joy-ui/react-snackbar', planned: true }, + { pathname: '/joy-ui/react-snackbar' }, ], }, { From 33c72ee0aa882980aadce86414517a1070619d8d Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Tue, 5 Sep 2023 20:15:13 +0530 Subject: [PATCH 16/58] improve styles --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 75bb8c75688b3e..2dbd8ff386357b 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -50,12 +50,10 @@ const SnackbarRoot = styled('div', { display: 'flex', left: 8, right: 8, - justifyContent: 'center', + justifyContent: 'flex-start', alignItems: 'center', - maxWidth: 300, + minWidth: 300, ...(ownerState.anchorOrigin!.vertical === 'top' ? { top: 8 } : { bottom: 8 }), - ...(ownerState.anchorOrigin!.horizontal === 'left' && { justifyContent: 'flex-start' }), - ...(ownerState.anchorOrigin!.horizontal === 'right' && { justifyContent: 'flex-end' }), [theme.breakpoints.up('sm')]: { ...(ownerState.anchorOrigin!.vertical === 'top' ? { top: 24 } : { bottom: 24 }), ...(ownerState.anchorOrigin!.horizontal === 'center' && { @@ -88,6 +86,7 @@ const SnackbarRoot = styled('div', { '--Icon-fontSize': theme.vars.fontSize.xl2, gap: '0.875rem', }), + boxShadow: theme.vars.shadow.md, backgroundColor: theme.vars.palette.background.surface, padding: `var(--Snackbar-padding)`, borderRadius: 'var(--Snackbar-radius)', From 94a2e4b3cad9e298cd4d04df849d14da2eb519bc Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 6 Sep 2023 15:32:59 +0530 Subject: [PATCH 17/58] add default value to slots --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 2dbd8ff386357b..4a8b40fdeeb524 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -151,7 +151,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { open, resumeHideDuration, size = 'md', - slots, + slots = {}, slotProps, variant = 'outlined', ...other @@ -203,16 +203,15 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { return ( - {slots?.startDecorator && ( + {slots.startDecorator && ( - {slots?.startDecorator as React.ReactNode} + {slots.startDecorator as React.ReactNode} )} - {children} - {slots?.endDecorator && ( + {slots.endDecorator && ( - {slots?.endDecorator as React.ReactNode} + {slots.endDecorator as React.ReactNode} )} From 0d5173ea383663c0a60d3b349553c5aa84641dc9 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 6 Sep 2023 15:37:08 +0530 Subject: [PATCH 18/58] update positioned snackbar demo --- .../joy/components/snackbar/PositionedSnackbar.js | 14 +++++++------- .../joy/components/snackbar/PositionedSnackbar.tsx | 14 +++++++------- .../snackbar/PositionedSnackbar.tsx.preview | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/data/joy/components/snackbar/PositionedSnackbar.js b/docs/data/joy/components/snackbar/PositionedSnackbar.js index 71a3ede563dc9c..46a79fe2097dc4 100644 --- a/docs/data/joy/components/snackbar/PositionedSnackbar.js +++ b/docs/data/joy/components/snackbar/PositionedSnackbar.js @@ -27,7 +27,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'top', horizontal: 'center' })} > - Top-Center + TOP-CENTER @@ -36,7 +36,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'top', horizontal: 'left' })} > - Top-Left + TOP-LEFT @@ -44,7 +44,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'top', horizontal: 'right' })} > - Top-Right + TOP-RIGHT @@ -52,7 +52,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'bottom', horizontal: 'left' })} > - Bottom-Left + BOTTOM-LEFT @@ -60,7 +60,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'bottom', horizontal: 'right' })} > - Bottom-Right + BOTTOM-RIGHT @@ -69,7 +69,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'bottom', horizontal: 'center' })} > - Bottom-Center + BOTTOM-CENTER @@ -82,7 +82,7 @@ export default function PositionedSnackbar() { anchorOrigin={{ vertical, horizontal }} open={open} onClose={handleClose} - variant="soft" + variant="solid" color="success" size="lg" > diff --git a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx index 6a7e8fb6d95bec..dcf51671542833 100644 --- a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx +++ b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx @@ -31,7 +31,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'top', horizontal: 'center' })} > - Top-Center + TOP-CENTER @@ -40,7 +40,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'top', horizontal: 'left' })} > - Top-Left + TOP-LEFT @@ -48,7 +48,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'top', horizontal: 'right' })} > - Top-Right + TOP-RIGHT @@ -56,7 +56,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'bottom', horizontal: 'left' })} > - Bottom-Left + BOTTOM-LEFT @@ -64,7 +64,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'bottom', horizontal: 'right' })} > - Bottom-Right + BOTTOM-RIGHT @@ -73,7 +73,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'bottom', horizontal: 'center' })} > - Bottom-Center + BOTTOM-CENTER @@ -86,7 +86,7 @@ export default function PositionedSnackbar() { anchorOrigin={{ vertical, horizontal }} open={open} onClose={handleClose} - variant="soft" + variant="solid" color="success" size="lg" > diff --git a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview index f57a0dd32b64fe..92771e3d3f31c3 100644 --- a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview +++ b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview @@ -3,7 +3,7 @@ anchorOrigin={{ vertical, horizontal }} open={open} onClose={handleClose} - variant="soft" + variant="solid" color="success" size="lg" > From 5850daa58d2d7da55c59f48bcdb41998eb4d1836 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 6 Sep 2023 16:22:08 +0530 Subject: [PATCH 19/58] add snackbar with decorators demo --- .../snackbar/SnackbarWithDecorators.js | 49 +++++++++++++++++++ .../snackbar/SnackbarWithDecorators.tsx | 48 ++++++++++++++++++ docs/data/joy/components/snackbar/snackbar.md | 8 +++ packages/mui-joy/src/Snackbar/Snackbar.tsx | 16 ++++-- 4 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 docs/data/joy/components/snackbar/SnackbarWithDecorators.js create mode 100644 docs/data/joy/components/snackbar/SnackbarWithDecorators.tsx diff --git a/docs/data/joy/components/snackbar/SnackbarWithDecorators.js b/docs/data/joy/components/snackbar/SnackbarWithDecorators.js new file mode 100644 index 00000000000000..da007a56d38de9 --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarWithDecorators.js @@ -0,0 +1,49 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import Button from '@mui/joy/Button'; +import Snackbar from '@mui/joy/Snackbar'; +import PlaylistAddCheckCircleRoundedIcon from '@mui/icons-material/PlaylistAddCheckCircleRounded'; + +function CloseButton(props) { + const { onClose } = props; + return ( + + ); +} + +CloseButton.propTypes = { + onClose: PropTypes.func.isRequired, +}; + +export default function SnackbarWithDecorators() { + const [open, setOpen] = React.useState(false); + + const handleOpen = () => setOpen(true); + + const handleClose = () => setOpen(false); + + return ( + + + + Your message was sent successfully. + + + ); +} diff --git a/docs/data/joy/components/snackbar/SnackbarWithDecorators.tsx b/docs/data/joy/components/snackbar/SnackbarWithDecorators.tsx new file mode 100644 index 00000000000000..af888a00a0dd8b --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarWithDecorators.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import Button from '@mui/joy/Button'; +import Snackbar from '@mui/joy/Snackbar'; +import PlaylistAddCheckCircleRoundedIcon from '@mui/icons-material/PlaylistAddCheckCircleRounded'; + +interface CloseButtonProps { + onClose: () => React.Dispatch>; +} + +function CloseButton(props: CloseButtonProps) { + const { onClose } = props; + return ( + + ); +} + +export default function SnackbarWithDecorators() { + const [open, setOpen] = React.useState(false); + + const handleOpen = () => setOpen(true); + + const handleClose = () => setOpen(false); + + return ( + + + + Your message was sent successfully. + + + ); +} diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index d81e516e0fdb0a..b297ef1720a684 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -26,3 +26,11 @@ In wide layouts, snackbars can be left-aligned or center-aligned if they are con You can control the position of the snackbar by specifying the `anchorOrigin` prop. {{"demo": "PositionedSnackbar.js"}} + +## Customization + +### Decorators + +Use the `slots.startDecorator` and `slots.endDecorator` props to append actions and icons to either side of the Snackbar: + +{{"demo": "SnackbarWithDecorators.js"}} diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 4a8b40fdeeb524..ce6e0070fb0b3d 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -71,18 +71,28 @@ const SnackbarRoot = styled('div', { }), }, '--Snackbar-radius': theme.vars.radius.sm, + '--Snackbar-decoratorChildRadius': + 'max((var(--Snackbar-radius) - var(--variant-borderWidth, 0px)) - var(--Snackbar-padding), min(var(--Snackbar-padding) + var(--variant-borderWidth, 0px), var(--Snackbar-radius) / 2))', + '--Button-minHeight': 'var(--Snackbar-decoratorChildHeight)', + '--IconButton-size': 'var(--Snackbar-decoratorChildHeight)', + '--Button-radius': 'var(--Snackbar-decoratorChildRadius)', + '--IconButton-radius': 'var(--Snackbar-decoratorChildRadius)', + '--Icon-color': 'currentColor', ...(ownerState.size === 'sm' && { '--Snackbar-padding': '0.5rem', + '--Snackbar-decoratorChildHeight': '1.5rem', '--Icon-fontSize': theme.vars.fontSize.xl, gap: '0.5rem', }), ...(ownerState.size === 'md' && { '--Snackbar-padding': '0.75rem', + '--Snackbar-decoratorChildHeight': '2rem', '--Icon-fontSize': theme.vars.fontSize.xl, gap: '0.625rem', }), ...(ownerState.size === 'lg' && { '--Snackbar-padding': '1rem', + '--Snackbar-decoratorChildHeight': '2.375rem', '--Icon-fontSize': theme.vars.fontSize.xl2, gap: '0.875rem', }), @@ -205,14 +215,12 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { {slots.startDecorator && ( - {slots.startDecorator as React.ReactNode} + {} )} {children} {slots.endDecorator && ( - - {slots.endDecorator as React.ReactNode} - + {} )} From 69667db9944635a33ddf8bd289b25cd3d20182a2 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Wed, 6 Sep 2023 16:29:30 +0530 Subject: [PATCH 20/58] yarn proptypes --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index ce6e0070fb0b3d..c10ff537aa076f 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -218,6 +218,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { {} )} + {children} {slots.endDecorator && ( {} From 7d6e6f24302872d9471396e73a573e59d35f9b0d Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 7 Sep 2023 12:24:09 +0530 Subject: [PATCH 21/58] add color inversion feature --- .../snackbar/SnackbarInvertedColors.js | 81 +++++++++++++++++++ .../snackbar/SnackbarInvertedColors.tsx | 80 ++++++++++++++++++ docs/data/joy/components/snackbar/snackbar.md | 8 ++ docs/pages/joy-ui/api/snackbar.json | 1 + .../api-docs-joy/snackbar/snackbar.json | 3 + packages/mui-joy/src/Snackbar/Snackbar.tsx | 37 ++++++--- .../mui-joy/src/Snackbar/SnackbarProps.ts | 5 ++ 7 files changed, 206 insertions(+), 9 deletions(-) create mode 100644 docs/data/joy/components/snackbar/SnackbarInvertedColors.js create mode 100644 docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx diff --git a/docs/data/joy/components/snackbar/SnackbarInvertedColors.js b/docs/data/joy/components/snackbar/SnackbarInvertedColors.js new file mode 100644 index 00000000000000..80f6de50f07786 --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarInvertedColors.js @@ -0,0 +1,81 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import Snackbar from '@mui/joy/Snackbar'; +import IconButton from '@mui/joy/IconButton'; +import AspectRatio from '@mui/joy/AspectRatio'; +import Typography from '@mui/joy/Typography'; +import Button from '@mui/joy/Button'; +import Check from '@mui/icons-material/Check'; +import Close from '@mui/icons-material/Close'; + +function SnackbarStartDecorator() { + return ( + +
+ +
+
+ ); +} + +function SnackbarEndDecorator(props) { + return ( + + + + ); +} + +SnackbarEndDecorator.propTypes = { + onClose: PropTypes.func.isRequired, +}; + +export default function SnackbarInvertedColors() { + const [open, setOpen] = React.useState(false); + + const handleOpen = () => setOpen(true); + + const handleClose = () => setOpen(false); + + return ( + + + + Your message was sent successfully. + + + ); +} diff --git a/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx b/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx new file mode 100644 index 00000000000000..80de5db4784ed5 --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx @@ -0,0 +1,80 @@ +import * as React from 'react'; +import Snackbar from '@mui/joy/Snackbar'; +import IconButton from '@mui/joy/IconButton'; +import AspectRatio from '@mui/joy/AspectRatio'; +import Typography from '@mui/joy/Typography'; +import Button from '@mui/joy/Button'; +import Check from '@mui/icons-material/Check'; +import Close from '@mui/icons-material/Close'; + +function SnackbarStartDecorator() { + return ( + +
+ +
+
+ ); +} + +interface SnackbarEndDecoratorProps { + onClose: () => React.Dispatch>; +} + +function SnackbarEndDecorator(props: SnackbarEndDecoratorProps) { + return ( + + + + ); +} + +export default function SnackbarInvertedColors() { + const [open, setOpen] = React.useState(false); + + const handleOpen = () => setOpen(true); + + const handleClose = () => setOpen(false); + + return ( + + + + Your message was sent successfully. + + + ); +} diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index b297ef1720a684..3a126ec0c5dc19 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -34,3 +34,11 @@ You can control the position of the snackbar by specifying the `anchorOrigin` pr Use the `slots.startDecorator` and `slots.endDecorator` props to append actions and icons to either side of the Snackbar: {{"demo": "SnackbarWithDecorators.js"}} + +### Inverted colors + +When the Snackbar's variant is `soft` or `solid`, you can use the `invertedColors={true}` prop to invert the colors of the children to have enough contrast. + +To learn more about this, check out [Color Inversion](/joy-ui/main-features/color-inversion/) feature. + +{{"demo": "SnackbarInvertedColors.js"}} diff --git a/docs/pages/joy-ui/api/snackbar.json b/docs/pages/joy-ui/api/snackbar.json index fb1038dca08992..a5115cb467b6eb 100644 --- a/docs/pages/joy-ui/api/snackbar.json +++ b/docs/pages/joy-ui/api/snackbar.json @@ -19,6 +19,7 @@ }, "component": { "type": { "name": "elementType" } }, "disableWindowBlurListener": { "type": { "name": "bool" }, "default": "false" }, + "invertedColors": { "type": { "name": "bool" }, "default": "false" }, "onClose": { "type": { "name": "func" }, "signature": { diff --git a/docs/translations/api-docs-joy/snackbar/snackbar.json b/docs/translations/api-docs-joy/snackbar/snackbar.json index a9c6b1c03bf326..82adecb3d7108b 100644 --- a/docs/translations/api-docs-joy/snackbar/snackbar.json +++ b/docs/translations/api-docs-joy/snackbar/snackbar.json @@ -19,6 +19,9 @@ "disableWindowBlurListener": { "description": "If true, the autoHideDuration timer will expire even if the window is not focused." }, + "invertedColors": { + "description": "If true, the children with an implicit color prop invert their colors to match the component's variant and color." + }, "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": { diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index c10ff537aa076f..6d1a31848a8296 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -7,6 +7,7 @@ import { ClickAwayListener } from '@mui/base/ClickAwayListener'; import { useSnackbar } from '@mui/base/useSnackbar'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { OverridableComponent } from '@mui/types'; +import { ColorInversionProvider } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { useThemeProps } from '../styles'; @@ -104,6 +105,9 @@ const SnackbarRoot = styled('div', { fontWeight: theme.vars.fontWeight.md, ...theme.variants[ownerState.variant!]?.[ownerState.color!], } as const, + ownerState.color !== 'context' && + ownerState.invertedColors && + theme.colorInversion[ownerState.variant!]?.[ownerState.color!], p !== undefined && { '--Snackbar-padding': p }, padding !== undefined && { '--Snackbar-padding': padding }, borderRadius !== undefined && { '--Snackbar-radius': borderRadius }, @@ -153,6 +157,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { ClickAwayListenerProps, component, disableWindowBlurListener = false, + invertedColors = false, onBlur, onClose, onFocus, @@ -173,6 +178,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { autoHideDuration, color, disableWindowBlurListener, + invertedColors, size, variant, }; @@ -210,18 +216,26 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { return null; } + const result = ( + + {slots.startDecorator && ( + {} + )} + + {children} + {slots.endDecorator && ( + {} + )} + + ); + return ( - {slots.startDecorator && ( - - {} - - )} - - {children} - {slots.endDecorator && ( - {} + {invertedColors ? ( + {result} + ) : ( + result )} @@ -278,6 +292,11 @@ Snackbar.propTypes /* remove-proptypes */ = { * @default false */ disableWindowBlurListener: PropTypes.bool, + /** + * If `true`, the children with an implicit color prop invert their colors to match the component's variant and color. + * @default false + */ + invertedColors: PropTypes.bool, /** * @ignore */ diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index af03247aa686f0..bbe45c1f4508b4 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -62,6 +62,11 @@ export interface SnackbarTypeMap

{ * @default 'neutral' */ color?: OverridableStringUnion; + /** + * If `true`, the children with an implicit color prop invert their colors to match the component's variant and color. + * @default false + */ + invertedColors?: boolean; /** * The size of the component. * @default 'md' From 0ca43440219b5322fc1455ca19f82d56e18eeda8 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 7 Sep 2023 12:30:50 +0530 Subject: [PATCH 22/58] add colorContext class --- docs/pages/joy-ui/api/snackbar.json | 1 + docs/translations/api-docs-joy/snackbar/snackbar.json | 5 +++++ packages/mui-joy/src/Snackbar/snackbarClasses.ts | 3 +++ 3 files changed, 9 insertions(+) diff --git a/docs/pages/joy-ui/api/snackbar.json b/docs/pages/joy-ui/api/snackbar.json index a5115cb467b6eb..a1fc1a2c19c0b3 100644 --- a/docs/pages/joy-ui/api/snackbar.json +++ b/docs/pages/joy-ui/api/snackbar.json @@ -96,6 +96,7 @@ "anchorOriginTopCenter", "anchorOriginTopLeft", "anchorOriginTopRight", + "colorContext", "colorDanger", "colorNeutral", "colorPrimary", diff --git a/docs/translations/api-docs-joy/snackbar/snackbar.json b/docs/translations/api-docs-joy/snackbar/snackbar.json index 82adecb3d7108b..a65d3c9bbb4c8b 100644 --- a/docs/translations/api-docs-joy/snackbar/snackbar.json +++ b/docs/translations/api-docs-joy/snackbar/snackbar.json @@ -100,6 +100,11 @@ "nodeName": "the root element", "conditions": "color=\"warning\"" }, + "colorContext": { + "description": "Class name applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the root element", + "conditions": "color inversion is triggered" + }, "endDecorator": { "description": "Class name applied to {{nodeName}} if {{conditions}}.", "nodeName": "the endDecorator element", diff --git a/packages/mui-joy/src/Snackbar/snackbarClasses.ts b/packages/mui-joy/src/Snackbar/snackbarClasses.ts index 9a294d04a6b06b..4e481d5de353f7 100644 --- a/packages/mui-joy/src/Snackbar/snackbarClasses.ts +++ b/packages/mui-joy/src/Snackbar/snackbarClasses.ts @@ -25,6 +25,8 @@ export interface SnackbarClasses { colorSuccess: string; /** Class name applied to the root element if `color="warning"`. */ colorWarning: string; + /** Class name applied to the root element when color inversion is triggered. */ + colorContext: string; /** Class name applied to the endDecorator element if supplied. */ endDecorator: string; /** Class name applied to the root element if `size="sm"`. */ @@ -64,6 +66,7 @@ const snackbarClasses: SnackbarClasses = generateUtilityClasses('MuiSnackbar', [ 'colorNeutral', 'colorSuccess', 'colorWarning', + 'colorContext', 'endDecorator', 'sizeSm', 'sizeMd', From c7a79fc93f10bef6cca2fb42a30927164281632b Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 7 Sep 2023 12:46:28 +0530 Subject: [PATCH 23/58] Revert "add colorContext class" This reverts commit 0ca43440219b5322fc1455ca19f82d56e18eeda8. --- docs/pages/joy-ui/api/snackbar.json | 1 - docs/translations/api-docs-joy/snackbar/snackbar.json | 5 ----- packages/mui-joy/src/Snackbar/snackbarClasses.ts | 3 --- 3 files changed, 9 deletions(-) diff --git a/docs/pages/joy-ui/api/snackbar.json b/docs/pages/joy-ui/api/snackbar.json index a1fc1a2c19c0b3..a5115cb467b6eb 100644 --- a/docs/pages/joy-ui/api/snackbar.json +++ b/docs/pages/joy-ui/api/snackbar.json @@ -96,7 +96,6 @@ "anchorOriginTopCenter", "anchorOriginTopLeft", "anchorOriginTopRight", - "colorContext", "colorDanger", "colorNeutral", "colorPrimary", diff --git a/docs/translations/api-docs-joy/snackbar/snackbar.json b/docs/translations/api-docs-joy/snackbar/snackbar.json index a65d3c9bbb4c8b..82adecb3d7108b 100644 --- a/docs/translations/api-docs-joy/snackbar/snackbar.json +++ b/docs/translations/api-docs-joy/snackbar/snackbar.json @@ -100,11 +100,6 @@ "nodeName": "the root element", "conditions": "color=\"warning\"" }, - "colorContext": { - "description": "Class name applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the root element", - "conditions": "color inversion is triggered" - }, "endDecorator": { "description": "Class name applied to {{nodeName}} if {{conditions}}.", "nodeName": "the endDecorator element", diff --git a/packages/mui-joy/src/Snackbar/snackbarClasses.ts b/packages/mui-joy/src/Snackbar/snackbarClasses.ts index 4e481d5de353f7..9a294d04a6b06b 100644 --- a/packages/mui-joy/src/Snackbar/snackbarClasses.ts +++ b/packages/mui-joy/src/Snackbar/snackbarClasses.ts @@ -25,8 +25,6 @@ export interface SnackbarClasses { colorSuccess: string; /** Class name applied to the root element if `color="warning"`. */ colorWarning: string; - /** Class name applied to the root element when color inversion is triggered. */ - colorContext: string; /** Class name applied to the endDecorator element if supplied. */ endDecorator: string; /** Class name applied to the root element if `size="sm"`. */ @@ -66,7 +64,6 @@ const snackbarClasses: SnackbarClasses = generateUtilityClasses('MuiSnackbar', [ 'colorNeutral', 'colorSuccess', 'colorWarning', - 'colorContext', 'endDecorator', 'sizeSm', 'sizeMd', From 8855c32080524e371181259f32454677f3741cb0 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 7 Sep 2023 14:25:09 +0530 Subject: [PATCH 24/58] add new feature tag --- docs/data/joy/pages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/joy/pages.ts b/docs/data/joy/pages.ts index 5a9213ebf8e893..9a4dcf1c0ef205 100644 --- a/docs/data/joy/pages.ts +++ b/docs/data/joy/pages.ts @@ -73,7 +73,7 @@ const pages: readonly MuiPage[] = [ { pathname: '/joy-ui/react-linear-progress', title: 'Linear Progress' }, { pathname: '/joy-ui/react-modal' }, { pathname: '/joy-ui/react-skeleton', newFeature: true }, - { pathname: '/joy-ui/react-snackbar' }, + { pathname: '/joy-ui/react-snackbar', newFeature: true }, ], }, { From ac67ec1554e34998a8076dc039f6a665cc80aafb Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Thu, 28 Sep 2023 12:41:27 +0530 Subject: [PATCH 25/58] fix displaying postioned snackbars --- docs/data/joy/components/snackbar/PositionedSnackbar.js | 1 + docs/data/joy/components/snackbar/PositionedSnackbar.tsx | 1 + .../joy/components/snackbar/PositionedSnackbar.tsx.preview | 1 + docs/pages/joy-ui/api/snackbar.json | 1 + docs/translations/api-docs-joy/snackbar/snackbar.json | 3 +++ packages/mui-joy/src/Snackbar/Snackbar.tsx | 7 +++++++ packages/mui-joy/src/Snackbar/SnackbarProps.ts | 7 +++++++ 7 files changed, 21 insertions(+) diff --git a/docs/data/joy/components/snackbar/PositionedSnackbar.js b/docs/data/joy/components/snackbar/PositionedSnackbar.js index 46a79fe2097dc4..47c496f08c9df4 100644 --- a/docs/data/joy/components/snackbar/PositionedSnackbar.js +++ b/docs/data/joy/components/snackbar/PositionedSnackbar.js @@ -85,6 +85,7 @@ export default function PositionedSnackbar() { variant="solid" color="success" size="lg" + key={vertical + horizontal} > I love snacks diff --git a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx index dcf51671542833..822ed4a0794aa1 100644 --- a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx +++ b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx @@ -89,6 +89,7 @@ export default function PositionedSnackbar() { variant="solid" color="success" size="lg" + key={vertical + horizontal} > I love snacks diff --git a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview index 92771e3d3f31c3..634f8de7095aab 100644 --- a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview +++ b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview @@ -6,6 +6,7 @@ variant="solid" color="success" size="lg" + key={vertical + horizontal} > I love snacks \ No newline at end of file diff --git a/docs/pages/joy-ui/api/snackbar.json b/docs/pages/joy-ui/api/snackbar.json index a5115cb467b6eb..ee6bc805843cba 100644 --- a/docs/pages/joy-ui/api/snackbar.json +++ b/docs/pages/joy-ui/api/snackbar.json @@ -20,6 +20,7 @@ "component": { "type": { "name": "elementType" } }, "disableWindowBlurListener": { "type": { "name": "bool" }, "default": "false" }, "invertedColors": { "type": { "name": "bool" }, "default": "false" }, + "key": { "type": { "name": "custom", "description": "any" } }, "onClose": { "type": { "name": "func" }, "signature": { diff --git a/docs/translations/api-docs-joy/snackbar/snackbar.json b/docs/translations/api-docs-joy/snackbar/snackbar.json index 82adecb3d7108b..0130bbab0a17d1 100644 --- a/docs/translations/api-docs-joy/snackbar/snackbar.json +++ b/docs/translations/api-docs-joy/snackbar/snackbar.json @@ -22,6 +22,9 @@ "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": { diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 6d1a31848a8296..9dd59c4a3f764b 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -297,6 +297,13 @@ Snackbar.propTypes /* remove-proptypes */ = { * @default false */ invertedColors: PropTypes.bool, + /** + * When displaying multiple consecutive snackbars using a single parent-rendered + * ``, add the `key` prop to ensure independent treatment of each message. + * For instance, use ``. Otherwise, messages might update + * in place, and features like `autoHideDuration` could be affected. + */ + key: () => null, /** * @ignore */ diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index bbe45c1f4508b4..a1416c53c285b3 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -67,6 +67,13 @@ export interface SnackbarTypeMap

{ * @default false */ invertedColors?: boolean; + /** + * When displaying multiple consecutive snackbars using a single parent-rendered + * ``, add the `key` prop to ensure independent treatment of each message. + * For instance, use ``. Otherwise, messages might update + * in place, and features like `autoHideDuration` could be affected. + */ + key?: any; /** * The size of the component. * @default 'md' From 734f8ac0b60ef5bb3197f05c70001ebd60b50c9e Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 29 Sep 2023 19:27:56 +0530 Subject: [PATCH 26/58] Add defaultanimation and demo on how to add custom animation --- .../snackbar/CustomAnimatedSnackbar.js | 65 +++++++++++++ .../snackbar/CustomAnimatedSnackbar.tsx | 65 +++++++++++++ docs/data/joy/components/snackbar/snackbar.md | 6 ++ docs/pages/joy-ui/api/snackbar.json | 3 +- .../api-docs-joy/snackbar/snackbar.json | 3 + packages/mui-joy/src/Snackbar/Snackbar.tsx | 94 ++++++++++++++++++- .../mui-joy/src/Snackbar/SnackbarProps.ts | 17 +++- 7 files changed, 248 insertions(+), 5 deletions(-) create mode 100644 docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js create mode 100644 docs/data/joy/components/snackbar/CustomAnimatedSnackbar.tsx diff --git a/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js new file mode 100644 index 00000000000000..155e029216628c --- /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 ( +

+ + + I love this animation! + +
+ ); +} diff --git a/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.tsx b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.tsx new file mode 100644 index 00000000000000..155e029216628c --- /dev/null +++ b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.tsx @@ -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 ( +
+ + + I love this animation! + +
+ ); +} diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index 3a126ec0c5dc19..190cf2e5c95ecb 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -42,3 +42,9 @@ When the Snackbar's variant is `soft` or `solid`, you can use the `invertedColor To learn more about this, check out [Color Inversion](/joy-ui/main-features/color-inversion/) feature. {{"demo": "SnackbarInvertedColors.js"}} + +### Custom Animation + +You can apply your custom animation, as demonstrated below, when opening and closing the snackbar. To ensure precise unmount timing, please provide the animationDuration, which we'll use to match the component's unmount animation accurately. + +{{"demo": "CustomAnimatedSnackbar.js"}} diff --git a/docs/pages/joy-ui/api/snackbar.json b/docs/pages/joy-ui/api/snackbar.json index ee6bc805843cba..0ecb76d53d9d71 100644 --- a/docs/pages/joy-ui/api/snackbar.json +++ b/docs/pages/joy-ui/api/snackbar.json @@ -1,5 +1,6 @@ { "props": { + "open": { "type": { "name": "bool" }, "required": true }, "anchorOrigin": { "type": { "name": "shape", @@ -7,6 +8,7 @@ }, "default": "{ vertical: 'bottom', horizontal: 'left' }" }, + "animationDuration": { "type": { "name": "number" }, "default": "500" }, "autoHideDuration": { "type": { "name": "number" }, "default": "null" }, "ClickAwayListenerProps": { "type": { "name": "object" } }, "color": { @@ -28,7 +30,6 @@ "describedArgs": ["event", "reason"] } }, - "open": { "type": { "name": "bool" } }, "resumeHideDuration": { "type": { "name": "number" } }, "size": { "type": { "name": "enum", "description": "'sm'
| 'md'
| 'lg'" }, diff --git a/docs/translations/api-docs-joy/snackbar/snackbar.json b/docs/translations/api-docs-joy/snackbar/snackbar.json index 0130bbab0a17d1..cad8fdd678bd73 100644 --- a/docs/translations/api-docs-joy/snackbar/snackbar.json +++ b/docs/translations/api-docs-joy/snackbar/snackbar.json @@ -4,6 +4,9 @@ "anchorOrigin": { "description": "The anchor of the 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." }, diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 9dd59c4a3f764b..c92da501f08d80 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -7,6 +7,7 @@ import { ClickAwayListener } from '@mui/base/ClickAwayListener'; import { useSnackbar } from '@mui/base/useSnackbar'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { OverridableComponent } from '@mui/types'; +import { keyframes } from '@mui/system'; import { ColorInversionProvider } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; @@ -33,6 +34,50 @@ const useUtilityClasses = (ownerState: SnackbarOwnerState) => { return composeClasses(slots, getSnackbarUtilityClass, {}); }; +const inAnimationVerticalTop = keyframes` + 0% { + transform: translateY(-24px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +`; + +const outAnimationVerticalTop = keyframes` + 0% { + transform: translateY(0); + opacity: 1; + } + 100% { + transform: translateY(-24px); + opacity: 0; + } +`; + +const inAnimationVerticalBottom = keyframes` + 0% { + transform: translateY(24px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +`; + +const outAnimationVerticalBottom = keyframes` + 0% { + transform: translateY(0); + opacity: 1; + } + 100% { + transform: translateY(24px); + opacity: 0; + } +`; + const SnackbarRoot = styled('div', { name: 'JoySnackbar', slot: 'Root', @@ -60,7 +105,7 @@ const SnackbarRoot = styled('div', { ...(ownerState.anchorOrigin!.horizontal === 'center' && { left: '50%', right: 'auto', - transform: 'translateX(-50%)', + marginLeft: -150, }), ...(ownerState.anchorOrigin!.horizontal === 'left' && { left: 24, @@ -71,6 +116,20 @@ const SnackbarRoot = styled('div', { left: 'auto', }), }, + ...(ownerState.open && { + animation: `${ + ownerState.anchorOrigin!.vertical === 'top' + ? inAnimationVerticalTop + : inAnimationVerticalBottom + } ${ownerState.animationDuration}ms`, + }), + ...(!ownerState.open && { + animation: `${ + ownerState.anchorOrigin!.vertical === 'top' + ? outAnimationVerticalTop + : outAnimationVerticalBottom + } ${ownerState.animationDuration}ms`, + }), '--Snackbar-radius': theme.vars.radius.sm, '--Snackbar-decoratorChildRadius': 'max((var(--Snackbar-radius) - var(--variant-borderWidth, 0px)) - var(--Snackbar-padding), min(var(--Snackbar-padding) + var(--variant-borderWidth, 0px), var(--Snackbar-radius) / 2))', @@ -132,6 +191,22 @@ const SnackbarEndDecorator = styled('span', { flex: 'none', marginLeft: 'auto', }); + +function useDelayUnmount(isMounted: boolean, delayTime: number) { + const [shouldRender, setShouldRender] = React.useState(false); + + React.useEffect(() => { + let timeoutId: number; + if (isMounted && !shouldRender) { + setShouldRender(true); + } else if (!isMounted && shouldRender) { + timeoutId = window.setTimeout(() => setShouldRender(false), delayTime); + } + return () => window.clearTimeout(timeoutId); + }, [isMounted, delayTime, shouldRender]); + return shouldRender; +} + /** * * Demos: @@ -150,6 +225,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { const { anchorOrigin: { vertical, horizontal } = { vertical: 'bottom', horizontal: 'left' }, + animationDuration = 500, autoHideDuration = null, color = 'neutral', children, @@ -177,6 +253,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { anchorOrigin: { vertical, horizontal }, autoHideDuration, color, + animationDuration, disableWindowBlurListener, invertedColors, size, @@ -187,6 +264,8 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { const { getRootProps, onClickAway } = useSnackbar({ ...ownerState }); + const shouldRender = useDelayUnmount(open, animationDuration); + const externalForwardedProps = { ...other, component, slots, slotProps }; const [SlotRoot, rootProps] = useSlot('root', { @@ -212,7 +291,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { ownerState, }); - if (!open) { + if (!shouldRender) { return null; } @@ -257,6 +336,15 @@ Snackbar.propTypes /* remove-proptypes */ = { horizontal: PropTypes.oneOf(['center', 'left', 'right']).isRequired, vertical: PropTypes.oneOf(['bottom', 'top']).isRequired, }), + /** + * 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. + * @default 500 + */ + animationDuration: PropTypes.number, /** * The number of milliseconds to wait before automatically calling the * `onClose` function. `onClose` should then set the state of the `open` @@ -334,7 +422,7 @@ Snackbar.propTypes /* remove-proptypes */ = { /** * If `true`, the component is shown. */ - open: PropTypes.bool, + open: PropTypes.bool.isRequired, /** * The number of milliseconds to wait before dismissing after user interaction. * If `autoHideDuration` prop isn't specified, it does nothing. diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index a1416c53c285b3..ad054894c73fb9 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -5,6 +5,8 @@ import { UseSnackbarParameters } from '@mui/base/useSnackbar'; import { ColorPaletteProp, VariantProp, ApplyColorInversion, SxProps } from '../styles/types'; import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types'; +export type SnackbarSlot = 'root' | 'startDecorator' | 'endDecorator'; + export interface SnackbarSlots { /** * The component that renders the root. @@ -45,7 +47,7 @@ export type { SnackbarCloseReason } from '@mui/base/useSnackbar'; export interface SnackbarTypeMap

{ props: P & - UseSnackbarParameters & { + Omit & { /** * The anchor of the `Snackbar`. * On smaller screens, the component grows to occupy all the available width, @@ -53,6 +55,15 @@ export interface SnackbarTypeMap

{ * @default { vertical: 'bottom', horizontal: 'left' } */ anchorOrigin?: SnackbarOrigin; + /** + * 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. + * @default 500 + */ + animationDuration?: number; /** * Props applied to the `ClickAwayListener` element. */ @@ -74,6 +85,10 @@ export interface SnackbarTypeMap

{ * in place, and features like `autoHideDuration` could be affected. */ key?: any; + /** + * If `true`, the component is shown. + */ + open: boolean; /** * The size of the component. * @default 'md' From ed5447a87f8d88664d552e888ef0e574569dfccf Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 29 Sep 2023 19:28:19 +0530 Subject: [PATCH 27/58] Add JoySnackbar in theme --- packages/mui-joy/src/styles/components.d.ts | 5 +++++ .../mui-joy/src/styles/extendTheme.spec.ts | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/mui-joy/src/styles/components.d.ts b/packages/mui-joy/src/styles/components.d.ts index f12a710f7ee8c6..03072c89286c4b 100644 --- a/packages/mui-joy/src/styles/components.d.ts +++ b/packages/mui-joy/src/styles/components.d.ts @@ -194,6 +194,7 @@ import { SkeletonProps, SkeletonOwnerState, SkeletonSlot } from '../Skeleton/Ske import { SelectProps, SelectOwnerState, SelectSlot } from '../Select/SelectProps'; import { OptionProps, OptionOwnerState, OptionSlot } from '../Option/OptionProps'; import { SliderProps, SliderOwnerState, SliderSlot } from '../Slider/SliderProps'; +import { SnackbarProps, SnackbarOwnerState, SnackbarSlot } from '../Snackbar/SnackbarProps'; import { StackProps, StackSlot } from '../Stack/StackProps'; import { SvgIconProps, SvgIconOwnerState, SvgIconSlot } from '../SvgIcon/SvgIconProps'; import { SwitchProps, SwitchOwnerState, SwitchSlot } from '../Switch/SwitchProps'; @@ -467,6 +468,10 @@ export interface Components { defaultProps?: Partial; styleOverrides?: StyleOverrides; }; + JoySnackbar?: { + defaultProps?: Partial; + styleOverrides?: StyleOverrides; + }; JoyTabs?: { defaultProps?: Partial; styleOverrides?: StyleOverrides; diff --git a/packages/mui-joy/src/styles/extendTheme.spec.ts b/packages/mui-joy/src/styles/extendTheme.spec.ts index 73e42542bd3583..429ef60ddf3efb 100644 --- a/packages/mui-joy/src/styles/extendTheme.spec.ts +++ b/packages/mui-joy/src/styles/extendTheme.spec.ts @@ -57,6 +57,7 @@ import { RadioGroupOwnerState } from '@mui/joy/RadioGroup'; import { SelectOwnerState } from '@mui/joy/Select'; import { SheetOwnerState } from '@mui/joy/Sheet'; import { SliderOwnerState } from '@mui/joy/Slider'; +import { SnackbarOwnerState } from '@mui/joy/Snackbar'; import { StackProps } from '@mui/joy/Stack'; import { extendTheme } from '@mui/joy/styles'; import { SvgIconOwnerState } from '@mui/joy/SvgIcon'; @@ -1111,6 +1112,26 @@ extendTheme({ }, }, }, + JoySnackbar: { + defaultProps: { + variant: 'plain', + color: 'neutral', + }, + styleOverrides: { + root: ({ ownerState }) => { + expectType, typeof ownerState>(ownerState); + return {}; + }, + startDecorator: ({ ownerState }) => { + expectType, typeof ownerState>(ownerState); + return {}; + }, + endDecorator: ({ ownerState }) => { + expectType, typeof ownerState>(ownerState); + return {}; + }, + }, + }, JoyStack: { defaultProps: { spacing: 1, From dbdee1ed88c40e2f81decb940b5546eb1c7f9a49 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 29 Sep 2023 19:47:05 +0530 Subject: [PATCH 28/58] add code quote --- docs/data/joy/components/snackbar/snackbar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index 190cf2e5c95ecb..4ed0d5c8783813 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -45,6 +45,6 @@ To learn more about this, check out [Color Inversion](/joy-ui/main-features/colo ### Custom Animation -You can apply your custom animation, as demonstrated below, when opening and closing the snackbar. To ensure precise unmount timing, please provide the animationDuration, which we'll use to match the component's unmount animation accurately. +You can apply your custom animation, as demonstrated below, when opening and closing the snackbar. To ensure precise unmount timing, please provide the `animationDuration` prop, which we'll use to match the component's unmount animation accurately. {{"demo": "CustomAnimatedSnackbar.js"}} From a710021c6941d555ca5c73920165ab0f72191841 Mon Sep 17 00:00:00 2001 From: zanivan Date: Thu, 5 Oct 2023 15:23:17 -0300 Subject: [PATCH 29/58] Stray design tweaks to the demos --- .../snackbar/CustomAnimatedSnackbar.tsx | 6 +++--- .../snackbar/PositionedSnackbar.tsx | 17 +++++++---------- .../snackbar/SnackbarInvertedColors.tsx | 19 +++++-------------- .../snackbar/SnackbarWithDecorators.tsx | 11 +++++++---- 4 files changed, 22 insertions(+), 31 deletions(-) diff --git a/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.tsx b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.tsx index 155e029216628c..400db7c05485c4 100644 --- a/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.tsx +++ b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.tsx @@ -40,13 +40,13 @@ export default function CustomAnimatedSnackbar() { return (

- + - TOP-CENTER + Top-center @@ -40,15 +40,15 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'top', horizontal: 'left' })} > - TOP-LEFT + Top-left - + @@ -56,7 +56,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'bottom', horizontal: 'left' })} > - BOTTOM-LEFT + Bottom-left @@ -64,7 +64,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'bottom', horizontal: 'right' })} > - BOTTOM-RIGHT + Bottom-right @@ -73,7 +73,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'bottom', horizontal: 'center' })} > - BOTTOM-CENTER + Bottom-center @@ -86,9 +86,6 @@ export default function PositionedSnackbar() { anchorOrigin={{ vertical, horizontal }} open={open} onClose={handleClose} - variant="solid" - color="success" - size="lg" key={vertical + horizontal} > I love snacks diff --git a/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx b/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx index 80de5db4784ed5..03061f0cc1c585 100644 --- a/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx +++ b/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx @@ -53,27 +53,18 @@ export default function SnackbarInvertedColors() { return ( - + - Your message was sent successfully. + I love snacks ); diff --git a/docs/data/joy/components/snackbar/SnackbarWithDecorators.tsx b/docs/data/joy/components/snackbar/SnackbarWithDecorators.tsx index af888a00a0dd8b..479257b83349e4 100644 --- a/docs/data/joy/components/snackbar/SnackbarWithDecorators.tsx +++ b/docs/data/joy/components/snackbar/SnackbarWithDecorators.tsx @@ -10,8 +10,8 @@ interface CloseButtonProps { function CloseButton(props: CloseButtonProps) { const { onClose } = props; return ( - ); } @@ -25,12 +25,15 @@ export default function SnackbarWithDecorators() { return ( - + Date: Thu, 5 Oct 2023 15:42:11 -0300 Subject: [PATCH 30/58] Run yarn docs:typescript:formatted --- .../snackbar/CustomAnimatedSnackbar.js | 6 +++--- .../components/snackbar/PositionedSnackbar.js | 17 +++++++-------- .../snackbar/PositionedSnackbar.tsx.preview | 3 --- .../snackbar/SnackbarInvertedColors.js | 21 ++++++------------- .../SnackbarInvertedColors.tsx.preview | 15 +++++++++++++ .../snackbar/SnackbarWithDecorators.js | 11 ++++++---- 6 files changed, 38 insertions(+), 35 deletions(-) create mode 100644 docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx.preview diff --git a/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js index 155e029216628c..400db7c05485c4 100644 --- a/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js +++ b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js @@ -40,13 +40,13 @@ export default function CustomAnimatedSnackbar() { return (
- + - TOP-CENTER + Top-center @@ -36,15 +36,15 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'top', horizontal: 'left' })} > - TOP-LEFT + Top-left - + @@ -52,7 +52,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'bottom', horizontal: 'left' })} > - BOTTOM-LEFT + Bottom-left @@ -60,7 +60,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'bottom', horizontal: 'right' })} > - BOTTOM-RIGHT + Bottom-right @@ -69,7 +69,7 @@ export default function PositionedSnackbar() { variant="plain" onClick={handleClick({ vertical: 'bottom', horizontal: 'center' })} > - BOTTOM-CENTER + Bottom-center @@ -82,9 +82,6 @@ export default function PositionedSnackbar() { anchorOrigin={{ vertical, horizontal }} open={open} onClose={handleClose} - variant="solid" - color="success" - size="lg" key={vertical + horizontal} > I love snacks diff --git a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview index 634f8de7095aab..55489f2580350a 100644 --- a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview +++ b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx.preview @@ -3,9 +3,6 @@ anchorOrigin={{ vertical, horizontal }} open={open} onClose={handleClose} - variant="solid" - color="success" - size="lg" key={vertical + horizontal} > I love snacks diff --git a/docs/data/joy/components/snackbar/SnackbarInvertedColors.js b/docs/data/joy/components/snackbar/SnackbarInvertedColors.js index 80f6de50f07786..307f355fce1101 100644 --- a/docs/data/joy/components/snackbar/SnackbarInvertedColors.js +++ b/docs/data/joy/components/snackbar/SnackbarInvertedColors.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import Snackbar from '@mui/joy/Snackbar'; import IconButton from '@mui/joy/IconButton'; import AspectRatio from '@mui/joy/AspectRatio'; -import Typography from '@mui/joy/Typography'; + import Button from '@mui/joy/Button'; import Check from '@mui/icons-material/Check'; import Close from '@mui/icons-material/Close'; @@ -54,27 +54,18 @@ export default function SnackbarInvertedColors() { return ( - + - Your message was sent successfully. + I love snacks ); diff --git a/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx.preview b/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx.preview new file mode 100644 index 00000000000000..782ab48b8c6d7a --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx.preview @@ -0,0 +1,15 @@ + + + + I love snacks + + \ No newline at end of file diff --git a/docs/data/joy/components/snackbar/SnackbarWithDecorators.js b/docs/data/joy/components/snackbar/SnackbarWithDecorators.js index da007a56d38de9..b7dda9a7d77f8e 100644 --- a/docs/data/joy/components/snackbar/SnackbarWithDecorators.js +++ b/docs/data/joy/components/snackbar/SnackbarWithDecorators.js @@ -7,8 +7,8 @@ import PlaylistAddCheckCircleRoundedIcon from '@mui/icons-material/PlaylistAddCh function CloseButton(props) { const { onClose } = props; return ( - ); } @@ -26,12 +26,15 @@ export default function SnackbarWithDecorators() { return ( - + Date: Thu, 5 Oct 2023 15:48:37 -0300 Subject: [PATCH 31/58] Fix imports --- .../snackbar/SnackbarInvertedColors.js | 43 ------------------- .../snackbar/SnackbarInvertedColors.tsx | 42 ------------------ 2 files changed, 85 deletions(-) diff --git a/docs/data/joy/components/snackbar/SnackbarInvertedColors.js b/docs/data/joy/components/snackbar/SnackbarInvertedColors.js index 307f355fce1101..40b684fe98e368 100644 --- a/docs/data/joy/components/snackbar/SnackbarInvertedColors.js +++ b/docs/data/joy/components/snackbar/SnackbarInvertedColors.js @@ -1,49 +1,6 @@ import * as React from 'react'; -import PropTypes from 'prop-types'; import Snackbar from '@mui/joy/Snackbar'; -import IconButton from '@mui/joy/IconButton'; -import AspectRatio from '@mui/joy/AspectRatio'; - import Button from '@mui/joy/Button'; -import Check from '@mui/icons-material/Check'; -import Close from '@mui/icons-material/Close'; - -function SnackbarStartDecorator() { - return ( - -
- -
-
- ); -} - -function SnackbarEndDecorator(props) { - return ( - - - - ); -} - -SnackbarEndDecorator.propTypes = { - onClose: PropTypes.func.isRequired, -}; export default function SnackbarInvertedColors() { const [open, setOpen] = React.useState(false); diff --git a/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx b/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx index 03061f0cc1c585..40b684fe98e368 100644 --- a/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx +++ b/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx @@ -1,48 +1,6 @@ import * as React from 'react'; import Snackbar from '@mui/joy/Snackbar'; -import IconButton from '@mui/joy/IconButton'; -import AspectRatio from '@mui/joy/AspectRatio'; -import Typography from '@mui/joy/Typography'; import Button from '@mui/joy/Button'; -import Check from '@mui/icons-material/Check'; -import Close from '@mui/icons-material/Close'; - -function SnackbarStartDecorator() { - return ( - -
- -
-
- ); -} - -interface SnackbarEndDecoratorProps { - onClose: () => React.Dispatch>; -} - -function SnackbarEndDecorator(props: SnackbarEndDecoratorProps) { - return ( - - - - ); -} export default function SnackbarInvertedColors() { const [open, setOpen] = React.useState(false); From cbaf73f374b1a3c7f822612bc0064e1d923ef69c Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 6 Oct 2023 11:16:23 +0700 Subject: [PATCH 32/58] refine snackbar styles and logic --- .../joy/components/snackbar/SnackbarUsage.js | 69 +++--- docs/data/joy/components/snackbar/snackbar.md | 82 +++++++- docs/src/modules/components/JoyUsageDemo.tsx | 32 ++- packages/mui-joy/src/Snackbar/Snackbar.tsx | 197 +++++++----------- .../mui-joy/src/Snackbar/SnackbarProps.ts | 32 +-- 5 files changed, 249 insertions(+), 163 deletions(-) diff --git a/docs/data/joy/components/snackbar/SnackbarUsage.js b/docs/data/joy/components/snackbar/SnackbarUsage.js index 1230acef4cbd00..48e67c460c9521 100644 --- a/docs/data/joy/components/snackbar/SnackbarUsage.js +++ b/docs/data/joy/components/snackbar/SnackbarUsage.js @@ -1,34 +1,14 @@ import * as React from 'react'; import Snackbar from '@mui/joy/Snackbar'; import Button from '@mui/joy/Button'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import Close from '@mui/icons-material/Close'; import JoyUsageDemo from 'docs/src/modules/components/JoyUsageDemo'; -function SimpleSnackbar(props) { +export default function SnackbarUsage() { const [open, setOpen] = React.useState(false); - const handleClick = () => { - setOpen(true); - }; - - const handleClose = (event, reason) => { - if (reason === 'clickaway') { - return; - } - - setOpen(false); - }; - - return ( - - - - Hello World - - - ); -} - -export default function SnackbarUsage() { return ( } + renderDemo={(props) => ( + + + { + if (reason === 'clickaway') { + return; + } + + setOpen(false); + }} + endDecorator={ + setOpen(false)}> + + + } + {...props} + > +
+ Notification alert + + 102 unread messages since last month. + +
+
+
+ )} /> ); } diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index 4ed0d5c8783813..a35504a99d2077 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -20,7 +20,13 @@ Snackbars contain a single line of text directly related to the operation perfor {{"demo": "SnackbarUsage.js", "hideToolbar": true, "bg": "gradient"}} -## Positioned snackbars +## Basics + +```jsx +import Snackbar from '@mui/joy/Snackbar'; +``` + +### Position In wide layouts, snackbars can be left-aligned or center-aligned if they are consistently placed on the same spot at the bottom of the screen, however there may be circumstances where the placement of the snackbar needs to be more flexible. You can control the position of the snackbar by specifying the `anchorOrigin` prop. @@ -29,22 +35,88 @@ You can control the position of the snackbar by specifying the `anchorOrigin` pr ## Customization +### Variants + +The Snackbar component supports Joy UI's four [global variants](/joy-ui/main-features/global-variants/): `outlined` (default), `solid`, `soft`, and `plain`. + + + +:::info +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. +::: + +### Sizes + +The Snackbar component comes in three sizes: `sm`, `md` (default), and `lg`. + + + +:::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. + + + +### 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. + + + +### Close reason + +There are three reasons for the Snackbar to close: + +- `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. + +You can access the value from the second argument of the `onClose` callback. + +```js + { + // reason will be one of: timeout, clickaway, escapeKeyDown +}}> +``` + + + +#### Ignore clickaway + +This pattern is useful when you don't want the Snackbar to close when the user clicks outside of it. + +```js + { + if (reason === 'clickaway') { + return; + } + }} +> +``` + ### Decorators -Use the `slots.startDecorator` and `slots.endDecorator` props to append actions and icons to either side of the Snackbar: +Use the `startDecorator` and `endDecorator` props to append icons and/or actions to either side of the Snackbar. {{"demo": "SnackbarWithDecorators.js"}} ### Inverted colors -When the Snackbar's variant is `soft` or `solid`, you can use the `invertedColors={true}` prop to invert the colors of the children to have enough contrast. +When the Snackbar's variant is `soft` or `solid`, you can set `invertedColors` prop to `true` to invert the colors of the children for increasing the contrast. To learn more about this, check out [Color Inversion](/joy-ui/main-features/color-inversion/) feature. {{"demo": "SnackbarInvertedColors.js"}} -### Custom Animation +### Animation -You can apply your custom animation, as demonstrated below, when opening and closing the snackbar. To ensure precise unmount timing, please provide the `animationDuration` prop, which we'll use to match the component's unmount animation accurately. +To apply a custom animation, provide the `animationDuration` prop, which we'll use to match the component's unmount animation accurately. {{"demo": "CustomAnimatedSnackbar.js"}} diff --git a/docs/src/modules/components/JoyUsageDemo.tsx b/docs/src/modules/components/JoyUsageDemo.tsx index dcc1eadb069406..00bde782ba070a 100644 --- a/docs/src/modules/components/JoyUsageDemo.tsx +++ b/docs/src/modules/components/JoyUsageDemo.tsx @@ -7,6 +7,7 @@ import Divider from '@mui/joy/Divider'; import Chip from '@mui/joy/Chip'; import FormControl from '@mui/joy/FormControl'; import FormLabel, { formLabelClasses } from '@mui/joy/FormLabel'; +import FormHelperText from '@mui/joy/FormHelperText'; import IconButton from '@mui/joy/IconButton'; import Input, { inputClasses } from '@mui/joy/Input'; import ListItemDecorator, { listItemDecoratorClasses } from '@mui/joy/ListItemDecorator'; @@ -155,6 +156,10 @@ interface JoyUsageDemoProps { * If not provided, the `propName` is displayed as Pascal case. */ formLabel?: string; + /** + * The helper text to be displayed for the knob. + */ + helperText?: string; }>; /** * A function to override the code block result. @@ -296,7 +301,15 @@ export default function JoyUsageDemo({ }} > {data.map( - ({ propName, formLabel = propName, knob, options = [], defaultValue, labels }) => { + ({ + propName, + formLabel = propName, + knob, + options = [], + defaultValue, + labels, + helperText, + }) => { const resolvedValue = props[propName] ?? defaultValue; if (!knob) { return null; @@ -306,10 +319,9 @@ export default function JoyUsageDemo({ - {formLabel} + {formLabel} @@ -328,6 +340,11 @@ export default function JoyUsageDemo({ }, }} /> + {helperText && ( + + {helperText} + + )} ); } @@ -379,6 +396,7 @@ export default function JoyUsageDemo({ ); })} + {helperText && {helperText}} ); } @@ -432,6 +450,7 @@ export default function JoyUsageDemo({ ); })} + {helperText && {helperText}} ); } @@ -513,6 +532,7 @@ export default function JoyUsageDemo({ }, )} + {helperText && {helperText}} ); } @@ -557,6 +577,7 @@ export default function JoyUsageDemo({ ))} + {helperText && {helperText}} ); } @@ -580,6 +601,7 @@ export default function JoyUsageDemo({ }, }} /> + {helperText && {helperText}} ); } @@ -610,6 +632,7 @@ export default function JoyUsageDemo({ }, }} /> + {helperText && {helperText}} ); } @@ -710,6 +733,7 @@ export default function JoyUsageDemo({ ))} + {helperText && {helperText}} ); } diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index c92da501f08d80..adeac217d0a6f8 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -8,11 +8,11 @@ import { useSnackbar } from '@mui/base/useSnackbar'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { OverridableComponent } from '@mui/types'; import { keyframes } from '@mui/system'; -import { ColorInversionProvider } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { useThemeProps } from '../styles'; import { resolveSxValue } from '../styles/styleUtils'; +import { applySolidInversion, applySoftInversion } from '../colorInversion'; import { SnackbarProps, SnackbarOwnerState, SnackbarTypeMap } from './SnackbarProps'; import { getSnackbarUtilityClass } from './snackbarClasses'; @@ -34,46 +34,26 @@ const useUtilityClasses = (ownerState: SnackbarOwnerState) => { return composeClasses(slots, getSnackbarUtilityClass, {}); }; -const inAnimationVerticalTop = keyframes` +const enterAnimation = keyframes` 0% { - transform: translateY(-24px); + transform: translateX(var(--Snackbar-translateX, 0px)) translateY(calc(var(--_Snackbar-anchorBottom, 1) * (50% + var(--Snackbar-inset)))); opacity: 0; } - 100% { - transform: translateY(0); - opacity: 1; - } -`; - -const outAnimationVerticalTop = keyframes` - 0% { - transform: translateY(0); + 50% { opacity: 1; } 100% { - transform: translateY(-24px); - opacity: 0; + transform: translateX(var(--Snackbar-translateX, 0px)) translateY(0); } `; -const inAnimationVerticalBottom = keyframes` +const exitAnimation = keyframes` 0% { - transform: translateY(24px); - opacity: 0; - } - 100% { - transform: translateY(0); - opacity: 1; - } -`; - -const outAnimationVerticalBottom = keyframes` - 0% { - transform: translateY(0); + transform: translateX(var(--Snackbar-translateX, 0px)) translateY(0); opacity: 1; } 100% { - transform: translateY(24px); + transform: translateX(var(--Snackbar-translateX, 0px)) translateY(calc(var(--_Snackbar-anchorBottom, 1) * (50% + var(--Snackbar-inset)))); opacity: 0; } `; @@ -91,45 +71,6 @@ const SnackbarRoot = styled('div', { return [ { - zIndex: theme.vars.zIndex.snackbar, - position: 'fixed', - display: 'flex', - left: 8, - right: 8, - justifyContent: 'flex-start', - alignItems: 'center', - minWidth: 300, - ...(ownerState.anchorOrigin!.vertical === 'top' ? { top: 8 } : { bottom: 8 }), - [theme.breakpoints.up('sm')]: { - ...(ownerState.anchorOrigin!.vertical === 'top' ? { top: 24 } : { bottom: 24 }), - ...(ownerState.anchorOrigin!.horizontal === 'center' && { - left: '50%', - right: 'auto', - marginLeft: -150, - }), - ...(ownerState.anchorOrigin!.horizontal === 'left' && { - left: 24, - right: 'auto', - }), - ...(ownerState.anchorOrigin!.horizontal === 'right' && { - right: 24, - left: 'auto', - }), - }, - ...(ownerState.open && { - animation: `${ - ownerState.anchorOrigin!.vertical === 'top' - ? inAnimationVerticalTop - : inAnimationVerticalBottom - } ${ownerState.animationDuration}ms`, - }), - ...(!ownerState.open && { - animation: `${ - ownerState.anchorOrigin!.vertical === 'top' - ? outAnimationVerticalTop - : outAnimationVerticalBottom - } ${ownerState.animationDuration}ms`, - }), '--Snackbar-radius': theme.vars.radius.sm, '--Snackbar-decoratorChildRadius': 'max((var(--Snackbar-radius) - var(--variant-borderWidth, 0px)) - var(--Snackbar-padding), min(var(--Snackbar-padding) + var(--variant-borderWidth, 0px), var(--Snackbar-radius) / 2))', @@ -139,34 +80,63 @@ const SnackbarRoot = styled('div', { '--IconButton-radius': 'var(--Snackbar-decoratorChildRadius)', '--Icon-color': 'currentColor', ...(ownerState.size === 'sm' && { - '--Snackbar-padding': '0.5rem', + '--Snackbar-padding': '0.75rem', + '--Snackbar-inset': '0.5rem', '--Snackbar-decoratorChildHeight': '1.5rem', '--Icon-fontSize': theme.vars.fontSize.xl, gap: '0.5rem', }), ...(ownerState.size === 'md' && { - '--Snackbar-padding': '0.75rem', + '--Snackbar-padding': '1rem', + '--Snackbar-inset': '0.75rem', // the spacing between Snackbar and the viewport '--Snackbar-decoratorChildHeight': '2rem', '--Icon-fontSize': theme.vars.fontSize.xl, gap: '0.625rem', }), ...(ownerState.size === 'lg' && { - '--Snackbar-padding': '1rem', + '--Snackbar-padding': '1.25rem', + '--Snackbar-inset': '1rem', '--Snackbar-decoratorChildHeight': '2.375rem', '--Icon-fontSize': theme.vars.fontSize.xl2, gap: '0.875rem', }), - boxShadow: theme.vars.shadow.md, + zIndex: theme.vars.zIndex.snackbar, + position: 'fixed', + display: 'flex', + alignItems: 'center', + minWidth: 300, + top: ownerState.anchorOrigin?.vertical === 'top' ? 'var(--Snackbar-inset)' : undefined, + left: ownerState.anchorOrigin?.horizontal === 'left' ? 'var(--Snackbar-inset)' : undefined, + bottom: ownerState.anchorOrigin?.vertical === 'bottom' ? 'var(--Snackbar-inset)' : undefined, + right: ownerState.anchorOrigin?.horizontal === 'right' ? 'var(--Snackbar-inset)' : undefined, + ...(ownerState.anchorOrigin?.horizontal === 'center' && { + '--Snackbar-translateX': '-50%', + left: '50%', + transform: 'translateX(var(--Snackbar-translateX))', + }), + ...(ownerState.anchorOrigin?.vertical === 'top' && { + '--_Snackbar-anchorBottom': '-1', + }), + animation: `${enterAnimation} ${ownerState.animationDuration}ms forwards`, + ...(!ownerState.open && { + animationName: exitAnimation, + }), + boxShadow: theme.vars.shadow.lg, backgroundColor: theme.vars.palette.background.surface, padding: `var(--Snackbar-padding)`, borderRadius: 'var(--Snackbar-radius)', ...theme.typography[`body-${({ sm: 'xs', md: 'sm', lg: 'md' } as const)[ownerState.size!]}`], - fontWeight: theme.vars.fontWeight.md, + ...(ownerState.variant === 'solid' && + ownerState.color && + ownerState.invertedColors && + applySolidInversion(ownerState.color)(theme)), + ...(ownerState.variant === 'soft' && + ownerState.color && + ownerState.invertedColors && + applySoftInversion(ownerState.color)(theme)), + ...theme.variants[ownerState.variant!]?.[ownerState.color!], ...theme.variants[ownerState.variant!]?.[ownerState.color!], } as const, - ownerState.color !== 'context' && - ownerState.invertedColors && - theme.colorInversion[ownerState.variant!]?.[ownerState.color!], p !== undefined && { '--Snackbar-padding': p }, padding !== undefined && { '--Snackbar-padding': padding }, borderRadius !== undefined && { '--Snackbar-radius': borderRadius }, @@ -192,21 +162,6 @@ const SnackbarEndDecorator = styled('span', { marginLeft: 'auto', }); -function useDelayUnmount(isMounted: boolean, delayTime: number) { - const [shouldRender, setShouldRender] = React.useState(false); - - React.useEffect(() => { - let timeoutId: number; - if (isMounted && !shouldRender) { - setShouldRender(true); - } else if (!isMounted && shouldRender) { - timeoutId = window.setTimeout(() => setShouldRender(false), delayTime); - } - return () => window.clearTimeout(timeoutId); - }, [isMounted, delayTime, shouldRender]); - return shouldRender; -} - /** * * Demos: @@ -224,15 +179,15 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { }); const { - anchorOrigin: { vertical, horizontal } = { vertical: 'bottom', horizontal: 'left' }, - animationDuration = 500, + anchorOrigin = { vertical: 'bottom', horizontal: 'center' }, + animationDuration = 300, autoHideDuration = null, color = 'neutral', children, className, - ClickAwayListenerProps, component, disableWindowBlurListener = false, + endDecorator, invertedColors = false, onBlur, onClose, @@ -244,13 +199,27 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { size = 'md', slots = {}, slotProps, + startDecorator, variant = 'outlined', ...other } = props; + const [exited, setExited] = React.useState(true); + React.useEffect(() => { + if (open) { + setExited(false); + } else { + const timer = setTimeout(() => { + setExited(true); + }, animationDuration); + return () => { + clearTimeout(timer); + }; + } + }, [open, animationDuration]); const ownerState = { ...props, - anchorOrigin: { vertical, horizontal }, + anchorOrigin, autoHideDuration, color, animationDuration, @@ -262,9 +231,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { const classes = useUtilityClasses(ownerState); - const { getRootProps, onClickAway } = useSnackbar({ ...ownerState }); - - const shouldRender = useDelayUnmount(open, animationDuration); + const { getRootProps, onClickAway } = useSnackbar(ownerState); const externalForwardedProps = { ...other, component, slots, slotProps }; @@ -291,33 +258,29 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { ownerState, }); - if (!shouldRender) { + const SlotClickAway = slots.clickAway || ClickAwayListener; + + // So we only render active snackbars. + if (!open && exited) { return null; } - const result = ( - - {slots.startDecorator && ( - {} - )} - - {children} - {slots.endDecorator && ( - {} - )} - - ); - return ( - + - {invertedColors ? ( - {result} - ) : ( - result + {startDecorator && ( + {startDecorator} )} + + {children} + {endDecorator && {endDecorator}} - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index ad054894c73fb9..7c70d30f4f6bd8 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -5,7 +5,7 @@ import { UseSnackbarParameters } from '@mui/base/useSnackbar'; import { ColorPaletteProp, VariantProp, ApplyColorInversion, SxProps } from '../styles/types'; import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types'; -export type SnackbarSlot = 'root' | 'startDecorator' | 'endDecorator'; +export type SnackbarSlot = 'root' | 'startDecorator' | 'endDecorator' | 'clickAway'; export interface SnackbarSlots { /** @@ -23,6 +23,11 @@ export interface SnackbarSlots { * @default 'span' */ endDecorator?: React.ElementType; + /** + * The component that renders the click away. + * @default ClickAwayListener + */ + clickAway?: React.ElementType; } export type SnackbarSlotsAndSlotProps = CreateSlotsAndSlotProps< @@ -31,6 +36,9 @@ export type SnackbarSlotsAndSlotProps = CreateSlotsAndSlotProps< root: SlotProps<'div', {}, SnackbarOwnerState>; startDecorator: SlotProps<'span', {}, SnackbarOwnerState>; endDecorator: SlotProps<'span', {}, SnackbarOwnerState>; + clickAway: + | ClickAwayListenerProps + | ((ownerState: SnackbarOwnerState) => ClickAwayListenerProps); } >; @@ -47,12 +55,12 @@ export type { SnackbarCloseReason } from '@mui/base/useSnackbar'; export interface SnackbarTypeMap

{ props: P & - Omit & { + UseSnackbarParameters & { /** * The anchor of the `Snackbar`. * On smaller screens, the component grows to occupy all the available width, * the horizontal alignment is ignored. - * @default { vertical: 'bottom', horizontal: 'left' } + * @default { vertical: 'bottom', horizontal: 'center' } */ anchorOrigin?: SnackbarOrigin; /** @@ -61,18 +69,18 @@ export interface SnackbarTypeMap

{ * 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. - * @default 500 + * @default 300 */ animationDuration?: number; - /** - * Props applied to the `ClickAwayListener` element. - */ - ClickAwayListenerProps?: Partial; /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' */ color?: OverridableStringUnion; + /** + * Element placed after the children. + */ + endDecorator?: React.ReactNode; /** * If `true`, the children with an implicit color prop invert their colors to match the component's variant and color. * @default false @@ -85,15 +93,15 @@ export interface SnackbarTypeMap

{ * in place, and features like `autoHideDuration` could be affected. */ key?: any; - /** - * If `true`, the component is shown. - */ - open: boolean; /** * The size of the component. * @default 'md' */ size?: OverridableStringUnion<'sm' | 'md' | 'lg', SnackbarPropsSizeOverrides>; + /** + * Element placed before the children. + */ + startDecorator?: React.ReactNode; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ From 90c0ae154308e5e5977b7aea7e2ceac8d77801be Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 6 Oct 2023 11:29:43 +0700 Subject: [PATCH 33/58] add icons to position demo --- .../snackbar/PositionedSnackbar.tsx | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx index a91dbe6c11fb8b..12b7e695bdc0d1 100644 --- a/docs/data/joy/components/snackbar/PositionedSnackbar.tsx +++ b/docs/data/joy/components/snackbar/PositionedSnackbar.tsx @@ -3,6 +3,12 @@ import Grid from '@mui/joy/Grid'; import Box from '@mui/joy/Box'; import Button from '@mui/joy/Button'; import Snackbar, { SnackbarOrigin } from '@mui/joy/Snackbar'; +import NorthWestIcon from '@mui/icons-material/NorthWest'; +import NorthEastIcon from '@mui/icons-material/NorthEast'; +import NorthIcon from '@mui/icons-material/North'; +import SouthIcon from '@mui/icons-material/South'; +import SouthEastIcon from '@mui/icons-material/SouthEast'; +import SouthWestIcon from '@mui/icons-material/SouthWest'; interface State extends SnackbarOrigin { open: boolean; @@ -29,51 +35,59 @@ export default function PositionedSnackbar() { From 26f437df1956e51b60742fdf8ca25ae8d00ef137 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 6 Oct 2023 11:51:28 +0700 Subject: [PATCH 34/58] add variant demo --- .../components/snackbar/SnackbarVariants.js | 67 +++++++++++++++++++ docs/data/joy/components/snackbar/snackbar.md | 2 +- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 docs/data/joy/components/snackbar/SnackbarVariants.js diff --git a/docs/data/joy/components/snackbar/SnackbarVariants.js b/docs/data/joy/components/snackbar/SnackbarVariants.js new file mode 100644 index 00000000000000..3fde203c05c369 --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarVariants.js @@ -0,0 +1,67 @@ +import * as React from 'react'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Stack from '@mui/joy/Stack'; +import Snackbar, { SnackbarOrigin } from '@mui/joy/Snackbar'; + +export default function SnackbarVariants() { + const [open, setOpen] = React.useState(false); + const [variant, setVariant] = React.useState('outlined'); + return ( + + + + + + { + if (reason === 'clickaway') { + return; + } + return setOpen(false); + }} + > + A snackbar with {variant} variant. + + + ); +} diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index a35504a99d2077..d0553c6fa84657 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -39,7 +39,7 @@ You can control the position of the snackbar by specifying the `anchorOrigin` pr The Snackbar component supports Joy UI's four [global variants](/joy-ui/main-features/global-variants/): `outlined` (default), `solid`, `soft`, and `plain`. - +{{"demo": "SnackbarVariants.js"}} :::info To learn how to add your own variants, check out [Themed components—Extend variants](/joy-ui/customization/themed-components/#extend-variants). From 8d02dd9eb99ae5d9a1dbc06ce912116e8be4043a Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 6 Oct 2023 12:48:50 +0700 Subject: [PATCH 35/58] add sizes and colors demo --- .../joy/components/snackbar/SnackbarColors.js | 52 ++++++++++++++++ .../joy/components/snackbar/SnackbarSizes.js | 60 +++++++++++++++++++ docs/data/joy/components/snackbar/snackbar.md | 4 +- 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 docs/data/joy/components/snackbar/SnackbarColors.js create mode 100644 docs/data/joy/components/snackbar/SnackbarSizes.js diff --git a/docs/data/joy/components/snackbar/SnackbarColors.js b/docs/data/joy/components/snackbar/SnackbarColors.js new file mode 100644 index 00000000000000..a649f00891471d --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarColors.js @@ -0,0 +1,52 @@ +import * as React from 'react'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Stack from '@mui/joy/Stack'; +import Snackbar, { SnackbarOrigin } from '@mui/joy/Snackbar'; + +export default function SnackbarVariants() { + const [open, setOpen] = React.useState(false); + const [color, setColor] = React.useState('neutral'); + return ( + + {['primary', 'neutral', 'danger', 'success', 'warning'].map((color) => ( + + ))} + {['plain', 'outlined', 'soft', 'solid'].map((variant, index) => ( + { + if (reason === 'clickaway') { + return; + } + return setOpen(false); + }} + > + {variant} snackbar with {color} color. + + ))} + + ); +} diff --git a/docs/data/joy/components/snackbar/SnackbarSizes.js b/docs/data/joy/components/snackbar/SnackbarSizes.js new file mode 100644 index 00000000000000..c8ca04b64b2c19 --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarSizes.js @@ -0,0 +1,60 @@ +import * as React from 'react'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Stack from '@mui/joy/Stack'; +import Snackbar, { SnackbarOrigin } from '@mui/joy/Snackbar'; + +export default function SnackbarVariants() { + const [open, setOpen] = React.useState(false); + const [size, setSize] = React.useState('md'); + return ( + + + + + { + if (reason === 'clickaway') { + return; + } + return setOpen(false); + }} + > + A snackbar with {size} size. + + + ); +} diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index d0553c6fa84657..033f74f51a5cc1 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -50,7 +50,7 @@ Note that you lose the global variants when you add custom variants. 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). @@ -61,7 +61,7 @@ To learn how to add custom sizes to the component, check out [Themed components 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 From 04bb8ee2bd9a045730b06b13386caf33fe3725e9 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 6 Oct 2023 13:26:03 +0700 Subject: [PATCH 36/58] add hide duration demo --- .../snackbar/SnackbarHideDuration.js | 76 +++++++++++++++++++ docs/data/joy/components/snackbar/snackbar.md | 2 +- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 docs/data/joy/components/snackbar/SnackbarHideDuration.js diff --git a/docs/data/joy/components/snackbar/SnackbarHideDuration.js b/docs/data/joy/components/snackbar/SnackbarHideDuration.js new file mode 100644 index 00000000000000..8cda3a560b6830 --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarHideDuration.js @@ -0,0 +1,76 @@ +import * as React from 'react'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Input from '@mui/joy/Input'; +import Stack from '@mui/joy/Stack'; +import Snackbar, { SnackbarOrigin } from '@mui/joy/Snackbar'; + +export default function SnackbarVariants() { + const [open, setOpen] = React.useState(false); + const [duration, setDuration] = React.useState(); + const [left, setLeft] = React.useState(); + const timer = React.useRef(); + const countdown = () => { + timer.current = setInterval(() => { + setLeft((prev) => prev - 16); + }, 16); // 60fps = 16ms / frame + }; + React.useEffect(() => { + if (open && duration > 0) { + setLeft(duration); + countdown(); + } else { + setLeft(undefined); + window.clearInterval(timer.current); + } + }, [open]); + const handlePause = () => { + window.clearInterval(timer.current); + }; + const handleResume = () => { + countdown(); + }; + return ( + + + Auto Hide Duration (ms) + { + setDuration(event.target.valueAsNumber || undefined); + }} + /> + + + { + setOpen(false); + }} + > + This snackbar will{' '} + {left ? `disappear in ${left}ms` : `not disappear until you click away`}. + + + ); +} diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index 033f74f51a5cc1..7112f12ae597a7 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -67,7 +67,7 @@ Play around combining different colors with different variants. 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. - +{{"demo": "SnackbarHideDuration.js"}} ### Close reason From 76f5c0859aa982815f840083597196494f39221c Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 6 Oct 2023 11:58:20 +0530 Subject: [PATCH 37/58] remove duplicate style --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index adeac217d0a6f8..fd2ed06d13d325 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -135,7 +135,6 @@ const SnackbarRoot = styled('div', { ownerState.invertedColors && applySoftInversion(ownerState.color)(theme)), ...theme.variants[ownerState.variant!]?.[ownerState.color!], - ...theme.variants[ownerState.variant!]?.[ownerState.color!], } as const, p !== undefined && { '--Snackbar-padding': p }, padding !== undefined && { '--Snackbar-padding': padding }, From 7478fba5a8cf4d2a17c07d15f893e503e3777ed8 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 6 Oct 2023 12:14:01 +0530 Subject: [PATCH 38/58] update proptypes and API docs --- docs/pages/joy-ui/api/snackbar.json | 22 ++++++++---- .../api-docs-joy/snackbar/snackbar.json | 8 ++--- packages/mui-joy/src/Snackbar/Snackbar.tsx | 36 +++++++++++++++---- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/docs/pages/joy-ui/api/snackbar.json b/docs/pages/joy-ui/api/snackbar.json index 0ecb76d53d9d71..dd74f6a3da9caa 100644 --- a/docs/pages/joy-ui/api/snackbar.json +++ b/docs/pages/joy-ui/api/snackbar.json @@ -1,16 +1,14 @@ { "props": { - "open": { "type": { "name": "bool" }, "required": true }, "anchorOrigin": { "type": { "name": "shape", "description": "{ horizontal: 'center'
| 'left'
| 'right', vertical: 'bottom'
| 'top' }" }, - "default": "{ vertical: 'bottom', horizontal: 'left' }" + "default": "{ vertical: 'bottom', horizontal: 'center' }" }, - "animationDuration": { "type": { "name": "number" }, "default": "500" }, + "animationDuration": { "type": { "name": "number" }, "default": "300" }, "autoHideDuration": { "type": { "name": "number" }, "default": "null" }, - "ClickAwayListenerProps": { "type": { "name": "object" } }, "color": { "type": { "name": "enum", @@ -21,6 +19,7 @@ }, "component": { "type": { "name": "elementType" } }, "disableWindowBlurListener": { "type": { "name": "bool" }, "default": "false" }, + "endDecorator": { "type": { "name": "node" } }, "invertedColors": { "type": { "name": "bool" }, "default": "false" }, "key": { "type": { "name": "custom", "description": "any" } }, "onClose": { @@ -30,6 +29,7 @@ "describedArgs": ["event", "reason"] } }, + "open": { "type": { "name": "bool" } }, "resumeHideDuration": { "type": { "name": "number" } }, "size": { "type": { "name": "enum", "description": "'sm'
| 'md'
| 'lg'" }, @@ -39,18 +39,19 @@ "slotProps": { "type": { "name": "shape", - "description": "{ endDecorator?: func
| object, root?: func
| object, startDecorator?: func
| object }" + "description": "{ clickAway?: func
| { children: element, disableReactTree?: bool, mouseEvent?: 'onClick'
| 'onMouseDown'
| 'onMouseUp'
| 'onPointerDown'
| 'onPointerUp'
| false, onClickAway: func, touchEvent?: 'onTouchEnd'
| 'onTouchStart'
| false }, endDecorator?: func
| object, root?: func
| object, startDecorator?: func
| object }" }, "default": "{}" }, "slots": { "type": { "name": "shape", - "description": "{ endDecorator?: elementType, root?: elementType, startDecorator?: elementType }" + "description": "{ clickAway?: elementType, endDecorator?: elementType, root?: elementType, startDecorator?: elementType }" }, "default": "{}", "additionalInfo": { "slotsApi": true } }, + "startDecorator": { "type": { "name": "node" } }, "sx": { "type": { "name": "union", @@ -88,6 +89,12 @@ "description": "The component that renders the end decorator.", "default": "'span'", "class": ".MuiSnackbar-endDecorator" + }, + { + "name": "clickAway", + "description": "The component that renders the click away.", + "default": "ClickAwayListener", + "class": null } ], "classes": { @@ -114,8 +121,9 @@ "globalClasses": {} }, "spread": true, - "themeDefaultProps": null, + "themeDefaultProps": true, "muiName": "JoySnackbar", + "forwardsRefTo": "HTMLDivElement", "filename": "/packages/mui-joy/src/Snackbar/Snackbar.tsx", "inheritance": null, "demos": "

", diff --git a/docs/translations/api-docs-joy/snackbar/snackbar.json b/docs/translations/api-docs-joy/snackbar/snackbar.json index cad8fdd678bd73..4f9f6b41d6258b 100644 --- a/docs/translations/api-docs-joy/snackbar/snackbar.json +++ b/docs/translations/api-docs-joy/snackbar/snackbar.json @@ -10,9 +10,6 @@ "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." }, - "ClickAwayListenerProps": { - "description": "Props applied to the ClickAwayListener element." - }, "color": { "description": "The color of the component. It supports those theme colors that make sense for this component." }, @@ -22,6 +19,7 @@ "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." }, @@ -42,6 +40,7 @@ "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." }, @@ -155,6 +154,7 @@ "slotDescriptions": { "root": "The component that renders the root.", "startDecorator": "The component that renders the start decorator.", - "endDecorator": "The component that renders the end decorator." + "endDecorator": "The component that renders the end decorator.", + "clickAway": "The component that renders the click away." } } diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index fd2ed06d13d325..369ebfb438d809 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -292,7 +292,7 @@ Snackbar.propTypes /* remove-proptypes */ = { * The anchor of the `Snackbar`. * On smaller screens, the component grows to occupy all the available width, * the horizontal alignment is ignored. - * @default { vertical: 'bottom', horizontal: 'left' } + * @default { vertical: 'bottom', horizontal: 'center' } */ anchorOrigin: PropTypes.shape({ horizontal: PropTypes.oneOf(['center', 'left', 'right']).isRequired, @@ -304,7 +304,7 @@ Snackbar.propTypes /* remove-proptypes */ = { * 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. - * @default 500 + * @default 300 */ animationDuration: PropTypes.number, /** @@ -323,10 +323,6 @@ Snackbar.propTypes /* remove-proptypes */ = { * @ignore */ className: PropTypes.string, - /** - * Props applied to the `ClickAwayListener` element. - */ - ClickAwayListenerProps: PropTypes.object, /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' @@ -342,6 +338,10 @@ Snackbar.propTypes /* remove-proptypes */ = { * @default false */ disableWindowBlurListener: PropTypes.bool, + /** + * Element placed after the children. + */ + endDecorator: PropTypes.node, /** * If `true`, the children with an implicit color prop invert their colors to match the component's variant and color. * @default false @@ -384,7 +384,7 @@ Snackbar.propTypes /* remove-proptypes */ = { /** * If `true`, the component is shown. */ - open: PropTypes.bool.isRequired, + open: PropTypes.bool, /** * The number of milliseconds to wait before dismissing after user interaction. * If `autoHideDuration` prop isn't specified, it does nothing. @@ -402,6 +402,23 @@ Snackbar.propTypes /* remove-proptypes */ = { * @default {} */ slotProps: PropTypes.shape({ + clickAway: 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.isRequired, + touchEvent: PropTypes.oneOf(['onTouchEnd', 'onTouchStart', false]), + }), + ]), endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), @@ -411,10 +428,15 @@ Snackbar.propTypes /* remove-proptypes */ = { * @default {} */ slots: PropTypes.shape({ + clickAway: PropTypes.elementType, endDecorator: PropTypes.elementType, root: PropTypes.elementType, startDecorator: PropTypes.elementType, }), + /** + * Element placed before the children. + */ + startDecorator: PropTypes.node, /** * The system prop that allows defining system overrides as well as additional CSS styles. */ From c57233f3aa6682b7780724312a99931a94030aac Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 6 Oct 2023 12:16:57 +0530 Subject: [PATCH 39/58] fix lint --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 369ebfb438d809..dbdf786f6b7d5f 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -214,6 +214,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { clearTimeout(timer); }; } + return undefined; }, [open, animationDuration]); const ownerState = { From 13e66e009c8d96337cdb408db47f1ccc36562ad8 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 6 Oct 2023 14:12:17 +0700 Subject: [PATCH 40/58] add onUnmount prop --- packages/mui-joy/src/Snackbar/Snackbar.tsx | 5 +++++ packages/mui-joy/src/Snackbar/SnackbarProps.ts | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index adeac217d0a6f8..9c71e9fab3f7a8 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -194,6 +194,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { onFocus, onMouseEnter, onMouseLeave, + onUnmount, open, resumeHideDuration, size = 'md', @@ -204,12 +205,15 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { ...other } = props; const [exited, setExited] = React.useState(true); + const unmountRef = React.useRef(onUnmount); + unmountRef.current = onUnmount; React.useEffect(() => { if (open) { setExited(false); } else { const timer = setTimeout(() => { setExited(true); + unmountRef.current?.(); }, animationDuration); return () => { clearTimeout(timer); @@ -228,6 +232,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { size, variant, }; + delete ownerState.onUnmount; // `on*` are considered as event handler which does not work with ClickAwayListener const classes = useUtilityClasses(ownerState); diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index 7c70d30f4f6bd8..5b67bd878839f1 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -93,6 +93,10 @@ export interface SnackbarTypeMap

{ * in place, and features like `autoHideDuration` could be affected. */ key?: any; + /** + * A callback fired when the component is about to be unmounted. + */ + onUnmount?: () => void; /** * The size of the component. * @default 'md' From 7299b929ebb68801cafb360c2cc2908577beb383 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 6 Oct 2023 14:12:26 +0700 Subject: [PATCH 41/58] add more demos --- .../snackbar/SnackbarCloseReason.js | 84 +++++++++++++++++++ .../snackbar/SnackbarHideDuration.js | 10 ++- docs/data/joy/components/snackbar/snackbar.md | 2 +- 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 docs/data/joy/components/snackbar/SnackbarCloseReason.js diff --git a/docs/data/joy/components/snackbar/SnackbarCloseReason.js b/docs/data/joy/components/snackbar/SnackbarCloseReason.js new file mode 100644 index 00000000000000..c7c5d911e70e13 --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarCloseReason.js @@ -0,0 +1,84 @@ +import * as React from 'react'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import List from '@mui/joy/List'; +import ListItem from '@mui/joy/ListItem'; +import Input from '@mui/joy/Input'; +import Typography from '@mui/joy/Typography'; +import Stack from '@mui/joy/Stack'; +import Snackbar, { SnackbarOrigin } from '@mui/joy/Snackbar'; +import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; + +export default function SnackbarVariants() { + const [open, setOpen] = React.useState(false); + const [reasons, setReasons] = React.useState([]); + console.log('reasons', reasons); + React.useEffect(() => { + if ( + ['timeout', 'clickaway', 'escapeKeyDown'].every((item) => + reasons.includes(item), + ) + ) { + setOpen(false); + } + }, [reasons]); + return ( +

+ + { + setReasons((prev) => [...new Set([...prev, reason])]); + }} + onUnmount={() => { + setReasons([]); + }} + sx={{ minWidth: 360 }} + > + + + To close this snackbar, you have to: + + + + {reasons.includes('timeout') ? ( + + ) : ( + + )}{' '} + Wait for 3 seconds. + + + {reasons.includes('clickaway') ? ( + + ) : ( + + )}{' '} + Click outside of the snackbar. + + + {reasons.includes('escapeKeyDown') ? ( + + ) : ( + + )}{' '} + Press ESC key. + + + + +
+ ); +} diff --git a/docs/data/joy/components/snackbar/SnackbarHideDuration.js b/docs/data/joy/components/snackbar/SnackbarHideDuration.js index 8cda3a560b6830..211f63d377027a 100644 --- a/docs/data/joy/components/snackbar/SnackbarHideDuration.js +++ b/docs/data/joy/components/snackbar/SnackbarHideDuration.js @@ -22,8 +22,16 @@ export default function SnackbarVariants() { setLeft(duration); countdown(); } else { - setLeft(undefined); + setLeft(0); window.clearInterval(timer.current); + + // account for the animation delay (~300ms) + const timeout = setTimeout(() => { + setLeft(undefined); + }, 300); + return () => { + clearTimeout(timeout); + }; } }, [open]); const handlePause = () => { diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index 7112f12ae597a7..b3dade4cf1aa68 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -85,7 +85,7 @@ You can access the value from the second argument of the `onClose` callback. }}> ``` - +{{"demo": "SnackbarCloseReason.js"}} #### Ignore clickaway From b845562de57016da2db18f4a7ff3492755319d7d Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 6 Oct 2023 12:50:01 +0530 Subject: [PATCH 42/58] add clickAway Snackbar slot in extendTheme spec --- packages/mui-joy/src/styles/extendTheme.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/mui-joy/src/styles/extendTheme.spec.ts b/packages/mui-joy/src/styles/extendTheme.spec.ts index 21b64c91f87a0e..6508292a435d6d 100644 --- a/packages/mui-joy/src/styles/extendTheme.spec.ts +++ b/packages/mui-joy/src/styles/extendTheme.spec.ts @@ -1120,6 +1120,10 @@ extendTheme({ expectType, typeof ownerState>(ownerState); return {}; }, + clickAway: ({ ownerState }) => { + expectType, typeof ownerState>(ownerState); + return {}; + }, }, }, JoyStack: { From 43964cd30c358e26088adaa570ccf3640730973d Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 6 Oct 2023 14:32:12 +0700 Subject: [PATCH 43/58] update demos --- .../snackbar/SnackbarInvertedColors.tsx | 39 +++++++++++++---- .../snackbar/SnackbarWithDecorators.tsx | 43 ++++++------------- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx b/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx index 40b684fe98e368..7981fdf74f3b13 100644 --- a/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx +++ b/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx @@ -1,28 +1,49 @@ import * as React from 'react'; import Snackbar from '@mui/joy/Snackbar'; import Button from '@mui/joy/Button'; +import Stack from '@mui/joy/Stack'; +import Typography from '@mui/joy/Typography'; export default function SnackbarInvertedColors() { const [open, setOpen] = React.useState(false); - const handleOpen = () => setOpen(true); - - const handleClose = () => setOpen(false); - return ( - setOpen(false)} + anchorOrigin={{ vertical: 'top', horizontal: 'center' }} + sx={(theme) => ({ + background: `linear-gradient(45deg, ${theme.palette.primary[600]} 30%, ${theme.palette.primary[500]} 90%})`, + maxWidth: 360, + })} > - I love snacks +
+ Hey, Wait!! + + Are you sure, you want to leave this page without confirming your order? + + + + + +
); diff --git a/docs/data/joy/components/snackbar/SnackbarWithDecorators.tsx b/docs/data/joy/components/snackbar/SnackbarWithDecorators.tsx index 479257b83349e4..99d1da756c28f0 100644 --- a/docs/data/joy/components/snackbar/SnackbarWithDecorators.tsx +++ b/docs/data/joy/components/snackbar/SnackbarWithDecorators.tsx @@ -3,46 +3,31 @@ import Button from '@mui/joy/Button'; import Snackbar from '@mui/joy/Snackbar'; import PlaylistAddCheckCircleRoundedIcon from '@mui/icons-material/PlaylistAddCheckCircleRounded'; -interface CloseButtonProps { - onClose: () => React.Dispatch>; -} - -function CloseButton(props: CloseButtonProps) { - const { onClose } = props; - return ( - - ); -} - export default function SnackbarWithDecorators() { const [open, setOpen] = React.useState(false); - const handleOpen = () => setOpen(true); - - const handleClose = () => setOpen(false); - return ( - setOpen(false)} anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} - slots={{ - startDecorator: PlaylistAddCheckCircleRoundedIcon, - endDecorator: CloseButton, - }} - slotProps={{ - endDecorator: { - onClose: handleClose, - }, - }} + startDecorator={} + endDecorator={ + + } > Your message was sent successfully. From 3d2ce7451d9413ebf0ee0eb20b403a5ae397ddf8 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 6 Oct 2023 14:39:25 +0700 Subject: [PATCH 44/58] add ts demos --- .../components/snackbar/PositionedSnackbar.js | 26 ++++-- .../snackbar/SnackbarCloseReason.tsx | 80 +++++++++++++++++++ .../components/snackbar/SnackbarColors.tsx | 55 +++++++++++++ .../snackbar/SnackbarHideDuration.tsx | 76 ++++++++++++++++++ .../snackbar/SnackbarInvertedColors.js | 39 ++++++--- .../SnackbarInvertedColors.tsx.preview | 15 ---- .../joy/components/snackbar/SnackbarSizes.tsx | 59 ++++++++++++++ .../components/snackbar/SnackbarVariants.tsx | 66 +++++++++++++++ .../snackbar/SnackbarWithDecorators.js | 44 ++++------ 9 files changed, 400 insertions(+), 60 deletions(-) create mode 100644 docs/data/joy/components/snackbar/SnackbarCloseReason.tsx create mode 100644 docs/data/joy/components/snackbar/SnackbarColors.tsx create mode 100644 docs/data/joy/components/snackbar/SnackbarHideDuration.tsx delete mode 100644 docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx.preview create mode 100644 docs/data/joy/components/snackbar/SnackbarSizes.tsx create mode 100644 docs/data/joy/components/snackbar/SnackbarVariants.tsx diff --git a/docs/data/joy/components/snackbar/PositionedSnackbar.js b/docs/data/joy/components/snackbar/PositionedSnackbar.js index 2dc18db78df629..da5ad2379fbc40 100644 --- a/docs/data/joy/components/snackbar/PositionedSnackbar.js +++ b/docs/data/joy/components/snackbar/PositionedSnackbar.js @@ -3,6 +3,12 @@ import Grid from '@mui/joy/Grid'; import Box from '@mui/joy/Box'; import Button from '@mui/joy/Button'; import Snackbar from '@mui/joy/Snackbar'; +import NorthWestIcon from '@mui/icons-material/NorthWest'; +import NorthEastIcon from '@mui/icons-material/NorthEast'; +import NorthIcon from '@mui/icons-material/North'; +import SouthIcon from '@mui/icons-material/South'; +import SouthEastIcon from '@mui/icons-material/SouthEast'; +import SouthWestIcon from '@mui/icons-material/SouthWest'; export default function PositionedSnackbar() { const [state, setState] = React.useState({ @@ -25,51 +31,59 @@ export default function PositionedSnackbar() { diff --git a/docs/data/joy/components/snackbar/SnackbarCloseReason.tsx b/docs/data/joy/components/snackbar/SnackbarCloseReason.tsx new file mode 100644 index 00000000000000..ae4bcbc27fce2c --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarCloseReason.tsx @@ -0,0 +1,80 @@ +import * as React from 'react'; +import Button from '@mui/joy/Button'; +import List from '@mui/joy/List'; +import ListItem from '@mui/joy/ListItem'; +import Typography from '@mui/joy/Typography'; +import Stack from '@mui/joy/Stack'; +import Snackbar, { SnackbarCloseReason } from '@mui/joy/Snackbar'; +import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; + +export default function SnackbarCloseReason() { + const [open, setOpen] = React.useState(false); + const [reasons, setReasons] = React.useState([]); + React.useEffect(() => { + if ( + (['timeout', 'clickaway', 'escapeKeyDown'] as const).every((item) => + reasons.includes(item), + ) + ) { + setOpen(false); + } + }, [reasons]); + return ( +
+ + { + // @ts-ignore + setReasons((prev) => [...new Set([...prev, reason])]); + }} + onUnmount={() => { + setReasons([]); + }} + sx={{ minWidth: 360 }} + > + + + To close this snackbar, you have to: + + + + {reasons.includes('timeout') ? ( + + ) : ( + + )}{' '} + Wait for 3 seconds. + + + {reasons.includes('clickaway') ? ( + + ) : ( + + )}{' '} + Click outside of the snackbar. + + + {reasons.includes('escapeKeyDown') ? ( + + ) : ( + + )}{' '} + Press ESC key. + + + + +
+ ); +} diff --git a/docs/data/joy/components/snackbar/SnackbarColors.tsx b/docs/data/joy/components/snackbar/SnackbarColors.tsx new file mode 100644 index 00000000000000..79a0f8faea4ab6 --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarColors.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; +import Button from '@mui/joy/Button'; +import Stack from '@mui/joy/Stack'; +import Snackbar, { SnackbarProps } from '@mui/joy/Snackbar'; + +export default function SnackbarColors() { + const [open, setOpen] = React.useState(false); + const [color, setColor] = React.useState('neutral'); + return ( + + {(['primary', 'neutral', 'danger', 'success', 'warning'] as const).map( + (color) => ( + + ), + )} + {(['plain', 'outlined', 'soft', 'solid'] as const).map((variant, index) => ( + { + if (reason === 'clickaway') { + return; + } + return setOpen(false); + }} + > + {variant} snackbar with {color} color. + + ))} + + ); +} diff --git a/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx b/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx new file mode 100644 index 00000000000000..efdc578b7facb0 --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx @@ -0,0 +1,76 @@ +import * as React from 'react'; +import Button from '@mui/joy/Button'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Input from '@mui/joy/Input'; +import Stack from '@mui/joy/Stack'; +import Snackbar from '@mui/joy/Snackbar'; + +export default function SnackbarHideDuration() { + const [open, setOpen] = React.useState(false); + const [duration, setDuration] = React.useState(); + const [left, setLeft] = React.useState(); + const timer = React.useRef(); + const countdown = () => { + timer.current = window.setInterval(() => { + setLeft((prev) => (prev === undefined ? prev : prev - 16)); + }, 16); // 60fps = 16ms / frame + }; + React.useEffect(() => { + if (open && duration !== undefined && duration > 0) { + setLeft(duration); + countdown(); + } else { + setLeft(0); + window.clearInterval(timer.current); + } + }, [open]); + const handlePause = () => { + window.clearInterval(timer.current); + }; + const handleResume = () => { + countdown(); + }; + return ( + + + Auto Hide Duration (ms) + { + setDuration(event.target.valueAsNumber || undefined); + }} + /> + + + setLeft(undefined)} + open={open} + onClose={() => { + setOpen(false); + }} + > + This snackbar will{' '} + {left ? `disappear in ${left}ms` : `not disappear until you click away`}. + + + ); +} diff --git a/docs/data/joy/components/snackbar/SnackbarInvertedColors.js b/docs/data/joy/components/snackbar/SnackbarInvertedColors.js index 40b684fe98e368..7981fdf74f3b13 100644 --- a/docs/data/joy/components/snackbar/SnackbarInvertedColors.js +++ b/docs/data/joy/components/snackbar/SnackbarInvertedColors.js @@ -1,28 +1,49 @@ import * as React from 'react'; import Snackbar from '@mui/joy/Snackbar'; import Button from '@mui/joy/Button'; +import Stack from '@mui/joy/Stack'; +import Typography from '@mui/joy/Typography'; export default function SnackbarInvertedColors() { const [open, setOpen] = React.useState(false); - const handleOpen = () => setOpen(true); - - const handleClose = () => setOpen(false); - return ( - setOpen(false)} + anchorOrigin={{ vertical: 'top', horizontal: 'center' }} + sx={(theme) => ({ + background: `linear-gradient(45deg, ${theme.palette.primary[600]} 30%, ${theme.palette.primary[500]} 90%})`, + maxWidth: 360, + })} > - I love snacks +
+ Hey, Wait!! + + Are you sure, you want to leave this page without confirming your order? + + + + + +
); diff --git a/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx.preview b/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx.preview deleted file mode 100644 index 782ab48b8c6d7a..00000000000000 --- a/docs/data/joy/components/snackbar/SnackbarInvertedColors.tsx.preview +++ /dev/null @@ -1,15 +0,0 @@ - - - - I love snacks - - \ No newline at end of file diff --git a/docs/data/joy/components/snackbar/SnackbarSizes.tsx b/docs/data/joy/components/snackbar/SnackbarSizes.tsx new file mode 100644 index 00000000000000..f70aac99236b6b --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarSizes.tsx @@ -0,0 +1,59 @@ +import * as React from 'react'; +import Button from '@mui/joy/Button'; +import Stack from '@mui/joy/Stack'; +import Snackbar, { SnackbarProps } from '@mui/joy/Snackbar'; + +export default function SnackbarSizes() { + const [open, setOpen] = React.useState(false); + const [size, setSize] = React.useState('md'); + return ( + + + + + { + if (reason === 'clickaway') { + return; + } + return setOpen(false); + }} + > + A snackbar with {size} size. + + + ); +} diff --git a/docs/data/joy/components/snackbar/SnackbarVariants.tsx b/docs/data/joy/components/snackbar/SnackbarVariants.tsx new file mode 100644 index 00000000000000..2d77d32662747b --- /dev/null +++ b/docs/data/joy/components/snackbar/SnackbarVariants.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import Button from '@mui/joy/Button'; +import Stack from '@mui/joy/Stack'; +import Snackbar, { SnackbarProps } from '@mui/joy/Snackbar'; + +export default function SnackbarVariants() { + const [open, setOpen] = React.useState(false); + const [variant, setVariant] = React.useState('outlined'); + return ( + + + + + + { + if (reason === 'clickaway') { + return; + } + return setOpen(false); + }} + > + A snackbar with {variant} variant. + + + ); +} diff --git a/docs/data/joy/components/snackbar/SnackbarWithDecorators.js b/docs/data/joy/components/snackbar/SnackbarWithDecorators.js index b7dda9a7d77f8e..99d1da756c28f0 100644 --- a/docs/data/joy/components/snackbar/SnackbarWithDecorators.js +++ b/docs/data/joy/components/snackbar/SnackbarWithDecorators.js @@ -1,49 +1,33 @@ import * as React from 'react'; -import PropTypes from 'prop-types'; import Button from '@mui/joy/Button'; import Snackbar from '@mui/joy/Snackbar'; import PlaylistAddCheckCircleRoundedIcon from '@mui/icons-material/PlaylistAddCheckCircleRounded'; -function CloseButton(props) { - const { onClose } = props; - return ( - - ); -} - -CloseButton.propTypes = { - onClose: PropTypes.func.isRequired, -}; - export default function SnackbarWithDecorators() { const [open, setOpen] = React.useState(false); - const handleOpen = () => setOpen(true); - - const handleClose = () => setOpen(false); - return ( - setOpen(false)} anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} - slots={{ - startDecorator: PlaylistAddCheckCircleRoundedIcon, - endDecorator: CloseButton, - }} - slotProps={{ - endDecorator: { - onClose: handleClose, - }, - }} + startDecorator={} + endDecorator={ + + } > Your message was sent successfully. From ae9c8dfdddfc377550e345fed6704a4fb4329a43 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 6 Oct 2023 14:46:44 +0700 Subject: [PATCH 45/58] run scripts --- .../joy/components/snackbar/SnackbarColors.js | 5 +-- .../snackbar/SnackbarHideDuration.js | 20 +++------- .../joy/components/snackbar/SnackbarSizes.js | 5 +-- .../components/snackbar/SnackbarVariants.js | 3 +- docs/pages/joy-ui/api/snackbar.json | 20 +++++++--- .../api-docs-joy/snackbar/snackbar.json | 9 +++-- packages/mui-joy/src/Snackbar/Snackbar.tsx | 40 +++++++++++++++---- 7 files changed, 63 insertions(+), 39 deletions(-) diff --git a/docs/data/joy/components/snackbar/SnackbarColors.js b/docs/data/joy/components/snackbar/SnackbarColors.js index a649f00891471d..503bf1eb75b311 100644 --- a/docs/data/joy/components/snackbar/SnackbarColors.js +++ b/docs/data/joy/components/snackbar/SnackbarColors.js @@ -1,10 +1,9 @@ import * as React from 'react'; -import Box from '@mui/joy/Box'; import Button from '@mui/joy/Button'; import Stack from '@mui/joy/Stack'; -import Snackbar, { SnackbarOrigin } from '@mui/joy/Snackbar'; +import Snackbar from '@mui/joy/Snackbar'; -export default function SnackbarVariants() { +export default function SnackbarColors() { const [open, setOpen] = React.useState(false); const [color, setColor] = React.useState('neutral'); return ( diff --git a/docs/data/joy/components/snackbar/SnackbarHideDuration.js b/docs/data/joy/components/snackbar/SnackbarHideDuration.js index 211f63d377027a..944dd1aeb1e740 100644 --- a/docs/data/joy/components/snackbar/SnackbarHideDuration.js +++ b/docs/data/joy/components/snackbar/SnackbarHideDuration.js @@ -1,37 +1,28 @@ import * as React from 'react'; -import Box from '@mui/joy/Box'; import Button from '@mui/joy/Button'; import FormControl from '@mui/joy/FormControl'; import FormLabel from '@mui/joy/FormLabel'; import Input from '@mui/joy/Input'; import Stack from '@mui/joy/Stack'; -import Snackbar, { SnackbarOrigin } from '@mui/joy/Snackbar'; +import Snackbar from '@mui/joy/Snackbar'; -export default function SnackbarVariants() { +export default function SnackbarHideDuration() { const [open, setOpen] = React.useState(false); const [duration, setDuration] = React.useState(); const [left, setLeft] = React.useState(); const timer = React.useRef(); const countdown = () => { - timer.current = setInterval(() => { - setLeft((prev) => prev - 16); + timer.current = window.setInterval(() => { + setLeft((prev) => (prev === undefined ? prev : prev - 16)); }, 16); // 60fps = 16ms / frame }; React.useEffect(() => { - if (open && duration > 0) { + if (open && duration !== undefined && duration > 0) { setLeft(duration); countdown(); } else { setLeft(0); window.clearInterval(timer.current); - - // account for the animation delay (~300ms) - const timeout = setTimeout(() => { - setLeft(undefined); - }, 300); - return () => { - clearTimeout(timeout); - }; } }, [open]); const handlePause = () => { @@ -71,6 +62,7 @@ export default function SnackbarVariants() { onMouseLeave={handleResume} onFocus={handlePause} onBlur={handleResume} + onUnmount={() => setLeft(undefined)} open={open} onClose={() => { setOpen(false); diff --git a/docs/data/joy/components/snackbar/SnackbarSizes.js b/docs/data/joy/components/snackbar/SnackbarSizes.js index c8ca04b64b2c19..84233e6504e926 100644 --- a/docs/data/joy/components/snackbar/SnackbarSizes.js +++ b/docs/data/joy/components/snackbar/SnackbarSizes.js @@ -1,10 +1,9 @@ import * as React from 'react'; -import Box from '@mui/joy/Box'; import Button from '@mui/joy/Button'; import Stack from '@mui/joy/Stack'; -import Snackbar, { SnackbarOrigin } from '@mui/joy/Snackbar'; +import Snackbar from '@mui/joy/Snackbar'; -export default function SnackbarVariants() { +export default function SnackbarSizes() { const [open, setOpen] = React.useState(false); const [size, setSize] = React.useState('md'); return ( diff --git a/docs/data/joy/components/snackbar/SnackbarVariants.js b/docs/data/joy/components/snackbar/SnackbarVariants.js index 3fde203c05c369..78b453f866162f 100644 --- a/docs/data/joy/components/snackbar/SnackbarVariants.js +++ b/docs/data/joy/components/snackbar/SnackbarVariants.js @@ -1,8 +1,7 @@ import * as React from 'react'; -import Box from '@mui/joy/Box'; import Button from '@mui/joy/Button'; import Stack from '@mui/joy/Stack'; -import Snackbar, { SnackbarOrigin } from '@mui/joy/Snackbar'; +import Snackbar from '@mui/joy/Snackbar'; export default function SnackbarVariants() { const [open, setOpen] = React.useState(false); diff --git a/docs/pages/joy-ui/api/snackbar.json b/docs/pages/joy-ui/api/snackbar.json index 0ecb76d53d9d71..1f2f5bca61e146 100644 --- a/docs/pages/joy-ui/api/snackbar.json +++ b/docs/pages/joy-ui/api/snackbar.json @@ -1,16 +1,14 @@ { "props": { - "open": { "type": { "name": "bool" }, "required": true }, "anchorOrigin": { "type": { "name": "shape", "description": "{ horizontal: 'center'
| 'left'
| 'right', vertical: 'bottom'
| 'top' }" }, - "default": "{ vertical: 'bottom', horizontal: 'left' }" + "default": "{ vertical: 'bottom', horizontal: 'center' }" }, - "animationDuration": { "type": { "name": "number" }, "default": "500" }, + "animationDuration": { "type": { "name": "number" }, "default": "300" }, "autoHideDuration": { "type": { "name": "number" }, "default": "null" }, - "ClickAwayListenerProps": { "type": { "name": "object" } }, "color": { "type": { "name": "enum", @@ -21,6 +19,7 @@ }, "component": { "type": { "name": "elementType" } }, "disableWindowBlurListener": { "type": { "name": "bool" }, "default": "false" }, + "endDecorator": { "type": { "name": "node" } }, "invertedColors": { "type": { "name": "bool" }, "default": "false" }, "key": { "type": { "name": "custom", "description": "any" } }, "onClose": { @@ -30,6 +29,8 @@ "describedArgs": ["event", "reason"] } }, + "onUnmount": { "type": { "name": "func" } }, + "open": { "type": { "name": "bool" } }, "resumeHideDuration": { "type": { "name": "number" } }, "size": { "type": { "name": "enum", "description": "'sm'
| 'md'
| 'lg'" }, @@ -39,18 +40,19 @@ "slotProps": { "type": { "name": "shape", - "description": "{ endDecorator?: func
| object, root?: func
| object, startDecorator?: func
| object }" + "description": "{ clickAway?: func
| { children: element, disableReactTree?: bool, mouseEvent?: 'onClick'
| 'onMouseDown'
| 'onMouseUp'
| 'onPointerDown'
| 'onPointerUp'
| false, onClickAway: func, touchEvent?: 'onTouchEnd'
| 'onTouchStart'
| false }, endDecorator?: func
| object, root?: func
| object, startDecorator?: func
| object }" }, "default": "{}" }, "slots": { "type": { "name": "shape", - "description": "{ endDecorator?: elementType, root?: elementType, startDecorator?: elementType }" + "description": "{ clickAway?: elementType, endDecorator?: elementType, root?: elementType, startDecorator?: elementType }" }, "default": "{}", "additionalInfo": { "slotsApi": true } }, + "startDecorator": { "type": { "name": "node" } }, "sx": { "type": { "name": "union", @@ -88,6 +90,12 @@ "description": "The component that renders the end decorator.", "default": "'span'", "class": ".MuiSnackbar-endDecorator" + }, + { + "name": "clickAway", + "description": "The component that renders the click away.", + "default": "ClickAwayListener", + "class": null } ], "classes": { diff --git a/docs/translations/api-docs-joy/snackbar/snackbar.json b/docs/translations/api-docs-joy/snackbar/snackbar.json index cad8fdd678bd73..8fd52978670428 100644 --- a/docs/translations/api-docs-joy/snackbar/snackbar.json +++ b/docs/translations/api-docs-joy/snackbar/snackbar.json @@ -10,9 +10,6 @@ "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." }, - "ClickAwayListenerProps": { - "description": "Props applied to the ClickAwayListener element." - }, "color": { "description": "The color of the component. It supports those theme colors that make sense for this component." }, @@ -22,6 +19,7 @@ "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." }, @@ -35,6 +33,7 @@ "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." @@ -42,6 +41,7 @@ "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." }, @@ -155,6 +155,7 @@ "slotDescriptions": { "root": "The component that renders the root.", "startDecorator": "The component that renders the start decorator.", - "endDecorator": "The component that renders the end decorator." + "endDecorator": "The component that renders the end decorator.", + "clickAway": "The component that renders the click away." } } diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 9c71e9fab3f7a8..f401a6ef8f24a9 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -298,7 +298,7 @@ Snackbar.propTypes /* remove-proptypes */ = { * The anchor of the `Snackbar`. * On smaller screens, the component grows to occupy all the available width, * the horizontal alignment is ignored. - * @default { vertical: 'bottom', horizontal: 'left' } + * @default { vertical: 'bottom', horizontal: 'center' } */ anchorOrigin: PropTypes.shape({ horizontal: PropTypes.oneOf(['center', 'left', 'right']).isRequired, @@ -310,7 +310,7 @@ Snackbar.propTypes /* remove-proptypes */ = { * 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. - * @default 500 + * @default 300 */ animationDuration: PropTypes.number, /** @@ -329,10 +329,6 @@ Snackbar.propTypes /* remove-proptypes */ = { * @ignore */ className: PropTypes.string, - /** - * Props applied to the `ClickAwayListener` element. - */ - ClickAwayListenerProps: PropTypes.object, /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' @@ -348,6 +344,10 @@ Snackbar.propTypes /* remove-proptypes */ = { * @default false */ disableWindowBlurListener: PropTypes.bool, + /** + * Element placed after the children. + */ + endDecorator: PropTypes.node, /** * If `true`, the children with an implicit color prop invert their colors to match the component's variant and color. * @default false @@ -387,10 +387,14 @@ Snackbar.propTypes /* remove-proptypes */ = { * @ignore */ onMouseLeave: PropTypes.func, + /** + * A callback fired when the component is about to be unmounted. + */ + onUnmount: PropTypes.func, /** * If `true`, the component is shown. */ - open: PropTypes.bool.isRequired, + open: PropTypes.bool, /** * The number of milliseconds to wait before dismissing after user interaction. * If `autoHideDuration` prop isn't specified, it does nothing. @@ -408,6 +412,23 @@ Snackbar.propTypes /* remove-proptypes */ = { * @default {} */ slotProps: PropTypes.shape({ + clickAway: 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.isRequired, + touchEvent: PropTypes.oneOf(['onTouchEnd', 'onTouchStart', false]), + }), + ]), endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), @@ -417,10 +438,15 @@ Snackbar.propTypes /* remove-proptypes */ = { * @default {} */ slots: PropTypes.shape({ + clickAway: PropTypes.elementType, endDecorator: PropTypes.elementType, root: PropTypes.elementType, startDecorator: PropTypes.elementType, }), + /** + * Element placed before the children. + */ + startDecorator: PropTypes.node, /** * The system prop that allows defining system overrides as well as additional CSS styles. */ From e40be2a3c39f88dc97040d94f55d120da9df2054 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 6 Oct 2023 14:22:52 +0530 Subject: [PATCH 46/58] add describeConformance tests --- .../mui-joy/src/Snackbar/Snackbar.test.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.test.tsx b/packages/mui-joy/src/Snackbar/Snackbar.test.tsx index e69de29bb2d1d6..76e406b6e8d6be 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.test.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.test.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { spy } from 'sinon'; +import { describeConformance, createRenderer, fireEvent } from '@mui-internal/test-utils'; +import Snackbar, { snackbarClasses as classes } from '@mui/joy/Snackbar'; +import { ThemeProvider } from '@mui/joy/styles'; + +describe('Joy ', () => { + const { render } = createRenderer(); + + describeConformance( + + Hello World! + , + () => ({ + render, + classes, + ThemeProvider, + muiName: 'JoySnackbar', + refInstanceof: window.HTMLDivElement, + slots: { + root: { expectedClassName: classes.root }, + startDecorator: { expectedClassName: classes.startDecorator }, + endDecorator: { expectedClassName: classes.endDecorator }, + }, + skip: ['propsSpread', 'componentsProp', 'classesRoot'], + }), + ); +}); From b9080f5e49c9a659720310d249eed894d674429c Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 6 Oct 2023 14:31:17 +0530 Subject: [PATCH 47/58] fix duplicate declaration --- docs/data/joy/components/snackbar/SnackbarCloseReason.js | 9 ++------- .../data/joy/components/snackbar/SnackbarCloseReason.tsx | 6 ++++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/data/joy/components/snackbar/SnackbarCloseReason.js b/docs/data/joy/components/snackbar/SnackbarCloseReason.js index c7c5d911e70e13..946bfa482c30d2 100644 --- a/docs/data/joy/components/snackbar/SnackbarCloseReason.js +++ b/docs/data/joy/components/snackbar/SnackbarCloseReason.js @@ -1,21 +1,16 @@ import * as React from 'react'; -import Box from '@mui/joy/Box'; import Button from '@mui/joy/Button'; -import FormControl from '@mui/joy/FormControl'; -import FormLabel from '@mui/joy/FormLabel'; import List from '@mui/joy/List'; import ListItem from '@mui/joy/ListItem'; -import Input from '@mui/joy/Input'; import Typography from '@mui/joy/Typography'; import Stack from '@mui/joy/Stack'; -import Snackbar, { SnackbarOrigin } from '@mui/joy/Snackbar'; +import Snackbar from '@mui/joy/Snackbar'; import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; import CheckBoxIcon from '@mui/icons-material/CheckBox'; -export default function SnackbarVariants() { +export default function SnackbarCloseReason() { const [open, setOpen] = React.useState(false); const [reasons, setReasons] = React.useState([]); - console.log('reasons', reasons); React.useEffect(() => { if ( ['timeout', 'clickaway', 'escapeKeyDown'].every((item) => diff --git a/docs/data/joy/components/snackbar/SnackbarCloseReason.tsx b/docs/data/joy/components/snackbar/SnackbarCloseReason.tsx index ae4bcbc27fce2c..91709fbe4fde16 100644 --- a/docs/data/joy/components/snackbar/SnackbarCloseReason.tsx +++ b/docs/data/joy/components/snackbar/SnackbarCloseReason.tsx @@ -4,13 +4,15 @@ import List from '@mui/joy/List'; import ListItem from '@mui/joy/ListItem'; import Typography from '@mui/joy/Typography'; import Stack from '@mui/joy/Stack'; -import Snackbar, { SnackbarCloseReason } from '@mui/joy/Snackbar'; +import Snackbar, { + SnackbarCloseReason as SnackbarCloseReasonType, +} from '@mui/joy/Snackbar'; import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; import CheckBoxIcon from '@mui/icons-material/CheckBox'; export default function SnackbarCloseReason() { const [open, setOpen] = React.useState(false); - const [reasons, setReasons] = React.useState([]); + const [reasons, setReasons] = React.useState([]); React.useEffect(() => { if ( (['timeout', 'clickaway', 'escapeKeyDown'] as const).every((item) => From 4e1243a0482d979dfe5a911c128ff78c75958cf1 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 6 Oct 2023 14:42:35 +0530 Subject: [PATCH 48/58] fix lint --- docs/data/joy/components/snackbar/SnackbarColors.js | 12 ++++++------ docs/data/joy/components/snackbar/SnackbarColors.tsx | 12 ++++++------ .../joy/components/snackbar/SnackbarHideDuration.js | 2 +- .../joy/components/snackbar/SnackbarHideDuration.tsx | 2 +- docs/data/joy/components/snackbar/SnackbarSizes.js | 2 +- docs/data/joy/components/snackbar/SnackbarSizes.tsx | 2 +- .../data/joy/components/snackbar/SnackbarVariants.js | 2 +- .../joy/components/snackbar/SnackbarVariants.tsx | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/data/joy/components/snackbar/SnackbarColors.js b/docs/data/joy/components/snackbar/SnackbarColors.js index 503bf1eb75b311..2b65e7f2c60e74 100644 --- a/docs/data/joy/components/snackbar/SnackbarColors.js +++ b/docs/data/joy/components/snackbar/SnackbarColors.js @@ -8,17 +8,17 @@ export default function SnackbarColors() { const [color, setColor] = React.useState('neutral'); return ( - {['primary', 'neutral', 'danger', 'success', 'warning'].map((color) => ( + {['primary', 'neutral', 'danger', 'success', 'warning'].map((currentColor) => ( ))} {['plain', 'outlined', 'soft', 'solid'].map((variant, index) => ( @@ -40,7 +40,7 @@ export default function SnackbarColors() { if (reason === 'clickaway') { return; } - return setOpen(false); + setOpen(false); }} > {variant} snackbar with {color} color. diff --git a/docs/data/joy/components/snackbar/SnackbarColors.tsx b/docs/data/joy/components/snackbar/SnackbarColors.tsx index 79a0f8faea4ab6..4e0440a48b35bb 100644 --- a/docs/data/joy/components/snackbar/SnackbarColors.tsx +++ b/docs/data/joy/components/snackbar/SnackbarColors.tsx @@ -9,17 +9,17 @@ export default function SnackbarColors() { return ( {(['primary', 'neutral', 'danger', 'success', 'warning'] as const).map( - (color) => ( + (currentColor) => ( ), )} @@ -44,7 +44,7 @@ export default function SnackbarColors() { if (reason === 'clickaway') { return; } - return setOpen(false); + setOpen(false); }} > {variant} snackbar with {color} color. diff --git a/docs/data/joy/components/snackbar/SnackbarHideDuration.js b/docs/data/joy/components/snackbar/SnackbarHideDuration.js index 944dd1aeb1e740..1bb23c9632a862 100644 --- a/docs/data/joy/components/snackbar/SnackbarHideDuration.js +++ b/docs/data/joy/components/snackbar/SnackbarHideDuration.js @@ -24,7 +24,7 @@ export default function SnackbarHideDuration() { setLeft(0); window.clearInterval(timer.current); } - }, [open]); + }, [open, duration]); const handlePause = () => { window.clearInterval(timer.current); }; diff --git a/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx b/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx index efdc578b7facb0..ecae082cb59625 100644 --- a/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx +++ b/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx @@ -24,7 +24,7 @@ export default function SnackbarHideDuration() { setLeft(0); window.clearInterval(timer.current); } - }, [open]); + }, [open, duration]); const handlePause = () => { window.clearInterval(timer.current); }; diff --git a/docs/data/joy/components/snackbar/SnackbarSizes.js b/docs/data/joy/components/snackbar/SnackbarSizes.js index 84233e6504e926..3eb71979e16564 100644 --- a/docs/data/joy/components/snackbar/SnackbarSizes.js +++ b/docs/data/joy/components/snackbar/SnackbarSizes.js @@ -49,7 +49,7 @@ export default function SnackbarSizes() { if (reason === 'clickaway') { return; } - return setOpen(false); + setOpen(false); }} > A snackbar with {size} size. diff --git a/docs/data/joy/components/snackbar/SnackbarSizes.tsx b/docs/data/joy/components/snackbar/SnackbarSizes.tsx index f70aac99236b6b..0c211bd82dcf3e 100644 --- a/docs/data/joy/components/snackbar/SnackbarSizes.tsx +++ b/docs/data/joy/components/snackbar/SnackbarSizes.tsx @@ -49,7 +49,7 @@ export default function SnackbarSizes() { if (reason === 'clickaway') { return; } - return setOpen(false); + setOpen(false); }} > A snackbar with {size} size. diff --git a/docs/data/joy/components/snackbar/SnackbarVariants.js b/docs/data/joy/components/snackbar/SnackbarVariants.js index 78b453f866162f..de7d0346ceda84 100644 --- a/docs/data/joy/components/snackbar/SnackbarVariants.js +++ b/docs/data/joy/components/snackbar/SnackbarVariants.js @@ -56,7 +56,7 @@ export default function SnackbarVariants() { if (reason === 'clickaway') { return; } - return setOpen(false); + setOpen(false); }} > A snackbar with {variant} variant. diff --git a/docs/data/joy/components/snackbar/SnackbarVariants.tsx b/docs/data/joy/components/snackbar/SnackbarVariants.tsx index 2d77d32662747b..b1163c36ae7d0f 100644 --- a/docs/data/joy/components/snackbar/SnackbarVariants.tsx +++ b/docs/data/joy/components/snackbar/SnackbarVariants.tsx @@ -56,7 +56,7 @@ export default function SnackbarVariants() { if (reason === 'clickaway') { return; } - return setOpen(false); + setOpen(false); }} > A snackbar with {variant} variant. From bb24b3fb21934614590e7a4d92a9dc7dbb9f08ef Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 6 Oct 2023 14:54:54 +0530 Subject: [PATCH 49/58] fix CI --- docs/pages/joy-ui/api/snackbar.json | 2 +- packages/mui-joy/src/Snackbar/Snackbar.test.tsx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/pages/joy-ui/api/snackbar.json b/docs/pages/joy-ui/api/snackbar.json index e0e8bc30a637e6..b09074b2e61a1b 100644 --- a/docs/pages/joy-ui/api/snackbar.json +++ b/docs/pages/joy-ui/api/snackbar.json @@ -121,7 +121,7 @@ ], "globalClasses": {} }, - "spread": true, + "spread": false, "themeDefaultProps": true, "muiName": "JoySnackbar", "forwardsRefTo": "HTMLDivElement", diff --git a/packages/mui-joy/src/Snackbar/Snackbar.test.tsx b/packages/mui-joy/src/Snackbar/Snackbar.test.tsx index 76e406b6e8d6be..d2f3e4284aa535 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.test.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.test.tsx @@ -1,7 +1,5 @@ import * as React from 'react'; -import { expect } from 'chai'; -import { spy } from 'sinon'; -import { describeConformance, createRenderer, fireEvent } from '@mui-internal/test-utils'; +import { describeConformance, createRenderer } from '@mui-internal/test-utils'; import Snackbar, { snackbarClasses as classes } from '@mui/joy/Snackbar'; import { ThemeProvider } from '@mui/joy/styles'; @@ -18,6 +16,7 @@ describe('Joy ', () => { ThemeProvider, muiName: 'JoySnackbar', refInstanceof: window.HTMLDivElement, + testVariantProps: { variant: 'solid' }, slots: { root: { expectedClassName: classes.root }, startDecorator: { expectedClassName: classes.startDecorator }, From 48312914cf4eac094184fa53916b2325db770212 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 6 Oct 2023 17:29:45 +0530 Subject: [PATCH 50/58] add more tests --- .../mui-joy/src/Snackbar/Snackbar.test.tsx | 428 +++++++++++++++++- 1 file changed, 426 insertions(+), 2 deletions(-) diff --git a/packages/mui-joy/src/Snackbar/Snackbar.test.tsx b/packages/mui-joy/src/Snackbar/Snackbar.test.tsx index d2f3e4284aa535..b8c12d8535e237 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.test.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.test.tsx @@ -1,10 +1,28 @@ import * as React from 'react'; -import { describeConformance, createRenderer } from '@mui-internal/test-utils'; +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 ', () => { - const { render } = createRenderer(); + const { render: clientRender, clock } = createRenderer({ clock: 'fake' }); + + /** + * @type {typeof plainRender extends (...args: infer T) => any ? T : never} 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; + } describeConformance( @@ -25,4 +43,410 @@ describe('Joy ', () => { skip: ['propsSpread', 'componentsProp', 'classesRoot'], }), ); + + 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.equal('escapeKeyDown'); + }); + + it('can limit which Snackbars are closed when pressing Escape', () => { + const handleCloseA = spy((event) => event.preventDefault()); + const handleCloseB = spy(); + render( + + + Message A + + + Message B + + , + ); + + 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( + + Message + , + ); + + 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( + + Message + , + ); + + 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( + + Message + , + ); + + 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( + + Message + , + ); + + 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( + + Message + , + ); + + 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( + + Message + , + ); + + 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( + undo} + open + onBlur={handleBlur} + onFocus={handleFocus} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} + onClose={handleClose} + autoHideDuration={autoHideDuration} + > + 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( + undo} + open + onClose={handleClose} + autoHideDuration={autoHideDuration} + resumeHideDuration={resumeHideDuration} + > + 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( + undo} + open + onClose={handleClose} + autoHideDuration={autoHideDuration} + resumeHideDuration={resumeHideDuration} + > + 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( + undo} + open + onClose={handleClose} + autoHideDuration={autoHideDuration} + resumeHideDuration={resumeHideDuration} + > + 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!'); + }); + }); }); From 82568b7093b535281821377dcde8a775b2dccb47 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 13 Oct 2023 11:58:03 +0700 Subject: [PATCH 51/58] fix demos --- .../snackbar/CustomAnimatedSnackbar.js | 4 +- .../snackbar/CustomAnimatedSnackbar.tsx | 4 +- .../snackbar/SnackbarHideDuration.js | 56 ++++++++++--------- .../snackbar/SnackbarHideDuration.tsx | 56 ++++++++++--------- 4 files changed, 66 insertions(+), 54 deletions(-) diff --git a/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js index 400db7c05485c4..7b20091a88ef16 100644 --- a/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js +++ b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.js @@ -51,10 +51,10 @@ export default function CustomAnimatedSnackbar() { animationDuration={animationDuration} sx={{ ...(open && { - animation: `${inAnimation} ${animationDuration}ms`, + animation: `${inAnimation} ${animationDuration}ms forwards`, }), ...(!open && { - animation: `${outAnimation} ${animationDuration}ms`, + animation: `${outAnimation} ${animationDuration}ms forwards`, }), }} > diff --git a/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.tsx b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.tsx index 400db7c05485c4..7b20091a88ef16 100644 --- a/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.tsx +++ b/docs/data/joy/components/snackbar/CustomAnimatedSnackbar.tsx @@ -51,10 +51,10 @@ export default function CustomAnimatedSnackbar() { animationDuration={animationDuration} sx={{ ...(open && { - animation: `${inAnimation} ${animationDuration}ms`, + animation: `${inAnimation} ${animationDuration}ms forwards`, }), ...(!open && { - animation: `${outAnimation} ${animationDuration}ms`, + animation: `${outAnimation} ${animationDuration}ms forwards`, }), }} > diff --git a/docs/data/joy/components/snackbar/SnackbarHideDuration.js b/docs/data/joy/components/snackbar/SnackbarHideDuration.js index 1bb23c9632a862..2e4a73c0a9d1f7 100644 --- a/docs/data/joy/components/snackbar/SnackbarHideDuration.js +++ b/docs/data/joy/components/snackbar/SnackbarHideDuration.js @@ -13,7 +13,7 @@ export default function SnackbarHideDuration() { const timer = React.useRef(); const countdown = () => { timer.current = window.setInterval(() => { - setLeft((prev) => (prev === undefined ? prev : prev - 16)); + setLeft((prev) => (prev === undefined ? prev : Math.max(0, prev - 16))); }, 16); // 60fps = 16ms / frame }; React.useEffect(() => { @@ -21,7 +21,6 @@ export default function SnackbarHideDuration() { setLeft(duration); countdown(); } else { - setLeft(0); window.clearInterval(timer.current); } }, [open, duration]); @@ -32,27 +31,31 @@ export default function SnackbarHideDuration() { countdown(); }; return ( - - - Auto Hide Duration (ms) - { - setDuration(event.target.valueAsNumber || undefined); - }} - /> - - +
+ + + + Auto Hide Duration (ms) + + { + setDuration(event.target.valueAsNumber || undefined); + }} + /> + + + This snackbar will{' '} - {left ? `disappear in ${left}ms` : `not disappear until you click away`}. + {left !== undefined + ? `disappear in ${left}ms` + : `not disappear until you click away`} + . - +
); } diff --git a/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx b/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx index ecae082cb59625..6ef79b23395b3c 100644 --- a/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx +++ b/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx @@ -13,7 +13,7 @@ export default function SnackbarHideDuration() { const timer = React.useRef(); const countdown = () => { timer.current = window.setInterval(() => { - setLeft((prev) => (prev === undefined ? prev : prev - 16)); + setLeft((prev) => (prev === undefined ? prev : Math.max(0, prev - 16))); }, 16); // 60fps = 16ms / frame }; React.useEffect(() => { @@ -21,7 +21,6 @@ export default function SnackbarHideDuration() { setLeft(duration); countdown(); } else { - setLeft(0); window.clearInterval(timer.current); } }, [open, duration]); @@ -32,27 +31,31 @@ export default function SnackbarHideDuration() { countdown(); }; return ( - - - Auto Hide Duration (ms) - { - setDuration(event.target.valueAsNumber || undefined); - }} - /> - - +
+ + + + Auto Hide Duration (ms) + + { + setDuration(event.target.valueAsNumber || undefined); + }} + /> + + + This snackbar will{' '} - {left ? `disappear in ${left}ms` : `not disappear until you click away`}. + {left !== undefined + ? `disappear in ${left}ms` + : `not disappear until you click away`} + . - +
); } From 796f97a15606a38863c309a4e248cdef61f56b93 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 13 Oct 2023 12:52:37 +0700 Subject: [PATCH 52/58] fix: open at exiting bug --- .../joy/components/snackbar/SnackbarUsage.js | 2 ++ packages/mui-joy/src/Snackbar/Snackbar.tsx | 26 ++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/data/joy/components/snackbar/SnackbarUsage.js b/docs/data/joy/components/snackbar/SnackbarUsage.js index 48e67c460c9521..229c212bb07ce6 100644 --- a/docs/data/joy/components/snackbar/SnackbarUsage.js +++ b/docs/data/joy/components/snackbar/SnackbarUsage.js @@ -5,6 +5,7 @@ import IconButton from '@mui/joy/IconButton'; import Typography from '@mui/joy/Typography'; import Close from '@mui/icons-material/Close'; import JoyUsageDemo from 'docs/src/modules/components/JoyUsageDemo'; +import InfoOutlined from '@mui/icons-material/InfoOutlined'; export default function SnackbarUsage() { const [open, setOpen] = React.useState(false); @@ -55,6 +56,7 @@ export default function SnackbarUsage() { setOpen(false); }} + startDecorator={} endDecorator={ setOpen(false)}> diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index 9428fffc7e0336..c8ae6e25a9704e 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -36,7 +36,7 @@ const useUtilityClasses = (ownerState: SnackbarOwnerState) => { const enterAnimation = keyframes` 0% { - transform: translateX(var(--Snackbar-translateX, 0px)) translateY(calc(var(--_Snackbar-anchorBottom, 1) * (50% + var(--Snackbar-inset)))); + transform: translateX(var(--Snackbar-translateX, 0px)) translateY(calc(var(--_Snackbar-anchorBottom, 1) * 100%)); opacity: 0; } 50% { @@ -53,7 +53,7 @@ const exitAnimation = keyframes` opacity: 1; } 100% { - transform: translateX(var(--Snackbar-translateX, 0px)) translateY(calc(var(--_Snackbar-anchorBottom, 1) * (50% + var(--Snackbar-inset)))); + transform: translateX(var(--Snackbar-translateX, 0px)) translateY(calc(var(--_Snackbar-anchorBottom, 1) * 100%)); opacity: 0; } `; @@ -203,15 +203,29 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { variant = 'outlined', ...other } = props; + + // For animation const [exited, setExited] = React.useState(true); + + // `exiting` is a state for preventing click away event during exiting + // because there is a case where the Snackbar is exiting and the user open a Snackbar again. + // Without this state, the snack will open and close immediately since click away is called immediately after the click event. + const [exiting, setExiting] = React.useState(false); + + // To call a function when the component is about to be unmounted. + // Useful for preserving content in the Snackbar when undergoing exit animation. const unmountRef = React.useRef(onUnmount); unmountRef.current = onUnmount; + React.useEffect(() => { if (open) { + setExiting(false); setExited(false); } else { + setExiting(true); const timer = setTimeout(() => { setExited(true); + setExiting(false); unmountRef.current?.(); }, animationDuration); return () => { @@ -238,6 +252,12 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { const { getRootProps, onClickAway } = useSnackbar(ownerState); + const handleClickAway = (event: React.SyntheticEvent | Event) => { + if (!exiting) { + onClickAway(event); + } + }; + const externalForwardedProps = { ...other, component, slots, slotProps }; const [SlotRoot, rootProps] = useSlot('root', { @@ -272,7 +292,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { return ( Date: Mon, 23 Oct 2023 14:47:17 +0530 Subject: [PATCH 53/58] make open prop required --- docs/pages/joy-ui/api/snackbar.json | 2 +- packages/mui-joy/src/Snackbar/Snackbar.tsx | 2 +- packages/mui-joy/src/Snackbar/SnackbarProps.ts | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/pages/joy-ui/api/snackbar.json b/docs/pages/joy-ui/api/snackbar.json index b09074b2e61a1b..c973e3caea70bd 100644 --- a/docs/pages/joy-ui/api/snackbar.json +++ b/docs/pages/joy-ui/api/snackbar.json @@ -1,5 +1,6 @@ { "props": { + "open": { "type": { "name": "bool" }, "required": true }, "anchorOrigin": { "type": { "name": "shape", @@ -30,7 +31,6 @@ } }, "onUnmount": { "type": { "name": "func" } }, - "open": { "type": { "name": "bool" } }, "resumeHideDuration": { "type": { "name": "number" } }, "size": { "type": { "name": "enum", "description": "'sm'
| 'md'
| 'lg'" }, diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index c8ae6e25a9704e..d5f6141da3b85f 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -414,7 +414,7 @@ Snackbar.propTypes /* remove-proptypes */ = { /** * If `true`, the component is shown. */ - open: PropTypes.bool, + open: PropTypes.bool.isRequired, /** * The number of milliseconds to wait before dismissing after user interaction. * If `autoHideDuration` prop isn't specified, it does nothing. diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index 5b67bd878839f1..78c4321ac0a83d 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -55,7 +55,7 @@ export type { SnackbarCloseReason } from '@mui/base/useSnackbar'; export interface SnackbarTypeMap

{ props: P & - UseSnackbarParameters & { + Omit & { /** * The anchor of the `Snackbar`. * On smaller screens, the component grows to occupy all the available width, @@ -97,6 +97,10 @@ export interface SnackbarTypeMap

{ * A callback fired when the component is about to be unmounted. */ onUnmount?: () => void; + /** + * If `true`, the component is shown. + */ + open: boolean; /** * The size of the component. * @default 'md' From f84eb60484d9a1f14eb91bb0fa07afa0eceb0236 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Mon, 23 Oct 2023 16:35:44 +0700 Subject: [PATCH 54/58] update demos as suggested --- .../joy/components/snackbar/SnackbarColors.js | 83 ++++++++++-------- .../components/snackbar/SnackbarColors.tsx | 87 ++++++++++--------- .../snackbar/SnackbarHideDuration.js | 1 + .../snackbar/SnackbarHideDuration.tsx | 1 + .../joy/components/snackbar/SnackbarUsage.js | 16 ++-- packages/mui-joy/src/Snackbar/Snackbar.tsx | 4 +- .../mui-joy/src/Snackbar/SnackbarProps.ts | 2 +- 7 files changed, 104 insertions(+), 90 deletions(-) diff --git a/docs/data/joy/components/snackbar/SnackbarColors.js b/docs/data/joy/components/snackbar/SnackbarColors.js index 2b65e7f2c60e74..35b0ecb149e2d2 100644 --- a/docs/data/joy/components/snackbar/SnackbarColors.js +++ b/docs/data/joy/components/snackbar/SnackbarColors.js @@ -1,51 +1,58 @@ import * as React from 'react'; import Button from '@mui/joy/Button'; import Stack from '@mui/joy/Stack'; +import Select from '@mui/joy/Select'; +import Option from '@mui/joy/Option'; import Snackbar from '@mui/joy/Snackbar'; export default function SnackbarColors() { const [open, setOpen] = React.useState(false); + const [variant, setVariant] = React.useState('outlined'); const [color, setColor] = React.useState('neutral'); return ( - - {['primary', 'neutral', 'danger', 'success', 'warning'].map((currentColor) => ( - - ))} - {['plain', 'outlined', 'soft', 'solid'].map((variant, index) => ( - + + + {['primary', 'neutral', 'danger', 'success', 'warning'].map( + (currentColor) => ( + + ), + )} + + { + if (reason === 'clickaway') { + return; } - onClose={(event, reason) => { - if (reason === 'clickaway') { - return; - } - setOpen(false); - }} - > - {variant} snackbar with {color} color. - - ))} + setOpen(false); + }} + > + {variant} snackbar with {color} color. + ); } diff --git a/docs/data/joy/components/snackbar/SnackbarColors.tsx b/docs/data/joy/components/snackbar/SnackbarColors.tsx index 4e0440a48b35bb..f9b68693e00c22 100644 --- a/docs/data/joy/components/snackbar/SnackbarColors.tsx +++ b/docs/data/joy/components/snackbar/SnackbarColors.tsx @@ -1,55 +1,58 @@ import * as React from 'react'; import Button from '@mui/joy/Button'; import Stack from '@mui/joy/Stack'; +import Select from '@mui/joy/Select'; +import Option from '@mui/joy/Option'; import Snackbar, { SnackbarProps } from '@mui/joy/Snackbar'; export default function SnackbarColors() { const [open, setOpen] = React.useState(false); + const [variant, setVariant] = React.useState('outlined'); const [color, setColor] = React.useState('neutral'); return ( - - {(['primary', 'neutral', 'danger', 'success', 'warning'] as const).map( - (currentColor) => ( - - ), - )} - {(['plain', 'outlined', 'soft', 'solid'] as const).map((variant, index) => ( - + + + {(['primary', 'neutral', 'danger', 'success', 'warning'] as const).map( + (currentColor) => ( + + ), + )} + + { + if (reason === 'clickaway') { + return; } - onClose={(event, reason) => { - if (reason === 'clickaway') { - return; - } - setOpen(false); - }} - > - {variant} snackbar with {color} color. - - ))} + setOpen(false); + }} + > + {variant} snackbar with {color} color. + ); } diff --git a/docs/data/joy/components/snackbar/SnackbarHideDuration.js b/docs/data/joy/components/snackbar/SnackbarHideDuration.js index 2e4a73c0a9d1f7..d3d5ff0fbad568 100644 --- a/docs/data/joy/components/snackbar/SnackbarHideDuration.js +++ b/docs/data/joy/components/snackbar/SnackbarHideDuration.js @@ -39,6 +39,7 @@ export default function SnackbarHideDuration() { { setDuration(event.target.valueAsNumber || undefined); diff --git a/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx b/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx index 6ef79b23395b3c..e7ec9dd8f4e81d 100644 --- a/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx +++ b/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx @@ -39,6 +39,7 @@ export default function SnackbarHideDuration() { { setDuration(event.target.valueAsNumber || undefined); diff --git a/docs/data/joy/components/snackbar/SnackbarUsage.js b/docs/data/joy/components/snackbar/SnackbarUsage.js index 229c212bb07ce6..8e202f3be6304e 100644 --- a/docs/data/joy/components/snackbar/SnackbarUsage.js +++ b/docs/data/joy/components/snackbar/SnackbarUsage.js @@ -36,11 +36,6 @@ export default function SnackbarUsage() { helperText: 'The duration to be shown (in ms)', knob: 'number', }, - { - propName: 'invertedColors', - knob: 'switch', - helperText: 'To invert children colors', - }, ]} renderDemo={(props) => ( @@ -58,15 +53,20 @@ export default function SnackbarUsage() { }} startDecorator={} endDecorator={ - setOpen(false)}> + setOpen(false)} + sx={{ color: 'inherit', '--Icon-color': 'inherit' }} + > } {...props} >

- Notification alert - + + Notification alert + + 102 unread messages since last month.
diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index c8ae6e25a9704e..fd715b44cffa4a 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -161,6 +161,8 @@ const SnackbarEndDecorator = styled('span', { marginLeft: 'auto', }); +const defaultAnchorOrigin = { vertical: 'bottom', horizontal: 'right' } as const; + /** * * Demos: @@ -178,7 +180,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { }); const { - anchorOrigin = { vertical: 'bottom', horizontal: 'center' }, + anchorOrigin = defaultAnchorOrigin, animationDuration = 300, autoHideDuration = null, color = 'neutral', diff --git a/packages/mui-joy/src/Snackbar/SnackbarProps.ts b/packages/mui-joy/src/Snackbar/SnackbarProps.ts index 5b67bd878839f1..0dda9c71048608 100644 --- a/packages/mui-joy/src/Snackbar/SnackbarProps.ts +++ b/packages/mui-joy/src/Snackbar/SnackbarProps.ts @@ -60,7 +60,7 @@ export interface SnackbarTypeMap

{ * The anchor of the `Snackbar`. * On smaller screens, the component grows to occupy all the available width, * the horizontal alignment is ignored. - * @default { vertical: 'bottom', horizontal: 'center' } + * @default { vertical: 'bottom', horizontal: 'right' } */ anchorOrigin?: SnackbarOrigin; /** From c843595d1841012a6f77a4f65978a08dc8d03b95 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Mon, 23 Oct 2023 16:52:35 +0700 Subject: [PATCH 55/58] run scripts --- docs/pages/joy-ui/api/snackbar.json | 2 +- packages/mui-joy/src/Snackbar/Snackbar.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/joy-ui/api/snackbar.json b/docs/pages/joy-ui/api/snackbar.json index b09074b2e61a1b..8b81c2e30b3c9e 100644 --- a/docs/pages/joy-ui/api/snackbar.json +++ b/docs/pages/joy-ui/api/snackbar.json @@ -5,7 +5,7 @@ "name": "shape", "description": "{ horizontal: 'center'
| 'left'
| 'right', vertical: 'bottom'
| 'top' }" }, - "default": "{ vertical: 'bottom', horizontal: 'center' }" + "default": "{ vertical: 'bottom', horizontal: 'right' }" }, "animationDuration": { "type": { "name": "number" }, "default": "300" }, "autoHideDuration": { "type": { "name": "number" }, "default": "null" }, diff --git a/packages/mui-joy/src/Snackbar/Snackbar.tsx b/packages/mui-joy/src/Snackbar/Snackbar.tsx index fd715b44cffa4a..6b1f090ca7e357 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.tsx @@ -320,7 +320,7 @@ Snackbar.propTypes /* remove-proptypes */ = { * The anchor of the `Snackbar`. * On smaller screens, the component grows to occupy all the available width, * the horizontal alignment is ignored. - * @default { vertical: 'bottom', horizontal: 'center' } + * @default { vertical: 'bottom', horizontal: 'right' } */ anchorOrigin: PropTypes.shape({ horizontal: PropTypes.oneOf(['center', 'left', 'right']).isRequired, From 63c813953f296e1175eaf18b125ee31ffe9891de Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Mon, 23 Oct 2023 16:08:31 +0530 Subject: [PATCH 56/58] Decrement the duration counter by 100ms --- docs/data/joy/components/snackbar/SnackbarHideDuration.js | 4 ++-- docs/data/joy/components/snackbar/SnackbarHideDuration.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/data/joy/components/snackbar/SnackbarHideDuration.js b/docs/data/joy/components/snackbar/SnackbarHideDuration.js index d3d5ff0fbad568..8c3968bd9bd7e3 100644 --- a/docs/data/joy/components/snackbar/SnackbarHideDuration.js +++ b/docs/data/joy/components/snackbar/SnackbarHideDuration.js @@ -13,8 +13,8 @@ export default function SnackbarHideDuration() { const timer = React.useRef(); const countdown = () => { timer.current = window.setInterval(() => { - setLeft((prev) => (prev === undefined ? prev : Math.max(0, prev - 16))); - }, 16); // 60fps = 16ms / frame + setLeft((prev) => (prev === undefined ? prev : Math.max(0, prev - 100))); + }, 100); }; React.useEffect(() => { if (open && duration !== undefined && duration > 0) { diff --git a/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx b/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx index e7ec9dd8f4e81d..c98174c2cf33c5 100644 --- a/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx +++ b/docs/data/joy/components/snackbar/SnackbarHideDuration.tsx @@ -13,8 +13,8 @@ export default function SnackbarHideDuration() { const timer = React.useRef(); const countdown = () => { timer.current = window.setInterval(() => { - setLeft((prev) => (prev === undefined ? prev : Math.max(0, prev - 16))); - }, 16); // 60fps = 16ms / frame + setLeft((prev) => (prev === undefined ? prev : Math.max(0, prev - 100))); + }, 100); }; React.useEffect(() => { if (open && duration !== undefined && duration > 0) { From 36a078a7633b6bf0fc09405f15ea0cd0c25485d6 Mon Sep 17 00:00:00 2001 From: zanivan Date: Mon, 23 Oct 2023 13:56:50 -0300 Subject: [PATCH 57/58] Small text adjustments --- docs/data/joy/components/snackbar/snackbar.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/data/joy/components/snackbar/snackbar.md b/docs/data/joy/components/snackbar/snackbar.md index b3dade4cf1aa68..b7e4ca61740a7f 100644 --- a/docs/data/joy/components/snackbar/snackbar.md +++ b/docs/data/joy/components/snackbar/snackbar.md @@ -14,9 +14,11 @@ waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/alert/ ## Introduction -Snackbars inform users of a process that an app has performed or will perform. They appear temporarily, towards the bottom of the screen. They shouldn't interrupt the user experience, and they don't require user input to disappear. +Snackbars are designed to provide brief, non-intrusive notifications to users, informing them about processes an app has performed or will perform. -Snackbars contain a single line of text directly related to the operation performed. They may contain a text action, but no icons. You can use them to display notifications. +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"}} @@ -28,8 +30,9 @@ import Snackbar from '@mui/joy/Snackbar'; ### Position -In wide layouts, snackbars can be left-aligned or center-aligned if they are consistently placed on the same spot at the bottom of the screen, however there may be circumstances where the placement of the snackbar needs to be more flexible. -You can control the position of the snackbar by specifying the `anchorOrigin` prop. +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"}} @@ -37,7 +40,7 @@ You can control the position of the snackbar by specifying the `anchorOrigin` pr ### Variants -The Snackbar component supports Joy UI's four [global variants](/joy-ui/main-features/global-variants/): `outlined` (default), `solid`, `soft`, and `plain`. +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"}} From 42f0ebb1b7cd47b39697684c0fc3cca0d459f48c Mon Sep 17 00:00:00 2001 From: zanivan Date: Mon, 23 Oct 2023 14:03:56 -0300 Subject: [PATCH 58/58] Tweaks to the Colors demo --- docs/data/joy/components/snackbar/SnackbarColors.js | 4 ++-- docs/data/joy/components/snackbar/SnackbarColors.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/data/joy/components/snackbar/SnackbarColors.js b/docs/data/joy/components/snackbar/SnackbarColors.js index 35b0ecb149e2d2..e2b67594236ef2 100644 --- a/docs/data/joy/components/snackbar/SnackbarColors.js +++ b/docs/data/joy/components/snackbar/SnackbarColors.js @@ -26,7 +26,7 @@ export default function SnackbarColors() { (currentColor) => (