diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 26ec9a6..4f5544d 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -7,6 +7,7 @@ "Произошла непредвиденная ошибка": "Unexpected error", "Сломать": "Broke", "Главная страница": "Main page", + "Статьи": "Articles", "О сайте": "About us", "Короткий язык": "En", "Введите логин": "Username", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index eaa3033..d5bf10b 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -7,6 +7,7 @@ "Произошла непредвиденная ошибка": "Произошла непредвиденная ошибка", "Сломать": "Сломать", "Главная страница": "Главная страница", + "Статьи": "Статьи", "О сайте": "О нас", "Короткий язык": "Ру", "Введите логин": "Логин", diff --git a/src/app/styles/index.scss b/src/app/styles/index.scss index 6eb7bba..1aaae84 100644 --- a/src/app/styles/index.scss +++ b/src/app/styles/index.scss @@ -23,4 +23,5 @@ body { padding: 20px 20px 20px 45px; height: calc(100vh - var(--navbar-height)); overflow-y: auto; + width: 100%; } diff --git a/src/app/styles/themes/dark.scss b/src/app/styles/themes/dark.scss index 2d6dfe6..eab3a2e 100644 --- a/src/app/styles/themes/dark.scss +++ b/src/app/styles/themes/dark.scss @@ -9,4 +9,7 @@ // skeleton --skeleton-color: #1515ad; --skeleton-shadow: #2b2be8; + + // code + --code-bg: #1212a1; } diff --git a/src/app/styles/themes/light.scss b/src/app/styles/themes/light.scss index 803f0bc..e610491 100644 --- a/src/app/styles/themes/light.scss +++ b/src/app/styles/themes/light.scss @@ -9,4 +9,7 @@ // skeleton --skeleton-color: #fff; --skeleton-shadow: rgba(0 0 0 / 20%); + + // code + --code-bg: #fff; } diff --git a/src/app/styles/themes/yellow.scss b/src/app/styles/themes/yellow.scss index ea3bcf8..f705c3d 100644 --- a/src/app/styles/themes/yellow.scss +++ b/src/app/styles/themes/yellow.scss @@ -9,4 +9,7 @@ // skeleton --skeleton-color: #fff; --skeleton-shadow: rgba(0 0 0 / 20%); + + // code + --code-bg: #d7b11b; } diff --git a/src/entities/Article/model/selectors/articleDetails.test.ts b/src/entities/Article/model/selectors/articleDetails.test.ts new file mode 100644 index 0000000..adb6a2d --- /dev/null +++ b/src/entities/Article/model/selectors/articleDetails.test.ts @@ -0,0 +1,56 @@ +import { StateSchema } from 'app/providers/StoreProvider'; +import { + getArticleDetailsData, + getArticleDetailsIsLoading, + getArticleDetailsError, +} from './articleDetails'; + +describe('getArticleDetailsData.test', () => { + test('should return data', () => { + const dataInit = { + id: '1', + title: 'Javascript news', + }; + + const state: DeepPartial = { + articleDetails: { + data: dataInit, + }, + }; + expect(getArticleDetailsData(state as StateSchema)).toEqual(dataInit); + }); + test('should work with empty data', () => { + const state: DeepPartial = {}; + expect(getArticleDetailsData(state as StateSchema)).toEqual(undefined); + }); +}); + +describe('getArticleDetailsIsLoading.test', () => { + test('should return isLoading state', () => { + const state: DeepPartial = { + articleDetails: { + isLoading: true, + }, + }; + expect(getArticleDetailsIsLoading(state as StateSchema)).toEqual(true); + }); + test('should work with empty state isLoading', () => { + const state: DeepPartial = {}; + expect(getArticleDetailsIsLoading(state as StateSchema)).toEqual(false); + }); +}); + +describe('getArticleDetailsError.error', () => { + test('should return isLoading state', () => { + const state: DeepPartial = { + articleDetails: { + error: 'error', + }, + }; + expect(getArticleDetailsError(state as StateSchema)).toEqual('error'); + }); + test('should work with empty state error', () => { + const state: DeepPartial = {}; + expect(getArticleDetailsError(state as StateSchema)).toEqual(undefined); + }); +}); diff --git a/src/entities/Article/model/selectors/articleDetails.ts b/src/entities/Article/model/selectors/articleDetails.ts index 96dc753..e8a7101 100644 --- a/src/entities/Article/model/selectors/articleDetails.ts +++ b/src/entities/Article/model/selectors/articleDetails.ts @@ -1,5 +1,6 @@ import { StateSchema } from 'app/providers/StoreProvider'; export const getArticleDetailsData = (state: StateSchema) => state.articleDetails?.data; -export const getArticleDetailsIsLoading = (state: StateSchema) => state.articleDetails?.isLoading; +export const getArticleDetailsIsLoading = (state: StateSchema) => state.articleDetails?.isLoading + || false; export const getArticleDetailsError = (state: StateSchema) => state.articleDetails?.error; diff --git a/src/entities/Article/ui/ArticleCodeBlockComponent/ArticleCodeBlockComponent.module.scss b/src/entities/Article/ui/ArticleCodeBlockComponent/ArticleCodeBlockComponent.module.scss new file mode 100644 index 0000000..837db03 --- /dev/null +++ b/src/entities/Article/ui/ArticleCodeBlockComponent/ArticleCodeBlockComponent.module.scss @@ -0,0 +1,3 @@ +.ArticleCodeBlockComponent { + width: 100%; +} diff --git a/src/entities/Article/ui/ArticleCodeBlockComponent/ArticleCodeBlockComponent.tsx b/src/entities/Article/ui/ArticleCodeBlockComponent/ArticleCodeBlockComponent.tsx new file mode 100644 index 0000000..b78b4de --- /dev/null +++ b/src/entities/Article/ui/ArticleCodeBlockComponent/ArticleCodeBlockComponent.tsx @@ -0,0 +1,28 @@ +import { memo } from 'react'; +import { classNames } from 'shared/lib/classNames/classNames'; +import { useTranslation } from 'react-i18next'; + +import { Code } from 'shared/ui/Code/Code'; +import { ArticleCodeBlock } from '../../model/types/article'; + +import cls from './ArticleCodeBlockComponent.module.scss'; + +interface ArticleCodeBlockComponentProps { + className?: string; + block: ArticleCodeBlock; +} + +export const ArticleCodeBlockComponent = memo( + (props: ArticleCodeBlockComponentProps) => { + const { className, block } = props; + const { t } = useTranslation(); + + return ( +
+ +
+ ); + }, +); diff --git a/src/entities/Article/ui/ArticleDetails/ArticleDetails.module.scss b/src/entities/Article/ui/ArticleDetails/ArticleDetails.module.scss index 35f54ab..bd331fc 100644 --- a/src/entities/Article/ui/ArticleDetails/ArticleDetails.module.scss +++ b/src/entities/Article/ui/ArticleDetails/ArticleDetails.module.scss @@ -13,3 +13,22 @@ .skeleton { margin-top: 15px; } + +.avatarWrapper { + display: flex; + justify-content: center; + align-items: center; +} + +.articleInfo { + display: flex; + align-items: center; +} + +.icon { + margin-right: 8px; +} + +.block { + margin-top: 16px; +} diff --git a/src/entities/Article/ui/ArticleDetails/ArticleDetails.stories.tsx b/src/entities/Article/ui/ArticleDetails/ArticleDetails.stories.tsx index 6e0f284..c7776d1 100644 --- a/src/entities/Article/ui/ArticleDetails/ArticleDetails.stories.tsx +++ b/src/entities/Article/ui/ArticleDetails/ArticleDetails.stories.tsx @@ -1,23 +1,134 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Theme } from 'app/providers/ThemeProvider'; +import { Article } from 'entities/Article'; +import { + ArticleBlockType, + ArticleType, +} from 'entities/Article/model/types/article'; import { StoreDecorator } from 'shared/config/storybook/StoreDecorator/StoreDecorator'; import { ThemeDecorator } from 'shared/config/storybook/ThemeDecorator/ThemeDecorator'; import { ArticleDetails } from './ArticleDetails'; const meta: Meta = { - title: 'category/ArticleDetails', + title: 'entities/ArticleDetails', component: ArticleDetails, args: {}, }; +const article: Article = { + id: '1', + title: 'Javascript news', + subtitle: 'Что нового в JS за 2022 год?', + img: 'https://teknotower.com/wp-content/uploads/2020/11/js.png', + views: 1022, + createdAt: '26.02.2022', + type: [ArticleType.IT], + blocks: [ + { + id: '1', + type: ArticleBlockType.TEXT, + title: 'Заголовок этого блока', + paragraphs: [ + 'Программа, которую по традиции называют «Hello, world!», очень проста. Она выводит куда-либо фразу «Hello, world!», или другую подобную, средствами некоего языка.', + 'JavaScript — это язык, программы на котором можно выполнять в разных средах. В нашем случае речь идёт о браузерах и о серверной платформе Node.js. Если до сих пор вы не написали ни строчки кода на JS и читаете этот текст в браузере, на настольном компьютере, это значит, что вы буквально в считанных секундах от своей первой JavaScript-программы.', + 'Существуют и другие способы запуска JS-кода в браузере. Так, если говорить об обычном использовании программ на JavaScript, они загружаются в браузер для обеспечения работы веб-страниц. Как правило, код оформляют в виде отдельных файлов с расширением .js, которые подключают к веб-страницам, но программный код можно включать и непосредственно в код страницы. Всё это делается с помощью тега \n \n;', + }, + { + id: '5', + type: ArticleBlockType.TEXT, + title: 'Заголовок этого блока', + paragraphs: [ + 'Программа, которую по традиции называют «Hello, world!», очень проста. Она выводит куда-либо фразу «Hello, world!», или другую подобную, средствами некоего языка.', + 'Существуют и другие способы запуска JS-кода в браузере. Так, если говорить об обычном использовании программ на JavaScript, они загружаются в браузер для обеспечения работы веб-страниц. Как правило, код оформляют в виде отдельных файлов с расширением .js, которые подключают к веб-страницам, но программный код можно включать и непосредственно в код страницы. Всё это делается с помощью тега \n \n;', + }, + { + id: '5', + type: ArticleBlockType.TEXT, + title: 'Заголовок этого блока', + paragraphs: [ + 'Программа, которую по традиции называют «Hello, world!», очень проста. Она выводит куда-либо фразу «Hello, world!», или другую подобную, средствами некоего языка.', + 'Существуют и другие способы запуска JS-кода в браузере. Так, если говорить об обычном использовании программ на JavaScript, они загружаются в браузер для обеспечения работы веб-страниц. Как правило, код оформляют в виде отдельных файлов с расширением .js, которые подключают к веб-страницам, но программный код можно включать и непосредственно в код страницы. Всё это делается с помощью тега