Skip to content

Commit

Permalink
[Toast] Add: tone, actionOnComponent, leadingIcon
Browse files Browse the repository at this point in the history
Add the following options to the Toast component:
- tone
- actionOnComponent
- leadingIcon
  • Loading branch information
lone-star committed Jan 11, 2024
1 parent befded2 commit a977064
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
58 changes: 50 additions & 8 deletions polaris-react/src/components/Frame/components/Toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, {useEffect} from 'react';
import {AlertMinor, CancelSmallMinor} 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';
Expand All @@ -24,6 +24,9 @@ export function Toast({
duration,
error,
action,
tone,
actionOnComponent,
leadingIcon,
}: ToastProps) {
useEffect(() => {
let timeoutDuration = duration || DEFAULT_TOAST_DURATION;
Expand Down Expand Up @@ -67,20 +70,59 @@ export function Toast({
</div>
) : null;

const leadingIconMarkup = error ? (
<div className={styles.LeadingIcon}>
<Icon source={AlertMinor} tone="inherit" />
</div>
) : null;
let leadingIconMarkup;
if (error) {
leadingIconMarkup = (
<div className={styles.LeadingIcon}>
<Icon source={AlertMinor} tone="inherit" />
</div>
);
} else if (leadingIcon) {
leadingIconMarkup = (
<div className={styles.LeadingIcon}>
<Icon source={leadingIcon} tone="inherit" />
</div>
);
}
const className = classNames(
styles.Toast,
error && styles.error,
tone && styles[variationName('tone', tone)],
);

const className = classNames(styles.Toast, error && styles.error);
if (action && actionOnComponent) {
return (
<button
aria-live="assertive"
className={classNames(className, styles.WithActionOnComponent)}
type="button"
onClick={action.onAction}
>
<KeypressListener keyCode={Key.Escape} handler={onDismiss} />
{leadingIconMarkup}
<InlineStack gap="400" blockAlign="center">
<Text
as="span"
fontWeight="medium"
tone={tone && tone === 'magic' ? 'magic-subdued' : undefined}
>
{action.content}
</Text>
</InlineStack>
</button>
);
}

return (
<div className={className} aria-live="assertive">
<KeypressListener keyCode={Key.Escape} handler={onDismiss} />
{leadingIconMarkup}
<InlineStack gap="400" blockAlign="center">
<Text as="span" fontWeight="medium">
<Text
as="span"
fontWeight="medium"
tone={tone && tone === 'magic' ? 'magic-subdued' : undefined}
>
{content}
</Text>
</InlineStack>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React from 'react';
import {timer} from '@shopify/jest-dom-mocks';
import {mountWithApp} from 'tests/utilities';
import {TickMinor} 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;
Expand Down Expand Up @@ -38,13 +40,27 @@ describe('<Toast />', () => {
});
});

it('renders a Toast with the magic tone when tone is "magic"', () => {
const message = mountWithApp(<Toast {...mockProps} tone="magic" />);
expect(message).toContainReactComponent('div', {
className: 'Toast toneMagic',
});
});

describe('dismiss button', () => {
it('renders by default', () => {
const message = mountWithApp(<Toast {...mockProps} />);
expect(message).toContainReactComponent('button');
});
});

it('renders a leading icon if an icon is provided', () => {
const message = mountWithApp(
<Toast leadingIcon={TickMinor} {...mockProps} />,
);
expect(message).toContainReactComponent(Icon, {source: TickMinor});
});

describe('action', () => {
const mockAction = {
content: 'Do something',
Expand Down Expand Up @@ -193,6 +209,40 @@ describe('<Toast />', () => {
expect(spy).not.toHaveBeenCalled();
});
});

describe('actionOnComponent', () => {
const mockAction = {
content: 'Do something',
onAction: noop,
};

it('wraps the toast in a button', () => {
const message = mountWithApp(
<Toast {...mockProps} action={mockAction} actionOnComponent />,
);

expect(message.find('button')).toContainReactText(mockAction.content);
});

it('does not show the content', () => {
const message = mountWithApp(
<Toast {...mockProps} action={mockAction} actionOnComponent />,
);
expect(message).not.toContainReactText(mockProps.content);
});

it('triggers action when button is clicked', () => {
const spy = jest.fn();
const mockActionWithSpy = {...mockAction, onAction: spy};
const message = mountWithApp(
<Toast {...mockProps} action={mockActionWithSpy} actionOnComponent />,
);

message.find('button')?.trigger('onClick');

expect(spy).toHaveBeenCalledTimes(1);
});
});
});

function noop() {}
89 changes: 89 additions & 0 deletions polaris-react/src/components/Toast/Toast.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
BlockStack,
TextContainer,
} from '@shopify/polaris';
import {MagicMajor} from '@shopify/polaris-icons';

export default {
component: Toast,
Expand Down Expand Up @@ -217,3 +218,91 @@ export function InsideModal() {
</div>
);
}

export function Magic() {
const [active, setActive] = useState(false);

const toggleActive = useCallback(() => setActive((active) => !active), []);

const toastMarkup = active ? (
<Toast
content="Magic message"
onDismiss={toggleActive}
tone="magic"
duration={3000000}
/>
) : null;

return (
<div style={{height: '250px'}}>
<Frame>
<Page title="Default">
<Button onClick={toggleActive}>Show Magic Toast</Button>
{toastMarkup}
</Page>
</Frame>
</div>
);
}

export function ActionOnComponent() {
const [active, setActive] = useState(false);

const toggleActive = useCallback(() => setActive((active) => !active), []);

const toastMarkup = active ? (
<Toast
content=""
onDismiss={toggleActive}
action={{
content: 'Message Toast',
onAction: () => {},
}}
actionOnComponent
duration={3000000}
/>
) : null;

return (
<div style={{height: '250px'}}>
<Frame>
<Page title="Default">
<Button onClick={toggleActive}>Show Magic Toast</Button>
{toastMarkup}
</Page>
</Frame>
</div>
);
}

export function MagicActionOnComponent() {
const [active, setActive] = useState(false);

const toggleActive = useCallback(() => setActive((active) => !active), []);

const toastMarkup = active ? (
<Toast
content=""
onDismiss={toggleActive}
tone="magic"
action={{
content: 'Magic message',
onAction: () => {},
}}
actionOnComponent
duration={3000000}
leadingIcon={MagicMajor}
/>
) : null;

return (
<div style={{height: '250px'}}>
<Frame>
<Page title="Default">
<Button onClick={toggleActive}>Show Magic Toast</Button>
{toastMarkup}
</Page>
</Frame>
</div>
);
}
8 changes: 7 additions & 1 deletion polaris-react/src/utilities/frame/types.ts
Original file line number Diff line number Diff line change
@@ -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 */
Expand Down Expand Up @@ -67,6 +67,12 @@ export interface ToastProps {
onDismiss(): void;
/** Adds an action next to the message */
action?: Action;
/** Ignores the content and makes the action over the whole Toast */
actionOnComponent?: boolean;
/** Indicates the tone of the toast */
tone?: 'magic';
/** Leading icon */
leadingIcon?: IconSource;
}

export interface ToastID {
Expand Down

0 comments on commit a977064

Please sign in to comment.