Collections with products carousel
Collections with products carousel
shopifycollectionslist
import React from 'react';
import { Actionable, Box, Flexbox, Image, Text, Icon } from '@evlop/native-components';
import { useProducts } from '@evlop/shopify';
import { memoize } from 'lodash';
export default function App(props: NativeAppBlockProps) {
const collection = props.data?.collection ?? props.data;
const products = useProducts({ params: JSON.stringify({collectionId: collection?.id}), pageSize: 5 });
if (!collection) return null;
const layouts = calculateLayouts(products.length);
return (
<Actionable action={collection.actions?.openDetailsPage}>
<Flexbox bg="gray-50" p="2xs" flexDirection="column" gap={10} borderRadius={10}>
<Flexbox alignItems="center" justifyContent="space-between">
<Text textAlign="center" fontSize="3xl" color="gray-900" fontWeight="Semibold">
{collection.title}
</Text>
<Icon size={16} icon="feather:chevron-right" color="gray-500" />
</Flexbox>
{/* Collage container. We use a fixed aspect box so the absolute % positions work predictably. */}
<Box position="relative" backgroundColor="gray-0/700" width="100%" style={{aspectRatio: 1}}>
{products.map((p: any, i: number) => {
const layout = layouts[i];
if (!layout) return null;
return (
<Actionable key={p.id || p.handle || i} action={p.actions?.openDetailsPage} style={layout}>
<Image
src={p.thumbnail}
resizeMode="cover"
width="100%"
height="100%"
/>
</Actionable>
);
})}
</Box>
</Flexbox>
</Actionable>
);
}
// Calculate absolute positions/sizes using percentages so the collage adapts to any container size.
// Returns an array of { left, top, width, height } strings (percentages) for each product index.
const calculateLayouts = memoize((count: number) => {
const layouts = [];
switch (count) {
case 1:
// single full-bleed image
layouts.push({ left: 0, top: 0, width: '100%', height: '100%' });
break;
case 2:
// two equal columns
layouts.push({ left: 0, top: 0, width: '50%', height: '100%' });
layouts.push({ left: '50%', top: 0, width: '50%', height: '100%' });
break;
case 3:
// left column large, right column split in two
layouts.push({ left: 0, top: 0, width: '50%', height: '100%' });
layouts.push({ left: '50%', top: 0, width: '50%', height: '50%' });
layouts.push({ left: '50%', top: '50%', width: '50%', height: '50%' });
break;
case 4:
// 2x2 grid
layouts.push({ left: 0, top: 0, width: '50%', height: '50%' });
layouts.push({ left: '50%', top: 0, width: '50%', height: '50%' });
layouts.push({ left: 0, top: '50%', width: '50%', height: '50%' });
layouts.push({ left: '50%', top: '50%', width: '50%', height: '50%' });
break;
default:
// 5 or more: left column two stacked (50% each), right column three stacked (33.33% each)
layouts.push({ left: 0, top: 0, width: '50%', height: '50%' });
layouts.push({ left: 0, top: '50%', width: '50%', height: '50%' });
layouts.push({ left: '50%', top: 0, width: '50%', height: '33.3333%' });
layouts.push({ left: '50%', top: '33.3333%', width: '50%', height: '33.3333%' });
layouts.push({ left: '50%', top: '66.6666%', width: '50%', height: '33.3334%' });
break;
}
return layouts.map(l=>({...l, position: 'absolute', overflow: 'hidden', borderLeftWidth: l.left === 0? 0 : 1, borderTopWidth: l.top == 0? 0 : 1, borderColor: 'transparent'}));
}){}Loading...