mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
Merge pull request #825 from logto-io/charles-log-2423-add-page-skeletons-for-tables-and-details
feat(console): add page loading skeleton to data table and detail pages
This commit is contained in:
commit
4020096319
12 changed files with 181 additions and 78 deletions
|
@ -79,7 +79,7 @@ const Main = () => {
|
|||
}
|
||||
}, [location.pathname, navigate, sections]);
|
||||
|
||||
if (sections?.length === 0) {
|
||||
if (!sections?.length) {
|
||||
return <LogtoLoading message="general.loading" />;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: _.unit(6);
|
||||
border-radius: 16px;
|
||||
background-color: var(--color-layer-1);
|
||||
|
||||
.icon {
|
||||
@include _.shimmering-animation;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 12px;
|
||||
margin-right: _.unit(6);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
@include _.shimmering-animation;
|
||||
width: 113px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.tags {
|
||||
@include _.shimmering-animation;
|
||||
width: 453px;
|
||||
height: 20px;
|
||||
margin-top: _.unit(3);
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
@include _.shimmering-animation;
|
||||
width: 158px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: _.unit(6);
|
||||
margin-top: _.unit(6);
|
||||
border-radius: 16px;
|
||||
background-color: var(--color-layer-1);
|
||||
|
||||
.tabBar {
|
||||
@include _.shimmering-animation;
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
margin-bottom: _.unit(13.5);
|
||||
}
|
||||
|
||||
.field {
|
||||
@include _.shimmering-animation;
|
||||
width: 566px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.field + .field {
|
||||
margin-top: _.unit(6);
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
|
||||
.button {
|
||||
@include _.shimmering-animation;
|
||||
width: 194px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
packages/console/src/components/DetailsSkeleton/index.tsx
Normal file
32
packages/console/src/components/DetailsSkeleton/index.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
|
||||
import Spacer from '@/components/Spacer';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const DetailsSkeleton = () => (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.icon} />
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.title} />
|
||||
<div className={styles.tags} />
|
||||
</div>
|
||||
<Spacer />
|
||||
<div className={styles.button} />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.tabBar} />
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<div key={index} className={styles.field} />
|
||||
))}
|
||||
<Spacer />
|
||||
<div className={styles.footer}>
|
||||
<div className={styles.button} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default DetailsSkeleton;
|
|
@ -1,34 +0,0 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
td > div.loading {
|
||||
display: flex;
|
||||
background: none;
|
||||
height: 40px;
|
||||
border-radius: unset;
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: _.unit(2);
|
||||
margin-right: _.unit(4);
|
||||
background: var(--color-neutral-95);
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
|
||||
.title {
|
||||
background: var(--color-neutral-95);
|
||||
border-radius: _.unit(2);
|
||||
height: 14px;
|
||||
margin: _.unit(1) 0;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
background: var(--color-neutral-95);
|
||||
border-radius: _.unit(2);
|
||||
height: 12px;
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import * as styles from './ItemPreviewLoading.module.scss';
|
||||
|
||||
const ItemPreviewLoading = () => (
|
||||
<div className={styles.loading}>
|
||||
<div className={styles.avatar} />
|
||||
<div className={styles.content}>
|
||||
<div className={styles.title} />
|
||||
<div className={styles.subTitle} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ItemPreviewLoading;
|
|
@ -1,9 +1,37 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.loading {
|
||||
.itemPreview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.avatar {
|
||||
@include _.shimmering-animation;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: _.unit(4);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 135px;
|
||||
|
||||
.title {
|
||||
@include _.shimmering-animation;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include _.shimmering-animation;
|
||||
height: 8px;
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rect {
|
||||
border-radius: _.unit(2);
|
||||
background: var(--color-neutral-95);
|
||||
@include _.shimmering-animation;
|
||||
height: 32px;
|
||||
max-width: 344px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import ItemPreviewLoading from './ItemPreviewLoading';
|
||||
import * as styles from './TableLoading.module.scss';
|
||||
|
||||
type Props = {
|
||||
|
@ -8,27 +7,28 @@ type Props = {
|
|||
};
|
||||
|
||||
const TableLoading = ({ columns }: Props) => {
|
||||
const row = useMemo(
|
||||
() => (
|
||||
<tr className={styles.loading}>
|
||||
<td>
|
||||
<ItemPreviewLoading />
|
||||
</td>
|
||||
{Array.from({ length: columns - 1 }).map((_, index) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<td key={index}>
|
||||
<div className={styles.rect} />
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
),
|
||||
[columns]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{row}
|
||||
{row}
|
||||
{Array.from({ length: 8 }).map((_, rowIndex) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<tr key={`row-${rowIndex}`} className={styles.loading}>
|
||||
<td>
|
||||
<div className={styles.itemPreview}>
|
||||
<div className={styles.avatar} />
|
||||
<div className={styles.content}>
|
||||
<div className={styles.title} />
|
||||
<div className={styles.subTitle} />
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
{Array.from({ length: columns - 1 }).map((_, index) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<td key={index}>
|
||||
<div className={styles.rect} />
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu';
|
|||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import CopyToClipboard from '@/components/CopyToClipboard';
|
||||
import DetailsSkeleton from '@/components/DetailsSkeleton';
|
||||
import Drawer from '@/components/Drawer';
|
||||
import FormField from '@/components/FormField';
|
||||
import ImagePlaceholder from '@/components/ImagePlaceholder';
|
||||
|
@ -82,7 +83,7 @@ const ApiResourceDetails = () => {
|
|||
title="admin_console.api_resource_details.back_to_api_resources"
|
||||
className={styles.backLink}
|
||||
/>
|
||||
{isLoading && <div>loading</div>}
|
||||
{isLoading && <DetailsSkeleton />}
|
||||
{error && <div>{`error occurred: ${error.body.message}`}</div>}
|
||||
{data && (
|
||||
<>
|
||||
|
|
|
@ -12,6 +12,7 @@ import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu';
|
|||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import CopyToClipboard from '@/components/CopyToClipboard';
|
||||
import DetailsSkeleton from '@/components/DetailsSkeleton';
|
||||
import Drawer from '@/components/Drawer';
|
||||
import ImagePlaceholder from '@/components/ImagePlaceholder';
|
||||
import LinkButton from '@/components/LinkButton';
|
||||
|
@ -43,7 +44,7 @@ const ApplicationDetails = () => {
|
|||
SnakeCaseOidcConfig,
|
||||
RequestError
|
||||
>('/oidc/.well-known/openid-configuration');
|
||||
const isLoading = !data && !error && !oidcConfig && !fetchOidcConfigError;
|
||||
const isLoading = (!data && !error) || (!oidcConfig && !fetchOidcConfigError);
|
||||
const [isReadmeOpen, setIsReadmeOpen] = useState(false);
|
||||
const [isDeleteFormOpen, setIsDeleteFormOpen] = useState(false);
|
||||
const api = useApi();
|
||||
|
@ -102,7 +103,7 @@ const ApplicationDetails = () => {
|
|||
title="admin_console.application_details.back_to_applications"
|
||||
className={styles.backLink}
|
||||
/>
|
||||
{isLoading && <div>loading</div>}
|
||||
{isLoading && <DetailsSkeleton />}
|
||||
{data && oidcConfig && (
|
||||
<>
|
||||
<Card className={styles.header}>
|
||||
|
|
|
@ -10,6 +10,7 @@ import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu';
|
|||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import CodeEditor from '@/components/CodeEditor';
|
||||
import DetailsSkeleton from '@/components/DetailsSkeleton';
|
||||
import Drawer from '@/components/Drawer';
|
||||
import ImagePlaceholder from '@/components/ImagePlaceholder';
|
||||
import LinkButton from '@/components/LinkButton';
|
||||
|
@ -105,7 +106,7 @@ const ConnectorDetails = () => {
|
|||
title="admin_console.connector_details.back_to_connectors"
|
||||
className={styles.backLink}
|
||||
/>
|
||||
{isLoading && <div>loading</div>}
|
||||
{isLoading && <DetailsSkeleton />}
|
||||
{error && <div>{`error occurred: ${error.body.message}`}</div>}
|
||||
{data && (
|
||||
<Card className={styles.header}>
|
||||
|
|
|
@ -14,6 +14,7 @@ import Button from '@/components/Button';
|
|||
import Card from '@/components/Card';
|
||||
import CodeEditor from '@/components/CodeEditor';
|
||||
import CopyToClipboard from '@/components/CopyToClipboard';
|
||||
import DetailsSkeleton from '@/components/DetailsSkeleton';
|
||||
import FormField from '@/components/FormField';
|
||||
import ImagePlaceholder from '@/components/ImagePlaceholder';
|
||||
import LinkButton from '@/components/LinkButton';
|
||||
|
@ -111,7 +112,7 @@ const UserDetails = () => {
|
|||
title="admin_console.user_details.back_to_users"
|
||||
className={styles.backLink}
|
||||
/>
|
||||
{isLoading && <div>loading</div>}
|
||||
{isLoading && <DetailsSkeleton />}
|
||||
{error && <div>{`error occurred: ${error.body.message}`}</div>}
|
||||
{id && data && (
|
||||
<>
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
background-color: $baseColor;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
|
|
Loading…
Reference in a new issue