Skip to content

Commit

Permalink
Create ToastMessages pattern library page
Browse files Browse the repository at this point in the history
  • Loading branch information
acelaya committed Sep 27, 2023
1 parent 540cb66 commit 96bca8f
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 15 deletions.
229 changes: 229 additions & 0 deletions src/pattern-library/components/patterns/feedback/ToastMessagesPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import { useCallback, useState } from 'preact/hooks';

import type {
ToastMessagesProps,
ToastMessage,
} from '../../../../components/feedback/ToastMessages';
import ToastMessages from '../../../../components/feedback/ToastMessages';
import Button from '../../../../components/input/Button';
import Link from '../../../../components/navigation/Link';
import Library from '../../Library';

type BaseToastMessage = Omit<ToastMessage, 'id'>;

const toastMessages: BaseToastMessage[] = [
{
message: 'Success toast message',
type: 'success',
autoDismiss: false,
},
{
message: 'Warning toast message',
type: 'notice',
autoDismiss: false,
},
{
message: 'Error toast message',
type: 'error',
autoDismiss: false,
},
];

type ToastMessages_Props = Omit<ToastMessagesProps, 'messages'> & {
messages: BaseToastMessage[];
_printOnDismiss?: boolean;
_allowAppending?: boolean;
};

function ToastMessages_({
messages: initialMessages,
transitionClasses,
_printOnDismiss = false,
_allowAppending = false,
}: ToastMessages_Props) {
const [messages, setMessages] = useState(
initialMessages.map((message, index) => ({
id: `${index}`,
...message,
})),
);
const [lastDismissedMessage, setLastDismissedMessage] =
useState<BaseToastMessage>();
const onMessageDismiss = useCallback(
(id: string) => {
setLastDismissedMessage(messages.find(message => message.id === id));
setMessages(prev => prev.filter(message => message.id !== id));
},
[messages],
);
const appendRandomMessage = useCallback(() => {
const newId =
messages.length === 0 ? 1 : Number(messages[messages.length - 1].id) + 1;
const newMessage: ToastMessage = {
id: `${newId}`,
message: `New toast message`,
type: 'success',
autoDismiss: false,
};
setMessages(prev => [...prev, newMessage]);
}, [messages]);

return (
<div className="w-full">
{_allowAppending && (
<Button onClick={appendRandomMessage} classes="mb-2" variant="primary">
Append message
</Button>
)}
<div className="w-1/2 mx-auto flex flex-col gap-y-4">
<ToastMessages
messages={messages}
onMessageDismiss={onMessageDismiss}
transitionClasses={transitionClasses}
/>
{_printOnDismiss && lastDismissedMessage && (
<div>
Just dismissed message {'"'}
{lastDismissedMessage.message}
{'"'}
</div>
)}
</div>
</div>
);
}

