diff --git a/packages/manager/.changeset/pr-9282-tech-stories-1687053909997.md b/packages/manager/.changeset/pr-9282-tech-stories-1687053909997.md new file mode 100644 index 00000000000..5232f37995f --- /dev/null +++ b/packages/manager/.changeset/pr-9282-tech-stories-1687053909997.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tech Stories +--- + +MUI v5 Migration - `Components > LandingLoading` ([#9282](https://github.com/linode/manager/pull/9282)) diff --git a/packages/manager/src/components/LandingLoading/LandingLoading.stories.tsx b/packages/manager/src/components/LandingLoading/LandingLoading.stories.tsx new file mode 100644 index 00000000000..1c26429e261 --- /dev/null +++ b/packages/manager/src/components/LandingLoading/LandingLoading.stories.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { LandingLoading, DEFAULT_DELAY } from './LandingLoading'; + +const meta: Meta = { + title: 'Components/LandingLoading', + component: LandingLoading, + argTypes: {}, + args: { + shouldDelay: false, + delayInMS: DEFAULT_DELAY, + children: undefined, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: {}, + render: (args) => , +}; diff --git a/packages/manager/src/components/LandingLoading/LandingLoading.test.tsx b/packages/manager/src/components/LandingLoading/LandingLoading.test.tsx new file mode 100644 index 00000000000..4106fcc12ce --- /dev/null +++ b/packages/manager/src/components/LandingLoading/LandingLoading.test.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; +import { LandingLoading, DEFAULT_DELAY } from './LandingLoading'; +import { render, screen, act } from '@testing-library/react'; + +jest.useFakeTimers(); + +const LOADING_ICON = 'circle-progress'; + +describe('LandingLoading', () => { + afterEach(() => { + jest.clearAllTimers(); + }); + + it('renders the loading indicator by default', () => { + render(); + expect(screen.getByTestId(LOADING_ICON)).toBeInTheDocument(); + }); + + it('renders custom loading indicator when children are provided', () => { + render( + +
Loading...
+
+ ); + expect(screen.getByTestId('custom-loading-indicator')).toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_ICON)).toBeNull(); + }); + + it('does not render the loading indicator when shouldDelay is true', () => { + render(); + expect(screen.queryByTestId(LOADING_ICON)).toBeNull(); + }); + + it('renders the loading indicator after the delay', () => { + render(); + expect(screen.queryByTestId(LOADING_ICON)).toBeNull(); + act(() => { + jest.advanceTimersByTime(DEFAULT_DELAY); + }); + expect(screen.getByTestId(LOADING_ICON)).toBeInTheDocument(); + }); + + it('renders the loading indicator after the specified delayInMS', () => { + render(); + expect(screen.queryByTestId(LOADING_ICON)).toBeNull(); + act(() => { + jest.advanceTimersByTime(2000); + }); + expect(screen.getByTestId(LOADING_ICON)).toBeInTheDocument(); + }); + + it('does not render the loading indicator when shouldDelay is false and no delayInMS is provided', () => { + render(); + expect(screen.getByTestId(LOADING_ICON)).toBeInTheDocument(); + }); +}); diff --git a/packages/manager/src/components/LandingLoading/LandingLoading.tsx b/packages/manager/src/components/LandingLoading/LandingLoading.tsx index 17a48125c50..e7d7ecd919b 100644 --- a/packages/manager/src/components/LandingLoading/LandingLoading.tsx +++ b/packages/manager/src/components/LandingLoading/LandingLoading.tsx @@ -1,27 +1,22 @@ import * as React from 'react'; import { CircleProgress } from 'src/components/CircleProgress'; -const DEFAULT_DELAY = 1000; +export const DEFAULT_DELAY = 1000; -interface Props { +interface LandingLoadingProps { + /** If true, the loading indicator will not be rendered for 1 second which may give user's with fast connections a more fluid experience. */ shouldDelay?: boolean; + /** If given, the loading indicator will not be rendered for the given duration in milliseconds */ delayInMS?: number; + /** Allow children to be passed in to override the default loading indicator */ + children?: JSX.Element; } -/** - * - * LandingLoading - * - * If the `shouldDelay` prop is given, the loading indicator will - * not be rendered for 1 second, which may give user's with fast - * connections a more fluid experience. Use the `delayInMS` prop - * to specify an exact delay duration. - */ -export const LandingLoading: React.FC = ({ +export const LandingLoading = ({ shouldDelay, delayInMS, children, -}) => { +}: LandingLoadingProps): JSX.Element | null => { const [showLoading, setShowLoading] = React.useState(false); React.useEffect(() => { @@ -29,13 +24,15 @@ export const LandingLoading: React.FC = ({ * See: https://github.com/facebook/react/issues/14369#issuecomment-468267798 */ let didCancel = false; + // Reference to the timeoutId so we can cancel it + let timeoutId: NodeJS.Timeout | null = null; if (shouldDelay || typeof delayInMS === 'number') { // Used specified duration or default const delayDuration = typeof delayInMS === 'number' ? delayInMS : DEFAULT_DELAY; - setTimeout(() => { + timeoutId = setTimeout(() => { if (!didCancel) { setShowLoading(true); } @@ -45,16 +42,13 @@ export const LandingLoading: React.FC = ({ } return () => { didCancel = true; + if (timeoutId) { + clearTimeout(timeoutId); + } }; - }, []); - return showLoading ? ( - !!children ? ( - /** allows us to pass a custom Loader if we please */ - {children} - ) : ( - - ) - ) : null; -}; + }, [shouldDelay, delayInMS]); -export default LandingLoading; + return showLoading + ? children || + : null; +}; diff --git a/packages/manager/src/components/LandingLoading/index.ts b/packages/manager/src/components/LandingLoading/index.ts deleted file mode 100644 index 7b44222ebd1..00000000000 --- a/packages/manager/src/components/LandingLoading/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -import LandingLoading from './LandingLoading'; -export default LandingLoading; diff --git a/packages/manager/src/features/Linodes/LinodesCreate/SelectAppPanel.tsx b/packages/manager/src/features/Linodes/LinodesCreate/SelectAppPanel.tsx index 3f15fdfd4a5..3b9ff56c002 100644 --- a/packages/manager/src/features/Linodes/LinodesCreate/SelectAppPanel.tsx +++ b/packages/manager/src/features/Linodes/LinodesCreate/SelectAppPanel.tsx @@ -5,7 +5,7 @@ import Paper from 'src/components/core/Paper'; import { createStyles, withStyles, WithStyles } from '@mui/styles'; import { Theme } from '@mui/material/styles'; import { ErrorState } from 'src/components/ErrorState/ErrorState'; -import Loading from 'src/components/LandingLoading'; +import { LandingLoading } from 'src/components/LandingLoading/LandingLoading'; import { Notice } from 'src/components/Notice/Notice'; import AppPanelSection from 'src/features/Linodes/LinodesCreate/AppPanelSection'; import { getQueryParamFromQueryString } from 'src/utilities/queryParams'; @@ -132,7 +132,7 @@ class SelectAppPanel extends React.PureComponent { return ( - + ); diff --git a/packages/manager/src/features/Longview/LongviewDetail/DetailTabs/Disks/Disks.tsx b/packages/manager/src/features/Longview/LongviewDetail/DetailTabs/Disks/Disks.tsx index d824839517f..859b04bd007 100644 --- a/packages/manager/src/features/Longview/LongviewDetail/DetailTabs/Disks/Disks.tsx +++ b/packages/manager/src/features/Longview/LongviewDetail/DetailTabs/Disks/Disks.tsx @@ -6,7 +6,7 @@ import { makeStyles } from '@mui/styles'; import { Theme } from '@mui/material/styles'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; import { ErrorState } from 'src/components/ErrorState/ErrorState'; -import LandingLoading from 'src/components/LandingLoading'; +import { LandingLoading } from 'src/components/LandingLoading/LandingLoading'; import { Placeholder } from 'src/components/Placeholder/Placeholder'; import { WithStartAndEnd } from '../../../request.types'; import TimeRangeSelect from '../../../shared/TimeRangeSelect'; diff --git a/packages/manager/src/features/Volumes/VolumesLanding.tsx b/packages/manager/src/features/Volumes/VolumesLanding.tsx index 62872a09484..e1c5f9820c4 100644 --- a/packages/manager/src/features/Volumes/VolumesLanding.tsx +++ b/packages/manager/src/features/Volumes/VolumesLanding.tsx @@ -10,7 +10,7 @@ import { TableHead } from 'src/components/TableHead'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; import { ErrorState } from 'src/components/ErrorState/ErrorState'; import LandingHeader from 'src/components/LandingHeader'; -import Loading from 'src/components/LandingLoading'; +import { LandingLoading } from 'src/components/LandingLoading/LandingLoading'; import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter'; import { Table } from 'src/components/Table'; import { TableCell } from 'src/components/TableCell'; @@ -199,7 +199,7 @@ export const VolumesLanding = (props: CombinedProps) => { }; if (isLoading) { - return ; + return ; } if (error) {