Skip to content

Commit

Permalink
feat(config): add transition durations for tailwind (#2036)
Browse files Browse the repository at this point in the history
- add in durations via (transitionDuration)
- add in new toast notification implementation example
- use new medium durations in example

- demo autodismiss behavior with stack of notifications
- use state to track notifications
  • Loading branch information
booc0mtaco authored Aug 22, 2024
1 parent db6203e commit aed0f09
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 63 deletions.
63 changes: 0 additions & 63 deletions src/components/ToastNotification/ToastNotification.stories.ts

This file was deleted.

149 changes: 149 additions & 0 deletions src/components/ToastNotification/ToastNotification.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { Transition } from '@headlessui/react';
import type { StoryObj, Meta } from '@storybook/react';
import React from 'react';

import type { ComponentProps } from 'react';

import { ToastNotification } from './ToastNotification';

import Button from '../Button';

export default {
title: 'Components/ToastNotification',
component: ToastNotification,
parameters: {
layout: 'centered',
badges: ['intro-1.0', 'current-2.0'],
},
argTypes: {
onDismiss: { action: 'trigger dismiss' },
timeout: { table: { disable: true } },
},
args: {
title: "You've got a temporary notification!",
className: 'w-96',
},
} as Meta<Args>;

type Args = ComponentProps<typeof ToastNotification>;
type Story = StoryObj<Args>;

export const Default: Story = {};

export const Favorable: Story = {
args: {
status: 'favorable',
},
};

/**
* Notifications can have different status, to indicate errors or destructive actions have completed.
*/
export const Critical: Story = {
args: {
status: 'critical',
},
};

/**
* We can restrict the ability to dismiss the notification by not specifying the `onDismiss` method.
*/
export const NotDismissable: Story = {
args: {
...Default.args,
onDismiss: undefined,
},
};

/**
* Tooltips can be instructed to auto-close after a certain period. After the timeout, the component will call the defined
* `onDismiss` method. The behavior of the dissmisal is left up to the user, which allows for complete control.
*/
export const AutoDismiss: Story = {
args: {
...Default.args,
dissmissType: 'auto',
timeout: 500,
onDismiss: () => console.log('trigger onDismiss'),
},
};

let toastId = 0;
const ToastNotificationManager = (args: Args) => {
const [toasts, setToasts] = React.useState<
{ id: number | string; text: string; show?: boolean }[]
>([]);

// TODO: clean up `toasts` after .show is set to false (using useEffect? and .debounce)
// - In a production implementation, you can filter out any toasts where show=false

return (
<div className="flex h-[90vh] w-[90vw] items-center justify-center">
<Button
onClick={() => {
setToasts([
...toasts,
{
id: toastId++,
text: 'New Toast',
show: true,
},
]);
}}
>
Trigger A Toast Notification
</Button>
<div
className="dur absolute bottom-0 left-0 flex flex-col gap-size-2"
id="toast-container"
>
{toasts.map((toast) => (
<Transition
appear
enter="transition-all duration-medium"
enterFrom="opacity-0 transform-gpu scale-0"
enterTo="opacity-100 transform-gpu scale-100"
key={toast.id}
leave="ease-in-out transition-all duration-medium"
leaveFrom="opacity-100 transform-gpu translate-x-[0px]"
leaveTo="opacity-0 transform-gpu translate-x-[-100%]"
show={toast.show}
>
<ToastNotification
{...args}
dissmissType="auto"
onDismiss={() => {
setToasts(
toasts.map((thisToast) => {
return thisToast.id === toast.id
? { ...thisToast, show: false }
: thisToast;
}),
);
}}
title={'You got a new toast: ' + toast.text + toast.id}
/>
</Transition>
))}
</div>
</div>
);
};

/**
* This implementation example shows how you can use toasts with state to handle multiple, stacking notifications.
*
* For a full, production-ready implementation, clean up any toasts with show=false after the animation has completed.
* - Consider using lodash.debounce to time the re-render, and useEffect that watches the list of toasts
* - Any debouncing should map to whatever duration is used in `Transition`
*
* Here, we use `<Transition>` provided by [HeadlessUI](https://github.com/chanzuckerberg/edu-design-system/blob/main/package.json#L91-L93).
*/
export const ExampleDismissingToasts: Story = {
render: (args) => <ToastNotificationManager {...args} />,
parameters: {
// For interactive use, low value in snap testing again since already covered in other stories.
chromatic: { disableSnapshot: true },
snapshot: { skip: true },
},
};
16 changes: 16 additions & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ const {
// Add a type to the token sizes to avoid literals for keys
const sizes: { [x: string]: string } = edsTokens.size;

// add a type to the token sizes for movement durations
const movement: { [x: string]: string } = {
...Object.keys(edsTokens.anim.move)
.map((movement) => {
return { [movement]: `${edsTokens.anim.move[movement]}s` };
})
.reduce((accumulate, current) => {
const entry = Object.entries(current)[0];
accumulate[entry[0]] = entry[1];
return accumulate;
}, {}),
};

const sizeTokens = {
// We pull the spacing tokens and format them such that names are like 'size-${name} = ${value}px'
...Object.keys(sizes)
Expand Down Expand Up @@ -52,6 +65,9 @@ export default {
spacing: {
...sizeTokens,
},
transitionDuration: {
...movement,
},
},
fontWeight: {
normal: edsTokens['font-weight'].light,
Expand Down

0 comments on commit aed0f09

Please sign in to comment.