Skip to content

Commit d890ad9

Browse files
Pull request #60: UIE-10782 iam circle progress
Merge in FEE/cloud-manager from UIE-10782-iam-circle-progress to develop Squashed commit of the following: commit d0fe1d73dc8618fcf53523316f89af28605fc4ef Author: mpolotsk <mpolotsk@akamai.com> Date: Tue Apr 14 15:50:54 2026 +0200 rename LoadingSpinner, add test commit b13587d1e709a5dafc8b9fb3f607749fbe6155cd Author: mpolotsk <mpolotsk@akamai.com> Date: Mon Apr 13 16:22:45 2026 +0200 UIE-10782 IAM CircleProgress replace
1 parent c4d56cc commit d890ad9

15 files changed

Lines changed: 148 additions & 24 deletions

File tree

packages/manager/src/features/IAM/IAMLanding.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { Outlet, useLocation, useNavigate } from '@tanstack/react-router';
33
import * as React from 'react';
44

55
import { LandingHeader } from 'src/components/LandingHeader';
6-
import { SuspenseLoader } from 'src/components/SuspenseLoader';
76
import { TabPanels } from 'src/components/Tabs/TabPanels';
87
import { Tabs } from 'src/components/Tabs/Tabs';
98
import { TanStackTabLinkList } from 'src/components/Tabs/TanStackTabLinkList';
@@ -16,6 +15,7 @@ import {
1615
useIsIAMEnabled,
1716
} from './hooks/useIsIAMEnabled';
1817
import { IAM_DOCS_LINK, ROLES_LEARN_MORE_LINK } from './Shared/constants';
18+
import { SuspenseLoader } from './Shared/SuspenseLoader/SuspenseLoader';
1919

