refactor: update the carousel library used for a better and lighter one

removed the old 2 used carousel libs react-multi-carousel and react-responsive-carousel, and instead installed and used react-embla-carousel, as it provides an easier api, variable items width support, and less deps.
This commit is contained in:
MTG2000
2022-08-07 00:09:37 +03:00
parent aee07ef23e
commit 64f02359fa
8 changed files with 157 additions and 188 deletions

84
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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 <div className="flex gap-12">
{Array(5).fill(0).map((_, idx) =>
@@ -34,16 +46,28 @@ export default function Categories() {
return (
<Slider>
{data?.allCategories.map((category, idx) =>
<button
key={category.id}
onClick={() => navigate(`/apps/category/${category.id}`)}
className=' block p-16 rounded-16 hover:bg-gray-100 active:bg-gray-200 active:scale-90 transition-transform'
style={{ backgroundColor: colors[idx % colors.length] }}
>{category.icon} {category.title}</button>
)}
</Slider>
<div className="relative group">
<div className="overflow-hidden" ref={emblaRef}>
<div className="select-none w-full flex gap-16">
{data?.allCategories.map((category, idx) =>
<button
key={category.id}
onClick={() => emblaApi?.clickAllowed() && navigate(`/apps/category/${category.id}`)}
className='min-w-max block p-16 rounded-16 hover:bg-gray-100 active:bg-gray-200 active:scale-90 transition-transform'
style={{ backgroundColor: colors[idx % colors.length] }}
>{category.icon} {category.title}</button>
)}
</div>
</div>
<button className="absolute inset-y-0 w-42 left-0 opacity-0 group-hover:opacity-100 transition-opacity rounded-l-12 bg-gradient-to-r from-gray-700 text-white" onClick={scrollPrev}>
<FaChevronLeft />
</button>
<button className="absolute inset-y-0 w-42 right-0 opacity-0 group-hover:opacity-100 transition-opacity rounded-r-12 bg-gradient-to-l from-gray-700 text-white" onClick={scrollNext}>
<FaChevronRight />
</button>
</div>
)
}

View File

@@ -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 (

View File

@@ -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<number[]>([]);
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 (
<Carousel
showDots={!isDesktop}
arrows={false}
responsive={responsive}
customDot={<CustomDot />}
className={styles.header}
containerClass='!overflow-hidden'
>
<div className=" rounded-20 md:mr-20 h-[280px] relative overflow-hidden p-24 flex flex-col items-start justify-end">
<img
className="w-full h-full object-cover absolute top-0 left-0 z-[-2]"
src={headerLinks[0].img}
alt=""
/>
<div className="w-full h-full object-cover bg-gradient-to-t from-gray-900 absolute top-0 left-0 z-[-1]"></div>
<div className="max-w-[90%]">
{headerLinks[0].title}
</div>
<div className="relative group">
<div className="overflow-hidden" ref={emblaRef}>
<div className="w-full flex gap-16">
<div className="flex-[0_0_100%] md:flex-[0_0_calc(50%-8px)] rounded-20 h-[280px] relative overflow-hidden p-24 flex flex-col items-start justify-end">
<img
className="w-full h-full object-cover absolute top-0 left-0 z-[-2]"
src={headerLinks[0].img}
alt=""
/>
<div className="w-full h-full object-cover bg-gradient-to-t from-gray-900 absolute top-0 left-0 z-[-1]"></div>
<div className="max-w-[90%]">
{headerLinks[0].title}
</div>
<Button href={headerLinks[0].link.url} newTab color="white" className="mt-24">
{headerLinks[0].link.content}
</Button>
</div>
<div className="rounded-20 md:ml-20 h-[280px] relative overflow-hidden p-24 flex flex-col items-start justify-end">
<img
className="w-full h-full object-cover absolute top-0 left-0 z-[-2]"
src={headerLinks[1].img}
alt=""
/>
<div className="w-full h-full object-cover bg-gradient-to-t from-gray-900 absolute top-0 left-0 z-[-1]"></div>
<div className="max-w-[90%]">
{headerLinks[1].title}
<Button href={headerLinks[0].link.url} newTab color="white" className="mt-24">
{headerLinks[0].link.content}
</Button>
</div>
<div className="flex-[0_0_100%] md:flex-[0_0_calc(50%-8px)] rounded-20 h-[280px] relative overflow-hidden p-24 flex flex-col items-start justify-end">
<img
className="w-full h-full object-cover absolute top-0 left-0 z-[-2]"
src={headerLinks[1].img}
alt=""
/>
<div className="w-full h-full object-cover bg-gradient-to-t from-gray-900 absolute top-0 left-0 z-[-1]"></div>
<div className="max-w-[90%]">
{headerLinks[1].title}
</div>
<Button color="white" href={headerLinks[1].link.url} newTab className="mt-24">
{headerLinks[1].link.content}
</Button>
</div>
</div>
<Button color="white" href={headerLinks[1].link.url} newTab className="mt-24">
{headerLinks[1].link.content}
</Button>
</div>
</Carousel>
<div className="absolute inset-x-0 bottom-8 flex justify-center gap-4 md:hidden">
{scrollSnaps.map((_, index) => (
<CustomDot
key={index}
active={index === selectedIndex}
/>
))}
</div>
</div>
);
}

View File

@@ -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<HTMLDivElement>(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) {
<MdDoubleArrow className='text-gray-200 ml-8 hover:cursor-pointer transform scale-y-110 scale-x-125 origin-left' />
</Link>}
</h3>
<div ref={rowRef} className="">
<Carousel
showDots={false}
autoPlay={false}
// arrows={false}
responsive={responsive}
// centerMode
itemClass='pb-[1px]'
containerClass='group'
customLeftArrow={
<button className='carousel-btns opacity-0 group-hover:opacity-100 transition-opacity w-64 h-full absolute top-0 left-0 rounded-l-12 bg-gradient-to-r from-gray-700 text-white' >
<IoIosArrowBack className='scale-150' />
</button>
}
customRightArrow={
<button className='carousel-btns opacity-0 group-hover:opacity-100 transition-opacity w-64 h-full absolute top-0 right-0 rounded-r-12 bg-gradient-to-l from-gray-700 text-white' >
<IoIosArrowForward className='scale-150' />
</button>
}
>
{projects.map((project, idx) =>
<div key={project.id} className='max-w-[296px]' >
<ProjectCardMini project={project} onClick={handleClick} />
</div>
)}
</Carousel>
<div className="relative group">
<div className="overflow-hidden" ref={emblaRef}>
<div className="w-full flex gap-16">
{projects.map((project, idx) =>
<div key={project.id} className='flex-[0_0_100%] max-w-[296px]' >
<ProjectCardMini project={project} onClick={handleClick} />
</div>
)}
</div>
</div>
<button className="absolute inset-y-0 w-64 left-0 opacity-0 group-hover:opacity-100 transition-opacity rounded-l-12 bg-gradient-to-r from-gray-700 text-white" onClick={() => scrollSlides(-1)}>
<FaChevronLeft />
</button>
<button className="absolute inset-y-0 w-64 right-0 opacity-0 group-hover:opacity-100 transition-opacity rounded-r-12 bg-gradient-to-l from-gray-700 text-white" onClick={() => scrollSlides(1)}>
<FaChevronRight />
</button>
</div>
</div>
)
}

View File

@@ -1,9 +0,0 @@
@media (pointer: coarse) {
.carousel-btns {
display: none;
}
.react-multi-carousel-list {
overflow: visible;
}
}

View File

@@ -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';