Skip to content

Commit

Permalink
Add carousel with swipe/drag controls (#230)
Browse files Browse the repository at this point in the history
  • Loading branch information
redmikay authored May 5, 2024
1 parent d61397a commit c87e06f
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 0 deletions.
5 changes: 5 additions & 0 deletions categories/elements.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@
"name": "With Captions",
"url": "#",
"preview": "carousels/wcp"
},
{
"name": "With Swipe / Drag",
"url": "#",
"preview": "carousels/wsw"
}
]
},
Expand Down
5 changes: 5 additions & 0 deletions components/utils/route-categories/elements.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@
"name": "With Captions",
"url": "#",
"preview": "carousels/wcp"
},
{
"name": "With Swipe / Drag",
"url": "#",
"preview": "carousels/wsw"
}
]
},
Expand Down
188 changes: 188 additions & 0 deletions pages/preview/carousels/wsw/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import React, { useCallback, useState } from "react";
import { Box, Flex, HStack, Image, Stack, Text } from "@chakra-ui/react";

const Choc = () => {
const SLIDE_CHANGE_THRESHOLD = 100;

const arrowStyles = {
cursor: "pointer",
pos: "absolute",
top: "50%",
w: "auto",
mt: "-22px",
p: "16px",
color: "white",
fontWeight: "bold",
fontSize: "18px",
transition: "0.6s ease",
borderRadius: "0 3px 3px 0",
userSelect: "none",
_hover: {
opacity: 0.8,
bg: "black",
},
} as const;

const slides = [
{
img: "https://images.pexels.com/photos/2599537/pexels-photo-2599537.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
label: "First Slide",
description: "Nulla vitae elit libero, a pharetra augue mollis interdum.",
},
{
img: "https://images.pexels.com/photos/2714581/pexels-photo-2714581.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
label: "Second Slide",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
img: "https://images.pexels.com/photos/2878019/pexels-photo-2878019.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260",
label: "Third Slide",
description:
"Praesent commodo cursus magna, vel scelerisque nisl consectetur.",
},
{
img: "https://images.pexels.com/photos/1142950/pexels-photo-1142950.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
label: "Fourth Slide",
description: "Nulla vitae elit libero, a pharetra augue mollis interdum.",
},
{
img: "https://images.pexels.com/photos/3124111/pexels-photo-3124111.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
label: "Fifth Slide",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
];

const [currentSlide, setCurrentSlide] = useState(0);
const [dragging, setDragging] = useState(false);
const [dragStartX, setDragStartX] = useState(0);
const [dragOffset, setDragOffset] = useState(0);

const slidesCount = slides.length;

const prevSlide = useCallback(() => {
setCurrentSlide((s) => (s === 0 ? slidesCount - 1 : s - 1));
}, []);
const nextSlide = useCallback(() => {
setCurrentSlide((s) => (s === slidesCount - 1 ? 0 : s + 1));
}, []);

const handleMouseDown = useCallback((e) => {
setDragging(true);
setDragStartX(e.clientX);
e.preventDefault();
}, []);

const handleMouseMove = useCallback(
(e) => {
if (dragging) {
const diffX = e.clientX - dragStartX;
setDragOffset(diffX);
e.preventDefault();
}
},
[dragging, dragStartX]
);

const handleMouseUp = useCallback(() => {
if (dragging) {
setDragging(false);
if (Math.abs(dragOffset) > SLIDE_CHANGE_THRESHOLD) {
const slideChange = dragOffset > 0 ? prevSlide : nextSlide;
slideChange();
}
setDragOffset(0);
}
}, [dragging, dragOffset, prevSlide, nextSlide]);

// Do not move first slide to the right and last slide to the left
const slideOffset =
currentSlide === 0
? Math.min(dragOffset, 0)
: currentSlide === slidesCount - 1
? Math.max(dragOffset, 0)
: dragOffset;

const carouselStyle = {
transition: dragging ? "none" : "all .5s",
ml: `calc(-${currentSlide * 100}% + ${slideOffset}px)`,
};

return (
<Flex
w="full"
bg="#edf3f8"
_dark={{ bg: "#3e3e3e" }}
p={10}
alignItems="center"
justifyContent="center"
style={{ cursor: dragging ? "grabbing" : "auto" }}
onMouseLeave={handleMouseUp}
>
<Flex w="full" overflow="hidden" pos="relative">
<Flex
h="400px"
w="full"
onMouseUp={handleMouseUp}
onMouseMove={handleMouseMove}
onMouseDown={handleMouseDown}
{...carouselStyle}
>
{slides.map((slide, sid) => (
<Box key={`slide-${sid}`} boxSize="full" shadow="md" flex="none">
<Text
color="white"
fontSize="xs"
p="8px 12px"
pos="absolute"
top="0"
>
{sid + 1} / {slidesCount}
</Text>
<Image
src={slide.img}
alt="carousel image"
boxSize="full"
backgroundSize="cover"
/>
<Stack
p="8px 12px"
pos="absolute"
bottom="24px"
textAlign="center"
w="full"
mb="8"
color="white"
>
<Text fontSize="2xl">{slide.label}</Text>
<Text fontSize="lg">{slide.description}</Text>
</Stack>
</Box>
))}
</Flex>
<Text {...arrowStyles} left="0" onClick={prevSlide}>
&#10094;
</Text>
<Text {...arrowStyles} right="0" onClick={nextSlide}>
&#10095;
</Text>
<HStack justify="center" pos="absolute" bottom="8px" w="full">
{Array.from({ length: slidesCount }).map((_, slide) => (
<Box
key={`dots-${slide}`}
cursor="pointer"
boxSize={["7px", null, "15px"]}
m="0 2px"
bg={currentSlide === slide ? "blackAlpha.800" : "blackAlpha.500"}
rounded="50%"
display="inline-block"
transition="background-color 0.6s ease"
_hover={{ bg: "blackAlpha.800" }}
onClick={() => setCurrentSlide(slide)}
></Box>
))}
</HStack>
</Flex>
</Flex>
);
};
export default Choc;

0 comments on commit c87e06f

Please sign in to comment.