From b720f380a4755100323c9a93299d4300a10014bb Mon Sep 17 00:00:00 2001 From: "Leon.kov" Date: Sat, 23 Mar 2024 13:51:28 +0300 Subject: [PATCH] feat: add images, icons loading skeletons and fallback items --- .../ui/ArticleListItem/ArticleListItem.tsx | 18 ++++++- .../ui/AvatarDropDown/AvatarDropDown.tsx | 2 +- src/shared/assets/icons/list-24-24.svg | 2 +- src/shared/assets/icons/user-filled.svg | 3 ++ src/shared/ui/AppImage/AppImage.stories.tsx | 17 +++++++ src/shared/ui/AppImage/AppImage.tsx | 49 +++++++++++++++++++ src/shared/ui/AppImage/index.ts | 1 + src/shared/ui/Avatar/Avatar.tsx | 19 +++++-- src/shared/ui/Icon/Icon.module.scss | 2 + 9 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 src/shared/assets/icons/user-filled.svg create mode 100644 src/shared/ui/AppImage/AppImage.stories.tsx create mode 100644 src/shared/ui/AppImage/AppImage.tsx create mode 100644 src/shared/ui/AppImage/index.ts diff --git a/src/entities/Article/ui/ArticleListItem/ArticleListItem.tsx b/src/entities/Article/ui/ArticleListItem/ArticleListItem.tsx index 8649e70..e539720 100644 --- a/src/entities/Article/ui/ArticleListItem/ArticleListItem.tsx +++ b/src/entities/Article/ui/ArticleListItem/ArticleListItem.tsx @@ -13,6 +13,8 @@ import { ArticleTextBlockComponent } from '../ArticleTextBlockComponent/ArticleT import { Article, ArticleTextBlock } from '../../model/types/article'; import cls from './ArticleListItem.module.scss'; import { getRouterArticleDetails } from '@/shared/const/router'; +import { AppImage } from '@/shared/ui/AppImage'; +import { Skeleton } from '@/shared/ui/Skeleton'; interface ArticleListItemProps { className?: string; @@ -51,7 +53,13 @@ export const ArticleListItem = memo((props: ArticleListItemProps) => { {types} - {article.title} + } + errorFallback={} + /> {textBlock && ( )} @@ -79,7 +87,13 @@ export const ArticleListItem = memo((props: ArticleListItemProps) => { >
- {article.title} + } + errorFallback={} + />
diff --git a/src/features/avatarDropdown/ui/AvatarDropDown/AvatarDropDown.tsx b/src/features/avatarDropdown/ui/AvatarDropDown/AvatarDropDown.tsx index 7a20a1a..dea738b 100644 --- a/src/features/avatarDropdown/ui/AvatarDropDown/AvatarDropDown.tsx +++ b/src/features/avatarDropdown/ui/AvatarDropDown/AvatarDropDown.tsx @@ -52,7 +52,7 @@ export const AvatarDropDown = memo(() => { onClick: onLogout, }, ]} - trigger={} + trigger={} /> ); }); diff --git a/src/shared/assets/icons/list-24-24.svg b/src/shared/assets/icons/list-24-24.svg index a6a95a8..9424eea 100644 --- a/src/shared/assets/icons/list-24-24.svg +++ b/src/shared/assets/icons/list-24-24.svg @@ -1,3 +1,3 @@ - + diff --git a/src/shared/assets/icons/user-filled.svg b/src/shared/assets/icons/user-filled.svg new file mode 100644 index 0000000..620701c --- /dev/null +++ b/src/shared/assets/icons/user-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/shared/ui/AppImage/AppImage.stories.tsx b/src/shared/ui/AppImage/AppImage.stories.tsx new file mode 100644 index 0000000..5068df9 --- /dev/null +++ b/src/shared/ui/AppImage/AppImage.stories.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import { AppImage } from './AppImage'; + +export default { + title: 'shared/AppImage', + component: AppImage, + argTypes: { + backgroundColor: { control: 'color' }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Normal = Template.bind({}); +Normal.args = {}; diff --git a/src/shared/ui/AppImage/AppImage.tsx b/src/shared/ui/AppImage/AppImage.tsx new file mode 100644 index 0000000..5c0ec6c --- /dev/null +++ b/src/shared/ui/AppImage/AppImage.tsx @@ -0,0 +1,49 @@ +import { + ImgHTMLAttributes, memo, ReactElement, useLayoutEffect, useState, +} from 'react'; + +interface AppImageProps extends ImgHTMLAttributes{ + className?: string; + fallback?: ReactElement; + errorFallback?: ReactElement; +} + +export const AppImage = memo((props: AppImageProps) => { + const { + className, + fallback, + errorFallback, + src, + alt = 'image', + ...otherProps + } = props; + const [isLoading, setIsLoading] = useState(true); + const [hasError, setHasError] = useState(false); + + useLayoutEffect(() => { + const img = new Image(); + img.src = src ?? ''; + img.onload = () => { + setIsLoading(false); + }; + img.onerror = () => { + setIsLoading(false); + setHasError(true); + }; + }, [src]); + + if (isLoading && fallback) { + return fallback; + } + if (hasError && errorFallback) { + return errorFallback; + } + return ( + {alt} + ); +}); diff --git a/src/shared/ui/AppImage/index.ts b/src/shared/ui/AppImage/index.ts new file mode 100644 index 0000000..451f4df --- /dev/null +++ b/src/shared/ui/AppImage/index.ts @@ -0,0 +1 @@ +export { AppImage } from './AppImage'; diff --git a/src/shared/ui/Avatar/Avatar.tsx b/src/shared/ui/Avatar/Avatar.tsx index a8bf4a9..0569d11 100644 --- a/src/shared/ui/Avatar/Avatar.tsx +++ b/src/shared/ui/Avatar/Avatar.tsx @@ -1,34 +1,45 @@ import { CSSProperties, useMemo } from 'react'; import { classNames, Mods } from '@/shared/lib/classNames/classNames'; import cls from './Avatar.module.scss'; +import { AppImage } from '../AppImage'; +import UserIcon from '../../assets/icons/user-filled.svg'; +import { Icon } from '../Icon'; +import { Skeleton } from '../Skeleton'; export interface AvatarProps { className?: string; src?: string; size?: number; alt?: string; + fallbackInverted?: boolean; } export const Avatar = (props: AvatarProps) => { const { className, src, - size, + size = 100, alt, + fallbackInverted, } = props; const mods: Mods = {}; const styles = useMemo(() => ({ - width: size || 100, - height: size || 100, + width: size, + height: size, }), [size]); + const fallback = ; + const errorFallback = ; + return ( - {alt} ); }; diff --git a/src/shared/ui/Icon/Icon.module.scss b/src/shared/ui/Icon/Icon.module.scss index 9b331d3..25d1988 100644 --- a/src/shared/ui/Icon/Icon.module.scss +++ b/src/shared/ui/Icon/Icon.module.scss @@ -1,7 +1,9 @@ .Icon { fill: var(--primary-color); + color: var(--primary-color); } .inverted { fill: var(--inverted-primary-color); + color: var(--inverted-primary-color); }