Created
May 17, 2023 01:25
-
-
Save chaance/fdcdb61a32f69456570c771348253913 to your computer and use it in GitHub Desktop.
Low-level carousel API ideas
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function CarouselExample() { | |
// data we might use to render the carousel. This is just a generic example, | |
// data could come from anywhere. For us it comes from Replo component data. | |
const products = useProductsQuery({ limit: 10 }); | |
const matchesMobile = useMatchMedia(MOBILE_MQ); | |
const matchesTablet = useMatchMedia(TABLET_MQ); | |
const [activeSlide, setActiveSlide] = React.useState(0); | |
const slides = products.length; | |
// features like auto-play could be handled externally without the need for | |
// custom props | |
const { start, stop } = useCarouselAutoPlay(slides, setActiveSlide); | |
return ( | |
<Carousel.Root | |
slides={slides} | |
infinite={false} | |
activeSlide={activeSlide} | |
onActiveSlideChange={setActiveSlide} | |
orientation="horizontal" | |
dir="ltr" | |
transitionOnScroll | |
> | |
{({ currentSlide, slides }) => { | |
const lastSlideIndex = slides.length - 1; | |
const isFirstSlide = currentSlide.index === 0; | |
const isLastSlide = currentSlide.index === lastSlideIndex; | |
const previousSlideIndex = isFirstSlide | |
? lastSlideIndex | |
: currentSlide.index - 1; | |
const nextSlideIndex = isLastSlide ? 0 : currentSlide.index + 1; | |
// pagination could be derived from slides without custom props | |
const slidesPerPage = matchesMobile ? 1 : matchesTablet ? 2 : 3; | |
const { pages, currentPage } = getCarouselPages(slides, currentSlide, { | |
slidesPerPage, | |
}); | |
return ( | |
<div> | |
<div> | |
{/* low-level implementation of a "go back" button */} | |
<Carousel.Control | |
to={previousSlideIndex} | |
disabled={isFirstSlide} | |
/> | |
<Carousel.Slides> | |
{slides.map((slide) => { | |
// IMPORTANT: We'd want to render the largest total number of | |
// slides visible on any screen (probably desktop). You would | |
// want to use CSS to visually hide slides on other screen | |
// sizes to avoid layout shifts after hydration. | |
// | |
// One challenge here is with the concept of pagination. For | |
// example, say the user wants 3 slides visible on desktop and | |
// 1 on mobile. With slides it's no problem, we can just set a | |
// CSS var for each screen size and let CSS determine the | |
// number of slides to show in a MQ. But when you paginate and | |
// have controls that indicate and move you between pages, the | |
// total number of pages might depend on the number of visible | |
// slides. For that we'd need some React to determine the | |
// screen size, hydrate and then update in the next pass. | |
// | |
// I think a good way to deal with this to always SSR with one | |
// page. Indicators typically don't wrap to multiple lines, so | |
// when we hydrate and add new page indicators, the height | |
// shouldn't change. Even though you get a small flash when | |
// the new page indicators are added, overall layout shift | |
// should be avoided. In some cases it may be unavoidable when | |
// running the effect pre-hydration (post-hydration we could | |
// consider running it in a layout effect if it's an issue). | |
return <Carousel.Slide key={slide.id}></Carousel.Slide>; | |
})} | |
</Carousel.Slides> | |
{/* low-level implementation of a "go forward" button */} | |
<CarouselControl to={nextSlideIndex} disabled={isLastSlide} /> | |
</div> | |
{pages.length > 1 ? ( | |
<Carousel.ControlGroup> | |
{pages.map((page) => { | |
const isCurrentPage = page.index === currentPage.index; | |
const { index: to } = getActiveSlideFromPage(page, slides, { | |
slidesPerPage, | |
}); | |
return ( | |
<CarouselControl | |
to={to} | |
disabled={isCurrentPage} | |
key={page.number} | |
/> | |
); | |
})} | |
</Carousel.ControlGroup> | |
) : null} | |
</div> | |
); | |
}} | |
</Carousel.Root> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment