Skip to content

Commit

Permalink
feat: skeleton component \ article state styles
Browse files Browse the repository at this point in the history
  • Loading branch information
TomatoVan committed Dec 21, 2023
1 parent a682dd1 commit d6fbc37
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 8 deletions.
4 changes: 4 additions & 0 deletions extractedTranslations/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"Choose country": "",
"Choose currency": "",
"Edit": "",
"Error with getting article data": "",
"Error with getting profile data": "",
"Incorrect age": "",
"Incorrect country": "",
"Incorrect user data": "",
Expand All @@ -12,6 +14,8 @@
"Profile": "",
"Save": "",
"Server error": "",
"Try to reload page": "",
"article_not_found": "",
"articles": "",
"articles page": "",
"articles_details": "",
Expand Down
4 changes: 3 additions & 1 deletion src/app/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ body {

.page-wrapper {
flex-grow: 1;
padding: 20px;
padding: 20px 20px 20px 45px;
height: calc(100vh - var(--navbar-height));
overflow-y: auto;
}
4 changes: 4 additions & 0 deletions src/app/styles/themes/dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@
--secondary-color: #049604;
--inverted-primary-color: #0232c2;
--inverted-secondary-color: #0452ff;

// skeleton
--skeleton-color: #1515ad;
--skeleton-shadow: #2b2be8;
}
4 changes: 4 additions & 0 deletions src/app/styles/themes/normal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@
--secondary-color: #0449e0;
--inverted-primary-color: #04ff04;
--inverted-secondary-color: #049604;

// skeleton
--skeleton-color: #fff;
--skeleton-shadow: rgba(0 0 0 / 20%);
}
5 changes: 5 additions & 0 deletions src/entities/Article/model/selectors/articleDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { StateSchema } from 'app/providers/StoreProvider';

export const getArticlesDetailsData = (state: StateSchema) => state.articleDetails?.data;
export const getArticlesDetailsIsLoading = (state: StateSchema) => state.articleDetails?.isLoading;
export const getArticlesDetailsIsError = (state: StateSchema) => state.articleDetails?.error;
31 changes: 31 additions & 0 deletions src/entities/Article/ui/ArticleDetails/ArticleDetails.module.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
.ArticleDetails {
min-height: 100%;
}

.avatar {
margin: 0 auto;
}

.title {
margin-top: 20px;
}

.skeleton {
margin-top: 15px;
}

.articleInfo {
display: flex;
align-items: center;
}

.icon {
margin-right: 8px;
}

.avatarWrapper {
display: flex;
width: 100%;
justify-content: center;
}

.block {
margin-top: 16px;
}
56 changes: 50 additions & 6 deletions src/entities/Article/ui/ArticleDetails/ArticleDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,75 @@
import { classNames } from 'shared/lib/classNames/classNames';
import { useTranslation } from 'react-i18next';
import { DynamicModuleLoader, ReducersList } from 'shared/lib/components/DynamicModuleLoared/DynamicModuleLoared';
import {
DynamicModuleLoader,
ReducersList,
} from 'shared/lib/components/DynamicModuleLoared/DynamicModuleLoared';
import { memo, useEffect } from 'react';
import { useAppDispatch } from 'shared/lib/hook/useAppDispatch/useAppDispatch';
import { useSelector } from 'react-redux';
import { Text, TextAlign, TextTheme } from 'shared/ui/Text/Text';
import { Skeleton } from 'shared/ui/Skeleton/ui/Skeleton';
import {
getArticlesDetailsData,
getArticlesDetailsIsError,
getArticlesDetailsIsLoading,
} from '../../model/selectors/articleDetails';
import { fetchArticleById } from '../../services/fetchArticleById/fetchArticleById';
import { articleDetailsReducer } from '../../model/slice/articleDetailsSlice';
import cls from './ArticleDetails.module.scss';

interface ArticleDetailsProps {
className?: string;
className?: string;
id: string;
}

const reducers: ReducersList = {
articleDetails: articleDetailsReducer,
};

export const ArticleDetails = memo(({ className }: ArticleDetailsProps) => {
export const ArticleDetails = memo(({ className, id }: ArticleDetailsProps) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();

const article = useSelector(getArticlesDetailsData);
const isLoading = useSelector(getArticlesDetailsIsLoading);
const error = useSelector(getArticlesDetailsIsError);

useEffect(() => {
dispatch(fetchArticleById('1'));
}, [dispatch]);
dispatch(fetchArticleById(id));
}, [dispatch, id]);

let content;
if (isLoading) {
content = (
<div>
<Skeleton className={cls.avatar} width={200} height={200} border="50%" />
<Skeleton className={cls.title} width={300} height={32} />
<Skeleton className={cls.skeleton} width={600} height={24} />
<Skeleton className={cls.skeleton} width="100%" height={200} />
<Skeleton className={cls.skeleton} width="100%" height={200} />

</div>
);
} else if (error) {
content = (
<Text
theme={TextTheme.ERROR}
title={t('Error with getting article data')}
text={t('Try to reload page')}
align={TextAlign.CENTER}
/>
);
} else {
content = (
<div> ARTICLE DETAILS</div>
);
}

return (
<DynamicModuleLoader reducers={reducers} removeAfterUnmount>
<div className={classNames(cls.ArticleDetails, {}, [className])}>
ARTICLE DETAILS
{content}
</div>
</DynamicModuleLoader>
);
Expand Down
11 changes: 10 additions & 1 deletion src/pages/ArticlesDetailsPage/ui/ArticlesDetailsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { useTranslation } from 'react-i18next';
import { memo } from 'react';
import { useParams } from 'react-router-dom';
import { ArticleDetails } from '../../../entities/Article';

const ArticlesDetailsPage = (props: any) => {
const { t } = useTranslation();

const { id } = useParams<{id: string}>();
if (!id) {
return (
<div>{t('article_not_found')}</div>
);
}

return (
<div>
<ArticleDetails />
<ArticleDetails id={id} />
</div>
);
};
Expand Down
29 changes: 29 additions & 0 deletions src/shared/ui/Skeleton/ui/Skeleton.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.Skeleton {
width: 100%;
height: 50px;
position: relative;
box-shadow: 0 2px 10px 0 var(--skeleton-shadow);
overflow: hidden;

&::before {
content: "";
display: block;
position: absolute;
left: -150px;
top: 0;
height: 100%;
width: 80%;
background: linear-gradient(to right, transparent 0%, var(--skeleton-color) 50%, transparent 100%);
animation: load 1s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
}

@keyframes load {
from {
left: -150px;
}

to {
left: 100%;
}
}
41 changes: 41 additions & 0 deletions src/shared/ui/Skeleton/ui/Skeleton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';

import { ThemeDecorator } from 'shared/config/storybook/ThemeDecorator/ThemeDecorator';
import { Theme } from 'app/providers/ThemeProvider';
import { Skeleton } from './Skeleton';

export default {
title: 'shared/Skeleton',
component: Skeleton,
argTypes: {
backgroundColor: { control: 'color' },
},
} as ComponentMeta<typeof Skeleton>;

const Template: ComponentStory<typeof Skeleton> = (args) => <Skeleton {...args} />;

export const Normal = Template.bind({});
Normal.args = {
width: '100%',
height: 200,
};
export const Circle = Template.bind({});
Circle.args = {
border: '50%',
width: 100,
height: 100,
};
export const NormalDark = Template.bind({});
NormalDark.args = {
width: '100%',
height: 200,
};
NormalDark.decorators = [ThemeDecorator((Theme.DARK))];
export const CircleDark = Template.bind({});
CircleDark.args = {
border: '50%',
width: 100,
height: 100,
};
CircleDark.decorators = [ThemeDecorator((Theme.DARK))];
35 changes: 35 additions & 0 deletions src/shared/ui/Skeleton/ui/Skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { classNames } from 'shared/lib/classNames/classNames';
import { useTranslation } from 'react-i18next';
import { CSSProperties, memo } from 'react';
import cls from './Skeleton.module.scss';

interface SkeletonProps {
className?: string;
height?: string | number;
width?: string | number;
border?: string;
}

export const Skeleton = memo((props: SkeletonProps) => {
const {
border,
height,
width,
className,
} = props;

const styles: CSSProperties = {
width,
height,
borderRadius: border,

};

const { t } = useTranslation();
return (
<div
style={styles}
className={classNames(cls.Skeleton, {}, [className])}
/>
);
});

0 comments on commit d6fbc37

Please sign in to comment.