feat: toast component (1677) (#1998)
This commit is contained in:
parent
e598cd1247
commit
a3df65952d
@ -1,6 +1,24 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
stories: [],
|
stories: [],
|
||||||
addons: ['@storybook/addon-essentials', '@storybook/addon-a11y'],
|
addons: [
|
||||||
|
'@storybook/addon-actions',
|
||||||
|
'@storybook/addon-viewport',
|
||||||
|
{
|
||||||
|
name: '@storybook/addon-docs',
|
||||||
|
options: {
|
||||||
|
configureJSX: true,
|
||||||
|
babelOptions: {},
|
||||||
|
sourceLoaderOptions: null,
|
||||||
|
transcludeMarkdown: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'@storybook/addon-controls',
|
||||||
|
'@storybook/addon-backgrounds',
|
||||||
|
'@storybook/addon-toolbars',
|
||||||
|
'@storybook/addon-measure',
|
||||||
|
'@storybook/addon-outline',
|
||||||
|
'@storybook/addon-a11y',
|
||||||
|
],
|
||||||
// uncomment the property below if you want to apply some webpack config globally
|
// uncomment the property below if you want to apply some webpack config globally
|
||||||
// webpackFinal: async (config, { configType }) => {
|
// webpackFinal: async (config, { configType }) => {
|
||||||
// // Make whatever fine-grained changes you need that should apply to all storybook configs
|
// // Make whatever fine-grained changes you need that should apply to all storybook configs
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"lib": ["es5", "es6", "dom", "dom.iterable"]
|
"lib": ["es5", "es6", "dom", "dom.iterable"]
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"lib": ["es5", "es6", "dom", "dom.iterable"]
|
"lib": ["es5", "es6", "dom", "dom.iterable"]
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -13,5 +13,6 @@
|
|||||||
"incremental": true
|
"incremental": true
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"],
|
"include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"],
|
||||||
"exclude": ["node_modules", "jest.config.ts"]
|
"exclude": ["node_modules", "jest.config.ts"],
|
||||||
|
"files": ["../../node_modules/next/dist/client/image.d.ts"]
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -37,3 +37,4 @@ export * from './tooltip';
|
|||||||
export * from './vega-icons';
|
export * from './vega-icons';
|
||||||
export * from './vega-logo';
|
export * from './vega-logo';
|
||||||
export * from './traffic-light';
|
export * from './traffic-light';
|
||||||
|
export * from './toast';
|
||||||
|
3
libs/ui-toolkit/src/components/toast/index.tsx
Normal file
3
libs/ui-toolkit/src/components/toast/index.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './toast';
|
||||||
|
export * from './toasts-container';
|
||||||
|
export * from './use-toasts';
|
20
libs/ui-toolkit/src/components/toast/toast.module.css
Normal file
20
libs/ui-toolkit/src/components/toast/toast.module.css
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
.initial {
|
||||||
|
top: 20px;
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
border: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showing {
|
||||||
|
opacity: 1;
|
||||||
|
transition: all 0.3s;
|
||||||
|
max-height: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired {
|
||||||
|
right: -375px;
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
transition: all 0.75s;
|
||||||
|
}
|
59
libs/ui-toolkit/src/components/toast/toast.stories.tsx
Normal file
59
libs/ui-toolkit/src/components/toast/toast.stories.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/* eslint-disable jsx-a11y/accessible-emoji */
|
||||||
|
import { Toast } from './toast';
|
||||||
|
import type { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import { Intent } from '../../utils/intent';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Toast',
|
||||||
|
component: Toast,
|
||||||
|
} as ComponentMeta<typeof Toast>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof Toast> = (args) => {
|
||||||
|
return (
|
||||||
|
<Toast
|
||||||
|
{...args}
|
||||||
|
render={() => (
|
||||||
|
<>
|
||||||
|
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
|
||||||
|
<p>Eaque exercitationem saepe cupiditate sunt impedit.</p>
|
||||||
|
<p>I really like 🥪🥪🥪!</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
|
Default.args = {
|
||||||
|
id: 'def',
|
||||||
|
intent: Intent.None,
|
||||||
|
state: 'showing',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Primary = Template.bind({});
|
||||||
|
Primary.args = {
|
||||||
|
id: 'pri',
|
||||||
|
intent: Intent.Primary,
|
||||||
|
state: 'showing',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Danger = Template.bind({});
|
||||||
|
Danger.args = {
|
||||||
|
id: 'dan',
|
||||||
|
intent: Intent.Danger,
|
||||||
|
state: 'showing',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Warning = Template.bind({});
|
||||||
|
Warning.args = {
|
||||||
|
id: 'war',
|
||||||
|
intent: Intent.Warning,
|
||||||
|
state: 'showing',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Success = Template.bind({});
|
||||||
|
Success.args = {
|
||||||
|
id: 'suc',
|
||||||
|
intent: Intent.Success,
|
||||||
|
state: 'showing',
|
||||||
|
};
|
132
libs/ui-toolkit/src/components/toast/toast.tsx
Normal file
132
libs/ui-toolkit/src/components/toast/toast.tsx
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import styles from './toast.module.css';
|
||||||
|
|
||||||
|
import type { IconName } from '@blueprintjs/icons';
|
||||||
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useLayoutEffect } from 'react';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { Intent } from '../../utils/intent';
|
||||||
|
import { Icon } from '../icon';
|
||||||
|
|
||||||
|
type ToastContentProps = { id: string };
|
||||||
|
type ToastContent = (props: ToastContentProps) => JSX.Element;
|
||||||
|
|
||||||
|
type ToastState = 'initial' | 'showing' | 'expired';
|
||||||
|
|
||||||
|
export type Toast = {
|
||||||
|
id: string;
|
||||||
|
intent: Intent;
|
||||||
|
render: ToastContent;
|
||||||
|
closeAfter?: number;
|
||||||
|
signal?: 'close';
|
||||||
|
};
|
||||||
|
|
||||||
|
type ToastProps = Toast & {
|
||||||
|
state?: ToastState;
|
||||||
|
onClose?: (id: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toastIconMapping: { [i in Intent]: IconName } = {
|
||||||
|
[Intent.None]: IconNames.HELP,
|
||||||
|
[Intent.Primary]: IconNames.INFO_SIGN,
|
||||||
|
[Intent.Success]: IconNames.TICK_CIRCLE,
|
||||||
|
[Intent.Warning]: IconNames.ERROR,
|
||||||
|
[Intent.Danger]: IconNames.ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getToastAccent = (intent: Intent) => ({
|
||||||
|
// strip
|
||||||
|
'bg-gray-200 text-black text-opacity-70': intent === Intent.None,
|
||||||
|
'bg-vega-blue text-white text-opacity-70': intent === Intent.Primary,
|
||||||
|
'bg-success text-white text-opacity-70': intent === Intent.Success,
|
||||||
|
'bg-warning text-white text-opacity-70': intent === Intent.Warning,
|
||||||
|
'bg-vega-pink text-white text-opacity-70': intent === Intent.Danger,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CLOSE_DELAY = 750;
|
||||||
|
|
||||||
|
export const Toast = ({
|
||||||
|
id,
|
||||||
|
intent,
|
||||||
|
render,
|
||||||
|
closeAfter,
|
||||||
|
signal,
|
||||||
|
state = 'initial',
|
||||||
|
onClose,
|
||||||
|
}: ToastProps) => {
|
||||||
|
const toastRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const closeToast = useCallback(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (toastRef.current?.classList.contains(styles['showing'])) {
|
||||||
|
toastRef.current.classList.remove(styles['showing']);
|
||||||
|
toastRef.current.classList.add(styles['expired']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
onClose?.(id);
|
||||||
|
}, CLOSE_DELAY);
|
||||||
|
}, [id, onClose]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const req = requestAnimationFrame(() => {
|
||||||
|
if (toastRef.current?.classList.contains(styles['initial'])) {
|
||||||
|
toastRef.current.classList.remove(styles['initial']);
|
||||||
|
toastRef.current.classList.add(styles['showing']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () => cancelAnimationFrame(req);
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let t: NodeJS.Timeout;
|
||||||
|
if (closeAfter && closeAfter > 0) {
|
||||||
|
t = setTimeout(() => {
|
||||||
|
closeToast();
|
||||||
|
}, closeAfter);
|
||||||
|
}
|
||||||
|
return () => clearTimeout(t);
|
||||||
|
}, [closeAfter, closeToast]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (signal === 'close') {
|
||||||
|
closeToast();
|
||||||
|
}
|
||||||
|
}, [closeToast, signal]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-toast-id={id}
|
||||||
|
ref={toastRef}
|
||||||
|
className={classNames(
|
||||||
|
'relative w-[300px] top-0 right-0 rounded-md border overflow-hidden mb-2',
|
||||||
|
'text-black bg-white dark:border-zinc-700',
|
||||||
|
{
|
||||||
|
[styles['initial']]: state === 'initial',
|
||||||
|
[styles['showing']]: state === 'showing',
|
||||||
|
[styles['expired']]: state === 'expired',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex relative">
|
||||||
|
<button
|
||||||
|
data-testid="toast-close"
|
||||||
|
onClick={closeToast}
|
||||||
|
className="absolute p-2 top-0 right-0"
|
||||||
|
>
|
||||||
|
<Icon name="cross" size={3} className="!block" />
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
className={classNames(getToastAccent(intent), 'p-2 pt-3 text-center')}
|
||||||
|
>
|
||||||
|
<Icon name={toastIconMapping[intent]} size={4} className="!block" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 p-2 pr-6 text-sm" data-testid="toast-content">
|
||||||
|
{render({ id })}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
136
libs/ui-toolkit/src/components/toast/toasts-container.spec.tsx
Normal file
136
libs/ui-toolkit/src/components/toast/toasts-container.spec.tsx
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { act, render, renderHook, screen } from '@testing-library/react';
|
||||||
|
import { CLOSE_DELAY, ToastsContainer, useToasts } from '..';
|
||||||
|
import { Intent } from '../../utils/intent';
|
||||||
|
|
||||||
|
describe('ToastsContainer', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
beforeEach(() => {
|
||||||
|
const { result } = renderHook(() => useToasts((state) => state.removeAll));
|
||||||
|
act(() => result.current());
|
||||||
|
jest.clearAllTimers();
|
||||||
|
});
|
||||||
|
it('displays a list of toasts in ascending order', () => {
|
||||||
|
const { baseElement } = render(<ToastsContainer order="asc" />);
|
||||||
|
const { result } = renderHook(() => useToasts((state) => state.add));
|
||||||
|
const add = result.current;
|
||||||
|
act(() => {
|
||||||
|
add({
|
||||||
|
id: 'toast-a',
|
||||||
|
intent: Intent.None,
|
||||||
|
render: () => <p>A</p>,
|
||||||
|
});
|
||||||
|
add({
|
||||||
|
id: 'toast-b',
|
||||||
|
intent: Intent.None,
|
||||||
|
render: () => <p>B</p>,
|
||||||
|
});
|
||||||
|
add({
|
||||||
|
id: 'toast-c',
|
||||||
|
intent: Intent.None,
|
||||||
|
render: () => <p>C</p>,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const toasts = [...screen.queryAllByTestId('toast-content')].map((t) =>
|
||||||
|
t.textContent?.trim()
|
||||||
|
);
|
||||||
|
expect(toasts).toEqual(['A', 'B', 'C']);
|
||||||
|
expect(baseElement.classList).not.toContain('flex-col-reverse');
|
||||||
|
});
|
||||||
|
it('displays a list of toasts in descending order', () => {
|
||||||
|
const { baseElement } = render(<ToastsContainer order="desc" />);
|
||||||
|
const { result } = renderHook(() => useToasts((state) => state.add));
|
||||||
|
const add = result.current;
|
||||||
|
act(() => {
|
||||||
|
add({
|
||||||
|
id: 'toast-a',
|
||||||
|
intent: Intent.None,
|
||||||
|
render: () => <p>A</p>,
|
||||||
|
});
|
||||||
|
add({
|
||||||
|
id: 'toast-b',
|
||||||
|
intent: Intent.None,
|
||||||
|
render: () => <p>B</p>,
|
||||||
|
});
|
||||||
|
add({
|
||||||
|
id: 'toast-c',
|
||||||
|
intent: Intent.None,
|
||||||
|
render: () => <p>C</p>,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const toasts = [...screen.queryAllByTestId('toast-content')].map((t) =>
|
||||||
|
t.textContent?.trim()
|
||||||
|
);
|
||||||
|
expect(toasts).toEqual(['A', 'B', 'C']);
|
||||||
|
expect(baseElement.classList).not.toContain('flex-col-reverse');
|
||||||
|
});
|
||||||
|
it('closes a toast after clicking on "Close" button', () => {
|
||||||
|
const { baseElement } = render(<ToastsContainer order="asc" />);
|
||||||
|
const { result } = renderHook(() => useToasts((state) => state.add));
|
||||||
|
const add = result.current;
|
||||||
|
act(() => {
|
||||||
|
add({
|
||||||
|
id: 'toast-a',
|
||||||
|
intent: Intent.None,
|
||||||
|
render: () => <p>A</p>,
|
||||||
|
});
|
||||||
|
add({
|
||||||
|
id: 'toast-b',
|
||||||
|
intent: Intent.None,
|
||||||
|
render: () => <p>B</p>,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const closeBtn = baseElement.querySelector(
|
||||||
|
'[data-testid="toast-close"]'
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
act(() => {
|
||||||
|
closeBtn.click();
|
||||||
|
jest.runAllTimers();
|
||||||
|
});
|
||||||
|
const toasts = [...screen.queryAllByTestId('toast-content')].map((t) =>
|
||||||
|
t.textContent?.trim()
|
||||||
|
);
|
||||||
|
expect(toasts).toEqual(['B']);
|
||||||
|
});
|
||||||
|
it('auto-closes a toast after given time', () => {
|
||||||
|
render(<ToastsContainer order="asc" />);
|
||||||
|
const { result } = renderHook(() => useToasts((state) => state.add));
|
||||||
|
const add = result.current;
|
||||||
|
act(() => {
|
||||||
|
add({
|
||||||
|
id: 'toast-a',
|
||||||
|
intent: Intent.None,
|
||||||
|
render: () => <p>A</p>,
|
||||||
|
closeAfter: 1000,
|
||||||
|
});
|
||||||
|
add({
|
||||||
|
id: 'toast-b',
|
||||||
|
intent: Intent.None,
|
||||||
|
render: () => <p>B</p>,
|
||||||
|
closeAfter: 2000,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
jest.advanceTimersByTime(1000 + CLOSE_DELAY);
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
[...screen.queryAllByTestId('toast-content')].map((t) =>
|
||||||
|
t.textContent?.trim()
|
||||||
|
)
|
||||||
|
).toEqual(['B']);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
jest.advanceTimersByTime(1000 + CLOSE_DELAY);
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
[...screen.queryAllByTestId('toast-content')].map((t) =>
|
||||||
|
t.textContent?.trim()
|
||||||
|
)
|
||||||
|
).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,197 @@
|
|||||||
|
/* eslint-disable jsx-a11y/accessible-emoji */
|
||||||
|
import type { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import { Intent } from '../../utils/intent';
|
||||||
|
import type { Toast } from './toast';
|
||||||
|
import { ToastsContainer } from './toasts-container';
|
||||||
|
import random from 'lodash/random';
|
||||||
|
import sample from 'lodash/sample';
|
||||||
|
import uniqueId from 'lodash/uniqueId';
|
||||||
|
import { useToasts } from './use-toasts';
|
||||||
|
import create from 'zustand';
|
||||||
|
import { useEffect } from '@storybook/addons';
|
||||||
|
import { formatNumber } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'ToastContainer',
|
||||||
|
component: ToastsContainer,
|
||||||
|
} as ComponentMeta<typeof ToastsContainer>;
|
||||||
|
|
||||||
|
const contents = [
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eaque consequatur minima fugit dolorum assumenda maxime. ',
|
||||||
|
'Sint ut inventore voluptatem eos consectetur nesciunt corporis repudiandae fuga mollitia sit officia eum, ab hic nobis, velit et rem est vero!',
|
||||||
|
'Veritatis sit adipisci est inventore id maiores eaque!',
|
||||||
|
'Exercitationem, voluptatem voluptates animi est culpa dolorem sint, dicta aspernatur accusamus voluptatibus excepturi eius.',
|
||||||
|
'Fuga assumenda minus maiores dolor asperiores, error molestiae aperiam porro consequuntur soluta earum enim exercitationem.',
|
||||||
|
'Consequatur, voluptas sint ducimus excepturi sit totam itaque qui praesentium nobis optio blanditiis repellendus sunt ullam quaerat iste exercitationem fugiat fuga. Quia!',
|
||||||
|
];
|
||||||
|
|
||||||
|
const randomWords = [
|
||||||
|
'advertise',
|
||||||
|
'therapist',
|
||||||
|
'toss',
|
||||||
|
'beam',
|
||||||
|
'worm',
|
||||||
|
'solo',
|
||||||
|
'soldier',
|
||||||
|
'photography',
|
||||||
|
'accountant',
|
||||||
|
'satisfaction',
|
||||||
|
'think',
|
||||||
|
'suppress',
|
||||||
|
'sentiment',
|
||||||
|
'arise',
|
||||||
|
'grant',
|
||||||
|
'greeting',
|
||||||
|
'diagram',
|
||||||
|
'switch',
|
||||||
|
'opposition',
|
||||||
|
'destruction',
|
||||||
|
'flush',
|
||||||
|
'decline',
|
||||||
|
'banana',
|
||||||
|
'emotion',
|
||||||
|
'inject',
|
||||||
|
'avant-garde',
|
||||||
|
'fill',
|
||||||
|
'decay',
|
||||||
|
'wound',
|
||||||
|
'shelter',
|
||||||
|
];
|
||||||
|
|
||||||
|
const randomToast = (): Toast => {
|
||||||
|
const content = sample(contents);
|
||||||
|
return {
|
||||||
|
id: String(uniqueId('toast_')),
|
||||||
|
intent: sample<Intent>([
|
||||||
|
Intent.None,
|
||||||
|
Intent.Primary,
|
||||||
|
Intent.Warning,
|
||||||
|
Intent.Danger,
|
||||||
|
Intent.Success,
|
||||||
|
]) as Intent,
|
||||||
|
render: () => <p>{content}</p>,
|
||||||
|
closeAfter: sample([undefined, random(1000, 5000)]),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type PriceStore = { price: number; setPrice: (p: number) => void };
|
||||||
|
const usePrice = create<PriceStore>((set) => ({
|
||||||
|
price: 0,
|
||||||
|
setPrice: (p) => set((state) => ({ price: p })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof ToastsContainer> = (args) => {
|
||||||
|
const setPrice = usePrice((state) => state.setPrice);
|
||||||
|
|
||||||
|
const { add, close, closeAll, update } = useToasts((state) => ({
|
||||||
|
add: state.add,
|
||||||
|
close: state.close,
|
||||||
|
closeAll: state.closeAll,
|
||||||
|
update: state.update,
|
||||||
|
}));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const i = setInterval(() => {
|
||||||
|
setPrice(random(0, 30, true));
|
||||||
|
}, 1000);
|
||||||
|
return () => clearInterval(i);
|
||||||
|
}, [setPrice]);
|
||||||
|
|
||||||
|
const addRandomToast = () => add(randomToast());
|
||||||
|
const addRandomToastWithAction = () => {
|
||||||
|
const t = randomToast();
|
||||||
|
const words = [
|
||||||
|
sample(randomWords),
|
||||||
|
sample(randomWords),
|
||||||
|
sample(randomWords),
|
||||||
|
];
|
||||||
|
add({
|
||||||
|
...t,
|
||||||
|
render: ({ id }) => (
|
||||||
|
<>
|
||||||
|
<h1>{words[0]}</h1>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
className="underline text-gray-600 mr-2"
|
||||||
|
onClick={() => setTimeout(() => close(id), 500)}
|
||||||
|
>
|
||||||
|
{words[1]}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="underline text-gray-600"
|
||||||
|
onClick={() =>
|
||||||
|
update(id, {
|
||||||
|
intent: sample([
|
||||||
|
Intent.Danger,
|
||||||
|
Intent.Warning,
|
||||||
|
Intent.Success,
|
||||||
|
]) as Intent,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{words[2]}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addRandomToastWithUpdatingData = () => {
|
||||||
|
const t = randomToast();
|
||||||
|
const ToastContent = () => {
|
||||||
|
const { price } = usePrice();
|
||||||
|
const getIcon = () => {
|
||||||
|
if (price === 0) return '🤷';
|
||||||
|
if (price > 20) return '💰';
|
||||||
|
if (price > 10) return '📈';
|
||||||
|
if (price < 10) return '📉';
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<p className="text-3xl font-mono">
|
||||||
|
{getIcon()} {formatNumber(price, 5)}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
add({
|
||||||
|
...t,
|
||||||
|
render: () => <ToastContent />,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
className="bg-gray-200 dark:bg-gray-800 p-2 mr-2"
|
||||||
|
onClick={() => addRandomToast()}
|
||||||
|
>
|
||||||
|
🥪
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="bg-orange-200 dark:bg-orange-800 p-2 mr-2"
|
||||||
|
onClick={() => addRandomToastWithAction()}
|
||||||
|
>
|
||||||
|
🎬 + 🥪
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="bg-purple-200 dark:bg-purple-800 p-2 mr-2"
|
||||||
|
onClick={() => addRandomToastWithUpdatingData()}
|
||||||
|
>
|
||||||
|
📈 + 🥪
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="bg-red-200 dark:bg-red-800 p-2 mr-2"
|
||||||
|
onClick={() => closeAll()}
|
||||||
|
>
|
||||||
|
🧽
|
||||||
|
</button>
|
||||||
|
<ToastsContainer {...args} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
|
Default.args = {
|
||||||
|
order: 'asc',
|
||||||
|
};
|
37
libs/ui-toolkit/src/components/toast/toasts-container.tsx
Normal file
37
libs/ui-toolkit/src/components/toast/toasts-container.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { Toast } from './toast';
|
||||||
|
import { useToasts } from './use-toasts';
|
||||||
|
|
||||||
|
type ToastsContainerProps = {
|
||||||
|
order: 'asc' | 'desc';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ToastsContainer = ({ order = 'asc' }: ToastsContainerProps) => {
|
||||||
|
const { toasts, remove } = useToasts();
|
||||||
|
const onClose = useCallback(
|
||||||
|
(id: string) => {
|
||||||
|
remove(id);
|
||||||
|
},
|
||||||
|
[remove]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul
|
||||||
|
className={classNames(
|
||||||
|
'absolute top-2 right-2 overflow-hidden max-w-full',
|
||||||
|
{
|
||||||
|
'flex flex-col-reverse': order === 'desc',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{toasts.map((toast) => {
|
||||||
|
return (
|
||||||
|
<li key={toast.id}>
|
||||||
|
<Toast onClose={onClose} {...toast} />
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
64
libs/ui-toolkit/src/components/toast/use-toasts.ts
Normal file
64
libs/ui-toolkit/src/components/toast/use-toasts.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import create from 'zustand';
|
||||||
|
import type { Toast } from './toast';
|
||||||
|
|
||||||
|
type ToastsStore = {
|
||||||
|
/**
|
||||||
|
* A list of active toasts
|
||||||
|
*/
|
||||||
|
toasts: Toast[];
|
||||||
|
/**
|
||||||
|
* Adds/displays a new toast
|
||||||
|
*/
|
||||||
|
add: (toast: Toast) => void;
|
||||||
|
/**
|
||||||
|
* Updates a toast
|
||||||
|
*/
|
||||||
|
update: (id: string, toastData: Partial<Toast>) => void;
|
||||||
|
/**
|
||||||
|
* Closes a toast
|
||||||
|
*/
|
||||||
|
close: (id: string) => void;
|
||||||
|
/**
|
||||||
|
* Closes all toasts
|
||||||
|
*/
|
||||||
|
closeAll: () => void;
|
||||||
|
/**
|
||||||
|
* Arbitrary removes a toast
|
||||||
|
*/
|
||||||
|
remove: (id: string) => void;
|
||||||
|
/**
|
||||||
|
* Arbitrary removes all toasts
|
||||||
|
*/
|
||||||
|
removeAll: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const update =
|
||||||
|
(id: string, toastData: Partial<Toast>) =>
|
||||||
|
(store: ToastsStore): Partial<ToastsStore> => {
|
||||||
|
const toasts = [...store.toasts];
|
||||||
|
const toastIdx = toasts.findIndex((t) => t.id === id);
|
||||||
|
if (toastIdx > -1) toasts[toastIdx] = { ...toasts[toastIdx], ...toastData };
|
||||||
|
return { toasts };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useToasts = create<ToastsStore>((set) => ({
|
||||||
|
toasts: [],
|
||||||
|
add: (toast) =>
|
||||||
|
set((state) => ({
|
||||||
|
toasts: [...state.toasts, toast],
|
||||||
|
})),
|
||||||
|
update: (id, toastData) => set(update(id, toastData)),
|
||||||
|
close: (id) => set(update(id, { signal: 'close' })),
|
||||||
|
closeAll: () =>
|
||||||
|
set((state) => ({
|
||||||
|
toasts: [...state.toasts].map((t) => ({ ...t, signal: 'close' })),
|
||||||
|
})),
|
||||||
|
remove: (id) =>
|
||||||
|
set((state) => ({
|
||||||
|
toasts: [...state.toasts].filter((t) => t.id !== id),
|
||||||
|
})),
|
||||||
|
removeAll: () =>
|
||||||
|
set(() => ({
|
||||||
|
toasts: [],
|
||||||
|
})),
|
||||||
|
}));
|
@ -1,3 +1,3 @@
|
|||||||
@tailwind base;
|
@import 'tailwindcss/base';
|
||||||
@tailwind components;
|
@import 'tailwindcss/components';
|
||||||
@tailwind utilities;
|
@import 'tailwindcss/utilities';
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"**/*.test.jsx",
|
"**/*.test.jsx",
|
||||||
"**/*.spec.jsx",
|
"**/*.spec.jsx",
|
||||||
"**/*.d.ts",
|
"**/*.d.ts",
|
||||||
"jest.config.ts"
|
"jest.config.ts",
|
||||||
|
"../../index.d.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
"include": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
@ -12,8 +12,6 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": [],
|
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.lib.json"
|
"path": "./tsconfig.lib.json"
|
||||||
|
@ -4,10 +4,6 @@
|
|||||||
"outDir": "../../dist/out-tsc",
|
"outDir": "../../dist/out-tsc",
|
||||||
"types": ["node"]
|
"types": ["node"]
|
||||||
},
|
},
|
||||||
"files": [
|
|
||||||
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
|
||||||
"../../node_modules/@nrwl/react/typings/image.d.ts"
|
|
||||||
],
|
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"**/*.spec.ts",
|
"**/*.spec.ts",
|
||||||
"**/*.test.ts",
|
"**/*.test.ts",
|
||||||
|
@ -113,6 +113,7 @@
|
|||||||
"@nrwl/workspace": "14.5.10",
|
"@nrwl/workspace": "14.5.10",
|
||||||
"@sentry/webpack-plugin": "^1.18.8",
|
"@sentry/webpack-plugin": "^1.18.8",
|
||||||
"@storybook/addon-a11y": "^6.4.19",
|
"@storybook/addon-a11y": "^6.4.19",
|
||||||
|
"@storybook/addon-docs": "^6.5.13",
|
||||||
"@storybook/addon-essentials": "6.5.10",
|
"@storybook/addon-essentials": "6.5.10",
|
||||||
"@storybook/builder-webpack5": "6.5.10",
|
"@storybook/builder-webpack5": "6.5.10",
|
||||||
"@storybook/core-server": "6.5.10",
|
"@storybook/core-server": "6.5.10",
|
||||||
|
@ -48,5 +48,9 @@
|
|||||||
"@vegaprotocol/withdraws": ["libs/withdraws/src/index.ts"]
|
"@vegaprotocol/withdraws": ["libs/withdraws/src/index.ts"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "tmp"]
|
"exclude": ["node_modules", "tmp"],
|
||||||
|
"files": [
|
||||||
|
"node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||||
|
"node_modules/@nrwl/react/typings/image.d.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user