Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Composed Product Details #41

Merged
merged 2 commits into from
Oct 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions components/product/Gallery/FrontBack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ProductDetailsPage } from "apps/commerce/types.ts";
import Image from "apps/website/components/Image.tsx";

export interface Props {
/** @title Integration */
page: ProductDetailsPage | null;

layout: {
width: number;
height: number;
};
}

/**
* @title Product Image Front Back
* @description Renders two images side by side both on mobile and on desktop. On mobile, the overflow is reached causing a scrollbar to be rendered.
*/
function GalleryFrontBack(props: Props) {
if (props.page === null) {
throw new Error("Missing Product Details Page Info");
}

const {
page: { product: { image: images = [] } },
layout: { width, height },
} = props;
const aspectRatio = `${width} / ${height}`;

return (
<ul class="carousel carousel-center gap-6">
{[images[0], images[1] ?? images[0]].map((img, index) => (
<li class="carousel-item">
<Image
class="w-screen sm:w-[24vw]"
sizes="(max-width: 640px) 100vw, 24vw"
style={{ aspectRatio }}
src={img.url!}
alt={img.alternateName}
width={width}
height={height}
// Preload LCP image for better web vitals
preload={index === 0}
loading={index === 0 ? "eager" : "lazy"}
/>
</li>
))}
</ul>
);
}

export default GalleryFrontBack;
108 changes: 108 additions & 0 deletions components/product/Gallery/ImageSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import Icon from "$store/components/ui/Icon.tsx";
import Slider from "$store/components/ui/Slider.tsx";
import ProductImageZoom from "$store/islands/ProductImageZoom.tsx";
import SliderJS from "$store/islands/SliderJS.tsx";
import { useId } from "$store/sdk/useId.ts";
import { ProductDetailsPage } from "apps/commerce/types.ts";
import Image from "apps/website/components/Image.tsx";

export interface Props {
/** @title Integration */
page: ProductDetailsPage | null;

layout: {
width: number;
height: number;
};
}

/**
* @title Product Image Slider
* @description Creates a three columned grid on destkop, one for the dots preview, one for the image slider and the other for product info
* On mobile, there's one single column with 3 rows. Note that the orders are different from desktop to mobile, that's why
* we rearrange each cell with col-start- directives
*/
export default function GallerySlider(props: Props) {
const id = useId();

if (props.page === null) {
throw new Error("Missing Product Details Page Info");
}

const {
page: { product: { image: images = [] } },
layout: { width, height },
} = props;
const aspectRatio = `${width} / ${height}`;

return (
<div id={id} class="grid grid-flow-row sm:grid-flow-col">
{/* Image Slider */}
<div class="relative order-1 sm:order-2">
<Slider class="carousel carousel-center gap-6 w-screen sm:w-[40vw]">
{images.map((img, index) => (
<Slider.Item
index={index}
class="carousel-item w-full"
>
<Image
class="w-full"
sizes="(max-width: 640px) 100vw, 40vw"
style={{ aspectRatio }}
src={img.url!}
alt={img.alternateName}
width={width}
height={height}
// Preload LCP image for better web vitals
preload={index === 0}
loading={index === 0 ? "eager" : "lazy"}
/>
</Slider.Item>
))}
</Slider>

<Slider.PrevButton
class="no-animation absolute left-2 top-1/2 btn btn-circle btn-outline"
disabled
>
<Icon size={24} id="ChevronLeft" strokeWidth={3} />
</Slider.PrevButton>

<Slider.NextButton
class="no-animation absolute right-2 top-1/2 btn btn-circle btn-outline"
disabled={images.length < 2}
>
<Icon size={24} id="ChevronRight" strokeWidth={3} />
</Slider.NextButton>

<div class="absolute top-2 right-2 bg-base-100 rounded-full">
<ProductImageZoom
images={images}
width={700}
height={Math.trunc(700 * height / width)}
/>
</div>
</div>

{/* Dots */}
<ul class="carousel carousel-center gap-1 px-4 sm:px-0 sm:flex-col order-2 sm:order-1">
{images.map((img, index) => (
<li class="carousel-item min-w-[63px] sm:min-w-[100px]">
<Slider.Dot index={index}>
<Image
style={{ aspectRatio }}
class="group-disabled:border-base-300 border rounded "
width={63}
height={87.5}
src={img.url!}
alt={img.alternateName}
/>
</Slider.Dot>
</li>
))}
</ul>

<SliderJS rootId={id} />
</div>
);
}
Loading