From 9647b4c031334d698784fc30b9112478ba4cb0f7 Mon Sep 17 00:00:00 2001 From: Jaalah Ramos Date: Mon, 3 Jul 2023 11:54:29 -0400 Subject: [PATCH 1/6] refactor: [M3-6393] - MUI v5 - --- packages/manager/package.json | 2 +- .../manager/src/components/SnackBar/index.ts | 2 - .../{SnackBar => Snackbar}/CloseSnackbar.tsx | 0 .../SnackBar.tsx => Snackbar/Snackbar.tsx} | 63 ++++++------ .../features/Backups/BackupDrawer.test.tsx | 10 -- .../src/features/Backups/BackupDrawer.tsx | 6 +- .../src/features/Events/EventsLanding.tsx | 9 +- .../ClusterList/ClusterActionMenu.tsx | 9 +- .../LinodesCreate/LinodeCreateContainer.tsx | 15 +-- .../Linodes/LinodesLanding/LinodesLanding.tsx | 3 - .../LongviewLanding/LongviewClients.tsx | 5 +- .../LongviewLanding/LongviewLanding.test.tsx | 2 - .../LongviewLanding/LongviewLanding.tsx | 12 +-- .../ToastNotifications.stories.mdx | 92 ------------------ .../ToastNotifications.stories.tsx | 97 +++++++++++++++++++ .../ToastNotifications/ToastNotifications.tsx | 36 ++----- .../src/features/Users/UserPermissions.tsx | 20 ++-- packages/manager/src/index.tsx | 6 +- yarn.lock | 15 ++- 19 files changed, 184 insertions(+), 220 deletions(-) delete mode 100644 packages/manager/src/components/SnackBar/index.ts rename packages/manager/src/components/{SnackBar => Snackbar}/CloseSnackbar.tsx (100%) rename packages/manager/src/components/{SnackBar/SnackBar.tsx => Snackbar/Snackbar.tsx} (57%) delete mode 100644 packages/manager/src/features/ToastNotifications/ToastNotifications.stories.mdx create mode 100644 packages/manager/src/features/ToastNotifications/ToastNotifications.stories.tsx diff --git a/packages/manager/package.json b/packages/manager/package.json index 0ba8d77a331..7c26723794b 100644 --- a/packages/manager/package.json +++ b/packages/manager/package.json @@ -52,7 +52,7 @@ "luxon": "^3.2.1", "markdown-it": "^12.3.2", "md5": "^2.2.1", - "notistack": "^2.0.5", + "notistack": "^3.0.1", "patch-package": "^7.0.0", "qrcode.react": "^0.8.0", "ramda": "~0.25.0", diff --git a/packages/manager/src/components/SnackBar/index.ts b/packages/manager/src/components/SnackBar/index.ts deleted file mode 100644 index 9df7163ee1e..00000000000 --- a/packages/manager/src/components/SnackBar/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -import SnackBar from './SnackBar'; -export default SnackBar; diff --git a/packages/manager/src/components/SnackBar/CloseSnackbar.tsx b/packages/manager/src/components/Snackbar/CloseSnackbar.tsx similarity index 100% rename from packages/manager/src/components/SnackBar/CloseSnackbar.tsx rename to packages/manager/src/components/Snackbar/CloseSnackbar.tsx diff --git a/packages/manager/src/components/SnackBar/SnackBar.tsx b/packages/manager/src/components/Snackbar/Snackbar.tsx similarity index 57% rename from packages/manager/src/components/SnackBar/SnackBar.tsx rename to packages/manager/src/components/Snackbar/Snackbar.tsx index 07ab94a5d10..20680a839a9 100644 --- a/packages/manager/src/components/SnackBar/SnackBar.tsx +++ b/packages/manager/src/components/Snackbar/Snackbar.tsx @@ -1,16 +1,17 @@ -import { SnackbarProvider, SnackbarProviderProps } from 'notistack'; import * as React from 'react'; -import { makeStyles } from '@mui/styles'; -import { Theme } from '@mui/material/styles'; import CloseSnackbar from './CloseSnackbar'; +import { + MaterialDesignContent, + SnackbarProvider, + SnackbarProviderProps, +} from 'notistack'; +import { styled } from '@mui/material/styles'; -const useStyles = makeStyles((theme: Theme) => ({ - root: { - '& div': { - backgroundColor: `${theme.bg.white} !important`, - color: theme.palette.text.primary, - fontSize: '0.875rem', - }, +const StyledVariants = styled(MaterialDesignContent)(({ theme }) => ({ + '&.notistack-MuiContent': { + backgroundColor: `${theme.bg.white}`, + color: theme.palette.text.primary, + fontSize: '0.875rem', [theme.breakpoints.down('md')]: { '& .SnackbarItem-contentRoot': { flexWrap: 'nowrap', @@ -25,24 +26,27 @@ const useStyles = makeStyles((theme: Theme) => ({ }, }, }, - info: { - borderLeft: `6px solid ${theme.palette.primary.main}`, + '&.notistack-MuiContent-error': { + borderLeft: `6px solid ${theme.palette.error.dark}`, }, - success: { + '&.notistack-MuiContent-info': { borderLeft: `6px solid ${theme.palette.primary.main}`, }, - error: { - borderLeft: `6px solid ${theme.palette.error.dark}`, + '&.notistack-MuiContent-success': { + borderLeft: `6px solid ${theme.color.green}`, }, - warning: { + '&.notistack-MuiContent-warning': { borderLeft: `6px solid ${theme.palette.warning.dark}`, }, })); -type CombinedProps = SnackbarProviderProps; +interface CustomSnackbarProps extends SnackbarProviderProps { + 'data-qa-toast'?: boolean; +} + +export const Snackbar = (props: SnackbarProviderProps) => { + const { children, ...rest } = props; -const SnackBar: React.FC = (props) => { - const classes = useStyles(); /** * This pattern is taken from the Notistack docs: * https://iamhosseindhv.com/notistack/demos#action-for-all-snackbars @@ -54,29 +58,26 @@ const SnackBar: React.FC = (props) => { } }; - const { children, ...rest } = props; - return ( ( )} + Components={{ + default: StyledVariants, + error: StyledVariants, + info: StyledVariants, + success: StyledVariants, + warning: StyledVariants, + }} > {children} ); }; - -export default SnackBar; diff --git a/packages/manager/src/features/Backups/BackupDrawer.test.tsx b/packages/manager/src/features/Backups/BackupDrawer.test.tsx index b431ceffa3a..636ff2e1dc7 100644 --- a/packages/manager/src/features/Backups/BackupDrawer.test.tsx +++ b/packages/manager/src/features/Backups/BackupDrawer.test.tsx @@ -136,8 +136,6 @@ const props = { const { rerender, getByTestId, findByTestId, queryByTestId } = render( wrapWithTheme( { rerender( wrapWithTheme( { rerender( wrapWithTheme( { rerender( wrapWithTheme( { rerender( wrapWithTheme( { ? `${updatedCount} ${pluralizedLinodes} been enrolled in automatic backups, and all new Linodes will automatically be backed up.` : `${updatedCount} ${pluralizedLinodes} been enrolled in automatic backups.`; - this.props.enqueueSnackbar(text, { + enqueueSnackbar(text, { variant: 'success', }); dismissSuccess(); @@ -332,7 +331,6 @@ const enhanced = compose( // this awkward line avoids fetching all types until this dialog is opened (comp: React.ComponentType) => (props: CombinedProps) => withSpecificTypes(comp, props.open)(props), - withSnackbar, withAccountSettings, withQueryClient ); diff --git a/packages/manager/src/features/Events/EventsLanding.tsx b/packages/manager/src/features/Events/EventsLanding.tsx index 68b7c0dee43..528743e2a99 100644 --- a/packages/manager/src/features/Events/EventsLanding.tsx +++ b/packages/manager/src/features/Events/EventsLanding.tsx @@ -1,6 +1,6 @@ import { Event, getEvents } from '@linode/api-v4/lib/account'; import { ResourcePage } from '@linode/api-v4/lib/types'; -import { withSnackbar, WithSnackbarProps } from 'notistack'; +import { useSnackbar } from 'notistack'; import { compose as rCompose, concat, uniq } from 'ramda'; import * as React from 'react'; import { connect } from 'react-redux'; @@ -61,7 +61,7 @@ interface Props { emptyMessage?: string; // Custom message for the empty state (i.e. no events). } -type CombinedProps = Props & StateProps & WithSnackbarProps; +type CombinedProps = Props & StateProps; const appendToEvents = (oldEvents: Event[], newEvents: Event[]) => rCompose( @@ -173,6 +173,7 @@ export const reducer: EventsReducer = (state, action) => { export const EventsLanding: React.FC = (props) => { const classes = useStyles(); + const { enqueueSnackbar } = useSnackbar(); const [loading, setLoading] = React.useState(false); const [loadMoreEvents, setLoadMoreEvents] = React.useState(false); @@ -199,7 +200,7 @@ export const EventsLanding: React.FC = (props) => { getEventsRequest({ page: currentPage }) .then(handleEventsRequestSuccess) .catch(() => { - props.enqueueSnackbar('There was an error loading more events', { + enqueueSnackbar('There was an error loading more events', { variant: 'error', }); setLoading(false); @@ -403,6 +404,6 @@ const mapStateToProps = (state: ApplicationState) => ({ const connected = connect(mapStateToProps); -const enhanced = compose(connected, withSnackbar); +const enhanced = compose(connected); export default enhanced(EventsLanding); diff --git a/packages/manager/src/features/Kubernetes/ClusterList/ClusterActionMenu.tsx b/packages/manager/src/features/Kubernetes/ClusterList/ClusterActionMenu.tsx index d84c3ac152e..0806b4d0344 100644 --- a/packages/manager/src/features/Kubernetes/ClusterList/ClusterActionMenu.tsx +++ b/packages/manager/src/features/Kubernetes/ClusterList/ClusterActionMenu.tsx @@ -1,5 +1,5 @@ import { getKubeConfig } from '@linode/api-v4/lib/kubernetes'; -import { withSnackbar, WithSnackbarProps } from 'notistack'; +import { useSnackbar } from 'notistack'; import * as React from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; import { compose } from 'recompose'; @@ -19,15 +19,16 @@ interface Props { openDialog: () => void; } -type CombinedProps = Props & RouteComponentProps<{}> & WithSnackbarProps; +type CombinedProps = Props & RouteComponentProps<{}>; export const ClusterActionMenu: React.FunctionComponent = ( props ) => { const theme = useTheme(); + const { enqueueSnackbar } = useSnackbar(); const matchesSmDown = useMediaQuery(theme.breakpoints.down('md')); - const { clusterId, clusterLabel, enqueueSnackbar, openDialog } = props; + const { clusterId, clusterLabel, openDialog } = props; const actions: Action[] = [ { @@ -92,6 +93,6 @@ export const ClusterActionMenu: React.FunctionComponent = ( ); }; -const enhanced = compose(withSnackbar, withRouter); +const enhanced = compose(withRouter); export default enhanced(ClusterActionMenu); diff --git a/packages/manager/src/features/Linodes/LinodesCreate/LinodeCreateContainer.tsx b/packages/manager/src/features/Linodes/LinodesCreate/LinodeCreateContainer.tsx index 88bf7929c71..c76ea0ddf01 100644 --- a/packages/manager/src/features/Linodes/LinodesCreate/LinodeCreateContainer.tsx +++ b/packages/manager/src/features/Linodes/LinodesCreate/LinodeCreateContainer.tsx @@ -12,7 +12,7 @@ import { import { Region } from '@linode/api-v4/lib/regions'; import { StackScript, UserDefinedField } from '@linode/api-v4/lib/stackscripts'; import { APIError } from '@linode/api-v4/lib/types'; -import { withSnackbar, WithSnackbarProps } from 'notistack'; +import { enqueueSnackbar } from 'notistack'; import * as React from 'react'; import { connect } from 'react-redux'; import { RouteComponentProps } from 'react-router-dom'; @@ -111,8 +111,7 @@ interface State { userData: string | undefined; } -type CombinedProps = WithSnackbarProps & - CreateType & +type CombinedProps = CreateType & ImagesProps & WithTypesProps & WithLinodesProps & @@ -630,12 +629,9 @@ class LinodeCreateContainer extends React.PureComponent { ); /** show toast */ - this.props.enqueueSnackbar( - `Your Linode ${response.label} is being created.`, - { - variant: 'success', - } - ); + enqueueSnackbar(`Your Linode ${response.label} is being created.`, { + variant: 'success', + }); /** reset the Events polling */ resetEventsPolling(); @@ -826,7 +822,6 @@ export default recompose( withRegions, withTypes, connected, - withSnackbar, withLabelGenerator, withFlags, withProfile, diff --git a/packages/manager/src/features/Linodes/LinodesLanding/LinodesLanding.tsx b/packages/manager/src/features/Linodes/LinodesLanding/LinodesLanding.tsx index 8a5583c7245..501560fbb04 100644 --- a/packages/manager/src/features/Linodes/LinodesLanding/LinodesLanding.tsx +++ b/packages/manager/src/features/Linodes/LinodesLanding/LinodesLanding.tsx @@ -1,6 +1,5 @@ import { Config } from '@linode/api-v4/lib/linodes/types'; import { APIError } from '@linode/api-v4/lib/types'; -import { withSnackbar, WithSnackbarProps } from 'notistack'; import * as React from 'react'; import { QueryClient } from 'react-query'; import { connect, MapDispatchToProps } from 'react-redux'; @@ -94,7 +93,6 @@ type CombinedProps = Props & DispatchProps & RouteProps & StyleProps & - WithSnackbarProps & WithProfileProps; export class ListLinodes extends React.Component { @@ -480,7 +478,6 @@ const connected = connect(mapStateToProps, mapDispatchToProps); export const enhanced = compose( withRouter, - withSnackbar, connected, styled, withFeatureFlagConsumer, diff --git a/packages/manager/src/features/Longview/LongviewLanding/LongviewClients.tsx b/packages/manager/src/features/Longview/LongviewLanding/LongviewClients.tsx index 06995a00c21..5fbeb5bea70 100644 --- a/packages/manager/src/features/Longview/LongviewLanding/LongviewClients.tsx +++ b/packages/manager/src/features/Longview/LongviewLanding/LongviewClients.tsx @@ -3,7 +3,6 @@ import { LongviewClient, LongviewSubscription, } from '@linode/api-v4/lib/longview/types'; -import { withSnackbar, WithSnackbarProps } from 'notistack'; import { isEmpty, pathOr } from 'ramda'; import * as React from 'react'; import { connect } from 'react-redux'; @@ -79,7 +78,6 @@ interface Props { export type CombinedProps = Props & RouteComponentProps & LongviewProps & - WithSnackbarProps & StateProps; type SortKey = 'name' | 'cpu' | 'ram' | 'swap' | 'load' | 'network' | 'storage'; @@ -334,8 +332,7 @@ const connected = connect(mapStateToProps); export default compose( React.memo, connected, - withLongviewClients(), - withSnackbar + withLongviewClients() )(LongviewClients); /** diff --git a/packages/manager/src/features/Longview/LongviewLanding/LongviewLanding.test.tsx b/packages/manager/src/features/Longview/LongviewLanding/LongviewLanding.test.tsx index 65877e0679e..39540a05626 100644 --- a/packages/manager/src/features/Longview/LongviewLanding/LongviewLanding.test.tsx +++ b/packages/manager/src/features/Longview/LongviewLanding/LongviewLanding.test.tsx @@ -39,8 +39,6 @@ const props: CombinedProps = { deleteLongviewClient: jest.fn(), getLongviewClients: jest.fn().mockResolvedValue([]), updateLongviewClient: jest.fn(), - enqueueSnackbar: jest.fn(), - closeSnackbar: jest.fn(), activeSubscription: longviewSubscriptionFactory.build(), lvClientData: {}, handleAddClient: jest.fn(), diff --git a/packages/manager/src/features/Longview/LongviewLanding/LongviewLanding.tsx b/packages/manager/src/features/Longview/LongviewLanding/LongviewLanding.tsx index 915392dacb3..eb6cd79c779 100644 --- a/packages/manager/src/features/Longview/LongviewLanding/LongviewLanding.tsx +++ b/packages/manager/src/features/Longview/LongviewLanding/LongviewLanding.tsx @@ -6,7 +6,7 @@ import { ActiveLongviewPlan, LongviewSubscription, } from '@linode/api-v4/lib/longview/types'; -import { withSnackbar, WithSnackbarProps } from 'notistack'; +import { useSnackbar } from 'notistack'; import { isEmpty } from 'ramda'; import * as React from 'react'; import { matchPath, RouteComponentProps } from 'react-router-dom'; @@ -28,13 +28,12 @@ import SubscriptionDialog from './SubscriptionDialog'; const LongviewClients = React.lazy(() => import('./LongviewClients')); const LongviewPlans = React.lazy(() => import('./LongviewPlans')); -type CombinedProps = LongviewProps & - RouteComponentProps<{}> & - WithSnackbarProps; +type CombinedProps = LongviewProps & RouteComponentProps<{}>; export const LongviewLanding: React.FunctionComponent = ( props ) => { + const { enqueueSnackbar } = useSnackbar(); const activeSubscriptionRequestHook = useAPIRequest( () => getActiveLongviewPlan().then((response) => response), {} @@ -93,7 +92,7 @@ export const LongviewLanding: React.FunctionComponent = ( setSubscriptionDialogOpen(true); } else { // Any network or other errors handled with a toast - props.enqueueSnackbar( + enqueueSnackbar( getAPIErrorOrDefault( errorResponse, 'Error creating Longview client.' @@ -180,6 +179,5 @@ export const LongviewLanding: React.FunctionComponent = ( }; export default compose( - withLongviewClients(), - withSnackbar + withLongviewClients() )(LongviewLanding); diff --git a/packages/manager/src/features/ToastNotifications/ToastNotifications.stories.mdx b/packages/manager/src/features/ToastNotifications/ToastNotifications.stories.mdx deleted file mode 100644 index 238ed615954..00000000000 --- a/packages/manager/src/features/ToastNotifications/ToastNotifications.stories.mdx +++ /dev/null @@ -1,92 +0,0 @@ -import { Canvas, Meta, Story, ArgsTable } from '@storybook/addon-docs'; -import { withSnackbar, useSnackbar } from 'notistack'; -import { Button } from 'src/components/Button/Button'; -import Snackbar from 'src/components/SnackBar'; - - - -# Toasts - -### Timing - -- Default timing for toast notifications is 5 seconds. -- If the message is longer, more complex, or very important, consider increasing the display time accordingly. -- If the message is critical (e.g., an Image failed to upload), consider making it persistent until dismissed by the user. - -export const MyButton = (args) => { - const { variant, onClick } = args; - const handleClick = () => { - // just call the onClick with the provided variant - onClick(variant); - }; - return ; -}; - -export const Example = () => { - const { enqueueSnackbar } = useSnackbar(); - const variants = ['default', 'success', 'warning', 'error', 'info']; - const showToast = (variant) => - enqueueSnackbar( - 'Toast message. This will auto destruct after five seconds.', - { - variant, - } - ); - return ( - // eslint-disable-next-line react/jsx-no-useless-fragment - <> - {variants.map((eachVariant) => { - // map over each variant and show a button for each - return ( - - ); - })} - - ); -}; - -export const Template = (args) => { - const Enhanced = withSnackbar(Example); - return ( - - - - ); -}; - - - - {Template.bind({})} - - - - diff --git a/packages/manager/src/features/ToastNotifications/ToastNotifications.stories.tsx b/packages/manager/src/features/ToastNotifications/ToastNotifications.stories.tsx new file mode 100644 index 00000000000..bb08a807ad5 --- /dev/null +++ b/packages/manager/src/features/ToastNotifications/ToastNotifications.stories.tsx @@ -0,0 +1,97 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { useSnackbar } from 'notistack'; +import { Snackbar } from 'src/components/Snackbar/Snackbar'; +import { Button } from 'src/components/Button/Button'; + +const meta: Meta = { + title: 'Components/Notifications/Toasts', + component: Snackbar, + argTypes: { + anchorOrigin: { + description: + 'Determine where the toast initially appears vertically and horizontally.', + }, + maxSnack: { + description: + 'Set the maximum number of toasts that can appear simultaneously.', + }, + hideIconVariant: { + description: + 'Determine whether variant icons appear to the left of the text in the toast.', + }, + }, + args: {}, +}; + +export default meta; + +type Story = StoryObj; + +/** + * # Toasts + * ### Timing + * - Default timing for toast notifications is 5 seconds. + * - If the message is longer, more complex, or very important, consider increasing the display time accordingly. + * - If the message is critical (e.g., an Image failed to upload), consider making it persistent until dismissed by the user. + */ + +export const Default: Story = { + args: { + anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, + maxSnack: 5, + hideIconVariant: true, + }, + render: (args) =>