diff --git a/package-lock.json b/package-lock.json
index 9c41807f..f4f90c29 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,7 @@
"react-dom": "18.2.0",
"react-input-mask": "^2.0.4",
"react-leaflet": "^4.2.1",
+ "react-loading-skeleton": "^3.4.0",
"react-redux": "8.1.2",
"react-router": "6.15.0",
"react-router-dom": "6.15.0",
@@ -17311,6 +17312,14 @@
"react-dom": "^18.0.0"
}
},
+ "node_modules/react-loading-skeleton": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.4.0.tgz",
+ "integrity": "sha512-1oJEBc9+wn7BbkQQk7YodlYEIjgeR+GrRjD+QXkVjwZN7LGIcAFHrx4NhT7UHGBxNY1+zax3c+Fo6XQM4R7CgA==",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/react-redux": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
@@ -33317,6 +33326,12 @@
"@react-leaflet/core": "^2.1.0"
}
},
+ "react-loading-skeleton": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.4.0.tgz",
+ "integrity": "sha512-1oJEBc9+wn7BbkQQk7YodlYEIjgeR+GrRjD+QXkVjwZN7LGIcAFHrx4NhT7UHGBxNY1+zax3c+Fo6XQM4R7CgA==",
+ "requires": {}
+ },
"react-redux": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
diff --git a/package.json b/package.json
index 0f551ffe..25fa73a9 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
"react-dom": "18.2.0",
"react-input-mask": "^2.0.4",
"react-leaflet": "^4.2.1",
+ "react-loading-skeleton": "^3.4.0",
"react-redux": "8.1.2",
"react-router": "6.15.0",
"react-router-dom": "6.15.0",
diff --git a/src/components/PageControls/PageControlsSkeletons/PageControlsSkeletons.module.scss b/src/components/PageControls/PageControlsSkeletons/PageControlsSkeletons.module.scss
new file mode 100644
index 00000000..ffc7909f
--- /dev/null
+++ b/src/components/PageControls/PageControlsSkeletons/PageControlsSkeletons.module.scss
@@ -0,0 +1,28 @@
+@use '../../../shared/styles/utils/variables' as var;
+
+.sk-page-controls {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+
+ &__dropdowns {
+ display: flex;
+ gap: 12px;
+ }
+
+ &__dropdown {
+ width: 150px;
+ height: 36px;
+ }
+
+ &__cards-controls {
+ display: flex;
+ gap: 10px;
+ }
+
+ &__cards-control {
+ width: 36px;
+ height: 36px;
+ border-radius: 6px;
+ }
+}
diff --git a/src/components/PageControls/PageControlsSkeletons/PageControlsSkeletons.stories.tsx b/src/components/PageControls/PageControlsSkeletons/PageControlsSkeletons.stories.tsx
new file mode 100644
index 00000000..236034bb
--- /dev/null
+++ b/src/components/PageControls/PageControlsSkeletons/PageControlsSkeletons.stories.tsx
@@ -0,0 +1,23 @@
+import { Meta, StoryObj } from '@storybook/react'
+import { type FC } from 'react'
+
+import { PageControlsSkeletons } from '@/components/PageControls/PageControlsSkeletons/PageControlsSkeletons'
+
+const StorybookWrapper: FC = () => {
+ return (
+
+ )
+}
+
+const meta = {
+ title: 'shared/PageControlsSkeletons',
+ component: StorybookWrapper,
+ tags: ['autodocs']
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+export const Default: Story = {}
diff --git a/src/components/PageControls/PageControlsSkeletons/PageControlsSkeletons.tsx b/src/components/PageControls/PageControlsSkeletons/PageControlsSkeletons.tsx
new file mode 100644
index 00000000..e15eacb4
--- /dev/null
+++ b/src/components/PageControls/PageControlsSkeletons/PageControlsSkeletons.tsx
@@ -0,0 +1,21 @@
+import { type FC } from 'react'
+import Skeleton from 'react-loading-skeleton'
+import 'react-loading-skeleton/dist/skeleton.css'
+
+import styles from './PageControlsSkeletons.module.scss'
+
+export const PageControlsSkeletons: FC = () => {
+ return (
+
+ )
+}
diff --git a/src/components/PageDescription/PageDescription.tsx b/src/components/PageDescription/PageDescription.tsx
index 737ebbb7..f38c8dff 100644
--- a/src/components/PageDescription/PageDescription.tsx
+++ b/src/components/PageDescription/PageDescription.tsx
@@ -1,4 +1,6 @@
-import { FC } from 'react'
+import { type FC } from 'react'
+import 'react-loading-skeleton/dist/skeleton.css'
+import { useParams } from 'react-router-dom'
import { getNoun } from '@/shared/libs/helpers/getNoun'
import Breadcrumbs from '@/shared/ui/Breadcrumbs/Breadcrumbs'
@@ -17,9 +19,11 @@ type Props = {
* @param {string} heading - наименование-заголовок;
*/
export const PageDescription: FC = ({ count, heading }) => {
+ const { slug } = useParams()
+
const links = [
{ heading: 'Главная', href: '/' },
- { heading: heading, href: '/categories/' + heading }
+ { heading: heading, href: '/categories/' + slug }
]
return (
diff --git a/src/components/PageDescription/PageDescriptionSkeleton/PageDescriptionSkeleton.module.scss b/src/components/PageDescription/PageDescriptionSkeleton/PageDescriptionSkeleton.module.scss
new file mode 100644
index 00000000..61a7cd45
--- /dev/null
+++ b/src/components/PageDescription/PageDescriptionSkeleton/PageDescriptionSkeleton.module.scss
@@ -0,0 +1,17 @@
+@use '../../../shared/styles/utils/variables' as var;
+
+.sk-content {
+ width: 330px;
+ height:30px
+}
+
+.sk-content__description {
+ display: flex;
+ flex-direction: column;
+ align-self: flex-start;
+ gap: 10px;
+}
+
+.sk-content__breadcrumbs {
+ height: 16px;
+}
diff --git a/src/components/PageDescription/PageDescriptionSkeleton/PageDescriptionSkeleton.stories.tsx b/src/components/PageDescription/PageDescriptionSkeleton/PageDescriptionSkeleton.stories.tsx
new file mode 100644
index 00000000..ea2a2d68
--- /dev/null
+++ b/src/components/PageDescription/PageDescriptionSkeleton/PageDescriptionSkeleton.stories.tsx
@@ -0,0 +1,23 @@
+import { Meta, StoryObj } from '@storybook/react'
+import { type FC } from 'react'
+
+import { PageDescriptionSkeleton } from '@/components/PageDescription/PageDescriptionSkeleton/PageDescriptionSkeleton'
+
+const StorybookWrapper: FC = () => {
+ return (
+
+ )
+}
+
+const meta = {
+ title: 'shared/PageDescriptionSkeleton',
+ component: StorybookWrapper,
+ tags: ['autodocs']
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+export const Default: Story = {}
diff --git a/src/components/PageDescription/PageDescriptionSkeleton/PageDescriptionSkeleton.tsx b/src/components/PageDescription/PageDescriptionSkeleton/PageDescriptionSkeleton.tsx
new file mode 100644
index 00000000..9de55a5e
--- /dev/null
+++ b/src/components/PageDescription/PageDescriptionSkeleton/PageDescriptionSkeleton.tsx
@@ -0,0 +1,15 @@
+import { type FC } from 'react'
+import Skeleton from 'react-loading-skeleton'
+
+import styles from '@/components/PageDescription/PageDescriptionSkeleton/PageDescriptionSkeleton.module.scss'
+
+export const PageDescriptionSkeleton: FC = () => {
+ return (
+
+ )
+}
diff --git a/src/features/CategoryItem/CategoryItemSkeleton/CategoryItemSkeleton.module.scss b/src/features/CategoryItem/CategoryItemSkeleton/CategoryItemSkeleton.module.scss
new file mode 100644
index 00000000..eee44f42
--- /dev/null
+++ b/src/features/CategoryItem/CategoryItemSkeleton/CategoryItemSkeleton.module.scss
@@ -0,0 +1,6 @@
+.sk-category-list__item {
+ display: flex;
+ min-height: 45px;
+ border-radius: 5px;
+ margin-bottom: 5px;
+}
diff --git a/src/features/CategoryItem/CategoryItemSkeleton/CategoryItemSkeleton.stories.tsx b/src/features/CategoryItem/CategoryItemSkeleton/CategoryItemSkeleton.stories.tsx
new file mode 100644
index 00000000..a17a45df
--- /dev/null
+++ b/src/features/CategoryItem/CategoryItemSkeleton/CategoryItemSkeleton.stories.tsx
@@ -0,0 +1,23 @@
+import { Meta, StoryObj } from '@storybook/react'
+import { type FC } from 'react'
+
+import { CategoryItemSkeleton } from '@/features/CategoryItem/CategoryItemSkeleton/CategoryItemSkeleton'
+
+const StorybookWrapper: FC = () => {
+ return (
+
+
+
+ )
+}
+
+const meta = {
+ title: 'features/CategoryItemSkeleton',
+ component: StorybookWrapper,
+ tags: ['autodocs']
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+export const Default: Story = {}
diff --git a/src/features/CategoryItem/CategoryItemSkeleton/CategoryItemSkeleton.tsx b/src/features/CategoryItem/CategoryItemSkeleton/CategoryItemSkeleton.tsx
new file mode 100644
index 00000000..f754ce2e
--- /dev/null
+++ b/src/features/CategoryItem/CategoryItemSkeleton/CategoryItemSkeleton.tsx
@@ -0,0 +1,8 @@
+import { type FC } from 'react'
+import Skeleton from 'react-loading-skeleton'
+
+import styles from '@/features/CategoryItem/CategoryItemSkeleton/CategoryItemSkeleton.module.scss'
+
+export const CategoryItemSkeleton: FC = () => {
+ return
+}
diff --git a/src/pages/ProductsPage/ProductsPage.tsx b/src/pages/ProductsPage/ProductsPage.tsx
index bcd4797d..a6778b27 100644
--- a/src/pages/ProductsPage/ProductsPage.tsx
+++ b/src/pages/ProductsPage/ProductsPage.tsx
@@ -5,17 +5,20 @@ import { useLocation } from 'react-router'
import { selectFilterProducts, selectFilterQuantity } from '@/components/Dropdown/selectors/selectors'
import { setFilterProducts, setProductQuantityFilter } from '@/components/Dropdown/slice/filtersSlice'
import { PageControls } from '@/components/PageControls/PageControls'
+import { PageControlsSkeletons } from '@/components/PageControls/PageControlsSkeletons/PageControlsSkeletons'
import { PageDescription } from '@/components/PageDescription/PageDescription'
+import { PageDescriptionSkeleton } from '@/components/PageDescription/PageDescriptionSkeleton/PageDescriptionSkeleton'
import { Pagination } from '@/components/Pagination/Pagination'
import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent'
import { selectCategorySlug } from '@/entities/Category/selectors/categorySelectors'
import { TOTAL_PAGES } from '@/mockData/productsPageOptions'
-import { getProductsOfCategorySelector } from '@/pages/ProductsPage/selectors/selectors'
+import { getLoading, getProductsOfCategorySelector } from '@/pages/ProductsPage/selectors/selectors'
import { getProducts } from '@/pages/ProductsPage/services/getProducts'
-import { ITEMS_PER_PAGE_OPTION, SORT_OPTION } from '@/shared/constants/constants'
+import { ITEMS_PER_PAGE_OPTION, NUMBER_OF_PRODUCTS, SORT_OPTION } from '@/shared/constants/constants'
import { useAppDispatch } from '@/shared/libs/hooks/store'
import { ECardView } from '@/shared/model/types/common'
import { CategoryList } from '@/widgets/CategoryList/CategoryList'
+import { ProductSkeleton } from '@/widgets/ProductItem/ProductSkeleton/ProductSkeleton'
import { ProductsList } from '@/widgets/ProductsList/ProductsList'
import styles from './ProductsPage.module.scss'
@@ -46,6 +49,8 @@ export const ProductsPage = () => {
const selectQuantityFilter = useSelector(selectFilterQuantity)
const filterQuantity = selectQuantityFilter ? `&limit=${selectQuantityFilter.value}` : ''
+ const isLoading = useSelector(getLoading)
+
const handleSortChange: React.ChangeEventHandler = event => {
const selectedOption = event.target.value
const setCategoryFilters = SORT_OPTION.find(item => item.name === selectedOption)
@@ -83,12 +88,25 @@ export const ProductsPage = () => {
return (
<>
-
+ {isLoading ? (
+
+ ) : (
+
+ )}
- {categoriesProducts.results.length > 0 ? (
+ {isLoading ? (
+ <>
+
+ {Array(NUMBER_OF_PRODUCTS)
+ .fill(0)
+ .map((_, i) => (
+
+ ))}
+ >
+ ) : categoriesProducts.results.length > 0 ? (
<>
{
return state.categoryProduct.productsData
}
+
+export const getLoading = (state: RootState) => {
+ return state.categoryProduct.isLoading
+}
diff --git a/src/shared/constants/constants.ts b/src/shared/constants/constants.ts
index ecb8f8cc..f58a7f24 100644
--- a/src/shared/constants/constants.ts
+++ b/src/shared/constants/constants.ts
@@ -93,3 +93,7 @@ export const ITEMS_PER_PAGE_OPTION = [
{ name: '75', value: '75' },
{ name: '100', value: '100' }
]
+
+//For Skeleton
+export const NUMBER_OF_CATEGORY_LINES = 15
+export const NUMBER_OF_PRODUCTS = 15
diff --git a/src/widgets/CategoryList/CategoryList.tsx b/src/widgets/CategoryList/CategoryList.tsx
index 8e02ec3c..778b9787 100644
--- a/src/widgets/CategoryList/CategoryList.tsx
+++ b/src/widgets/CategoryList/CategoryList.tsx
@@ -1,9 +1,12 @@
-import { FC, useEffect } from 'react'
+import { type FC, useEffect } from 'react'
import { useSelector } from 'react-redux'
import { useParams } from 'react-router-dom'
import { selectCategorySlug } from '@/entities/Category/selectors/categorySelectors'
import { CategoryItem } from '@/features/CategoryItem/CategoryItem'
+import { CategoryItemSkeleton } from '@/features/CategoryItem/CategoryItemSkeleton/CategoryItemSkeleton'
+import { getLoading } from '@/pages/ProductsPage/selectors/selectors'
+import { NUMBER_OF_CATEGORY_LINES } from '@/shared/constants/constants'
import { useAppDispatch } from '@/shared/libs/hooks/store'
import Heading, { HeadingType } from '@/shared/ui/Heading/Heading'
import { getCategoryBranchesSelector, getCategorySelector } from '@/widgets/CategoryList/selectors/selectors'
@@ -24,14 +27,12 @@ export const CategoryList: FC = () => {
const { slug } = useParams()
- useEffect(() => {
- dispatch(getCategoryBranches(slug))
- }, [])
+ const isLoading = useSelector(getLoading)
useEffect(() => {
- dispatch(getCategoryBranches(categorySlug))
+ dispatch(getCategoryBranches(slug))
dispatch(getCategories())
- }, [categorySlug])
+ }, [categorySlug, slug])
return (
@@ -39,7 +40,11 @@ export const CategoryList: FC = () => {
Категории
- {categoryBranches.branches?.length > 0
+ {isLoading
+ ? Array(NUMBER_OF_CATEGORY_LINES)
+ .fill(0)
+ .map((_, i) => )
+ : categoryBranches.branches?.length > 0
? categoryBranches.branches.map(item => (
{
+ return (
+
+ )
+}
+
+const meta = {
+ title: 'shared/ProductSkeleton',
+ component: StorybookWrapper,
+ tags: ['autodocs']
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+export const Default: Story = {}
diff --git a/src/widgets/ProductItem/ProductSkeleton/ProductSkeleton.tsx b/src/widgets/ProductItem/ProductSkeleton/ProductSkeleton.tsx
new file mode 100644
index 00000000..cdcec39d
--- /dev/null
+++ b/src/widgets/ProductItem/ProductSkeleton/ProductSkeleton.tsx
@@ -0,0 +1,30 @@
+import { type FC } from 'react'
+import Skeleton from 'react-loading-skeleton'
+import 'react-loading-skeleton/dist/skeleton.css'
+
+import styles from './ProductSkeleton.module.scss'
+
+export const ProductSkeleton: FC = () => {
+ return (
+
+ )
+}