Skip to content

Commit

Permalink
Merge pull request #93 from thomasKn/carousel
Browse files Browse the repository at this point in the history
Update carousel
  • Loading branch information
thomasKn committed Feb 23, 2024
2 parents 5dbc65b + 218f38f commit 7dd7eca
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 274 deletions.
2 changes: 1 addition & 1 deletion app/components/layout/AnnoucementBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function AnnouncementBar() {
<div className="container">
<style dangerouslySetInnerHTML={{__html: colorsCssVars}} />
<Carousel opts={{active: isActive}} plugins={plugins}>
<CarouselContent className="relative">
<CarouselContent className="relative ml-0">
{annoucementBar?.map((item) => (
<CarouselItem key={item._key}>
<Item _key={item._key} link={item.link} text={item.text} />
Expand Down
54 changes: 0 additions & 54 deletions app/components/layout/LoadOnInteractionProvider.tsx

This file was deleted.

139 changes: 74 additions & 65 deletions app/components/product/MediaGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ import type {

import {useLoaderData} from '@remix-run/react';
import {flattenConnection} from '@shopify/hydrogen';
import React, {useState} from 'react';
import React, {useCallback, useState} from 'react';

import type {loader} from '~/routes/($locale).products.$productHandle';

import {useDevice} from '~/hooks/useDevice';
import {type AspectRatioData, cn} from '~/lib/utils';

import type {CarouselApi} from '../ui/Carousel';

import {ShopifyImage} from '../ShopifyImage';
import {Badge} from '../ui/Badge';
import {
Carousel,
CarouselContent,
CarouselCounter,
CarouselItem,
CarouselNext,
CarouselPrevious,
useCarousel,
} from '../ui/Carousel';

type Media =
Expand Down Expand Up @@ -100,21 +100,26 @@ function MobileCarousel({

return (
<Carousel
className="lg:hidden"
className="[--slide-size:100%] [--slide-spacing:1rem] lg:hidden"
opts={{
active: isActive && device !== 'desktop',
}}
style={{'--slidesPerView': 1} as React.CSSProperties}
>
<div className="relative">
<CarouselContent className="px-8 md:px-12">
<CarouselContent className="px-[--slide-spacing]">
{medias.map((media, index) => {
return (
<CarouselItem className="pl-3 last:pr-3" key={media.id}>
<CarouselItem
className="last:pr-[--slide-spacing] [&>span]:h-full"
key={media.id}
>
{media.__typename === 'MediaImage' && media.image && (
<ShopifyImage
aspectRatio={aspectRatio?.value}
className={cn('h-auto w-full', aspectRatio?.className)}
className={cn(
'size-full object-cover',
aspectRatio?.className,
)}
data={media.image}
decoding={index === 0 ? 'sync' : 'async'}
fetchpriority={index === 0 ? 'high' : 'low'}
Expand All @@ -126,28 +131,18 @@ function MobileCarousel({
);
})}
</CarouselContent>
<MobileCarouselCounter mediaLength={medias.length} />
<div className="mt-3 flex items-center justify-center">
<Badge variant="outline">
<CarouselCounter>
<span>{medias.length}</span>
</CarouselCounter>
</Badge>
</div>
</div>
</Carousel>
);
}

function MobileCarouselCounter({mediaLength}: {mediaLength: number}) {
const {selectedIndex} = useCarousel();
return (
<div className="mt-3 flex items-center justify-center">
<Badge
className="flex items-center gap-1 tabular-nums text-muted-foreground"
variant="outline"
>
<span>{selectedIndex + 1}</span>
<span>/</span>
<span>{mediaLength}</span>
</Badge>
</div>
);
}

function ThumbnailCarousel({
medias,
selectedImage,
Expand All @@ -158,54 +153,68 @@ function ThumbnailCarousel({
setActiveMediaId: React.Dispatch<React.SetStateAction<null | string>>;
}) {
const device = useDevice();
const slidesPerView = 6;
const [api, setApi] = useState<CarouselApi>();

const handleSelect = useCallback(
(index: number, mediaId: string) => {
api?.scrollTo(index);
setActiveMediaId(mediaId);
},
[api, setActiveMediaId],
);

if (medias.length <= 1) return null;

return (
<div className="mt-6 hidden lg:block">
<div className="mt-3 hidden lg:block">
<Carousel
className="[--slide-spacing:.5rem]"
opts={{
active: device === 'desktop',
container: '.thumbnails-container',
active: device === 'desktop' && slidesPerView < medias.length,
containScroll: 'keepSnaps',
dragFree: true,
}}
style={{'--slidesPerView': 5} as React.CSSProperties}
setApi={setApi}
style={
{
'--slides-per-view': slidesPerView,
} as React.CSSProperties
}
>
<div className="flex items-center justify-center gap-2">
<CarouselPrevious className="relative left-0 top-0 aspect-square size-11 translate-x-0 translate-y-0" />
<div className="thumbnails-container relative">
<CarouselContent className="px-3 py-1">
{medias.map((media) => {
return (
<CarouselItem
className="p-0 md:basis-1/2 md:pl-2 md:last:pr-2 lg:basis-[var(--slidesPerView)]"
key={media.id}
>
{media.__typename === 'MediaImage' && media.image && (
<button
className={cn(
'overflow-hidden rounded-[--media-border-corner-radius] border-2 border-foreground border-opacity-0 transition-opacity hover:border-opacity-100',
'ring-offset-background transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
media.id === selectedImage.id && 'border-opacity-100',
)}
onClick={() => setActiveMediaId(media.id)}
>
<ShopifyImage
aspectRatio="1/1"
className="aspect-square w-24 object-cover"
data={media.image}
loading="eager"
showBorder={false}
showShadow={false}
sizes="96px"
/>
</button>
)}
</CarouselItem>
);
})}
</CarouselContent>
</div>
<CarouselNext className="relative right-0 top-0 aspect-square size-11 translate-x-0 translate-y-0" />
<CarouselContent className="ml-0 py-1">
{medias.map((media, index) => {
return (
<CarouselItem
className="px-[calc(var(--slide-spacing)/2)]"
key={media.id}
>
{media.__typename === 'MediaImage' && media.image && (
<button
className={cn(
'overflow-hidden rounded-[--media-border-corner-radius] border-2 border-foreground border-opacity-0 transition-opacity hover:border-opacity-100',
'ring-offset-background transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
media.id === selectedImage.id && 'border-opacity-100',
)}
key={media.id}
onClick={() => handleSelect(index, media.id)}
>
<ShopifyImage
aspectRatio="1/1"
className="size-full object-cover"
data={media.image}
loading="eager"
showBorder={false}
showShadow={false}
sizes="96px"
/>
</button>
)}
</CarouselItem>
);
})}
</CarouselContent>
</div>
</Carousel>
</div>
Expand Down
83 changes: 39 additions & 44 deletions app/components/sections/CarouselSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export function CarouselSection(
} = data;
const ref = useRef<HTMLDivElement>(null);
const inView = useInView(ref);
const slidesPerView = slidesPerViewDesktop ? 100 / slidesPerViewDesktop : 100;
const plugins = useMemo(() => (autoplay ? [Autoplay()] : []), [autoplay]);
const imageSizes = slidesPerViewDesktop
? `(min-width: 1024px) ${
Expand All @@ -46,49 +45,45 @@ export function CarouselSection(
return (
<div className="container" ref={ref}>
<h2>{title}</h2>
<div
style={
{
'--slidesPerView': `${slidesPerView}%`,
} as React.CSSProperties
}
>
{slides && slides?.length > 0 && (
<Carousel
opts={{
active: isActive,
loop: loop || false,
}}
plugins={plugins}
>
<div className="relative">
<CarouselContent>
{slides.map((slide) => (
<CarouselItem
className="p-0 md:basis-1/2 md:pl-4 lg:basis-[var(--slidesPerView)]"
key={slide._key}
>
<SanityImage
data={slide.image}
loading={inView ? 'eager' : 'lazy'}
showBorder={false}
showShadow={false}
sizes={imageSizes}
/>
</CarouselItem>
))}
</CarouselContent>
{arrows && isActive && (
<div className="hidden md:block">
<CarouselPrevious />
<CarouselNext />
</div>
)}
</div>
{pagination && <CarouselPagination />}
</Carousel>
)}
</div>
{slides && slides?.length > 0 && (
<Carousel
className="[--slide-spacing:1rem]"
opts={{
active: isActive,
loop: loop || false,
}}
plugins={plugins}
style={
{
'--slides-per-view': slidesPerViewDesktop,
} as React.CSSProperties
}
>
<div className="relative">
<CarouselContent>
{slides.map((slide) => (
<CarouselItem className="[&>span]:h-full" key={slide._key}>
<SanityImage
className="size-full object-cover"
data={slide.image}
loading={inView ? 'eager' : 'lazy'}
showBorder={false}
showShadow={false}
sizes={imageSizes}
/>
</CarouselItem>
))}
</CarouselContent>
{arrows && isActive && (
<div className="hidden md:block">
<CarouselPrevious />
<CarouselNext />
</div>
)}
</div>
{pagination && <CarouselPagination />}
</Carousel>
)}
</div>
);
}
Loading

0 comments on commit 7dd7eca

Please sign in to comment.