2020
export const IdentityAccessLanding = React.memo(() => {
2121
const flags = useFlags();

packages/manager/src/features/IAM/Roles/Defaults/DefaultEntityAccess.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { useGetDefaultDelegationAccessQuery } from '@linode/queries';
2-
import { CircleProgress, Notice, Paper, Stack, Typography } from '@linode/ui';
2+
import { Notice, Paper, Stack, Typography } from '@linode/ui';
33
import * as React from 'react';
44

55
import { ErrorState } from 'src/features/IAM/Shared/ErrorState/ErrorState';
66

77
import { usePermissions } from '../../hooks/usePermissions';
88
import { AssignedEntitiesTable } from '../../Shared/AssignedEntitiesTable/AssignedEntitiesTable';
9+
import { CircleProgress } from '../../Shared/CircleProgress/CircleProgress';
910
import { NO_ASSIGNED_DEFAULT_ENTITIES_TEXT } from '../../Shared/constants';
1011
import { NoAssignedRoles } from '../../Shared/NoAssignedRoles/NoAssignedRoles';
1112

packages/manager/src/features/IAM/Roles/Defaults/DefaultRoles.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { useGetDefaultDelegationAccessQuery } from '@linode/queries';
2-
import { CircleProgress, Notice, Paper, Typography } from '@linode/ui';
2+
import { Notice, Paper, Typography } from '@linode/ui';
33
import * as React from 'react';
44

55
import { ErrorState } from 'src/features/IAM/Shared/ErrorState/ErrorState';
66

77
import { usePermissions } from '../../hooks/usePermissions';
88
import { AssignedRolesTable } from '../../Shared/AssignedRolesTable/AssignedRolesTable';
9+
import { CircleProgress } from '../../Shared/CircleProgress/CircleProgress';
910
import { NO_ASSIGNED_DEFAULT_ROLES_TEXT } from '../../Shared/constants';
1011
import { NoAssignedRoles } from '../../Shared/NoAssignedRoles/NoAssignedRoles';
1112

packages/manager/src/features/IAM/Roles/Defaults/DefaultsLanding.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import { Outlet, useLocation, useNavigate } from '@tanstack/react-router';
44
import * as React from 'react';
55

66
import { LandingHeader } from 'src/components/LandingHeader';
7-
import { SuspenseLoader } from 'src/components/SuspenseLoader';
87
import { Tabs } from 'src/components/Tabs/Tabs';
98
import { TanStackTabLinkList } from 'src/components/Tabs/TanStackTabLinkList';
109
import { useFlags } from 'src/hooks/useFlags';
1110
import { useTabs } from 'src/hooks/useTabs';
1211

1312
import { useIsIAMEnabled } from '../../hooks/useIsIAMEnabled';
1413
import { IAM_LABEL } from '../../Shared/constants';
14+
import { SuspenseLoader } from '../../Shared/SuspenseLoader/SuspenseLoader';
1515

1616
export const DefaultsLanding = () => {
1717
const location = useLocation();

packages/manager/src/features/IAM/Roles/Roles.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describe('RolesLanding', () => {
5252

5353
renderWithTheme(<RolesLanding />);
5454

55-
expect(screen.getByRole('progressbar')).toBeInTheDocument();
55+
expect(screen.getByTestId('circle-progress')).toBeInTheDocument();
5656
});
5757

5858
it('renders roles table when permissions are loaded', async () => {

packages/manager/src/features/IAM/Roles/Roles.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { useAccountRoles } from '@linode/queries';
2-
import { CircleProgress, Notice, Paper, Typography } from '@linode/ui';
2+
import { Notice, Paper, Typography } from '@linode/ui';
33
import React from 'react';
44

55
import { RolesTable } from 'src/features/IAM/Roles/RolesTable/RolesTable';
6+
import { CircleProgress } from 'src/features/IAM/Shared/CircleProgress/CircleProgress';
67
import { mapAccountPermissionsToRoles } from 'src/features/IAM/Shared/utilities';
78

89
import { useDelegationRole } from '../hooks/useDelegationRole';

packages/manager/src/features/IAM/Shared/AssignedRolesTable/AssignedRolesTable.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
useGetDefaultDelegationAccessQuery,
55
useUserRoles,
66
} from '@linode/queries';
7-
import { Button, CircleProgress, Typography } from '@linode/ui';
7+
import { Button, Typography } from '@linode/ui';
88
import { useTheme } from '@mui/material';
99
import Grid from '@mui/material/Grid';
1010
import { useNavigate, useParams, useSearch } from '@tanstack/react-router';
@@ -26,6 +26,7 @@ import { useIsDefaultDelegationRolesForChildAccount } from '../../hooks/useDeleg
2626
import { usePermissions } from '../../hooks/usePermissions';
2727
import { AssignedEntities } from '../../Users/UserRoles/AssignedEntities';
2828
import { AssignNewRoleDrawer } from '../../Users/UserRoles/AssignNewRoleDrawer';
29+
import { CircleProgress } from '../CircleProgress/CircleProgress';
2930
import {
3031
ASSIGNED_ROLES_TABLE_PREFERENCE_KEY,
3132
IAM_ROLES_PENDO_IDS,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { screen, waitFor, within } from '@testing-library/react';
2+
import React from 'react';
3+
4+
import { renderWithTheme } from 'src/utilities/testHelpers';
5+
6+
import { CircleProgress } from './CircleProgress';
7+
8+
const testId = 'circle-progress';
9+
10+
// Helper to get the shadow root of the cds-loading-spinner host element.
11+
const getShadow = (host: HTMLElement) =>
12+
host.shadowRoot as unknown as HTMLElement;
13+
14+
describe('CircleProgress', () => {
15+
it('renders', () => {
16+
renderWithTheme(<CircleProgress />);
17+
18+
screen.getByTestId(testId);
19+
});
20+
21+
it('renders a progressbar in its default loading state', async () => {
22+
renderWithTheme(<CircleProgress />);
23+
24+
const host = screen.getByTestId(testId);
25+
await waitFor(() => within(getShadow(host)).getByRole('progressbar'));
26+
});
27+
28+
it('renders an img in success state', async () => {
29+
renderWithTheme(<CircleProgress state="success" />);
30+
31+
const host = screen.getByTestId(testId);
32+
await waitFor(() => within(getShadow(host)).getByRole('img'));
33+
});
34+
35+
it('has extra-large size by default', () => {
36+
renderWithTheme(<CircleProgress />);
37+
38+
const host = screen.getByTestId(testId) as HTMLElement & { size: string };
39+
expect(host.size).toBe('extra-large');
40+
});
41+
42+
it('accepts a custom size', () => {
43+
renderWithTheme(<CircleProgress size="small" />);
44+
45+
const host = screen.getByTestId(testId) as HTMLElement & { size: string };
46+
expect(host.size).toBe('small');
47+
});
48+
});
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { LoadingSpinner } from '@akamai/cds-components/react/LoadingSpinner';
2+
import * as React from 'react';
3+
4+
export interface CircleProgressProps {
5+
/**
6+
* Optional accessible label for the spinner.
7+
*/
8+
label?: string;
9+
/**
10+
* The size of the spinner.
11+
* @default "extra-large"
12+
*/
13+
size?: 'extra-large' | 'large' | 'medium' | 'small';
14+
/**
15+
* Unique identifier for the spinner element used for aria binding.
16+
*/
17+
spinnerId?: string;
18+
/**
19+
* The current state of the spinner.
20+
* @default "loading"
21+
*/
22+
state?: 'failure' | 'loading' | 'success';
23+
}
24+
25+
export const CircleProgress = ({
26+
label,
27+
size = 'extra-large',
28+
spinnerId,
29+
state = 'loading',
30+
}: CircleProgressProps) => {
31+
return (
32+
<div
33+
style={{
34+
alignItems: 'center',
35+
display: 'flex',
36+
justifyContent: 'center',
37+
margin: '0 auto 20px',
38+
position: 'relative',
39+
flex: 1,
40+
height: 300,
41+
width: '100%',
42+
}}
43+
>
44+
<LoadingSpinner
45+
data-testid="circle-progress"
46+
label={label}
47+
size={size}
48+
spinnerId={spinnerId}
49+
state={state}
50+
/>
51+
</div>
52+
);
53+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as React from 'react';
2+
3+
import { CircleProgress } from '../CircleProgress/CircleProgress';
4+
5+
interface Props {
6+
/**
7+
* Ammount of time before the CircleProgress shows
8+
* @default 300
9+
*/
10+
delay?: number;
11+
}
12+
13+
export const SuspenseLoader = (props: Props) => {
14+
const { delay } = props;
15+
const [show, setShow] = React.useState<boolean>(false);
16+
17+
React.useEffect(() => {
18+
const timeout = setTimeout(() => setShow(true), delay ?? 300);
19+
return () => {
20+
clearTimeout(timeout);
21+
};
22+
}, [delay]);
23+
24+
return <>{show && <CircleProgress />}</>;
25+
};

0 commit comments

Comments
 (0)