diff --git a/src/assets/images/blogMainItem/img-article-02-1500x1000.webp.svg b/src/assets/images/blogMainItem/img-article-02-1500x1000.webp.svg deleted file mode 100644 index 57246677..00000000 --- a/src/assets/images/blogMainItem/img-article-02-1500x1000.webp.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/components/BlogCategories/BlogCategories.tsx b/src/components/BlogCategories/BlogCategories.tsx deleted file mode 100644 index 1828388d..00000000 --- a/src/components/BlogCategories/BlogCategories.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { FC, useMemo } from 'react' - -import type { PropsCategories } from '@/models/PropsBlog' -import Heading, { HeadingType } from '@/shared/ui/Heading/Heading' - -import styles from './blog-categories.module.scss' - -const BlogCategories: FC = props => { - const { cards, filterItems } = props - - const cat = useMemo( - () => - cards.map(item => { - return item.category - }), - [cards] - ) - - const result: { - key?: string - count: number - }[] = [] - // Create a unique list of items to loop over - // Add each item to the result list - ;[...new Set(cat)].forEach(item => - useMemo( - () => - result.push({ - key: item, - // Get the count of items of the current type - count: cat.filter(i => i == item).length - }), - [result] - ) - ) - - const uniqueCats = useMemo( - () => - result.map(item => { - return ( - - ) - }), - [[...new Set(cat)]] - ) - - return ( -
-
- Категории -
    {uniqueCats}
-
-
- ) -} - -export default BlogCategories diff --git a/src/components/BlogCategories/blog-categories.module.scss b/src/components/BlogCategories/blog-categories.module.scss deleted file mode 100644 index 47b03eb1..00000000 --- a/src/components/BlogCategories/blog-categories.module.scss +++ /dev/null @@ -1,30 +0,0 @@ -@use '@/app/styles/index' as var; - -.cats { - display: flex; - max-width: 340px; - padding: 40px 30px 65px; - align-items: flex-start; - border-radius: 10px; - background: #fff; - - &__items { - margin-top: 25px; - max-width: 280px; - display: flex; - flex-flow: column wrap; - } - - &__item { - display: flex; - width: 280px; - height: 45px; - padding: 13.5px 15px; - align-items: center; - border-radius: 5px; - margin-bottom: 5px; - margin-right: 5px; - background-color: #f7f7fb; - justify-content: space-between; - } -} diff --git a/src/components/BlogItemForContainer/BlogItemForContainer.tsx b/src/components/BlogItemForContainer/BlogItemForContainer.tsx deleted file mode 100644 index 4bd1ef8e..00000000 --- a/src/components/BlogItemForContainer/BlogItemForContainer.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { FC, useMemo } from 'react' - -import CommentIcon from '@/assets/images/blogMainItem/icon-comments.svg' -import ViewIcon from '@/assets/images/blogMainItem/icon-views.svg' -import type { TBlogItem } from '@/models/BlogItemModel' -import { TEXT_PROMO } from '@/shared/constants/constants' -import { fromSS } from '@/shared/constants/constants' -import Link from '@/shared/ui/Link/Link' - -import styles from './blog-item-for-container.module.scss' - -export type Props = { - card: TBlogItem -} - -const BlogItemForContainer: FC = props => { - const { card } = props - const tags = useMemo( - () => - card.tags.map(item => { - return ( -

- {item} -

- ) - }), - [] - ) - - return ( - - {card.alt} -
{tags}
-

{card.title || ''}

-
-

- {fromSS} -

-

- {card.comments.length} -

-
- {card.date || ''} - {card.promo ? {TEXT_PROMO} : null} - - ) -} - -export default BlogItemForContainer diff --git a/src/components/BlogItemForContainer/blog-item-for-container.module.scss b/src/components/BlogItemForContainer/blog-item-for-container.module.scss deleted file mode 100644 index 7f488eb6..00000000 --- a/src/components/BlogItemForContainer/blog-item-for-container.module.scss +++ /dev/null @@ -1,103 +0,0 @@ -@use '@/app/styles/index' as var; - -.card { - position: relative; - transition: transform 0.3s ease-in-out; - display: flex; - flex-direction: column; - justify-content: space-between; - gap: 15px; - padding-bottom: 50px; - - &__info { - position: absolute; - bottom: 160px; - left: 20px; - margin: 0; - display: flex; - align-items: center; - } - - &__icons { - margin-right: 20px; - color: #fff; - font-size: 15px; - font-style: normal; - font-weight: 500; - line-height: 24px; /* 160% */ - display: flex; - align-items: center; - - &:last-child { - margin: 0; - } - } - - &__icon { - border-radius: 6px; - transition: transform 0.3s ease-in-out; - scroll-snap-align: start; - margin-right: 7px; - } - - &__tags { - position: absolute; - top: 30px; - left: 30px; - display: flex; - } - - &__tag { - display: flex; - height: 35px; - padding: 0 12px; - align-items: center; - background-color: #f7f7fb33; - color: #fff; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: 16.8px; - border-radius: 5px; - margin-right: 10px; - } - - &__im { - width: 340px; - height: 462px; - } - - &:hover { - transform: scale(1.1, 1.05); - transition: transform 0.3s ease-in-out; - } - - h3 { - width: 95%; - font-size: #{'min(max(14px, 1.2vw), 16px)'}; - line-height: 150%; - font-weight: 500; - } - - &:hover h3 { - color: var.$link-color; - } - - span { - color: var.$body-color-light-grey; - } - - .promo { - display: inline-block; - position: absolute; - top: 15px; - left: 15px; - background: var.$promo-color; - border-radius: 5px; - padding: 5px 10px; - color: var.$white; - font-size: 15px; - line-height: 120%; - font-weight: 500; - } -} diff --git a/src/components/BlogMain/BlogMain.tsx b/src/components/BlogMain/BlogMain.tsx deleted file mode 100644 index 1ead36a2..00000000 --- a/src/components/BlogMain/BlogMain.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { FC, useState } from 'react' - -import type { PropsBlog } from '@/models/PropsBlog' -import Heading from '@/shared/ui/Heading/Heading' -import Subheading from '@/shared/ui/Subheading/Subheading' -import WrapperForMainContent from '@/shared/ui/WrapperForMainContent/WrapperForMainContent' - -import BlogCategories from '../BlogCategories/BlogCategories' -import BlogItemForContainer from '../BlogItemForContainer/BlogItemForContainer' -import BlogMainItem from '../BlogMainItem/BlogMainItem' -import BlogTags from '../BlogTags/BlogTags' -import { Pagination } from '../Pagination/Pagination' - -import styles from './blog-main.module.scss' - -const BlogMain: FC = props => { - const { cards } = props - const [items, setItems] = useState(cards) - const [itemNumber, setItemNumber] = useState(9) - const [currentPage, setCurrentPage] = useState(1) - const TOTAL_PAGES: number = Math.ceil(items.length / itemNumber) - const filterCategories = (curcat?: string) => { - const newItems = cards.filter(newVal => { - return newVal.category === curcat - // comparing category for displaying data - }) - setItems(newItems) - } - - const filterTags = (curcat: string) => { - const newItems = cards.filter(newVal => { - return newVal.tags.includes(curcat) - // comparing category for displaying data - }) - setItems(newItems) - } - - const handlePageChange = (pageNumber: number) => { - // Handle page change logic here - setCurrentPage(pageNumber) - } - - const handleShowMore = () => { - // ... - if (currentPage < TOTAL_PAGES) setCurrentPage(currentPage + 1) - setItemNumber(9) - } - - return ( - -
- Блог - Главная/Блог -
-
-
- - -
-
- -
    - {items - .slice(currentPage == 1 ? 0 : itemNumber * (currentPage - 1), itemNumber * currentPage) - .map(item => ( - - ))} -
-
-
- -
- ) -} - -export default BlogMain diff --git a/src/components/BlogMain/blog-main.module.scss b/src/components/BlogMain/blog-main.module.scss deleted file mode 100644 index 5e148b08..00000000 --- a/src/components/BlogMain/blog-main.module.scss +++ /dev/null @@ -1,80 +0,0 @@ -@use '@/app/styles/index' as var; - -.blog { - align-self: flex-start; - - &__title { - text-align: left; - margin: 24px 0 0; - padding: 0; - } - - &__path { - text-align: left; - margin: 0 0 30px; - padding: 0; - } - - &__filters { - display: flex; - max-width: 340px; - flex-direction: column; - } - - &__wrapper { - display: flex; - max-width: 100%; - gap: 10px; - } -} - -.wrapper { - display: flex; - flex-direction: column; - gap: 50px; - max-width: 100%; - box-sizing: border-box; - - h2 { - font-size: #{'min(max(18px, 1.6vw), 20px)'}; - line-height: 115%; - font-weight: 500; - } - - article { - display: flex; - justify-content: space-between; - align-items: flex-end; - padding: 0 10px; - max-width: 100%; - } - - .link { - font-size: 15px; - line-height: 120%; - font-weight: 500; - color: var.$link-color; - } - - ul { - max-width: 1080px; - display: flex; - flex-wrap: wrap; - justify-content: center; - cursor: grab; - - &::-webkit-scrollbar { - height: 3px; - } - - &::-webkit-scrollbar-thumb { - background: var.$theme-primary-color; - cursor: grab; - } - - &::-webkit-scrollbar-track { - margin-left: 10px; - margin-right: 10px; - } - } -} diff --git a/src/components/BlogMainItem/BlogMainItem.tsx b/src/components/BlogMainItem/BlogMainItem.tsx deleted file mode 100644 index 5c41b00f..00000000 --- a/src/components/BlogMainItem/BlogMainItem.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useEffect, useMemo } from 'react' - -import CommentIcon from '@/assets/images/blogMainItem/icon-comments.svg' -import DotIcon from '@/assets/images/blogMainItem/icon-dot.svg' -import ViewIcon from '@/assets/images/blogMainItem/icon-views.svg' -import { blogMainItemData } from '@/mockData/blogMainItemData' -import { fromSS } from '@/shared/constants/constants' -import Link from '@/shared/ui/Link/Link' - -import styles from './blog-main-item.module.scss' - -function BlogMainItem() { - const mainItem = blogMainItemData - const tags = useMemo( - () => - blogMainItemData.tags.map(item => { - return ( -

- {item} -

- ) - }), - [] - ) - - useEffect(() => { - const fromSS = sessionStorage.getItem('homeview') - if (!fromSS) { - const timer = setTimeout(() => { - sessionStorage.setItem('homeview', '1') - }, 10000) - - return () => clearTimeout(timer) - } - }, []) - - return ( - - {mainItem.alt} -
{tags}
-

{mainItem.title || ''}

-
-

- {fromSS} -

-

- {mainItem.comments.length} -

-

- - {mainItem.date || ''} -

-
- - ) -} - -export default BlogMainItem diff --git a/src/components/BlogMainItem/blog-main-item.module.scss b/src/components/BlogMainItem/blog-main-item.module.scss deleted file mode 100644 index c99f9276..00000000 --- a/src/components/BlogMainItem/blog-main-item.module.scss +++ /dev/null @@ -1,110 +0,0 @@ -@use '@/app/styles/index' as var; - -.main { - position: relative; - transition: transform 0.3s ease-in-out; - display: flex; - flex-direction: column; - justify-content: space-between; - gap: 25px; - max-width: 1060px; - padding-left: 20px; - padding-right: 10px; - - &:hover { - transform: scale(1.1, 1.05); - transition: transform 0.3s ease-in-out; - } - - &__tags { - position: absolute; - top: 30px; - left: 30px; - display: flex; - } - - &__tag { - display: flex; - height: 35px; - padding: 0 12px; - align-items: center; - background-color: #f7f7fb33; - color: #fff; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: 16.8px; - border-radius: 5px; - margin-right: 10px; - } - - img { - border-radius: 6px; - transition: transform 0.3s ease-in-out; - scroll-snap-align: start; - } - - h3 { - max-width: 373px; - position: absolute; - bottom: 38px; - left: 30px; - margin: 0; - color: white; - font-size: 20px; - font-style: normal; - font-weight: 500; - line-height: 30px; /* 150% */ - } - - &:hover h3 { - color: var.$link-color; - } - - span { - color: var.$body-color-light-grey; - margin-left: 18px; - } - - .main__info { - position: absolute; - bottom: 30px; - right: 30px; - margin: 0; - display: flex; - align-items: center; - } - - .main__icons { - margin-right: 20px; - color: #fff; - font-size: 15px; - font-style: normal; - font-weight: 500; - line-height: 24px; /* 160% */ - display: flex; - align-items: center; - - &:last-child { - margin: 0; - } - } - - .main__icon { - margin-right: 7px; - } - - .promo { - display: inline-block; - position: absolute; - top: 15px; - left: 15px; - background: var.$promo-color; - border-radius: 5px; - padding: 5px 10px; - color: var.$white; - font-size: 15px; - line-height: 120%; - font-weight: 500; - } -} diff --git a/src/components/BlogTags/BlogTags.tsx b/src/components/BlogTags/BlogTags.tsx deleted file mode 100644 index a5b8e422..00000000 --- a/src/components/BlogTags/BlogTags.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { FC, useMemo } from 'react' - -import type { PropsTags } from '@/models/PropsBlog' - -import styles from './blog-tags.module.scss' - -const BlogTags: FC = props => { - const { cards, filterItems } = props - const tags = useMemo( - () => - cards.map(item => { - return item.tags - }), - [cards] - ) - - const tagsAll = useMemo( - () => - tags.reduce(function (arr, e) { - return arr.concat(e) - }), - [tags] - ) - - const arrayOfTags = Array.from(new Set(tagsAll)) - const uniqueTags = useMemo( - () => - arrayOfTags.map(item => { - return ( - - ) - }), - arrayOfTags - ) - - return ( -
-
-

Тэги

-
    {uniqueTags}
-
-
- ) -} - -export default BlogTags diff --git a/src/components/BlogTags/blog-tags.module.scss b/src/components/BlogTags/blog-tags.module.scss deleted file mode 100644 index e9340a57..00000000 --- a/src/components/BlogTags/blog-tags.module.scss +++ /dev/null @@ -1,42 +0,0 @@ -@use '@/app/styles/index' as var; - -.tags { - display: flex; - max-width: 340px; - padding: 0 30px 25px; - align-items: flex-start; - border-radius: 10px; - background: #fff; - - &__title { - color: #343434; - font-size: 16px; - font-style: normal; - font-weight: 400; - line-height: 19.2px; - } - - &__items { - margin-top: 25px; - display: flex; - max-width: 280px; - flex-wrap: wrap; - } - - &__item { - color: #343434; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: 16.8px; /* 120% */ - background-color: #f7f7fb; - display: flex; - height: 35px; - padding: 0 12px; - justify-content: center; - align-items: center; - border-radius: 5px; - margin-bottom: 5px; - margin-right: 5px; - } -} diff --git a/src/entities/BlogCard/ui/BlogCard.module.scss b/src/entities/BlogCard/ui/BlogCard.module.scss index 8a84ddfe..c059f666 100644 --- a/src/entities/BlogCard/ui/BlogCard.module.scss +++ b/src/entities/BlogCard/ui/BlogCard.module.scss @@ -5,12 +5,17 @@ display: flex; flex-direction: column; justify-content: space-between; - gap: 15px; + gap: 8px; width: 100%; user-select: none; @include media.respond-to('middle') { width: 260px; + gap: 7px; + } + + &_blog { + width: 100%; } } @@ -18,14 +23,30 @@ line-height: 22px; font-size: 16px; font-weight: 500; + padding-top: 12px; transition: 0.5s; } +.subheading { + font-size: 17px; +} + +.imageContainer { + position: relative; + display: flex; +} + .image { width: 100%; border-radius: 10px; object-fit: cover; - transition: 0.25s ease-in-out; + transition: 0.25s; + + &_blog { + @include media.respond-to('small-middle') { + height: 418px; + } + } } .noImage { @@ -41,5 +62,16 @@ } .blogCard:hover .image { - transform: scale(1.025); + transform: scale(1.02); +} + +.tags { + position: absolute; + top: 0; + left: 0; + display: flex; + flex-wrap: wrap; + gap: 10px; + padding: 20px; + cursor: auto; } diff --git a/src/entities/BlogCard/ui/BlogCard.stories.tsx b/src/entities/BlogCard/ui/BlogCard.stories.tsx index 35f7d03e..a91ab975 100644 --- a/src/entities/BlogCard/ui/BlogCard.stories.tsx +++ b/src/entities/BlogCard/ui/BlogCard.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react' -import Img1 from '@/assets/images/blog/img-blog-01.png' +import blogImage from '@/assets/images/blog/img-blog-01.png' import BlogCard from './BlogCard' @@ -16,11 +16,24 @@ const meta = { export default meta type Story = StoryObj -export const Default: Story = { - args: { - id: 1, - image: Img1, - title: 'Покупай и не жди. До -50% на весь электротранспорт!', - date: '2022-07-8' - } +export const Default: Story = () => { + const id = 1 + const image = blogImage + const date = '2022-07-8' + const title = 'Дайджест интересных материалов для мобильного разработчика' + const tags = [{ name: 'тег-1' }, { name: 'тег-2' }, { name: 'тег-3' }] + + return ( +
+ +
+ ) +} + +Default.args = { + id: 1, + image: blogImage, + date: '2022-07-8', + title: 'Дайджест интересных материалов для мобильного разработчика', + tags: [{ name: 'тег-1' }, { name: 'тег-2' }, { name: 'тег-3' }] } diff --git a/src/entities/BlogCard/ui/BlogCard.tsx b/src/entities/BlogCard/ui/BlogCard.tsx index e732eb94..16914cb8 100644 --- a/src/entities/BlogCard/ui/BlogCard.tsx +++ b/src/entities/BlogCard/ui/BlogCard.tsx @@ -1,27 +1,39 @@ import { FC, useMemo } from 'react' import NoImage from '@/assets/icons/image-not-found-icon.svg' +import BlogItemForContainer from '@/entities/BlogItemForContainer' +import TagButton from '@/entities/TagButton' import Heading, { HeadingType } from '@/shared/ui/Heading/Heading' import Link from '@/shared/ui/Link/Link' import Subheading from '@/shared/ui/Subheading/Subheading' import styles from './BlogCard.module.scss' -type Props = { +interface IBlogCard { id: number image: string title: string date: string + tags?: TTag[] + views?: number + isBlog?: boolean +} + +type TTag = { + name: string } /** - * Компонент BlogCard - это карточка блога для BlogBlock. + * Компонент BlogCard - это карточка блога для BlogBlock и BlogMain. * @param {string} image - картинка блога * @param {string} title - заголовок блога * @param {string} date - дата блога + * @param {string} tags - теги блога + * @param {number} views - количество просмотров + * @param {boolean} isBlog - булевое значение для отрисовки тегов */ -const BlogCard: FC = ({ image, date, title }) => { +const BlogCard: FC = ({ image, date, title, tags, views = 0, isBlog = false }) => { const newDate = useMemo(() => { const _parsedDate = new Date(date) const year = _parsedDate.getFullYear() @@ -30,17 +42,41 @@ const BlogCard: FC = ({ image, date, title }) => { return `${formatter}, ${year}` }, [date]) + const blogTags = useMemo( + () => + tags?.map((tag, index) => ( +
  • + +
  • + )), + [] + ) + return ( - + {image ? ( - {'блог'} +
    + {'блог'} + {isBlog && ( + <> +
      {blogTags}
    + + + )} +
    ) : ( - + )} + {title} - {newDate} + {newDate} ) } diff --git a/src/entities/BlogCategories/index.tsx b/src/entities/BlogCategories/index.tsx new file mode 100644 index 00000000..3515a8a7 --- /dev/null +++ b/src/entities/BlogCategories/index.tsx @@ -0,0 +1,2 @@ +import BlogCategories from './ui/BlogCategories' +export default BlogCategories diff --git a/src/entities/BlogCategories/ui/BlogCategories.stories.tsx b/src/entities/BlogCategories/ui/BlogCategories.stories.tsx new file mode 100644 index 00000000..c6e4664f --- /dev/null +++ b/src/entities/BlogCategories/ui/BlogCategories.stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { blogPageData } from '@/mockData/blogPageData' + +import BlogCategories from './BlogCategories' + +const meta = { + title: 'entities/BlogCategories', + component: BlogCategories, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = () => { + return ( +
    + {}} /> +
    + ) +} + +Default.args = { + cards: blogPageData +} diff --git a/src/entities/BlogCategories/ui/BlogCategories.tsx b/src/entities/BlogCategories/ui/BlogCategories.tsx new file mode 100644 index 00000000..0dab1d0d --- /dev/null +++ b/src/entities/BlogCategories/ui/BlogCategories.tsx @@ -0,0 +1,79 @@ +import { FC, useMemo } from 'react' + +import { TBlogItem } from '@/models/BlogItemModel' +import { Button } from '@/shared/ui/Button/Button' +import Heading, { HeadingType } from '@/shared/ui/Heading/Heading' +import Scroll from '@/shared/ui/Scroll/Scroll' + +import styles from './blog-categories.module.scss' + +interface IBlogCategories { + cards: TBlogItem[] + filterItems: (curcat?: string) => void +} + +/** + * Компонент BlogCategories используется в компоненте SideBarBlog, делает фильтрацию по категориям + * @param {Array} cards - массив карточек блога + * @param {function} filterItems - функция фильтровки категорий + */ + +const BlogCategories: FC = ({ cards, filterItems }) => { + const currentCategory = useMemo( + () => + cards.map(item => { + return item.category + }), + [cards] + ) + + const result: { + key?: string + count: number + }[] = [] + // Create a unique list of items to loop over + // Add each item to the result list + ;[...new Set(currentCategory)].forEach(item => + useMemo( + () => + result.push({ + key: item, + // Get the count of items of the current type + count: currentCategory.filter(i => i == item).length + }), + [result] + ) + ) + + const Categories = useMemo( + () => + result.map(item => { + return ( +
  • + +
  • + ) + }), + [currentCategory] + ) + + return ( +
    + + Категории + + + {Categories} + +
    + ) +} + +export default BlogCategories diff --git a/src/entities/BlogCategories/ui/blog-categories.module.scss b/src/entities/BlogCategories/ui/blog-categories.module.scss new file mode 100644 index 00000000..004cf9bb --- /dev/null +++ b/src/entities/BlogCategories/ui/blog-categories.module.scss @@ -0,0 +1,54 @@ +@use '@/shared/styles/utils/variables' as var; +@use '@/shared/styles/utils/mixins' as media; + +.blogCategories { + display: flex; + flex-direction: column; + gap: 20px; + + &__title { + display: block; + + @include media.respond-to('large') { + display: none; + } + } + + &__list { + display: flex; + flex-direction: column; + gap: 5px; + + @include media.respond-to('large') { + flex-direction: row; + padding-bottom: 10px; + } + } + + &__button { + display: flex; + justify-content: space-between; + align-items: center; + gap: 20px; + width: 100%; + background: var.$body-bg; + border-radius: 5px; + font-size: 14px; + color: var.$body-color; + fill: var.$body-color; + padding: 10px 15px; + transition: 0.25s; + user-select: none; + + &:hover { + color: var.$header-color; + } + + @include media.respond-to('large') { + width: 100%; + background: var.$white; + padding-top: 0; + padding-bottom: 0; + } + } +} diff --git a/src/entities/BlogItemForContainer/index.tsx b/src/entities/BlogItemForContainer/index.tsx new file mode 100644 index 00000000..d2e6ed54 --- /dev/null +++ b/src/entities/BlogItemForContainer/index.tsx @@ -0,0 +1,2 @@ +import BlogItemForContainer from './ui/BlogItemForContainer' +export default BlogItemForContainer diff --git a/src/entities/BlogItemForContainer/ui/BlogItemForContainer.stories.tsx b/src/entities/BlogItemForContainer/ui/BlogItemForContainer.stories.tsx new file mode 100644 index 00000000..ae22c633 --- /dev/null +++ b/src/entities/BlogItemForContainer/ui/BlogItemForContainer.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import BlogItemForContainer from './BlogItemForContainer' + +const meta = { + title: 'entities/BlogItemForContainer', + component: BlogItemForContainer, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = () => { + const views = 107 + const reviews = 15 + + return ( +
    + +
    + ) +} + +Default.args = { + views: 107, + reviews: 15 +} diff --git a/src/entities/BlogItemForContainer/ui/BlogItemForContainer.tsx b/src/entities/BlogItemForContainer/ui/BlogItemForContainer.tsx new file mode 100644 index 00000000..d933e450 --- /dev/null +++ b/src/entities/BlogItemForContainer/ui/BlogItemForContainer.tsx @@ -0,0 +1,36 @@ +import { FC } from 'react' + +import IconReview from '@/assets/images/blogMainItem/icon-comments.svg' +import IconView from '@/assets/images/blogMainItem/icon-views.svg' + +import styles from './blog-item-for-container.module.scss' + +interface IBlogItemForContainer { + views: number + reviews: number + className?: string +} + +/** + * Компонент BlogItemForContainer показывает просмотры и коментарии в карточке блога BlogCard + * @param {number} views - количество просмотров + * @param {number} reviews - количество коментариев + * @param {string} className - дополнительный класс SCSS + */ + +const BlogItemForContainer: FC = ({ views, reviews, className }) => { + return ( +
      +
    • + + {views} +
    • +
    • + + {reviews} +
    • +
    + ) +} + +export default BlogItemForContainer diff --git a/src/entities/BlogItemForContainer/ui/blog-item-for-container.module.scss b/src/entities/BlogItemForContainer/ui/blog-item-for-container.module.scss new file mode 100644 index 00000000..af3b81b8 --- /dev/null +++ b/src/entities/BlogItemForContainer/ui/blog-item-for-container.module.scss @@ -0,0 +1,22 @@ +@use '@/shared/styles/utils/variables' as var; + +.blogItemForContainer { + position: absolute; + bottom: 0; + left: 0; + right: 0; + display: flex; + gap: 20px; + width: 100%; + padding: 20px; + cursor: auto; + + &__info { + display: flex; + align-items: center; + gap: 5px; + font-size: 15px; + font-weight: 500; + color: var.$white; + } +} diff --git a/src/entities/BlogMainItem/index.tsx b/src/entities/BlogMainItem/index.tsx new file mode 100644 index 00000000..653ff866 --- /dev/null +++ b/src/entities/BlogMainItem/index.tsx @@ -0,0 +1,2 @@ +import BlogMainItem from './ui/BlogMainItem' +export default BlogMainItem diff --git a/src/entities/BlogMainItem/ui/BlogMainItem.tsx b/src/entities/BlogMainItem/ui/BlogMainItem.tsx new file mode 100644 index 00000000..e5264227 --- /dev/null +++ b/src/entities/BlogMainItem/ui/BlogMainItem.tsx @@ -0,0 +1,40 @@ +import { FC, useMemo } from 'react' + +import DotIcon from '@/assets/images/blogMainItem/icon-dot.svg' +import BlogItemForContainer from '@/entities/BlogItemForContainer' +import TagButton from '@/entities/TagButton' +import { blogMainItemData } from '@/mockData/blogMainItemData' +import Heading, { HeadingType } from '@/shared/ui/Heading/Heading' +import Link from '@/shared/ui/Link/Link' +import Subheading from '@/shared/ui/Subheading/Subheading' + +import styles from './blog-main-item.module.scss' + +const BlogMainItem: FC = () => { + const tags = useMemo( + () => + blogMainItemData.tags.map((tag, index) => ( +
  • + +
  • + )), + [] + ) + + return ( + + {blogMainItemData.alt} +
      {tags}
    + + {blogMainItemData.title} + +
    + + + 8 мая, 2022 +
    + + ) +} + +export default BlogMainItem diff --git a/src/entities/BlogMainItem/ui/blog-main-item.module.scss b/src/entities/BlogMainItem/ui/blog-main-item.module.scss new file mode 100644 index 00000000..953aacb4 --- /dev/null +++ b/src/entities/BlogMainItem/ui/blog-main-item.module.scss @@ -0,0 +1,61 @@ +@use '@/shared/styles/utils/variables' as var; + +.blogMainItem { + position: relative; + display: flex; + + &__tags { + position: absolute; + top: 0; + left: 0; + display: flex; + flex-wrap: wrap; + gap: 10px; + padding: 30px; + cursor: auto; + } + + &__title { + position: absolute; + bottom: 0; + left: 0; + display: flex; + width: 100%; + max-width: 540px; + color: var.$white; + padding: 30px; + } + + &__infoBox { + position: absolute; + bottom: 0; + right: 0; + display: flex; + align-items: center; + gap: 30px; + padding: 30px; + cursor: auto; + } + + &__blogItem { + position: relative; + width: fit-content; + padding: 0; + } + + &__subheading { + font-size: 17px; + color: var.$white; + } +} + +.image { + width: 100%; + border-radius: 10px; + object-fit: cover; + transition: 0.15s; +} + +.blogMainItem:hover .image { + transform: scale(1.02); +} diff --git a/src/entities/BlogTags/index.tsx b/src/entities/BlogTags/index.tsx new file mode 100644 index 00000000..f9a5918a --- /dev/null +++ b/src/entities/BlogTags/index.tsx @@ -0,0 +1,2 @@ +import BlogTags from './ui/BlogTags' +export default BlogTags diff --git a/src/entities/BlogTags/ui/BlogTags.stories.tsx b/src/entities/BlogTags/ui/BlogTags.stories.tsx new file mode 100644 index 00000000..ed991b20 --- /dev/null +++ b/src/entities/BlogTags/ui/BlogTags.stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { blogPageData } from '@/mockData/blogPageData' + +import BlogTags from './BlogTags' + +const meta = { + title: 'entities/BlogTags', + component: BlogTags, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = () => { + return ( +
    + {}} /> +
    + ) +} + +Default.args = { + cards: blogPageData +} diff --git a/src/entities/BlogTags/ui/BlogTags.tsx b/src/entities/BlogTags/ui/BlogTags.tsx new file mode 100644 index 00000000..bc3f0bd8 --- /dev/null +++ b/src/entities/BlogTags/ui/BlogTags.tsx @@ -0,0 +1,65 @@ +import { FC, useMemo } from 'react' + +import { TBlogItem } from '@/models/BlogItemModel' +import { Button } from '@/shared/ui/Button/Button' +import Paragraph from '@/shared/ui/Paragraph/Paragraph' + +import styles from './blog-tags.module.scss' + +interface IBlogTags { + cards: TBlogItem[] + filterItems: (curcat: string) => void +} + +/** + * Компонент BlogTags используется в компоненте SideBarBlog, делает фильтрацию по тегам + * @param {Array} cards - массив карточек блога + * @param {function} filterItems - функция фильтровки тегов + */ + +const BlogTags: FC = ({ cards, filterItems }) => { + const tags = useMemo( + () => + cards.map(item => { + return item.tags + }), + [cards] + ) + + const tagsAll = useMemo( + () => + tags.reduce(function (arr, e) { + return arr.concat(e) + }), + [tags] + ) + + const arrayOfTags = Array.from(new Set(tagsAll)) + + const uniqueTags = useMemo( + () => + arrayOfTags.map(item => { + return ( +
  • + +
  • + ) + }), + arrayOfTags + ) + + return ( +
    + Тэги +
      {uniqueTags}
    +
    + ) +} + +export default BlogTags diff --git a/src/entities/BlogTags/ui/blog-tags.module.scss b/src/entities/BlogTags/ui/blog-tags.module.scss new file mode 100644 index 00000000..87872a3a --- /dev/null +++ b/src/entities/BlogTags/ui/blog-tags.module.scss @@ -0,0 +1,29 @@ +@use '@/shared/styles/utils/variables' as var; +@use '@/shared/styles/utils/mixins' as media; + +.blogTags { + display: flex; + flex-direction: column; + gap: 20px; + + &__list { + display: flex; + flex-wrap: wrap; + gap: 5px; + } + + &__button { + display: flex; + justify-content: center; + align-items: center; + background: var.$body-bg; + font-size: 14px; + color: var.$body-color; + padding: 0 12px; + transition: 0.25s; + + &:hover { + color: var.$header-color; + } + } +} diff --git a/src/entities/TagButton/index.tsx b/src/entities/TagButton/index.tsx new file mode 100644 index 00000000..1d53d01a --- /dev/null +++ b/src/entities/TagButton/index.tsx @@ -0,0 +1,2 @@ +import TagButton from './ui/TagButton' +export default TagButton diff --git a/src/entities/TagButton/ui/TagButton.module.scss b/src/entities/TagButton/ui/TagButton.module.scss new file mode 100644 index 00000000..6e552f00 --- /dev/null +++ b/src/entities/TagButton/ui/TagButton.module.scss @@ -0,0 +1,19 @@ +@use '@/shared/styles/utils/variables' as var; + +.tagButton { + display: flex; + align-items: center; + background: rgb(247 247 251 / 20%); + border-radius: 5px; + font-size: 14px; + line-height: 14px; + color: var.$white; + padding: 8px 12px; + cursor: pointer; + transition: 0.25s; + + &:hover { + background: var.$white; + color: var.$body-color; + } +} diff --git a/src/entities/TagButton/ui/TagButton.stories.tsx b/src/entities/TagButton/ui/TagButton.stories.tsx new file mode 100644 index 00000000..34d70016 --- /dev/null +++ b/src/entities/TagButton/ui/TagButton.stories.tsx @@ -0,0 +1,38 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import TagButton from './TagButton' + +const meta = { + title: 'entities/TagButton', + component: TagButton, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = () => { + const tag = 'название тега' + + return ( +
    + +
    + ) +} + +Default.args = { + tag: 'название тега' +} diff --git a/src/entities/TagButton/ui/TagButton.tsx b/src/entities/TagButton/ui/TagButton.tsx new file mode 100644 index 00000000..2ef2e2b8 --- /dev/null +++ b/src/entities/TagButton/ui/TagButton.tsx @@ -0,0 +1,19 @@ +import { FC } from 'react' + +import { Button } from '@/shared/ui/Button/Button' + +import styles from './TagButton.module.scss' + +interface ITagButton { + tag: string +} +/** + * Компонент TagButton - это кнопка тега отображается в таких компонентах как BlogCard, BlogTags и других. + * @param {string} tag - заголовок блога + */ + +const TagButton: FC = ({ tag }) => { + return +} + +export default TagButton diff --git a/src/mockData/blogMainItemData.ts b/src/mockData/blogMainItemData.ts index 41ad56b9..fcec9499 100644 --- a/src/mockData/blogMainItemData.ts +++ b/src/mockData/blogMainItemData.ts @@ -1,12 +1,12 @@ -import main from '@/assets/images/blogMainItem/img-article-02-1500x1000.webp.svg' +import image from '@/assets/images/blogMainItem/img-article-02-1500x1000.webp' export const blogMainItemData = { id: 1, - src: main, + src: image, alt: 'Дайджест интересных материалов для мобильного разработчика', title: 'Дайджест интересных материалов для мобильного разработчика', date: '8 Мая, 2022', - tags: ['#дорога', '#путешествия', '#экономика'], + tags: ['дорога', 'путешествия', 'экономика'], category: 'Обзоры', comments: ['', ''] } diff --git a/src/models/PropsBlog.ts b/src/models/PropsBlog.ts deleted file mode 100644 index 897232ed..00000000 --- a/src/models/PropsBlog.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { TBlogItem } from './BlogItemModel' - -export type PropsBlog = { - title?: string - linkText?: string - linkPath?: string - cards: TBlogItem[] -} - -export type PropsCategories = { - title?: string - linkText?: string - linkPath?: string - cards: TBlogItem[] - filterItems: (curcat?: string) => void -} - -export type PropsTags = { - title?: string - linkText?: string - linkPath?: string - cards: TBlogItem[] - filterItems: (curcat: string) => void -} - -export type ObjectType = { - name: string -} diff --git a/src/pages/BlogPage/BlogPage.tsx b/src/pages/BlogPage/BlogPage.tsx index 95529a2a..b413f0af 100644 --- a/src/pages/BlogPage/BlogPage.tsx +++ b/src/pages/BlogPage/BlogPage.tsx @@ -1,12 +1,36 @@ -import BlogMain from '@/components/BlogMain/BlogMain' -import { blogPageData } from '@/mockData/blogPageData' -import { TEXT_BLOG, LINK_SHOW_ALL } from '@/shared/constants/constants' +import { FC } from 'react' + +import { Routes } from '@/shared/config/routerConfig/routes' +import { TEXT_BLOG } from '@/shared/constants/constants' +import Breadcrumbs from '@/shared/ui/Breadcrumbs/Breadcrumbs' +import Heading, { HeadingType } from '@/shared/ui/Heading/Heading' import WrapperForMainContent from '@/shared/ui/WrapperForMainContent/WrapperForMainContent' +import BlogMain from '@/widgets/BlogMain' +import SideBarBlog from '@/widgets/SideBarBlog' + +import styles from './blog.module.scss' + +const links = [ + { heading: 'Главная', href: Routes.HOME }, + { heading: 'Блог', href: '' } +] -const BlogPage = () => { +/** + * Страница - Блог + */ + +const BlogPage: FC = () => { return ( - +
    + {TEXT_BLOG} + +
    + +
    + + +
    ) } diff --git a/src/pages/BlogPage/blog.module.scss b/src/pages/BlogPage/blog.module.scss index 8d94915e..6b501772 100644 --- a/src/pages/BlogPage/blog.module.scss +++ b/src/pages/BlogPage/blog.module.scss @@ -1,15 +1,20 @@ -.blog { +@use '@/shared/styles/utils/mixins' as media; + +.titleBox { display: flex; flex-direction: column; - gap: 60px; + gap: 10px; + width: 100%; +} + +.blogPage { + display: flex; + gap: 20px; + width: 100%; + margin-top: -18px; - &__wrapper { - max-width: 1080px; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); - grid-template-rows: auto; - row-gap: 20px; - column-gap: 20px; - justify-content: center; + @include media.respond-to('large') { + flex-direction: column; + gap: 10px; } } diff --git a/src/shared/styles/utils/_mixins.scss b/src/shared/styles/utils/_mixins.scss index 9dcfe10f..5ec1224b 100644 --- a/src/shared/styles/utils/_mixins.scss +++ b/src/shared/styles/utils/_mixins.scss @@ -10,6 +10,9 @@ $breakpoints: ( 'middle': ( max-width: 769px ), + 'small-middle': ( + max-width: 576px + ), 'small': ( max-width: 390px ) diff --git a/src/widgets/BlogMain/index.tsx b/src/widgets/BlogMain/index.tsx new file mode 100644 index 00000000..d17d36f8 --- /dev/null +++ b/src/widgets/BlogMain/index.tsx @@ -0,0 +1,2 @@ +import BlogMain from './ui/BlogMain' +export default BlogMain diff --git a/src/widgets/BlogMain/ui/BlogMain.stories.tsx b/src/widgets/BlogMain/ui/BlogMain.stories.tsx new file mode 100644 index 00000000..2dbbf808 --- /dev/null +++ b/src/widgets/BlogMain/ui/BlogMain.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import BlogMain from './BlogMain' + +const meta = { + title: 'widgets/BlogMain', + component: BlogMain, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = () => { + return ( +
    + +
    + ) +} + +Default.args = {} diff --git a/src/widgets/BlogMain/ui/BlogMain.tsx b/src/widgets/BlogMain/ui/BlogMain.tsx new file mode 100644 index 00000000..31a75908 --- /dev/null +++ b/src/widgets/BlogMain/ui/BlogMain.tsx @@ -0,0 +1,63 @@ +import { FC, useEffect } from 'react' +import { useSelector } from 'react-redux' + +// import { Pagination } from '@/components/Pagination/Pagination' + +import BlogCard from '@/entities/BlogCard' +import BlogMainItem from '@/entities/BlogMainItem' +import { useAppDispatch } from '@/shared/libs/hooks/store' +import { useResize } from '@/shared/libs/hooks/useResize' + +import { getBlogPostsSelector } from '../../BlogBlock/model/selectors/selectors' +import { getBlogPosts } from '../../BlogBlock/model/services/getBlogPosts' +import { IBlogPostData } from '../../BlogBlock/model/types/types' + +import styles from './blog-main.module.scss' + +/** + * Компонент BlogMain это основной контент который отрисовывается на странице BlogPage + */ + +const BlogMain: FC = () => { + const dispatch = useAppDispatch() + const posts: IBlogPostData[] = useSelector(getBlogPostsSelector) + const { isScreenMd } = useResize() + + useEffect(() => { + dispatch(getBlogPosts()) + }, []) + + return ( +
    + {/* Это временный компонент */} + {isScreenMd && } + +
      + {posts.map(item => ( +
    • + +
    • + ))} +
    + + {/* Далее буду использовать пагинацию, как это сделано на странице ProductsPage + + */} +
    + ) +} + +export default BlogMain diff --git a/src/widgets/BlogMain/ui/blog-main.module.scss b/src/widgets/BlogMain/ui/blog-main.module.scss new file mode 100644 index 00000000..3761ee7b --- /dev/null +++ b/src/widgets/BlogMain/ui/blog-main.module.scss @@ -0,0 +1,24 @@ +@use '@/shared/styles/utils/variables' as var; +@use '@/shared/styles/utils/mixins' as media; + +.blogMain { + display: flex; + flex-direction: column; + gap: 30px; + + &__list { + display: grid; + grid-template-columns: repeat(3, 1fr); + width: 100%; + column-gap: 20px; + row-gap: 30px; + + @include media.respond-to('middle') { + grid-template-columns: repeat(2, 1fr); + } + + @include media.respond-to('small-middle') { + grid-template-columns: 1fr; + } + } +} diff --git a/src/widgets/SideBarBlog/index.tsx b/src/widgets/SideBarBlog/index.tsx new file mode 100644 index 00000000..8ac452b0 --- /dev/null +++ b/src/widgets/SideBarBlog/index.tsx @@ -0,0 +1,2 @@ +import SideBarBlog from './ui/SideBarBlog' +export default SideBarBlog diff --git a/src/widgets/SideBarBlog/ui/SideBarBlog.module.scss b/src/widgets/SideBarBlog/ui/SideBarBlog.module.scss new file mode 100644 index 00000000..6c5add60 --- /dev/null +++ b/src/widgets/SideBarBlog/ui/SideBarBlog.module.scss @@ -0,0 +1,20 @@ +@use '@/shared/styles/utils/variables' as var; +@use '@/shared/styles/utils/mixins' as media; + +.sideBarBlog { + display: flex; + flex-direction: column; + width: 100%; + max-width: 340px; + height: fit-content; + border-radius: 10px; + background: var.$white; + padding: 30px; + + @include media.respond-to('large') { + max-width: 100%; + border-radius: none; + background: var.$body-bg; + padding: 0; + } +} diff --git a/src/widgets/SideBarBlog/ui/SideBarBlog.stories.tsx b/src/widgets/SideBarBlog/ui/SideBarBlog.stories.tsx new file mode 100644 index 00000000..66c74675 --- /dev/null +++ b/src/widgets/SideBarBlog/ui/SideBarBlog.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import SideBarBlog from './SideBarBlog' + +const meta = { + title: 'widgets/SideBarBlog', + component: SideBarBlog, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = () => { + return ( +
    + +
    + ) +} + +Default.args = {} diff --git a/src/widgets/SideBarBlog/ui/SideBarBlog.tsx b/src/widgets/SideBarBlog/ui/SideBarBlog.tsx new file mode 100644 index 00000000..b497b284 --- /dev/null +++ b/src/widgets/SideBarBlog/ui/SideBarBlog.tsx @@ -0,0 +1,41 @@ +import { FC, useState } from 'react' + +import BlogCategories from '@/entities/BlogCategories' +import BlogTags from '@/entities/BlogTags' +import { blogPageData } from '@/mockData/blogPageData' +import { useResize } from '@/shared/libs/hooks/useResize' + +import styles from './SideBarBlog.module.scss' + +/** + * Компонент SideBarBlog - это боковая панель навигации, используется на странице BlogPage + */ + +const SideBarBlog: FC = () => { + const { isScreenLg } = useResize() + + const [items, setItems] = useState(blogPageData) + + const filterCategories = (curcat?: string) => { + const newItems = items.filter(newVal => { + return newVal.category === curcat + }) + setItems(newItems) + } + + const filterTags = (curcat: string) => { + const newItems = items.filter(newVal => { + return newVal.tags.includes(curcat) + }) + setItems(newItems) + } + + return ( + + ) +} + +export default SideBarBlog