diff --git a/package-lock.json b/package-lock.json index 44e6f83..c24518c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "crypto": "^1.0.1", "dayjs": "^1.11.1", "dompurify": "^2.3.10", + "embla-carousel-react": "^7.0.0", "env-cmd": "^10.1.0", "express": "^4.18.1", "express-session": "^1.17.3", @@ -76,10 +77,8 @@ "react-loader-spinner": "^6.0.0-0", "react-loading-skeleton": "^3.1.0", "react-modal": "^3.15.1", - "react-multi-carousel": "^2.8.0", "react-query": "^3.35.0", "react-redux": "^8.0.0", - "react-responsive-carousel": "^3.2.23", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", "react-select": "^5.3.2", @@ -21866,6 +21865,22 @@ "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", "dev": true }, + "node_modules/embla-carousel": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-7.0.0.tgz", + "integrity": "sha512-vgyElJaBRTtzWROQuO9Qx/VlibzKdhwnuQ2Ldh5/7/jddrB4XTI6IQlgR5ZglRaXjH4nXjVtUSwWWZMEIZQLFQ==" + }, + "node_modules/embla-carousel-react": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-7.0.0.tgz", + "integrity": "sha512-y17TYtqvvziWbDwwfX5dh8n4qU1luytz4+6WWMBnR4pJfLfkKBGsqYNZ4WhmAgUcYL1Uliti8Cjg+NJd46MPxw==", + "dependencies": { + "embla-carousel": "7.0.0" + }, + "peerDependencies": { + "react": "^18.1.0" + } + }, "node_modules/emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -60197,17 +60212,6 @@ "react-dom": ">= 16.3.0" } }, - "node_modules/react-easy-swipe": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/react-easy-swipe/-/react-easy-swipe-0.0.21.tgz", - "integrity": "sha512-OeR2jAxdoqUMHIn/nS9fgreI5hSpgGoL5ezdal4+oO7YSSgJR8ga+PkYGJrSrJ9MKlPcQjMQXnketrD7WNmNsg==", - "dependencies": { - "prop-types": "^15.5.8" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/react-element-to-jsx-string": { "version": "14.3.4", "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-14.3.4.tgz", @@ -60382,14 +60386,6 @@ "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18" } }, - "node_modules/react-multi-carousel": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/react-multi-carousel/-/react-multi-carousel-2.8.0.tgz", - "integrity": "sha512-xuxQVGGiH8yWDDWgt9Z9+C+zzoACuBT740cV+6To52DYCwLlQGJIntDFLOCqMsO85Ist1x+630HA8lazb/a/tA==", - "engines": { - "node": ">=8" - } - }, "node_modules/react-onclickoutside": { "version": "6.12.1", "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.1.tgz", @@ -60503,16 +60499,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-responsive-carousel": { - "version": "3.2.23", - "resolved": "https://registry.npmjs.org/react-responsive-carousel/-/react-responsive-carousel-3.2.23.tgz", - "integrity": "sha512-pqJLsBaKHWJhw/ItODgbVoziR2z4lpcJg+YwmRlSk4rKH32VE633mAtZZ9kDXjy4wFO+pgUZmDKPsPe1fPmHCg==", - "dependencies": { - "classnames": "^2.2.5", - "prop-types": "^15.5.8", - "react-easy-swipe": "^0.0.21" - } - }, "node_modules/react-router": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", @@ -84862,6 +84848,19 @@ "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", "dev": true }, + "embla-carousel": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-7.0.0.tgz", + "integrity": "sha512-vgyElJaBRTtzWROQuO9Qx/VlibzKdhwnuQ2Ldh5/7/jddrB4XTI6IQlgR5ZglRaXjH4nXjVtUSwWWZMEIZQLFQ==" + }, + "embla-carousel-react": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-7.0.0.tgz", + "integrity": "sha512-y17TYtqvvziWbDwwfX5dh8n4qU1luytz4+6WWMBnR4pJfLfkKBGsqYNZ4WhmAgUcYL1Uliti8Cjg+NJd46MPxw==", + "requires": { + "embla-carousel": "7.0.0" + } + }, "emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -113858,14 +113857,6 @@ "prop-types": "^15.6.0" } }, - "react-easy-swipe": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/react-easy-swipe/-/react-easy-swipe-0.0.21.tgz", - "integrity": "sha512-OeR2jAxdoqUMHIn/nS9fgreI5hSpgGoL5ezdal4+oO7YSSgJR8ga+PkYGJrSrJ9MKlPcQjMQXnketrD7WNmNsg==", - "requires": { - "prop-types": "^15.5.8" - } - }, "react-element-to-jsx-string": { "version": "14.3.4", "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-14.3.4.tgz", @@ -113997,11 +113988,6 @@ "warning": "^4.0.3" } }, - "react-multi-carousel": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/react-multi-carousel/-/react-multi-carousel-2.8.0.tgz", - "integrity": "sha512-xuxQVGGiH8yWDDWgt9Z9+C+zzoACuBT740cV+6To52DYCwLlQGJIntDFLOCqMsO85Ist1x+630HA8lazb/a/tA==" - }, "react-onclickoutside": { "version": "6.12.1", "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.1.tgz", @@ -114063,16 +114049,6 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, - "react-responsive-carousel": { - "version": "3.2.23", - "resolved": "https://registry.npmjs.org/react-responsive-carousel/-/react-responsive-carousel-3.2.23.tgz", - "integrity": "sha512-pqJLsBaKHWJhw/ItODgbVoziR2z4lpcJg+YwmRlSk4rKH32VE633mAtZZ9kDXjy4wFO+pgUZmDKPsPe1fPmHCg==", - "requires": { - "classnames": "^2.2.5", - "prop-types": "^15.5.8", - "react-easy-swipe": "^0.0.21" - } - }, "react-router": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", diff --git a/package.json b/package.json index 6f8dfe6..330a812 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "crypto": "^1.0.1", "dayjs": "^1.11.1", "dompurify": "^2.3.10", + "embla-carousel-react": "^7.0.0", "env-cmd": "^10.1.0", "express": "^4.18.1", "express-session": "^1.17.3", @@ -71,10 +72,8 @@ "react-loader-spinner": "^6.0.0-0", "react-loading-skeleton": "^3.1.0", "react-modal": "^3.15.1", - "react-multi-carousel": "^2.8.0", "react-query": "^3.35.0", "react-redux": "^8.0.0", - "react-responsive-carousel": "^3.2.23", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", "react-select": "^5.3.2", diff --git a/src/features/Projects/pages/ExplorePage/Categories/Categories.tsx b/src/features/Projects/pages/ExplorePage/Categories/Categories.tsx index 54e30fd..4034919 100644 --- a/src/features/Projects/pages/ExplorePage/Categories/Categories.tsx +++ b/src/features/Projects/pages/ExplorePage/Categories/Categories.tsx @@ -1,7 +1,9 @@ -import Slider from 'src/Components/Slider/Slider' +import useEmblaCarousel from 'embla-carousel-react' import { useNavigate } from 'react-router-dom'; import { useAllCategoriesQuery } from 'src/graphql'; +import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; +import { useCallback } from 'react'; const colors = [ '#FDF2F8', @@ -18,8 +20,18 @@ const colors = [ export default function Categories() { + const [emblaRef, emblaApi] = useEmblaCarousel({ align: 'start', slidesToScroll: 2 }) const { data, loading } = useAllCategoriesQuery(); const navigate = useNavigate(); + + const scrollPrev = useCallback(() => { + if (emblaApi) emblaApi.scrollPrev() + }, [emblaApi]) + + const scrollNext = useCallback(() => { + if (emblaApi) emblaApi.scrollNext() + }, [emblaApi]) + if (loading || !data) return
{Array(5).fill(0).map((_, idx) => @@ -34,16 +46,28 @@ export default function Categories() { return ( - - {data?.allCategories.map((category, idx) => - - )} - + +
+
+
+ {data?.allCategories.map((category, idx) => + + )} +
+
+ + +
+ ) } diff --git a/src/features/Projects/pages/ExplorePage/Header/CustomDot/CustomDot.tsx b/src/features/Projects/pages/ExplorePage/Header/CustomDot/CustomDot.tsx index defc69b..8d3a5b8 100644 --- a/src/features/Projects/pages/ExplorePage/Header/CustomDot/CustomDot.tsx +++ b/src/features/Projects/pages/ExplorePage/Header/CustomDot/CustomDot.tsx @@ -1,8 +1,6 @@ -const CustomDot = ({ onClick, ...rest }: any) => { - const { - active, - } = rest; +const CustomDot = ({ onClick, active, ...rest }: any) => { + // onMove means if dragging or swiping in progress. // active is provided by this lib for checking if the item is active or not. return ( diff --git a/src/features/Projects/pages/ExplorePage/Header/Header.tsx b/src/features/Projects/pages/ExplorePage/Header/Header.tsx index 686fc5b..3d02b76 100644 --- a/src/features/Projects/pages/ExplorePage/Header/Header.tsx +++ b/src/features/Projects/pages/ExplorePage/Header/Header.tsx @@ -1,11 +1,12 @@ import { useMediaQuery } from "@react-hookz/web"; -import Carousel from "react-multi-carousel"; import Assets from "src/assets"; import Button from "src/Components/Button/Button"; import THEME from "src/utils/theme"; import { MEDIA_QUERIES } from "src/utils/theme/media_queries"; import CustomDot from "./CustomDot/CustomDot"; import styles from './styles.module.css' +import useEmblaCarousel from 'embla-carousel-react' +import { useCallback, useEffect, useState } from "react"; const headerLinks = [ { @@ -45,46 +46,74 @@ const responsive = { export default function Header() { const isDesktop = useMediaQuery(MEDIA_QUERIES.isMedium); + const [emblaRef, emblaApi] = useEmblaCarousel({ + align: 'start', + breakpoints: { + [MEDIA_QUERIES.isMedium]: { + draggable: false + } + } + }) + + const [selectedIndex, setSelectedIndex] = useState(0); + const [scrollSnaps, setScrollSnaps] = useState([]); + + const onSelect = useCallback(() => { + if (!emblaApi) return; + setSelectedIndex(emblaApi.selectedScrollSnap()); + }, [emblaApi, setSelectedIndex]); + + useEffect(() => { + if (!emblaApi) return; + onSelect(); + setScrollSnaps(emblaApi.scrollSnapList()); + emblaApi.on("select", onSelect); + }, [emblaApi, setScrollSnaps, onSelect]); return ( - } - className={styles.header} - containerClass='!overflow-hidden' - > -
- -
-
- {headerLinks[0].title} -
+
+
+
+
+ +
+
+ {headerLinks[0].title} +
- -
-
- -
-
- {headerLinks[1].title} + +
+
+ +
+
+ {headerLinks[1].title} +
+ +
-
- +
+ {scrollSnaps.map((_, index) => ( + + ))} +
+
); } diff --git a/src/features/Projects/pages/ExplorePage/ProjectsRow/ProjectsRow.tsx b/src/features/Projects/pages/ExplorePage/ProjectsRow/ProjectsRow.tsx index 76ba7fd..dea83a8 100644 --- a/src/features/Projects/pages/ExplorePage/ProjectsRow/ProjectsRow.tsx +++ b/src/features/Projects/pages/ExplorePage/ProjectsRow/ProjectsRow.tsx @@ -1,14 +1,12 @@ -import { ReactNode, useCallback, useLayoutEffect, useEffect, useRef, } from "react"; +import { ReactNode, useCallback, } from "react"; import { ProjectCard } from "src/utils/interfaces"; -import Carousel from 'react-multi-carousel'; import { MdDoubleArrow, } from 'react-icons/md'; import { useAppDispatch } from "src/utils/hooks"; import { openModal } from "src/redux/features/modals.slice"; -import { useResizeListener } from 'src/utils/hooks' -import { IoIosArrowBack, IoIosArrowForward } from "react-icons/io"; -import './style.css'; import { Link } from "react-router-dom"; import ProjectCardMini from "src/features/Projects/Components/ProjectCardMini/ProjectCardMini"; +import useEmblaCarousel from 'embla-carousel-react' +import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; interface Props { title: string | ReactNode, @@ -16,14 +14,6 @@ interface Props { projects: ProjectCard[] } -const responsive = { - all: { - breakpoint: { max: 5000, min: 0 }, - items: calcNumItems(), - slidesToSlide: Math.round(calcNumItems()) - } -} - function calcNumItems(width = Math.min(window.innerWidth - 32, 1440)) { const items = ((width / (296 + 20))); @@ -31,50 +21,25 @@ function calcNumItems(width = Math.min(window.innerWidth - 32, 1440)) { } - export default function ProjectsRow({ title, link, projects }: Props) { + const [emblaRef, emblaApi] = useEmblaCarousel({ align: 'start' }) const dispatch = useAppDispatch() - let drag = useRef(false); - const rowRef = useRef(null!); - const recalcItemsCnt = useCallback( - () => { - if (rowRef.current) { - const count = calcNumItems(rowRef.current.clientWidth); - responsive.all.items = count; - responsive.all.slidesToSlide = Math.round(count) - } - }, - [], - ); - useLayoutEffect(recalcItemsCnt, [recalcItemsCnt]); - useResizeListener(recalcItemsCnt) - useEffect(() => { - - const mousedownListener = () => drag.current = false - const mousemoveListener = () => drag.current = true - - document.addEventListener('mousedown', mousedownListener); - document.addEventListener('mousemove', mousemoveListener); - - return () => { - document.removeEventListener('mousedown', mousedownListener); - document.removeEventListener('mousemove', mousemoveListener); - } - }, []); + const scrollSlides = useCallback((direction = 1) => { + if (emblaApi) emblaApi.scrollTo(emblaApi.selectedScrollSnap() + direction * Math.floor(calcNumItems())) + }, [emblaApi]) if (projects.length === 0) return <> - const handleClick = (projectId: number) => { - if (!drag.current) { + if (emblaApi?.clickAllowed()) { dispatch(openModal({ Modal: "ProjectDetailsCard", props: { projectId } })) } } @@ -89,35 +54,23 @@ export default function ProjectsRow({ title, link, projects }: Props) { } -
- - - - } - customRightArrow={ - - } - > - {projects.map((project, idx) => -
- -
- )} -
+
+
+
+ {projects.map((project, idx) => +
+ +
+ )} +
+
+ +
- -
) } diff --git a/src/features/Projects/pages/ExplorePage/ProjectsRow/style.css b/src/features/Projects/pages/ExplorePage/ProjectsRow/style.css deleted file mode 100644 index 035fa15..0000000 --- a/src/features/Projects/pages/ExplorePage/ProjectsRow/style.css +++ /dev/null @@ -1,9 +0,0 @@ -@media (pointer: coarse) { - .carousel-btns { - display: none; - } - - .react-multi-carousel-list { - overflow: visible; - } -} diff --git a/src/utils/Wrapper.tsx b/src/utils/Wrapper.tsx index 37c4dbf..a4ba7bf 100644 --- a/src/utils/Wrapper.tsx +++ b/src/utils/Wrapper.tsx @@ -9,7 +9,6 @@ import { setIsMobileScreen } from 'src/redux/features/ui.slice'; import { isMobileScreen } from './helperFunctions'; import ReactTooltip from 'react-tooltip'; -import 'react-multi-carousel/lib/styles.css'; import 'react-loading-skeleton/dist/skeleton.css' import THEME from './theme'; import ErrorBoundary from 'src/Components/ErrorBoundary/ErrorBoundary';