export default function ToastMessagesPage() {
return (
<Library.Page
title="Callout"
intro={
<p>
ToastMessages is a component that wraps{' '}
<Library.Link href="/feedback-callout">Callouts</Library.Link> with
out of the box accessibility, message auto-dismiss, dismiss-on-click,
transitions and message positioning.
</p>
}
>
<Library.Section>
<Library.Pattern>
<Library.Usage componentName="ToastMessages" />
<Library.Example>
<Library.Demo withSource title="Basic ToastMessages">
<ToastMessages_
messages={toastMessages}
onMessageDismiss={() => {}}
/>
</Library.Demo>
</Library.Example>
</Library.Pattern>

<Library.Pattern title="Working with ToastMessages">
<Library.Example title="Accessibility">
<p>
<code>ToastMessages</code> includes an{' '}
<Link href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions">
<code>
aria-live={'"'}polite{'"'}
</code>
</Link>{' '}
wrapper which will announce added toast messages to screen
readers.
</p>
<p>
Additionally, it is possible to append hidden messages that are
only announced by screen readers, via{' '}
<code>{'{ visuallyHidden: true }'}</code>
</p>
</Library.Example>
<Library.Example title="Positioning">
<p>
By default, every message rendered by <code>ToastMessages</code>{' '}
will be relative positioned. It{"'"}s up to consumers to wrap it
in an absolute-positioned container if desired.
</p>
</Library.Example>
<Library.Example title="Auto-dismiss">
<p>
All messages will be auto-dismissed after 5 seconds. Pass
<code>{'{ autoDismiss: false }'}</code> for those messages where
you want this to be prevented.
</p>
</Library.Example>
<Library.Example title="Transitions">
<p>
All messages will fade-in when appended and fade-out when
dismissed, but you can provide your own transition classes via{' '}
<code>transitionClasses</code> prop.
</p>
</Library.Example>
</Library.Pattern>

<Library.Pattern title="Component API">
<Library.Example title="messages">
<Library.Info>
<Library.InfoItem label="description">
The list of toast messages to display at a given point.
</Library.InfoItem>
<Library.InfoItem label="type">
<code>{`ToastMessage[]`}</code>
</Library.InfoItem>
</Library.Info>

<Library.Demo title="ToastMessages with one message" withSource>
<ToastMessages_
messages={[toastMessages[0]]}
onMessageDismiss={() => {}}
/>
</Library.Demo>
</Library.Example>
<Library.Example title="onMessageDismiss">
<Library.Info>
<Library.InfoItem label="description">
Callback invoked with the toast message id, when it ends its
dismiss transition. Typically used to update the store handling
toast messages.
</Library.InfoItem>
<Library.InfoItem label="type">
<code>{`(toastMessageId: string) => void`}</code>
</Library.InfoItem>
</Library.Info>

<Library.Demo title="Click a message to dismiss it" withSource>
<ToastMessages_
messages={toastMessages}
onMessageDismiss={() => {}}
_printOnDismiss
/>
</Library.Demo>
</Library.Example>
<Library.Example title="transitionClasses">
<Library.Info>
<Library.InfoItem label="description">
Custom CSS classes to apply to toast messages when appended
and/or dismissed. By default they get{' '}
<code>animate-fade-in</code> and <code>animate-fade-out</code>
</Library.InfoItem>
<Library.InfoItem label="type">
<code>{`{ transitionIn?: string; transitionOut?: string; }`}</code>
</Library.InfoItem>
</Library.Info>

<Library.Demo title="Append and dismiss messages" withSource>
<ToastMessages_
messages={toastMessages}
onMessageDismiss={() => {}}
transitionClasses={{
transitionIn: 'animate-slide-in-from-right',
transitionOut: 'animate-slide-out-to-right',
}}
_allowAppending
/>
</Library.Demo>
</Library.Example>
</Library.Pattern>
</Library.Section>
</Library.Page>
);
}
7 changes: 7 additions & 0 deletions src/pattern-library/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import CalloutPage from './components/patterns/feedback/CalloutPage';
import DialogPage from './components/patterns/feedback/DialogPage';
import ModalPage from './components/patterns/feedback/ModalPage';
import SpinnerPage from './components/patterns/feedback/SpinnerPage';
import ToastMessagesPage from './components/patterns/feedback/ToastMessagesPage';
import ButtonsPage from './components/patterns/input/ButtonPage';
import CheckboxPage from './components/patterns/input/CheckboxPage';
import CloseButtonPage from './components/patterns/input/CloseButtonPage';
Expand Down Expand Up @@ -142,6 +143,12 @@ const routes: PlaygroundRoute[] = [
component: CalloutPage,
route: '/feedback-callout',
},
{
title: 'ToastMessages',
group: 'feedback',
component: ToastMessagesPage,
route: '/feedback-toast-messages',
},
{
title: 'Dialogs',
group: 'feedback',
Expand Down
2 changes: 1 addition & 1 deletion src/tailwind.preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const minimumTouchDimension = '44px';
export default /** @type {Partial<import('tailwindcss').Config>} */ ({
theme: {
extend: {
animations: {
animation: {
'fade-in': 'fade-in 0.3s forwards',
'fade-out': 'fade-out 0.3s forwards',
},
Expand Down
48 changes: 34 additions & 14 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,41 @@ import tailwindPreset from './src/tailwind.preset.js';
export default {
presets: [tailwindPreset],
content: ['./src/**/*.{js,ts,tsx}', './templates/**/*.html'],
animation: {
'slide-in-from-right': 'slide-in-from-right 0.3s forwards ease-in-out',
},
keyframes: {
'slide-in-from-right': {
'0%': {
opacity: '0',
left: '100%',
},
'80%': {
left: '-10px',

// Pattern library specific config
theme: {
extend: {
animation: {
'slide-in-from-right': 'slide-in-from-right 0.3s forwards ease-in-out',
'slide-out-to-right': 'slide-out-to-right 0.3s forwards ease-in-out',
},
'100%': {
left: '0',
opacity: '1',
keyframes: {
'slide-in-from-right': {
'0%': {
opacity: '0',
left: '100%',
},
'80%': {
left: '-10px',
},
'100%': {
left: '0',
opacity: '1',
},
},
'slide-out-to-right': {
'0%': {
left: '0',
opacity: '1',
},
'20%': {
left: '-10px',
},
'100%': {
opacity: '0',
left: '100%',
},
},
},
},
},
Expand Down

0 comments on commit 96bca8f

Please sign in to comment.