diff --git a/.changeset/unlucky-schools-attend.md b/.changeset/unlucky-schools-attend.md new file mode 100644 index 00000000000..c8155a73d7b --- /dev/null +++ b/.changeset/unlucky-schools-attend.md @@ -0,0 +1,5 @@ +--- +'@shopify/polaris': minor +--- + +Added `tone`, `icon`, and `onClick` props to `Toast` diff --git a/polaris-react/src/components/Frame/components/Toast/Toast.module.scss b/polaris-react/src/components/Frame/components/Toast/Toast.module.scss index 69f6a8c32c6..fdacd980b83 100644 --- a/polaris-react/src/components/Frame/components/Toast/Toast.module.scss +++ b/polaris-react/src/components/Frame/components/Toast/Toast.module.scss @@ -66,3 +66,35 @@ $Backdrop-opacity: 0.88; color: var(--p-color-text-inverse); } } + +.toneMagic { + background-color: var(--p-color-bg-fill-magic-secondary); + color: var(--p-color-text-magic); + + .CloseButton { + color: var(--p-color-text-magic); + } + + .Action { + color: var(--p-color-text-magic); + } +} + +.WithActionOnComponent { + border: none; + cursor: pointer; + line-height: var(--p-font-line-height-500); + font-size: var(--p-font-size-325); + padding-right: var(--p-space-500); +} + +.WithActionOnComponent.toneMagic { + &:focus, + &:hover { + background-color: var(--p-color-bg-fill-magic-secondary-hover); + } + + &:active { + background-color: var(--p-color-bg-fill-magic-secondary-active); + } +} diff --git a/polaris-react/src/components/Frame/components/Toast/Toast.tsx b/polaris-react/src/components/Frame/components/Toast/Toast.tsx index a1eb9e2eab9..32c0a1806ef 100644 --- a/polaris-react/src/components/Frame/components/Toast/Toast.tsx +++ b/polaris-react/src/components/Frame/components/Toast/Toast.tsx @@ -1,7 +1,7 @@ import React, {useEffect} from 'react'; import {AlertCircleIcon, XSmallIcon} from '@shopify/polaris-icons'; -import {classNames} from '../../../../utilities/css'; +import {classNames, variationName} from '../../../../utilities/css'; import {Key} from '../../../../types'; import {Button} from '../../../Button'; import {Icon} from '../../../Icon'; @@ -24,6 +24,9 @@ export function Toast({ duration, error, action, + tone, + onClick, + icon, }: ToastProps) { useEffect(() => { let timeoutDuration = duration || DEFAULT_TOAST_DURATION; @@ -67,20 +70,61 @@ export function Toast({ ) : null; - const leadingIconMarkup = error ? ( -
- -
- ) : null; + let leadingIconMarkup = null; + + if (error) { + leadingIconMarkup = ( +
+ +
+ ); + } else if (icon) { + leadingIconMarkup = ( +
+ +
+ ); + } - const className = classNames(styles.Toast, error && styles.error); + const className = classNames( + styles.Toast, + error && styles.error, + tone && styles[variationName('tone', tone)], + ); + + if (!action && onClick) { + return ( + + ); + } return (
{leadingIconMarkup} - + {content} diff --git a/polaris-react/src/components/Frame/components/Toast/tests/Toast.test.tsx b/polaris-react/src/components/Frame/components/Toast/tests/Toast.test.tsx index e6ed350d4eb..b7186f8ff0b 100644 --- a/polaris-react/src/components/Frame/components/Toast/tests/Toast.test.tsx +++ b/polaris-react/src/components/Frame/components/Toast/tests/Toast.test.tsx @@ -1,11 +1,13 @@ import React from 'react'; import {timer} from '@shopify/jest-dom-mocks'; import {mountWithApp} from 'tests/utilities'; +import {CheckIcon} from '@shopify/polaris-icons'; import {Button} from '../../../../Button'; import {Toast} from '../Toast'; import type {ToastProps} from '../Toast'; import {Key} from '../../../../../types'; +import {Icon} from '../../../../Icon'; interface HandlerMap { [eventName: string]: any; @@ -38,6 +40,13 @@ describe('', () => { }); }); + it('renders a Toast with the magic tone when tone is "magic"', () => { + const message = mountWithApp(); + expect(message).toContainReactComponent('div', { + className: 'Toast toneMagic', + }); + }); + describe('dismiss button', () => { it('renders by default', () => { const message = mountWithApp(); @@ -45,6 +54,11 @@ describe('', () => { }); }); + it('renders a leading icon if an icon is provided', () => { + const message = mountWithApp(); + expect(message).toContainReactComponent(Icon, {source: CheckIcon}); + }); + describe('action', () => { const mockAction = { content: 'Do something', @@ -193,6 +207,24 @@ describe('', () => { expect(spy).not.toHaveBeenCalled(); }); }); + + describe('onClick', () => { + it('wraps the toast in a button when provided', () => { + const spy = jest.fn(); + const message = mountWithApp(); + + expect(message.find('button')).toContainReactText(mockProps.content); + }); + + it('fires the callback when the toast is clicked', () => { + const spy = jest.fn(); + const message = mountWithApp(); + + message.find('button')?.trigger('onClick'); + + expect(spy).toHaveBeenCalledTimes(1); + }); + }); }); function noop() {} diff --git a/polaris-react/src/components/Toast/Toast.stories.tsx b/polaris-react/src/components/Toast/Toast.stories.tsx index dcd61516fc4..929e09f10ca 100644 --- a/polaris-react/src/components/Toast/Toast.stories.tsx +++ b/polaris-react/src/components/Toast/Toast.stories.tsx @@ -11,6 +11,7 @@ import { BlockStack, TextContainer, } from '@shopify/polaris'; +import {MagicIcon} from '@shopify/polaris-icons'; export default { component: Toast, @@ -217,3 +218,77 @@ export function InsideModal() {
); } + +export function Magic() { + const [active, setActive] = useState(false); + + const toggleActive = useCallback(() => setActive((active) => !active), []); + + const toastMarkup = active ? ( + + ) : null; + + return ( +
+ + + + {toastMarkup} + + +
+ ); +} + +export function WithOnClick() { + const [active, setActive] = useState(false); + + const toggleActive = useCallback(() => setActive((active) => !active), []); + + const toastMarkup = active ? ( + + ) : null; + + return ( +
+ + + + {toastMarkup} + + +
+ ); +} + +export function MagicWithOnClick() { + const [active, setActive] = useState(false); + + const toggleActive = useCallback(() => setActive((active) => !active), []); + + const toastMarkup = active ? ( + + ) : null; + + return ( +
+ + + + {toastMarkup} + + +
+ ); +} diff --git a/polaris-react/src/utilities/frame/types.ts b/polaris-react/src/utilities/frame/types.ts index e4dbb5ae5f7..290a512be95 100644 --- a/polaris-react/src/utilities/frame/types.ts +++ b/polaris-react/src/utilities/frame/types.ts @@ -1,4 +1,4 @@ -import type {Action} from '../../types'; +import type {Action, IconSource} from '../../types'; export interface Logo { /** Provides a path for a logo used on a dark background */ @@ -53,7 +53,7 @@ export interface ContextualSaveBarProps { // Toast -export interface ToastProps { +interface BaseToastProps { /** The content that should appear in the toast message */ content: string; /** @@ -63,12 +63,26 @@ export interface ToastProps { duration?: number; /** Display an error toast. */ error?: boolean; - /** Callback when the dismiss icon is clicked */ - onDismiss(): void; + /** Indicates the tone of the toast */ + tone?: 'magic'; + /** Icon prefix for the toast content */ + icon?: IconSource; +} + +interface ClickableToast { + /** Callback fired when the toast is clicked or keypressed */ + onClick?(): void; +} + +interface DismissableToast { /** Adds an action next to the message */ action?: Action; + /** Callback when the dismiss icon is clicked */ + onDismiss(): void; } +export type ToastProps = BaseToastProps & ClickableToast & DismissableToast; + export interface ToastID { id: string; }