feat: toast component (1677) (#1998)

This commit is contained in:
Art 2022-11-15 15:26:13 +01:00 committed by GitHub
parent e598cd1247
commit a3df65952d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1409 additions and 160 deletions

View File

@ -1,6 +1,24 @@
module.exports = {
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
// webpackFinal: async (config, { configType }) => {
// // Make whatever fine-grained changes you need that should apply to all storybook configs

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -13,7 +13,6 @@
"noFallthroughCasesInSwitch": true,
"lib": ["es5", "es6", "dom", "dom.iterable"]
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -13,7 +13,6 @@
"noFallthroughCasesInSwitch": true,
"lib": ["es5", "es6", "dom", "dom.iterable"]
},
"files": [],
"include": [],
"references": [
{

View File

@ -1,6 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -13,5 +13,6 @@
"incremental": true
},
"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"]
}

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -1,6 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -1,6 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -9,7 +9,6 @@
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -10,7 +10,6 @@
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -37,3 +37,4 @@ export * from './tooltip';
export * from './vega-icons';
export * from './vega-logo';
export * from './traffic-light';
export * from './toast';

View File

@ -0,0 +1,3 @@
export * from './toast';
export * from './toasts-container';
export * from './use-toasts';

View 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;
}

View 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',
};

View 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>
);
};

View 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([]);
});
});

View File

@ -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',
};

View 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>
);
};

View 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: [],
})),
}));

View File

@ -1,3 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -15,6 +15,7 @@
"**/*.test.jsx",
"**/*.spec.jsx",
"**/*.d.ts",
"jest.config.ts"
"jest.config.ts",
"../../index.d.ts"
]
}

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,7 +12,6 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{

View File

@ -12,8 +12,6 @@
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"

View File

@ -4,10 +4,6 @@
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": [
"**/*.spec.ts",
"**/*.test.ts",

View File

@ -113,6 +113,7 @@
"@nrwl/workspace": "14.5.10",
"@sentry/webpack-plugin": "^1.18.8",
"@storybook/addon-a11y": "^6.4.19",
"@storybook/addon-docs": "^6.5.13",
"@storybook/addon-essentials": "6.5.10",
"@storybook/builder-webpack5": "6.5.10",
"@storybook/core-server": "6.5.10",

View File

@ -48,5 +48,9 @@
"@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"
]
}

841
yarn.lock

File diff suppressed because it is too large Load Diff