From 8b891ac26f44417c5e162360539c298364f9f39f Mon Sep 17 00:00:00 2001 From: Anton Vikulov Date: Fri, 10 Nov 2023 16:27:07 +0500 Subject: [PATCH] feat(ChangelogDialog): loading state --- .../ChangelogDialog/ChangelogDialog.tsx | 36 ++++++---- .../__stories__/ChangelogDialog.stories.tsx | 14 +++- .../components/ItemSkeleton/ItemSkeleton.scss | 68 +++++++++++++++++++ .../components/ItemSkeleton/ItemSkeleton.tsx | 51 ++++++++++++++ 4 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 src/components/ChangelogDialog/components/ItemSkeleton/ItemSkeleton.scss create mode 100644 src/components/ChangelogDialog/components/ItemSkeleton/ItemSkeleton.tsx diff --git a/src/components/ChangelogDialog/ChangelogDialog.tsx b/src/components/ChangelogDialog/ChangelogDialog.tsx index bd1a7448..8a90a754 100644 --- a/src/components/ChangelogDialog/ChangelogDialog.tsx +++ b/src/components/ChangelogDialog/ChangelogDialog.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {Fragment} from 'react'; import {ArrowUpRightFromSquare} from '@gravity-ui/icons'; import type {DialogProps} from '@gravity-ui/uikit'; @@ -7,6 +7,7 @@ import {Dialog, Icon, Link} from '@gravity-ui/uikit'; import {block} from '../utils/cn'; import {Item} from './components/Item/Item'; +import {ItemSkeleton} from './components/ItemSkeleton/ItemSkeleton'; import i18n from './i18n'; import type {ChangelogItem} from './types'; @@ -24,6 +25,7 @@ export interface ChangelogDialogProps { onClose: DialogProps['onClose']; onLinkClick?: (link: string) => void; onStoryClick?: (storyId: string) => void; + loading?: boolean; } let nextId = 1; @@ -41,6 +43,7 @@ export function ChangelogDialog({ onClose, onStoryClick, onLinkClick, + loading = true, }: ChangelogDialogProps) { const idRef = React.useRef(); idRef.current = idRef.current || getNextId(); @@ -67,19 +70,26 @@ export function ChangelogDialog({ ) : null} - {items.length > 0 ? ( - items.map((item, index) => ( - - )) - ) : ( -
{i18n('label_empty')}
+ {loading && ( + + + + )} + {!loading && + (items.length > 0 ? ( + items.map((item, index) => ( + + )) + ) : ( +
{i18n('label_empty')}
+ ))}
); diff --git a/src/components/ChangelogDialog/__stories__/ChangelogDialog.stories.tsx b/src/components/ChangelogDialog/__stories__/ChangelogDialog.stories.tsx index 64975335..8c9e0c36 100644 --- a/src/components/ChangelogDialog/__stories__/ChangelogDialog.stories.tsx +++ b/src/components/ChangelogDialog/__stories__/ChangelogDialog.stories.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useEffect} from 'react'; import {Button} from '@gravity-ui/uikit'; import type {Meta, StoryFn} from '@storybook/react'; @@ -79,11 +79,22 @@ const items: ChangelogItem[] = [ const DefaultTemplate: StoryFn = (props: ChangelogDialogProps) => { const [visible, setVisible] = React.useState(props.open); + const [loading, setLoading] = React.useState(true); React.useEffect(() => { setVisible(props.open); }, [props.open]); + useEffect(() => { + if (!visible) return; + const timeoutId = setTimeout(() => setLoading(false), 1000); + // eslint-disable-next-line consistent-return + return () => { + clearTimeout(timeoutId); + setLoading(true); + }; + }, [visible]); + return (
@@ -98,6 +109,7 @@ const DefaultTemplate: StoryFn = (props: ChangelogDialogPr { setVisible(false); props.onClose?.(event, reason); diff --git a/src/components/ChangelogDialog/components/ItemSkeleton/ItemSkeleton.scss b/src/components/ChangelogDialog/components/ItemSkeleton/ItemSkeleton.scss new file mode 100644 index 00000000..d32f18fc --- /dev/null +++ b/src/components/ChangelogDialog/components/ItemSkeleton/ItemSkeleton.scss @@ -0,0 +1,68 @@ +@use '@gravity-ui/uikit/styles/mixins'; +@use '../../../variables'; + +$block: '.#{variables.$ns}changelog-dialog-item'; +$metaWidth: 80px; + +#{$block} { + display: flex; + + &__meta { + width: $metaWidth; + } + + &__date { + line-height: var(--g-text-subheader-3-line-height); + } + + &__date-skeleton { + width: 80px; + height: 20px; + } + + &__label-new-skeleton { + margin-top: var(--g-spacing-2); + width: 42px; + height: 20px; + } + + &__content { + flex: 1; + margin-left: var(--g-spacing-5); + } + + &__title { + margin: 0; + } + + &__title-skeleton { + width: 100%; + height: 20px; + } + + &__image-skeleton { + width: 100%; + height: 258px; + margin-top: var(--g-spacing-3); + border-radius: 16px; + } + + &__description { + margin-top: var(--g-spacing-3); + } + + &__description-skeleton { + width: 100%; + height: 54px; + } + + &__button-skeleton { + margin-top: var(--g-spacing-4); + width: 86px; + height: 28px; + } + + &__button-skeleton + &__button-skeleton { + margin-left: var(--g-spacing-4); + } +} diff --git a/src/components/ChangelogDialog/components/ItemSkeleton/ItemSkeleton.tsx b/src/components/ChangelogDialog/components/ItemSkeleton/ItemSkeleton.tsx new file mode 100644 index 00000000..ea8b7870 --- /dev/null +++ b/src/components/ChangelogDialog/components/ItemSkeleton/ItemSkeleton.tsx @@ -0,0 +1,51 @@ +import React from 'react'; + +import {Skeleton} from '@gravity-ui/uikit'; + +import {block} from '../../../utils/cn'; + +import './ItemSkeleton.scss'; + +const b = block('changelog-dialog-item'); + +interface ItemSkeletonProps { + className?: string; + isNew?: boolean; + withImage?: boolean; + withDescription?: boolean; + withLink?: boolean; + withStory?: boolean; +} + +export function ItemSkeleton({ + className, + withImage, + isNew, + withDescription, + withLink, + withStory, +}: ItemSkeletonProps) { + return ( +
+
+
+ + {isNew && } +
+
+
+

+ +

+ {withImage && } + {withDescription && ( +
+ +
+ )} + {withLink && } + {withStory && } +
+
+ ); +}