mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-13 19:33:55 +03:00
console: Add alert/confirm/prompt component
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8701 GitOrigin-RevId: 558b6d3e688e4d9d945ba33c459565f4b0f6f502
This commit is contained in:
parent
a8b94120d1
commit
aa273cdc4c
@ -12,6 +12,7 @@ import '../src/lib/theme/tailwind.css';
|
||||
import { store } from '../src/lib/store';
|
||||
import '../src/lib/components/Common/Common.module.scss';
|
||||
import { ToastsHub } from '../src/lib/new-components/Toasts';
|
||||
import { AlertProvider } from '../src/lib/new-components/Alert/AlertProvider';
|
||||
|
||||
const channel = addons.getChannel();
|
||||
initialize();
|
||||
@ -74,10 +75,14 @@ export const decorators = [
|
||||
Story => {
|
||||
document.body.classList.add('hasura-tailwind-on');
|
||||
return (
|
||||
<div>
|
||||
<ToastsHub />
|
||||
<div className={'bg-legacybg'}>{Story()}</div>
|
||||
</div>
|
||||
<AlertProvider>
|
||||
<div>
|
||||
<ToastsHub />
|
||||
<div className={'bg-legacybg'}>
|
||||
<Story />
|
||||
</div>
|
||||
</div>
|
||||
</AlertProvider>
|
||||
);
|
||||
},
|
||||
];
|
||||
|
@ -9,6 +9,7 @@ import ErrorBoundary from '../Error/ErrorBoundary';
|
||||
import globals from '../../Globals';
|
||||
import styles from './App.module.scss';
|
||||
import { ToastsHub } from '../../new-components/Toasts';
|
||||
import { AlertProvider } from '../../new-components/Alert/AlertProvider';
|
||||
|
||||
import { theme } from '../UIKit/theme';
|
||||
import { trackCustomEvent } from '../../features/Analytics';
|
||||
@ -60,19 +61,21 @@ const App = ({
|
||||
<GlobalContext.Provider value={globals}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<ErrorBoundary metadata={metadata} dispatch={dispatch}>
|
||||
<div>
|
||||
{connectionFailMsg}
|
||||
{ongoingRequest && (
|
||||
<ProgressBar
|
||||
percent={percent}
|
||||
autoIncrement={true} // eslint-disable-line react/jsx-boolean-value
|
||||
intervalTime={intervalTime}
|
||||
spinner={false}
|
||||
/>
|
||||
)}
|
||||
<div>{children}</div>
|
||||
<ToastsHub />
|
||||
</div>
|
||||
<AlertProvider>
|
||||
<div>
|
||||
{connectionFailMsg}
|
||||
{ongoingRequest && (
|
||||
<ProgressBar
|
||||
percent={percent}
|
||||
autoIncrement={true} // eslint-disable-line react/jsx-boolean-value
|
||||
intervalTime={intervalTime}
|
||||
spinner={false}
|
||||
/>
|
||||
)}
|
||||
<div>{children}</div>
|
||||
<ToastsHub />
|
||||
</div>
|
||||
</AlertProvider>
|
||||
</ErrorBoundary>
|
||||
</ThemeProvider>
|
||||
</GlobalContext.Provider>
|
||||
|
@ -0,0 +1,622 @@
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import React from 'react';
|
||||
|
||||
import { expect } from '@storybook/jest';
|
||||
import { screen, userEvent, within } from '@storybook/testing-library';
|
||||
import { useHasuraAlert } from '.';
|
||||
import { Button } from '../Button';
|
||||
import { useDestructiveAlert } from './AlertProvider';
|
||||
|
||||
export default {
|
||||
title: 'components/Alert Dialog 🧬',
|
||||
decorators: [
|
||||
Story => (
|
||||
<div className="p-4 flex gap-5 items-center max-w-screen">{Story()}</div>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<any>;
|
||||
|
||||
/**
|
||||
*
|
||||
* Basic Alert
|
||||
*
|
||||
*/
|
||||
|
||||
export const Alert: ComponentStory<any> = () => {
|
||||
const { hasuraAlert } = useHasuraAlert();
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraAlert({
|
||||
message: 'This is an alert!',
|
||||
title: 'Some Title',
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open an alert!
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Alert.storyName = '🧰 Alert';
|
||||
Alert.play = async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
await expect(await screen.findByText('Some Title')).toBeInTheDocument();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Basic Confirm
|
||||
*
|
||||
*/
|
||||
|
||||
export const Confirm: ComponentStory<any> = () => {
|
||||
const { hasuraConfirm } = useHasuraAlert();
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraConfirm({
|
||||
message: 'This is a confirm!',
|
||||
title: 'Some Title',
|
||||
onClose: ({ confirmed }) => {},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open a confirm!
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Confirm.storyName = '🧰 Confirm';
|
||||
Confirm.play = async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
await expect(await screen.findByText('Some Title')).toBeInTheDocument();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Confirm Interaction Test
|
||||
*
|
||||
* - tests if user selection of confirm/cancel is set correctly
|
||||
*
|
||||
*/
|
||||
|
||||
export const ConfirmTest: ComponentStory<any> = () => {
|
||||
const { hasuraConfirm } = useHasuraAlert();
|
||||
const [choice, setChoice] = React.useState<'cancelled' | 'confirmed'>();
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraConfirm({
|
||||
message: 'This is a confirm!',
|
||||
title: 'Some Title',
|
||||
onClose: ({ confirmed }) => {
|
||||
setChoice(confirmed ? 'confirmed' : 'cancelled');
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open a confirm!
|
||||
</Button>
|
||||
<div>
|
||||
Your selection: <span data-testid="choice">{choice ?? ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ConfirmTest.storyName = '🧪 Confirm';
|
||||
ConfirmTest.play = async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
await expect(await screen.findByText('Some Title')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(await screen.findByText('Ok'));
|
||||
await expect(await screen.findByTestId('choice')).toHaveTextContent(
|
||||
'confirmed'
|
||||
);
|
||||
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
await expect(await screen.findByText('Some Title')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(await screen.findByText('Cancel'));
|
||||
await expect(await screen.findByTestId('choice')).toHaveTextContent(
|
||||
'cancelled'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Basic Prompt
|
||||
*
|
||||
*/
|
||||
|
||||
export const Prompt: ComponentStory<any> = () => {
|
||||
const { hasuraPrompt } = useHasuraAlert();
|
||||
const [choice, setChoice] = React.useState('');
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraPrompt({
|
||||
message: 'This is a prompt',
|
||||
title: 'Some Title',
|
||||
onClose: result => {
|
||||
if (result.confirmed) {
|
||||
// discriminated union only makes result.promptValue available when user confirms
|
||||
setChoice(result.promptValue);
|
||||
} else {
|
||||
//no prompt value here.
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open a prompt!
|
||||
</Button>
|
||||
<div className="my-4">Your value: {choice}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Prompt.play = async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
await expect(await screen.findByText('Some Title')).toBeInTheDocument();
|
||||
};
|
||||
Prompt.storyName = '🧰 Prompt';
|
||||
|
||||
/**
|
||||
*
|
||||
* Prompt Interaction Test
|
||||
*
|
||||
* - Tests if value that user enters, and is passed to callback matches when set to component state
|
||||
*
|
||||
*/
|
||||
export const PromptTest: ComponentStory<any> = () => {
|
||||
const { hasuraPrompt } = useHasuraAlert();
|
||||
const [value, setValue] = React.useState('');
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraPrompt({
|
||||
message: 'This is a prompt',
|
||||
title: 'Some Title',
|
||||
promptLabel: 'Input Label',
|
||||
onClose: result => {
|
||||
if (result.confirmed) {
|
||||
// discriminated union only makes result.promptValue available when user confirms
|
||||
setValue(result.promptValue);
|
||||
} else {
|
||||
//no prompt value here.
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open a prompt!
|
||||
</Button>
|
||||
<div className="my-4">
|
||||
Your value: <span data-testid="prompt-value">{value}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
PromptTest.play = async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
await expect(await screen.findByText('Some Title')).toBeInTheDocument();
|
||||
await userEvent.type(await screen.findByLabelText('Input Label'), 'blah');
|
||||
await userEvent.click(await screen.findByText('Ok'));
|
||||
await expect(await screen.findByTestId('prompt-value')).toHaveTextContent(
|
||||
'blah'
|
||||
);
|
||||
};
|
||||
PromptTest.storyName = '🧪 Prompt';
|
||||
|
||||
/**
|
||||
*
|
||||
* Confirm with custom text
|
||||
*
|
||||
*/
|
||||
export const CustomText: ComponentStory<any> = () => {
|
||||
const { hasuraConfirm } = useHasuraAlert();
|
||||
|
||||
const [choice, setChoice] = React.useState('');
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraConfirm({
|
||||
message: 'Which path will you choose?',
|
||||
title: 'Choose Wisely!',
|
||||
cancelText: 'Good',
|
||||
confirmText: 'Evil',
|
||||
onClose: ({ confirmed }) => {
|
||||
setChoice(!confirmed ? 'Good 😇' : 'Evil 😈');
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open a confirm!
|
||||
</Button>
|
||||
<div className="my-4">You chose: {choice}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CustomText.storyName = '🎭 Variant - Custom Button Text';
|
||||
CustomText.play = async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
await expect(await screen.findByText('Choose Wisely!')).toBeInTheDocument();
|
||||
|
||||
await expect(await screen.findByText('Good')).toBeInTheDocument();
|
||||
await expect(await screen.findByText('Evil')).toBeInTheDocument();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Confirm - with destructive flag
|
||||
*
|
||||
*/
|
||||
|
||||
export const Destructive: ComponentStory<any> = () => {
|
||||
const { hasuraConfirm } = useHasuraAlert();
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraConfirm({
|
||||
message: 'Do the risky thing?',
|
||||
title: 'Are you sure?',
|
||||
destructive: true,
|
||||
onClose: ({ confirmed }) => {
|
||||
//do something
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open a destructive confirm!
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Destructive.storyName = '🎭 Variant - Destructive';
|
||||
|
||||
Destructive.play = async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
await expect(await screen.findByText('Are you sure?')).toBeInTheDocument();
|
||||
|
||||
await expect(
|
||||
screen.getByRole('button', {
|
||||
name: /Ok/i,
|
||||
})
|
||||
).toHaveClass('text-red-600');
|
||||
};
|
||||
const doAsyncAction = () => {
|
||||
return new Promise<void>(res => {
|
||||
setTimeout(() => {
|
||||
res();
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Confirm - demonstrates Async Mode
|
||||
*
|
||||
*/
|
||||
|
||||
export const AsyncMode: ComponentStory<any> = () => {
|
||||
const { hasuraConfirm } = useHasuraAlert();
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraConfirm({
|
||||
message: 'Async mode with a loading spinner',
|
||||
title: 'Async Operation',
|
||||
confirmText: 'Save Data',
|
||||
onCloseAsync: async ({ confirmed }) => {
|
||||
if (confirmed) {
|
||||
await doAsyncAction();
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open a confirm!
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AsyncMode.storyName = '🪄 Async Confirm';
|
||||
AsyncMode.parameters = {
|
||||
docs: {
|
||||
description: {
|
||||
story: `#### 🚦 Usage
|
||||
- Use \`onCloseAsync\` instead of \`onClose\` and return a \`Promise\`. Loading spinner will show until Promise is resolved.`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Confirm - demonstrates Async Mode w/ optional success state
|
||||
*
|
||||
*/
|
||||
|
||||
export const AsyncModeWithSuccess: ComponentStory<any> = () => {
|
||||
const { hasuraConfirm } = useHasuraAlert();
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraConfirm({
|
||||
message:
|
||||
'Async action with a loading spinner followed by a success indication',
|
||||
title: 'Async Operation',
|
||||
confirmText: 'Save Data',
|
||||
onCloseAsync: async ({ confirmed }) => {
|
||||
if (confirmed) {
|
||||
await doAsyncAction();
|
||||
return { withSuccess: true, successText: 'Saved!' };
|
||||
} else {
|
||||
return { withSuccess: false };
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open a confirm!
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AsyncModeWithSuccess.storyName = '🪄 Async Confirm - with success indicator';
|
||||
|
||||
AsyncModeWithSuccess.parameters = {
|
||||
docs: {
|
||||
description: {
|
||||
story: `#### 🚦 Usage
|
||||
- Use \`onCloseAsync\` instead of \`onClose\` and return a \`Promise\`. Loading spinner will show until Promise is resolved.
|
||||
- To enable a success indication, return an object from your Promise like this: \`{ withSuccess: true, successText: 'Saved!' }\``,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Prompt - demonstrates Async Mode w/ success state
|
||||
*
|
||||
*/
|
||||
|
||||
export const AsyncPrompt: ComponentStory<any> = () => {
|
||||
const { hasuraPrompt } = useHasuraAlert();
|
||||
const [value, setValue] = React.useState('');
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraPrompt({
|
||||
message:
|
||||
'Async action with a loading spinner followed by a success indication',
|
||||
title: 'Async Operation',
|
||||
confirmText: 'Save Data',
|
||||
onCloseAsync: async result => {
|
||||
if (result.confirmed) {
|
||||
await doAsyncAction();
|
||||
|
||||
setValue(result.promptValue);
|
||||
|
||||
return { withSuccess: true, successText: 'Saved!' };
|
||||
} else {
|
||||
return { withSuccess: false };
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open a prompt!
|
||||
</Button>
|
||||
<div className="my-4">
|
||||
Your value: <span data-testid="prompt-value">{value}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AsyncPrompt.storyName = '🪄 Async Prompt - with success indicator';
|
||||
|
||||
/**
|
||||
*
|
||||
* Alert - demonstrates Async Mode w/ success state
|
||||
*
|
||||
*/
|
||||
export const AsyncAlert: ComponentStory<any> = () => {
|
||||
const { hasuraAlert } = useHasuraAlert();
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraAlert({
|
||||
message:
|
||||
'Async action with a loading spinner followed by a success indication',
|
||||
title: 'Async Operation',
|
||||
confirmText: 'Save Data',
|
||||
onCloseAsync: async () => {
|
||||
await doAsyncAction();
|
||||
|
||||
return { withSuccess: true, successText: 'Saved!' };
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open an alert!
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AsyncAlert.storyName = '🪄 Async Alert - with success indicator';
|
||||
|
||||
/**
|
||||
*
|
||||
* Async Error Handling - demonstrates built-in error handling
|
||||
*
|
||||
*/
|
||||
export const ErrorHandling: ComponentStory<any> = () => {
|
||||
const { hasuraAlert } = useHasuraAlert();
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraAlert({
|
||||
message:
|
||||
'This alert will throw an error during the onClose callback',
|
||||
title: 'Some Operation',
|
||||
confirmText: 'Save Data',
|
||||
onClose: () => {
|
||||
throw new Error('Whoops this was not handled!');
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open an alert!
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ErrorHandling.storyName = '🪄 Error Handling';
|
||||
|
||||
/**
|
||||
*
|
||||
* Async Error Handling - demonstrates built-in error handling
|
||||
*
|
||||
*/
|
||||
export const AsyncErrorHandling: ComponentStory<any> = () => {
|
||||
const { hasuraAlert } = useHasuraAlert();
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
hasuraAlert({
|
||||
message: 'This alert will throw an error after a timeout',
|
||||
title: 'Async Operation',
|
||||
confirmText: 'Save Data',
|
||||
onCloseAsync: async () => {
|
||||
await doAsyncAction();
|
||||
throw new Error('Whoops this was not handled!');
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open an alert!
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AsyncErrorHandling.storyName = '🪄 Error Handling - Async Mode';
|
||||
|
||||
/**
|
||||
*
|
||||
* useDestructiveConfirm - demonstrates wrapper function
|
||||
*
|
||||
*/
|
||||
export const DestructiveConfirm: ComponentStory<any> = () => {
|
||||
const { destructiveConfirm } = useDestructiveAlert();
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
destructiveConfirm({
|
||||
resourceName: 'My Database',
|
||||
resourceType: 'Data Source',
|
||||
destroyTerm: 'remove',
|
||||
onConfirm: async () => {
|
||||
await doAsyncAction();
|
||||
|
||||
//return a boolean to indicate success:
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open a destructive confirm!
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DestructiveConfirm.storyName = '💥 Destructive Confirm';
|
||||
|
||||
DestructiveConfirm.parameters = {
|
||||
docs: {
|
||||
description: {
|
||||
story: `#### 🚦 Usage
|
||||
- When needing a confirm to delete a resource, this hook standardizes the UI/UX and language.`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* useDestructivePrompt - demonstrates wrapper function
|
||||
*
|
||||
*/
|
||||
export const DestructivePrompt: ComponentStory<any> = () => {
|
||||
const { destructivePrompt } = useDestructiveAlert();
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Button
|
||||
onClick={() => {
|
||||
destructivePrompt({
|
||||
resourceName: 'My Database',
|
||||
resourceType: 'Data Source',
|
||||
destroyTerm: 'remove',
|
||||
onConfirm: async () => {
|
||||
await doAsyncAction();
|
||||
|
||||
//return a boolean to indicate success:
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open an destructive prompt!
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DestructivePrompt.storyName = '💥 Destructive Prompt';
|
||||
DestructivePrompt.parameters = {
|
||||
docs: {
|
||||
description: {
|
||||
story: `#### 🚦 Usage
|
||||
- When needing a prompt to delete a resource, this hook standardizes the UI/UX and language.`,
|
||||
},
|
||||
},
|
||||
};
|
@ -0,0 +1,148 @@
|
||||
import * as AlertDialog from '@radix-ui/react-alert-dialog';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { BsCheckCircleFill } from 'react-icons/bs';
|
||||
import useUpdateEffect from '../../hooks/useUpdateEffect';
|
||||
import { sanitizeGraphQLFieldNames } from '../../utils';
|
||||
import { Button } from '../Button';
|
||||
import { Input } from '../Form';
|
||||
import { AlertComponentProps } from './component-types';
|
||||
|
||||
const buttonMode = (props: AlertComponentProps) => {
|
||||
return props.success
|
||||
? 'success'
|
||||
: props.mode !== 'alert' && props.destructive
|
||||
? 'destructive'
|
||||
: 'primary';
|
||||
};
|
||||
|
||||
function Buttons(props: AlertComponentProps & { promptValue: string }) {
|
||||
const {
|
||||
confirmText,
|
||||
onClose,
|
||||
onCloseAsync,
|
||||
promptValue,
|
||||
mode,
|
||||
isLoading,
|
||||
success,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="flex justify-end gap-[12px]">
|
||||
{(mode === 'confirm' || mode === 'prompt') && !success && (
|
||||
<AlertDialog.Cancel asChild>
|
||||
{/* CANCEL BUTTON: */}
|
||||
<Button
|
||||
autoFocus
|
||||
disabled={isLoading}
|
||||
onClick={() => {
|
||||
if (mode === 'prompt') {
|
||||
(onClose ?? onCloseAsync)?.({ confirmed: false });
|
||||
} else {
|
||||
(onClose ?? onCloseAsync)?.({ confirmed: false });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{props?.cancelText ?? 'Cancel'}
|
||||
</Button>
|
||||
</AlertDialog.Cancel>
|
||||
)}
|
||||
<AlertDialog.Action asChild>
|
||||
{/* CONFIRM BUTTON: */}
|
||||
<Button
|
||||
autoFocus
|
||||
disabled={isLoading}
|
||||
className={clsx(success && 'pointer-events-none select-none')}
|
||||
onClick={() => {
|
||||
// pointer-events-none should handle this, but just in case...
|
||||
if (success) return;
|
||||
|
||||
if (mode === 'prompt') {
|
||||
(onClose ?? onCloseAsync)?.({
|
||||
confirmed: true,
|
||||
promptValue,
|
||||
});
|
||||
} else {
|
||||
(onClose ?? onCloseAsync)?.({ confirmed: true });
|
||||
}
|
||||
}}
|
||||
iconPosition="end"
|
||||
icon={success ? <BsCheckCircleFill /> : undefined}
|
||||
mode={buttonMode(props)}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{confirmText ?? 'Ok'}
|
||||
</Button>
|
||||
</AlertDialog.Action>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Alert = (props: AlertComponentProps) => {
|
||||
const { title, message, mode, open, isLoading, success } = props;
|
||||
// we only want to apply an open prop if it's supplied as true/false, but NOT undefined. which likely means the trigger element is in use.
|
||||
const openProps = React.useMemo(
|
||||
() => ({
|
||||
...(typeof open !== 'undefined' && { open: open }),
|
||||
}),
|
||||
[open]
|
||||
);
|
||||
|
||||
// makes sure the prompt values are cleared after close
|
||||
useUpdateEffect(() => {
|
||||
if (open === false) {
|
||||
setPromptValue('');
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const [promptValue, setPromptValue] = React.useState('');
|
||||
return (
|
||||
<AlertDialog.Root {...openProps}>
|
||||
<AlertDialog.Portal>
|
||||
<AlertDialog.Overlay className="bg-gray-700/40 z-[100] data-[state=open]:animate-fadeIn fixed inset-0" />
|
||||
<AlertDialog.Content className="z-[101] data-[state=open]:animate-alertContentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[500px] translate-x-[-50%] translate-y-[-50%] rounded-[6px] bg-white p-[25px] shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] focus:outline-none">
|
||||
<AlertDialog.Title className="text-gray-900 m-0 text-[17px] font-medium">
|
||||
{title}
|
||||
</AlertDialog.Title>
|
||||
<AlertDialog.Description
|
||||
className={clsx(
|
||||
'text-gray-700 mt-4 text-[15px] leading-normal',
|
||||
mode === 'prompt' ? 'mb-3' : 'mb-5'
|
||||
)}
|
||||
>
|
||||
{message}
|
||||
</AlertDialog.Description>
|
||||
{mode === 'prompt' && (
|
||||
<div className="mb-5">
|
||||
{!!props.promptLabel && (
|
||||
<label
|
||||
className={clsx('block pt-1 text-muted mb-1')}
|
||||
htmlFor="prompt-input"
|
||||
>
|
||||
{props.promptLabel}
|
||||
</label>
|
||||
)}
|
||||
<Input
|
||||
disabled={isLoading || success}
|
||||
placeholder={props?.promptPlaceholder ?? ''}
|
||||
name="prompt-input"
|
||||
//disabled={isLoading || success}
|
||||
fieldProps={{ value: promptValue, autoFocus: true }}
|
||||
onChange={e => {
|
||||
let value = e.target.value;
|
||||
|
||||
if (props.sanitizeGraphQL) {
|
||||
value = sanitizeGraphQLFieldNames(e.target.value);
|
||||
}
|
||||
|
||||
setPromptValue(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Buttons {...props} promptValue={promptValue} />
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Portal>
|
||||
</AlertDialog.Root>
|
||||
);
|
||||
};
|
@ -0,0 +1,294 @@
|
||||
import capitalize from 'lodash/capitalize';
|
||||
import React, { createContext } from 'react';
|
||||
import { hasuraToast } from '../Toasts';
|
||||
import { Alert } from './Alert';
|
||||
import { AlertComponentProps } from './component-types';
|
||||
import {
|
||||
AlertMode,
|
||||
AlertParams,
|
||||
ConfirmParams,
|
||||
DismissAlertParams,
|
||||
Params,
|
||||
PromptParams,
|
||||
} from './types';
|
||||
|
||||
interface AlertContextType {
|
||||
hasuraAlert: (props: Params<'alert'>) => void;
|
||||
hasuraConfirm: (props: Params<'confirm'>) => void;
|
||||
hasuraPrompt: (props: Params<'prompt'>) => void;
|
||||
}
|
||||
|
||||
const AlertContext = createContext<AlertContextType>({
|
||||
hasuraAlert: async () => {
|
||||
// init
|
||||
},
|
||||
hasuraConfirm: async () => {
|
||||
// init
|
||||
},
|
||||
hasuraPrompt: async () => {
|
||||
// init
|
||||
},
|
||||
});
|
||||
|
||||
export const useHasuraAlert = () => {
|
||||
const ctx = React.useContext(AlertContext);
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
||||
export const AlertProvider: React.FC = ({ children }) => {
|
||||
const [showAlert, setShowAlert] = React.useState(false);
|
||||
|
||||
const [componentProps, setComponentProps] =
|
||||
React.useState<AlertComponentProps | null>(null);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [success, setSuccess] = React.useState(false);
|
||||
const [successText, setSuccessText] = React.useState<string | null>(null);
|
||||
|
||||
const closeAndCleanup = React.useCallback(() => {
|
||||
setShowAlert(false);
|
||||
|
||||
//reset success state
|
||||
setSuccess(false);
|
||||
setSuccessText(null);
|
||||
setLoading(false);
|
||||
setComponentProps(null);
|
||||
}, []);
|
||||
|
||||
const dismissAlert = React.useCallback(
|
||||
(props?: DismissAlertParams) => {
|
||||
const { withSuccess, successText, successDelay = 1500 } = props ?? {};
|
||||
|
||||
if (withSuccess) {
|
||||
if (successText) {
|
||||
setSuccessText(successText);
|
||||
}
|
||||
|
||||
//show a success state
|
||||
setSuccess(true);
|
||||
|
||||
setTimeout(() => {
|
||||
closeAndCleanup();
|
||||
}, successDelay);
|
||||
} else {
|
||||
closeAndCleanup();
|
||||
}
|
||||
},
|
||||
[closeAndCleanup]
|
||||
);
|
||||
|
||||
// ideally errors should be handled elsewhere
|
||||
// this is a failsafe to prevent an error state from freezing an alert on screen
|
||||
const handleUnhandledError = React.useCallback(
|
||||
(e: unknown, mode: AlertMode) => {
|
||||
setLoading(false);
|
||||
closeAndCleanup();
|
||||
hasuraToast({
|
||||
type: 'error',
|
||||
title: `Unhandled error occurred while executing ${mode} dialog.`,
|
||||
message: e?.toString() ?? JSON.stringify(e),
|
||||
});
|
||||
},
|
||||
[closeAndCleanup]
|
||||
);
|
||||
|
||||
// function that will fire an alert:
|
||||
const fireAlert = React.useCallback(
|
||||
(params: AlertParams | ConfirmParams | PromptParams) => {
|
||||
// just in case so we don't display a broken alert
|
||||
if (!params) throw Error('Invalid state passed to fireAlert()');
|
||||
|
||||
const extendedProps = { ...params };
|
||||
|
||||
if (
|
||||
extendedProps?.onClose ||
|
||||
//handle when alert has no onClose or onCloseAsync passed
|
||||
(params.mode === 'alert' && !params?.onClose && !params?.onCloseAsync)
|
||||
) {
|
||||
//SYNC MODE:
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
extendedProps.onClose = async (args: any) => {
|
||||
closeAndCleanup();
|
||||
// call original callback
|
||||
try {
|
||||
params.onClose?.(args);
|
||||
} catch (e) {
|
||||
handleUnhandledError(e, params.mode);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
//ASYNC MODE:
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
extendedProps.onCloseAsync = async (args: any) => {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const result = await params.onCloseAsync?.(args);
|
||||
|
||||
setLoading(false);
|
||||
|
||||
dismissAlert({
|
||||
withSuccess: result?.withSuccess,
|
||||
successText: result?.successText,
|
||||
});
|
||||
} catch (e) {
|
||||
handleUnhandledError(e, params.mode);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// this is to prevent an issue where if moving from other radix components that involve overlays, it will get confused and leave the overlay pointer-events:none property on the body.
|
||||
// the timeout prevents overlapping css calls to modify the pointer events on the body
|
||||
setTimeout(() => {
|
||||
setComponentProps({ ...extendedProps });
|
||||
setShowAlert(true);
|
||||
}, 0);
|
||||
},
|
||||
[closeAndCleanup, dismissAlert]
|
||||
);
|
||||
|
||||
return (
|
||||
<AlertContext.Provider
|
||||
value={{
|
||||
hasuraAlert: params => fireAlert({ ...params, mode: 'alert' }),
|
||||
hasuraConfirm: params => fireAlert({ ...params, mode: 'confirm' }),
|
||||
hasuraPrompt: params => fireAlert({ ...params, mode: 'prompt' }),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<Alert
|
||||
{...(componentProps ?? {
|
||||
message: 'error',
|
||||
title: 'error',
|
||||
mode: 'alert',
|
||||
onClose: () => {
|
||||
// init
|
||||
},
|
||||
})}
|
||||
isLoading={loading}
|
||||
open={showAlert}
|
||||
success={success}
|
||||
confirmText={
|
||||
success && !!successText ? successText : componentProps?.confirmText
|
||||
}
|
||||
/>
|
||||
</AlertContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* These are wrapper hooks that simplify and standardize the API/UI/UX for remove/delete operations
|
||||
*
|
||||
*/
|
||||
|
||||
const useDestructiveConfirm = () => {
|
||||
const { hasuraConfirm } = useHasuraAlert();
|
||||
return ({
|
||||
resourceName,
|
||||
resourceType,
|
||||
destroyTerm = 'remove',
|
||||
onConfirm,
|
||||
}: {
|
||||
resourceName: string;
|
||||
resourceType: string;
|
||||
destroyTerm?: 'delete' | 'remove';
|
||||
onConfirm: () => Promise<boolean>;
|
||||
}) => {
|
||||
if (!onConfirm) throw new Error('onCloseAsync() is required.');
|
||||
|
||||
hasuraConfirm({
|
||||
title: `${capitalize(destroyTerm)} ${resourceType}`,
|
||||
message: (
|
||||
<div>
|
||||
Are you sure you want to {destroyTerm} {resourceType}:{' '}
|
||||
<strong>{resourceName}</strong>?
|
||||
</div>
|
||||
),
|
||||
confirmText: 'Remove',
|
||||
destructive: true,
|
||||
onCloseAsync: async ({ confirmed }) => {
|
||||
if (!confirmed) return;
|
||||
|
||||
const success = await onConfirm();
|
||||
|
||||
if (success) {
|
||||
return {
|
||||
withSuccess: success,
|
||||
successText: `${capitalize(destroyTerm)}d ${resourceName}`,
|
||||
};
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const useDestructivePrompt = () => {
|
||||
const { hasuraPrompt } = useHasuraAlert();
|
||||
|
||||
return ({
|
||||
resourceName,
|
||||
resourceType,
|
||||
destroyTerm = 'remove',
|
||||
onConfirm,
|
||||
}: {
|
||||
resourceName: string;
|
||||
resourceType: string;
|
||||
destroyTerm?: 'delete' | 'remove';
|
||||
onConfirm: () => Promise<boolean>;
|
||||
}) => {
|
||||
if (!onConfirm) throw new Error('onCloseAsync() is required.');
|
||||
|
||||
hasuraPrompt({
|
||||
title: `${capitalize(destroyTerm)} ${resourceType}`,
|
||||
message: (
|
||||
<div>
|
||||
Are you sure you want to {destroyTerm} {resourceType}:{' '}
|
||||
<strong>{resourceName}</strong>?
|
||||
</div>
|
||||
),
|
||||
confirmText: 'Remove',
|
||||
destructive: true,
|
||||
promptLabel: (
|
||||
<div>
|
||||
Type <strong>{resourceName}</strong> to confirm this action.
|
||||
</div>
|
||||
),
|
||||
onCloseAsync: async result => {
|
||||
if (!result.confirmed) return;
|
||||
if (result.promptValue !== resourceName) {
|
||||
hasuraToast({
|
||||
type: 'error',
|
||||
title: `Entry Not Matching`,
|
||||
message: `Your entry "${result.promptValue}" does not match "${resourceName}".`,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
const success = await onConfirm();
|
||||
|
||||
if (success) {
|
||||
return {
|
||||
withSuccess: success,
|
||||
successText: `${capitalize(destroyTerm)}d ${resourceName}`,
|
||||
};
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const useDestructiveAlert = () => {
|
||||
const destructiveConfirm = useDestructiveConfirm();
|
||||
const destructivePrompt = useDestructivePrompt();
|
||||
return {
|
||||
destructiveConfirm,
|
||||
destructivePrompt,
|
||||
};
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
import {
|
||||
AlertMode,
|
||||
AlertParams,
|
||||
CommonAlertBase,
|
||||
ConfirmParams,
|
||||
PromptParams,
|
||||
} from './types';
|
||||
|
||||
export type AlertComponentProps = AlertProps | ConfirmProps | PromptProps;
|
||||
|
||||
type AlertProps = PropsBase<'alert'> & AlertParams;
|
||||
type ConfirmProps = PropsBase<'confirm'> & ConfirmParams;
|
||||
type PromptProps = PropsBase<'prompt'> & PromptParams;
|
||||
|
||||
export type PropsBase<Mode extends AlertMode> = CommonAlertBase<Mode> & {
|
||||
open?: boolean;
|
||||
isLoading?: boolean;
|
||||
success?: boolean;
|
||||
};
|
@ -0,0 +1 @@
|
||||
export { useHasuraAlert, useDestructiveAlert } from './AlertProvider';
|
@ -0,0 +1,87 @@
|
||||
// see: https://stackoverflow.com/questions/57103834/typescript-omit-a-property-from-all-interfaces-in-a-union-but-keep-the-union-s
|
||||
// TS docs: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
|
||||
type DistributiveOmit<T, K extends PropertyKey> = T extends any
|
||||
? Omit<T, K>
|
||||
: never;
|
||||
|
||||
export type AlertMode = 'alert' | 'confirm' | 'prompt';
|
||||
|
||||
export type onCloseAsyncPromiseReturn = DismissAlertParams | void;
|
||||
|
||||
export type CommonAlertBase<Mode extends AlertMode> = {
|
||||
title: string;
|
||||
message: React.ReactNode;
|
||||
mode: Mode;
|
||||
confirmText?: string;
|
||||
};
|
||||
|
||||
export type ConfirmableBase = {
|
||||
cancelText?: string;
|
||||
destructive?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* For all alert modes types, onClose/onCloseAsync are added with an XOR (exclusive or) -- that is, only one or the other can be present on the type
|
||||
*
|
||||
*/
|
||||
|
||||
export type AlertParams = CommonAlertBase<'alert'> &
|
||||
(
|
||||
| { onClose?: () => void; onCloseAsync?: never }
|
||||
| {
|
||||
onClose?: never;
|
||||
onCloseAsync?: () => Promise<onCloseAsyncPromiseReturn>;
|
||||
}
|
||||
);
|
||||
|
||||
export type ConfirmParams = CommonAlertBase<'confirm'> &
|
||||
ConfirmableBase &
|
||||
(
|
||||
| { onClose: (args: onCloseProps) => void; onCloseAsync?: never }
|
||||
| {
|
||||
onClose?: never;
|
||||
onCloseAsync: (
|
||||
args: onCloseProps
|
||||
) => Promise<onCloseAsyncPromiseReturn>;
|
||||
}
|
||||
);
|
||||
|
||||
export type PromptParams = CommonAlertBase<'prompt'> &
|
||||
ConfirmableBase & {
|
||||
promptLabel?: React.ReactNode;
|
||||
promptPlaceholder?: string;
|
||||
sanitizeGraphQL?: boolean;
|
||||
} & (
|
||||
| { onClose: (args: onClosePromptProps) => void; onCloseAsync?: never }
|
||||
| {
|
||||
onClose?: never;
|
||||
onCloseAsync: (
|
||||
args: onClosePromptProps
|
||||
) => Promise<onCloseAsyncPromiseReturn>;
|
||||
}
|
||||
);
|
||||
|
||||
export type Params<Mode extends AlertMode> = DistributiveOmit<
|
||||
Extract<AlertParams | ConfirmParams | PromptParams, { mode: Mode }>,
|
||||
'mode'
|
||||
>;
|
||||
|
||||
// alert/confirm
|
||||
export type onCloseProps = { confirmed: boolean };
|
||||
|
||||
// prompt:
|
||||
export type onClosePromptProps =
|
||||
| {
|
||||
confirmed: true;
|
||||
promptValue: string;
|
||||
}
|
||||
| {
|
||||
confirmed: false;
|
||||
};
|
||||
|
||||
export type DismissAlertParams = {
|
||||
withSuccess?: boolean;
|
||||
successText?: string;
|
||||
successDelay?: number;
|
||||
};
|
@ -55,6 +55,9 @@ export const VariantMode: ComponentStory<typeof Button> = () => (
|
||||
<Button mode="destructive" onClick={action('onClick')}>
|
||||
<span>Destructive</span>
|
||||
</Button>
|
||||
<Button mode="success" onClick={action('onClick')}>
|
||||
<span>Success</span>
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
VariantMode.storyName = '🎭 Variant - Mode';
|
||||
@ -144,6 +147,14 @@ export const StateLoading: ComponentStory<typeof Button> = () => (
|
||||
>
|
||||
<span>Loading</span>
|
||||
</Button>
|
||||
<Button
|
||||
isLoading
|
||||
loadingText="Loading..."
|
||||
mode="success"
|
||||
onClick={action('onClick')}
|
||||
>
|
||||
<span>Loading</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
@ -218,6 +229,9 @@ export const StateDisabled: ComponentStory<typeof Button> = () => (
|
||||
<Button disabled mode="destructive" onClick={action('onClick')}>
|
||||
<span>Disabled</span>
|
||||
</Button>
|
||||
<Button disabled mode="success" onClick={action('onClick')}>
|
||||
<span>Disabled</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button disabled size="sm" onClick={action('onClick')}>
|
||||
|
@ -2,7 +2,7 @@ import React, { ReactElement } from 'react';
|
||||
import { CgSpinner } from 'react-icons/cg';
|
||||
import clsx from 'clsx';
|
||||
|
||||
type ButtonModes = 'default' | 'destructive' | 'primary';
|
||||
type ButtonModes = 'default' | 'destructive' | 'primary' | 'success';
|
||||
type ButtonSize = 'sm' | 'md' | 'lg';
|
||||
|
||||
export interface ButtonProps extends React.ComponentProps<'button'> {
|
||||
@ -42,6 +42,10 @@ export interface ButtonProps extends React.ComponentProps<'button'> {
|
||||
* The button will take the maximum with possible
|
||||
*/
|
||||
full?: boolean;
|
||||
// /**
|
||||
// * Will use newer flat style buttons
|
||||
// */
|
||||
// appearance?: 'flat' | 'default';
|
||||
}
|
||||
|
||||
export const buttonSizing: Record<ButtonSize, string> = {
|
||||
@ -50,17 +54,15 @@ export const buttonSizing: Record<ButtonSize, string> = {
|
||||
sm: 'h-btnsm px-sm ',
|
||||
};
|
||||
|
||||
export const buttonModesStyles: Record<ButtonModes, string> = {
|
||||
default:
|
||||
'text-gray-600 bg-gray-50 from-transparent to-white border-gray-300 hover:border-gray-400 disabled:border-gray-300 focus-visible:from-bg-gray-50 focus-visible:to-bg-gray-50',
|
||||
destructive:
|
||||
'text-red-600 bg-gray-50 from-transparent to-white border-gray-300 hover:border-gray-400 disabled:border-gray-300 focus-visible:from-bg-gray-50 focus-visible:to-bg-gray-50',
|
||||
primary:
|
||||
'text-gray-600 from-primary to-primary-light border-primary-dark hover:border-primary-darker focus-visible:from-primary focus-visible:to-primary disabled:border-primary-dark',
|
||||
};
|
||||
const twWhiteBg = `bg-gray-50 from-transparent to-white border-gray-300 hover:border-gray-400 disabled:border-gray-300 focus-visible:from-bg-gray-50 focus-visible:to-bg-gray-50`;
|
||||
|
||||
export const sharedButtonStyle =
|
||||
'items-center max-w-full justify-center inline-flex items-center text-sm font-sans font-semibold bg-gradient-to-t border rounded shadow-sm focus-visible:outline-none focus-visible:bg-gradient-to-t focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-yellow-400 disabled:opacity-60';
|
||||
export const twButtonStyles = {
|
||||
all: `items-center max-w-full justify-center inline-flex text-sm font-sans font-semibold bg-gradient-to-t border rounded shadow-sm focus-visible:outline-none focus-visible:bg-gradient-to-t focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-yellow-400 disabled:opacity-60`,
|
||||
default: `text-gray-600 ${twWhiteBg} focus-visible:ring-gray-400`,
|
||||
destructive: `text-red-600 ${twWhiteBg} focus-visible:ring-red-400`,
|
||||
success: `text-green-600 ${twWhiteBg} focus-visible:ring-green-400`,
|
||||
primary: `text-gray-600 from-primary to-primary-light border-primary-dark hover:border-primary-darker focus-visible:from-primary focus-visible:to-primary disabled:border-primary-dark`,
|
||||
};
|
||||
|
||||
const fullWidth = 'w-full';
|
||||
|
||||
@ -78,15 +80,18 @@ export const Button = (props: ButtonProps) => {
|
||||
full,
|
||||
...otherHtmlAttributes
|
||||
} = props;
|
||||
|
||||
const isDisabled = disabled || isLoading;
|
||||
|
||||
const styles = twButtonStyles;
|
||||
|
||||
const buttonAttributes = {
|
||||
type,
|
||||
...otherHtmlAttributes,
|
||||
disabled: isDisabled,
|
||||
className: clsx(
|
||||
sharedButtonStyle,
|
||||
buttonModesStyles[mode],
|
||||
styles.all,
|
||||
styles[mode],
|
||||
buttonSizing[size],
|
||||
isDisabled ? 'cursor-not-allowed' : '',
|
||||
full && fullWidth,
|
||||
|
302
frontend/package-lock.json
generated
302
frontend/package-lock.json
generated
@ -18,12 +18,13 @@
|
||||
"@hookform/resolvers": "2.8.10",
|
||||
"@netsells/storybook-mockdate": "^0.3.2",
|
||||
"@radix-ui/colors": "^0.1.8",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.3",
|
||||
"@radix-ui/react-checkbox": "1.0.1",
|
||||
"@radix-ui/react-collapsible": "^1.0.0",
|
||||
"@radix-ui/react-dialog": "^1.0.0",
|
||||
"@radix-ui/react-dropdown-menu": "^1.0.0",
|
||||
"@radix-ui/react-radio-group": "^1.1.1",
|
||||
"@radix-ui/react-scroll-area": "^1.0.2",
|
||||
"@radix-ui/react-scroll-area": "^1.0.3",
|
||||
"@radix-ui/react-switch": "^1.0.0",
|
||||
"@radix-ui/react-tabs": "^1.0.0",
|
||||
"@radix-ui/react-tooltip": "^1.0.0",
|
||||
@ -104,7 +105,7 @@
|
||||
"react-helmet": "5.2.1",
|
||||
"react-hook-form": "7.15.4",
|
||||
"react-hot-toast": "2.4.0",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-loading-skeleton": "^3.1.0",
|
||||
"react-lottie": "^1.2.3",
|
||||
@ -155,6 +156,7 @@
|
||||
"@babel/preset-typescript": "7.12.13",
|
||||
"@babel/register": "7.9.0",
|
||||
"@babel/runtime": "7.14.8",
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@graphql-codegen/cli": "2.13.8",
|
||||
"@graphql-codegen/typescript-operations": "^2.5.5",
|
||||
"@hookform/devtools": "4.0.1",
|
||||
@ -216,6 +218,7 @@
|
||||
"@types/mini-css-extract-plugin": "0.9.1",
|
||||
"@types/node": "18.11.9",
|
||||
"@types/optimize-css-assets-webpack-plugin": "5.0.1",
|
||||
"@types/random-words": "^1.1.2",
|
||||
"@types/react": "17.0.39",
|
||||
"@types/react-addons-test-utils": "0.14.25",
|
||||
"@types/react-autosuggest": "^10.1.5",
|
||||
@ -303,6 +306,7 @@
|
||||
"path-browserify": "^1.0.1",
|
||||
"postcss": "8.4.19",
|
||||
"prettier": "^2.6.2",
|
||||
"random-words": "^1.3.0",
|
||||
"react-a11y": "0.2.8",
|
||||
"react-hot-loader": "4.13.0",
|
||||
"react-refresh": "^0.10.0",
|
||||
@ -3554,6 +3558,16 @@
|
||||
"@f/map-obj": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@faker-js/faker": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz",
|
||||
"integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
|
||||
@ -11147,6 +11161,37 @@
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-alert-dialog": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.3.tgz",
|
||||
"integrity": "sha512-QXFy7+bhGi0u+paF2QbJeSCHZs4gLMJIPm6sajUamyW0fro6g1CaSGc5zmc4QmK2NlSGUrq8m+UsUqJYtzvXow==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-dialog": "1.0.3",
|
||||
"@radix-ui/react-primitive": "1.0.2",
|
||||
"@radix-ui/react-slot": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
|
||||
"integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-arrow": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.0.tgz",
|
||||
@ -11289,21 +11334,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.2.tgz",
|
||||
"integrity": "sha512-EKxxp2WNSmUPkx4trtWNmZ4/vAYEg7JkAfa1HKBUnaubw9eHzf1Orr9B472lJYaYz327RHDrd4R95fsw7VR8DA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.3.tgz",
|
||||
"integrity": "sha512-owNhq36kNPqC2/a+zJRioPg6HHnTn5B/sh/NjTY8r4W9g1L5VJlrzZIVcBr7R9Mg8iLjVmh6MGgMlfoVf/WO/A==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.2",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.3",
|
||||
"@radix-ui/react-focus-guards": "1.0.0",
|
||||
"@radix-ui/react-focus-scope": "1.0.1",
|
||||
"@radix-ui/react-focus-scope": "1.0.2",
|
||||
"@radix-ui/react-id": "1.0.0",
|
||||
"@radix-ui/react-portal": "1.0.1",
|
||||
"@radix-ui/react-portal": "1.0.2",
|
||||
"@radix-ui/react-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.2",
|
||||
"@radix-ui/react-slot": "1.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0",
|
||||
"aria-hidden": "^1.1.1",
|
||||
@ -11314,6 +11359,49 @@
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.3.tgz",
|
||||
"integrity": "sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"@radix-ui/react-use-escape-keydown": "1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.2.tgz",
|
||||
"integrity": "sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
|
||||
"integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-direction": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz",
|
||||
@ -11398,13 +11486,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-focus-scope": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.1.tgz",
|
||||
"integrity": "sha512-Ej2MQTit8IWJiS2uuujGUmxXjF/y5xZptIIQnyd2JHLwtV0R2j9NRVoRj/1j/gJ7e3REdaBw4Hjf4a1ImhkZcQ==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.2.tgz",
|
||||
"integrity": "sha512-spwXlNTfeIprt+kaEWE/qYuYT3ZAqJiAGjN/JgdvgVDTu8yc+HuX+WOWXrKliKnLnwck0F6JDkqIERncnih+4A==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@ -11412,6 +11500,19 @@
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
|
||||
"integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-id": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
|
||||
@ -24406,6 +24507,12 @@
|
||||
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/random-words": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/random-words/-/random-words-1.1.2.tgz",
|
||||
"integrity": "sha512-gULpJ68bNovfBWPWNNhwJgd/GcKdfkPpXXQGgACQWffgy6LRiJB4+4s/IslhFJKQvb5wBlnlOwFJ6RawHU5z3A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/range-parser": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
|
||||
@ -55019,6 +55126,15 @@
|
||||
"url": "https://opencollective.com/ramda"
|
||||
}
|
||||
},
|
||||
"node_modules/random-words": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/random-words/-/random-words-1.3.0.tgz",
|
||||
"integrity": "sha512-brwCGe+DN9DqZrAQVNj1Tct1Lody6GrYL/7uei5wfjeQdacFyFd2h/51LNlOoBMzIKMS9xohuL4+wlF/z1g/xg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"seedrandom": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/randomatic": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz",
|
||||
@ -56243,9 +56359,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.7.1.tgz",
|
||||
"integrity": "sha512-yHd3oKGMgm7zxo3EA7H2n7vxSoiGmHk5t6Ou4bXsfcgWyhfDKMpyKfhHR6Bjnn63c+YXBLBPUql9H4wPJM6sXw==",
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz",
|
||||
"integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==",
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
@ -58864,6 +58980,12 @@
|
||||
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/seedrandom": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
|
||||
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/seek-bzip": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz",
|
||||
@ -62926,9 +63048,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.6.1.tgz",
|
||||
"integrity": "sha512-htXWckxlT6U4+ilVgweNliPqlsVSSucbxVexRYllyMVJDtf5rTjv6kF/s+qAd4QSL1BZcnJPEJavYBPQiWuZDA==",
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.8.0.tgz",
|
||||
"integrity": "sha512-FVNSzGQz9Th+/9R6Lvv7WIAkstylfHN2/JYxkyhhmKFYh9At2DST8t6L6Lref9eYO8PXFTfG9Sg1Agg0K3vq3Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
@ -65497,9 +65619,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.6.tgz",
|
||||
"integrity": "sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==",
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.7.tgz",
|
||||
"integrity": "sha512-dY8ERwB9Nd21ellgkBZFhudER8KVlelZm8388B5nDAXhO/+FZDhYMuRnqDgu5SYyRgz/iaf8RKnbUs/cHfOGlQ==",
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "1.2.0"
|
||||
},
|
||||
@ -67939,6 +68061,12 @@
|
||||
"@f/map-obj": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"@faker-js/faker": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz",
|
||||
"integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==",
|
||||
"dev": true
|
||||
},
|
||||
"@floating-ui/core": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
|
||||
@ -73843,6 +73971,31 @@
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-alert-dialog": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.3.tgz",
|
||||
"integrity": "sha512-QXFy7+bhGi0u+paF2QbJeSCHZs4gLMJIPm6sajUamyW0fro6g1CaSGc5zmc4QmK2NlSGUrq8m+UsUqJYtzvXow==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-dialog": "1.0.3",
|
||||
"@radix-ui/react-primitive": "1.0.2",
|
||||
"@radix-ui/react-slot": "1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
|
||||
"integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-arrow": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.0.tgz",
|
||||
@ -73953,25 +74106,58 @@
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-dialog": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.2.tgz",
|
||||
"integrity": "sha512-EKxxp2WNSmUPkx4trtWNmZ4/vAYEg7JkAfa1HKBUnaubw9eHzf1Orr9B472lJYaYz327RHDrd4R95fsw7VR8DA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.3.tgz",
|
||||
"integrity": "sha512-owNhq36kNPqC2/a+zJRioPg6HHnTn5B/sh/NjTY8r4W9g1L5VJlrzZIVcBr7R9Mg8iLjVmh6MGgMlfoVf/WO/A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.2",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.3",
|
||||
"@radix-ui/react-focus-guards": "1.0.0",
|
||||
"@radix-ui/react-focus-scope": "1.0.1",
|
||||
"@radix-ui/react-focus-scope": "1.0.2",
|
||||
"@radix-ui/react-id": "1.0.0",
|
||||
"@radix-ui/react-portal": "1.0.1",
|
||||
"@radix-ui/react-portal": "1.0.2",
|
||||
"@radix-ui/react-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.2",
|
||||
"@radix-ui/react-slot": "1.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.3.tgz",
|
||||
"integrity": "sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"@radix-ui/react-use-escape-keydown": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-portal": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.2.tgz",
|
||||
"integrity": "sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
|
||||
"integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-direction": {
|
||||
@ -74039,14 +74225,25 @@
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-focus-scope": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.1.tgz",
|
||||
"integrity": "sha512-Ej2MQTit8IWJiS2uuujGUmxXjF/y5xZptIIQnyd2JHLwtV0R2j9NRVoRj/1j/gJ7e3REdaBw4Hjf4a1ImhkZcQ==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.2.tgz",
|
||||
"integrity": "sha512-spwXlNTfeIprt+kaEWE/qYuYT3ZAqJiAGjN/JgdvgVDTu8yc+HuX+WOWXrKliKnLnwck0F6JDkqIERncnih+4A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
|
||||
"integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-id": {
|
||||
@ -83945,6 +84142,12 @@
|
||||
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/random-words": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/random-words/-/random-words-1.1.2.tgz",
|
||||
"integrity": "sha512-gULpJ68bNovfBWPWNNhwJgd/GcKdfkPpXXQGgACQWffgy6LRiJB4+4s/IslhFJKQvb5wBlnlOwFJ6RawHU5z3A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/range-parser": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
|
||||
@ -107703,6 +107906,15 @@
|
||||
"integrity": "sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==",
|
||||
"dev": true
|
||||
},
|
||||
"random-words": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/random-words/-/random-words-1.3.0.tgz",
|
||||
"integrity": "sha512-brwCGe+DN9DqZrAQVNj1Tct1Lody6GrYL/7uei5wfjeQdacFyFd2h/51LNlOoBMzIKMS9xohuL4+wlF/z1g/xg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"seedrandom": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"randomatic": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz",
|
||||
@ -108611,9 +108823,9 @@
|
||||
}
|
||||
},
|
||||
"react-icons": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.7.1.tgz",
|
||||
"integrity": "sha512-yHd3oKGMgm7zxo3EA7H2n7vxSoiGmHk5t6Ou4bXsfcgWyhfDKMpyKfhHR6Bjnn63c+YXBLBPUql9H4wPJM6sXw=="
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz",
|
||||
"integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg=="
|
||||
},
|
||||
"react-input-autosize": {
|
||||
"version": "2.2.2",
|
||||
@ -110625,6 +110837,12 @@
|
||||
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
|
||||
"dev": true
|
||||
},
|
||||
"seedrandom": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
|
||||
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
|
||||
"dev": true
|
||||
},
|
||||
"seek-bzip": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz",
|
||||
@ -113814,9 +114032,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"type-fest": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.6.1.tgz",
|
||||
"integrity": "sha512-htXWckxlT6U4+ilVgweNliPqlsVSSucbxVexRYllyMVJDtf5rTjv6kF/s+qAd4QSL1BZcnJPEJavYBPQiWuZDA==",
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.8.0.tgz",
|
||||
"integrity": "sha512-FVNSzGQz9Th+/9R6Lvv7WIAkstylfHN2/JYxkyhhmKFYh9At2DST8t6L6Lref9eYO8PXFTfG9Sg1Agg0K3vq3Q==",
|
||||
"dev": true
|
||||
},
|
||||
"type-is": {
|
||||
@ -115776,9 +115994,9 @@
|
||||
"integrity": "sha512-1MzNQdAvO+54H+EaK5YpyEy0T+Ejo/7YLHS93G3RnYWh5gaotGHwGeN/ZO687qEDU2y4CdStQYXVHIgrUl5UVQ=="
|
||||
},
|
||||
"zustand": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.6.tgz",
|
||||
"integrity": "sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==",
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.7.tgz",
|
||||
"integrity": "sha512-dY8ERwB9Nd21ellgkBZFhudER8KVlelZm8388B5nDAXhO/+FZDhYMuRnqDgu5SYyRgz/iaf8RKnbUs/cHfOGlQ==",
|
||||
"requires": {
|
||||
"use-sync-external-store": "1.2.0"
|
||||
}
|
||||
|
@ -56,12 +56,13 @@
|
||||
"@hookform/resolvers": "2.8.10",
|
||||
"@netsells/storybook-mockdate": "^0.3.2",
|
||||
"@radix-ui/colors": "^0.1.8",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.3",
|
||||
"@radix-ui/react-checkbox": "1.0.1",
|
||||
"@radix-ui/react-collapsible": "^1.0.0",
|
||||
"@radix-ui/react-dialog": "^1.0.0",
|
||||
"@radix-ui/react-dropdown-menu": "^1.0.0",
|
||||
"@radix-ui/react-radio-group": "^1.1.1",
|
||||
"@radix-ui/react-scroll-area": "^1.0.2",
|
||||
"@radix-ui/react-scroll-area": "^1.0.3",
|
||||
"@radix-ui/react-switch": "^1.0.0",
|
||||
"@radix-ui/react-tabs": "^1.0.0",
|
||||
"@radix-ui/react-tooltip": "^1.0.0",
|
||||
@ -142,7 +143,7 @@
|
||||
"react-helmet": "5.2.1",
|
||||
"react-hook-form": "7.15.4",
|
||||
"react-hot-toast": "2.4.0",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-loading-skeleton": "^3.1.0",
|
||||
"react-lottie": "^1.2.3",
|
||||
@ -193,6 +194,7 @@
|
||||
"@babel/preset-typescript": "7.12.13",
|
||||
"@babel/register": "7.9.0",
|
||||
"@babel/runtime": "7.14.8",
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@graphql-codegen/cli": "2.13.8",
|
||||
"@graphql-codegen/typescript-operations": "^2.5.5",
|
||||
"@hookform/devtools": "4.0.1",
|
||||
@ -254,6 +256,7 @@
|
||||
"@types/mini-css-extract-plugin": "0.9.1",
|
||||
"@types/node": "18.11.9",
|
||||
"@types/optimize-css-assets-webpack-plugin": "5.0.1",
|
||||
"@types/random-words": "^1.1.2",
|
||||
"@types/react": "17.0.39",
|
||||
"@types/react-addons-test-utils": "0.14.25",
|
||||
"@types/react-autosuggest": "^10.1.5",
|
||||
@ -341,6 +344,7 @@
|
||||
"path-browserify": "^1.0.1",
|
||||
"postcss": "8.4.19",
|
||||
"prettier": "^2.6.2",
|
||||
"random-words": "^1.3.0",
|
||||
"react-a11y": "0.2.8",
|
||||
"react-hot-loader": "4.13.0",
|
||||
"react-refresh": "^0.10.0",
|
||||
@ -357,10 +361,10 @@
|
||||
"stylus": "^0.55.0",
|
||||
"tailwindcss": "3.2.4",
|
||||
"tailwindcss-radix": "^2.5.0",
|
||||
"type-fest": "^3.6.1",
|
||||
"ts-jest": "29.0.5",
|
||||
"ts-node": "10.9.1",
|
||||
"tslib": "^2.3.0",
|
||||
"type-fest": "^3.6.1",
|
||||
"typescript": "4.9.5",
|
||||
"unplugin-dynamic-asset-loader": "1.0.0",
|
||||
"url-loader": "^4.1.1",
|
||||
|
@ -199,6 +199,14 @@ module.exports = {
|
||||
opacity: '0',
|
||||
},
|
||||
},
|
||||
fadeIn: {
|
||||
from: { opacity: 0 },
|
||||
to: { opacity: 1 },
|
||||
},
|
||||
alertContentShow: {
|
||||
from: { opacity: 0, transform: 'translate(-50%, -48%) scale(0.96)' },
|
||||
to: { opacity: 1, transform: 'translate(-50%, -50%) scale(1)' },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
collapsibleContentOpen: 'collapsibleContentOpen 300ms ease-out',
|
||||
@ -216,6 +224,8 @@ module.exports = {
|
||||
notificationClose: 'notificationClose 300ms ease-in-out',
|
||||
dropdownMenuContentOpen: 'dropdownMenuContentOpen 100ms ease-in',
|
||||
dropdownMenuContentClose: 'dropdownMenuContentClose 100ms ease-out',
|
||||
overlayShow: 'overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1)',
|
||||
contentShow: 'contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1)',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user