mirror of
https://github.com/vercel/commerce.git
synced 2025-05-08 10:47:51 +00:00
add: GridItem & Grid
This commit is contained in:
parent
3bc03dd7c8
commit
a17e00f875
265
app/data.ts
Normal file
265
app/data.ts
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
import { Product } from 'lib/shopify/types';
|
||||||
|
|
||||||
|
//temp: for ProductGridItems test
|
||||||
|
export const mockProducts: Product[] = [
|
||||||
|
{
|
||||||
|
id: 'prod_001',
|
||||||
|
handle: 'product-1',
|
||||||
|
availableForSale: true,
|
||||||
|
title: 'Product 1',
|
||||||
|
description: 'This is the description for Product 1',
|
||||||
|
descriptionHtml: '<p>This is the <strong>HTML</strong> description for Product 1</p>',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: 'option_001',
|
||||||
|
name: 'Size',
|
||||||
|
values: ['S', 'M', 'L']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
priceRange: {
|
||||||
|
maxVariantPrice: {
|
||||||
|
amount: '100.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
},
|
||||||
|
minVariantPrice: {
|
||||||
|
amount: '80.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
featuredImage: {
|
||||||
|
url: 'https://cdn.shopify.com/static/sample-images/bath.jpeg',
|
||||||
|
altText: 'Product 1 Featured Image',
|
||||||
|
width: 500,
|
||||||
|
height: 500
|
||||||
|
},
|
||||||
|
seo: {
|
||||||
|
title: 'Product 1 SEO Title',
|
||||||
|
description: 'This is the SEO description for Product 1'
|
||||||
|
},
|
||||||
|
tags: ['tag1', 'tag2'],
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: 'variant_001',
|
||||||
|
title: 'Variant 1',
|
||||||
|
availableForSale: true,
|
||||||
|
selectedOptions: [
|
||||||
|
{
|
||||||
|
name: 'Size',
|
||||||
|
value: 'M'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
price: {
|
||||||
|
amount: '90.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://cdn.shopify.com/static/sample-images/bath.jpeg',
|
||||||
|
altText: 'Product 1 Image 1',
|
||||||
|
width: 500,
|
||||||
|
height: 500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'https://cdn.shopify.com/static/sample-images/bath.jpeg',
|
||||||
|
altText: 'Product 1 Image 2',
|
||||||
|
width: 400,
|
||||||
|
height: 400
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'prod_002',
|
||||||
|
handle: 'product-2',
|
||||||
|
availableForSale: false,
|
||||||
|
title: 'Product 2',
|
||||||
|
description: 'This is the description for Product 2',
|
||||||
|
descriptionHtml: '<p>This is the <strong>HTML</strong> description for Product 2</p>',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: 'option_002',
|
||||||
|
name: 'Color',
|
||||||
|
values: ['Red', 'Blue', 'Green']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
priceRange: {
|
||||||
|
maxVariantPrice: {
|
||||||
|
amount: '120.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
},
|
||||||
|
minVariantPrice: {
|
||||||
|
amount: '100.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
featuredImage: {
|
||||||
|
url: 'https://cdn.shopify.com/static/sample-images/teapot.jpg',
|
||||||
|
altText: 'Product 2 Featured Image',
|
||||||
|
width: 500,
|
||||||
|
height: 500
|
||||||
|
},
|
||||||
|
seo: {
|
||||||
|
title: 'Product 2 SEO Title',
|
||||||
|
description: 'This is the SEO description for Product 2'
|
||||||
|
},
|
||||||
|
tags: ['tag3', 'tag4'],
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: 'variant_002',
|
||||||
|
title: 'Variant 2',
|
||||||
|
availableForSale: false,
|
||||||
|
selectedOptions: [
|
||||||
|
{
|
||||||
|
name: 'Color',
|
||||||
|
value: 'Red'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
price: {
|
||||||
|
amount: '110.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://cdn.shopify.com/static/sample-images/teapot.jpg',
|
||||||
|
altText: 'Product 2 Image 1',
|
||||||
|
width: 500,
|
||||||
|
height: 500
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'prod_003',
|
||||||
|
handle: 'product-3',
|
||||||
|
availableForSale: true,
|
||||||
|
title: 'Product 3',
|
||||||
|
description: 'This is the description for Product 3',
|
||||||
|
descriptionHtml: '<p>This is the <strong>HTML</strong> description for Product 3</p>',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: 'option_003',
|
||||||
|
name: 'Size',
|
||||||
|
values: ['S', 'M', 'L']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
priceRange: {
|
||||||
|
maxVariantPrice: {
|
||||||
|
amount: '300.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
},
|
||||||
|
minVariantPrice: {
|
||||||
|
amount: '80.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
featuredImage: {
|
||||||
|
url: 'https://cdn.shopify.com/static/sample-images/bath.jpeg',
|
||||||
|
altText: 'Product 3 Featured Image',
|
||||||
|
width: 500,
|
||||||
|
height: 500
|
||||||
|
},
|
||||||
|
seo: {
|
||||||
|
title: 'Product 3 SEO Title',
|
||||||
|
description: 'This is the SEO description for Product 3'
|
||||||
|
},
|
||||||
|
tags: ['tag3', 'tag2'],
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: 'variant_003',
|
||||||
|
title: 'Variant 3',
|
||||||
|
availableForSale: true,
|
||||||
|
selectedOptions: [
|
||||||
|
{
|
||||||
|
name: 'Size',
|
||||||
|
value: 'M'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
price: {
|
||||||
|
amount: '90.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://cdn.shopify.com/static/sample-images/bath.jpeg',
|
||||||
|
altText: 'Product 3 Image 1',
|
||||||
|
width: 500,
|
||||||
|
height: 500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'https://cdn.shopify.com/static/sample-images/bath.jpeg',
|
||||||
|
altText: 'Product 3 Image 2',
|
||||||
|
width: 400,
|
||||||
|
height: 400
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'prod_004',
|
||||||
|
handle: 'product-4',
|
||||||
|
availableForSale: false,
|
||||||
|
title: 'Product 4',
|
||||||
|
description: 'This is the description for Product 4',
|
||||||
|
descriptionHtml: '<p>This is the <strong>HTML</strong> description for Product 4</p>',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: 'option_004',
|
||||||
|
name: 'Color',
|
||||||
|
values: ['Red', 'Blue', 'Green']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
priceRange: {
|
||||||
|
maxVariantPrice: {
|
||||||
|
amount: '140.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
},
|
||||||
|
minVariantPrice: {
|
||||||
|
amount: '100.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
featuredImage: {
|
||||||
|
url: 'https://cdn.shopify.com/static/sample-images/teapot.jpg',
|
||||||
|
altText: 'Product 4 Featured Image',
|
||||||
|
width: 500,
|
||||||
|
height: 500
|
||||||
|
},
|
||||||
|
seo: {
|
||||||
|
title: 'Product 4 SEO Title',
|
||||||
|
description: 'This is the SEO description for Product 4'
|
||||||
|
},
|
||||||
|
tags: ['tag3', 'tag4'],
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: 'variant_004',
|
||||||
|
title: 'Variant 4',
|
||||||
|
availableForSale: false,
|
||||||
|
selectedOptions: [
|
||||||
|
{
|
||||||
|
name: 'Color',
|
||||||
|
value: 'Red'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
price: {
|
||||||
|
amount: '110.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://cdn.shopify.com/static/sample-images/teapot.jpg',
|
||||||
|
altText: 'Product 4 Image 1',
|
||||||
|
width: 500,
|
||||||
|
height: 500
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
@ -1,3 +1,4 @@
|
|||||||
|
import Footer from 'components/layout/footer';
|
||||||
import { Navbar } from 'components/layout/navbar';
|
import { Navbar } from 'components/layout/navbar';
|
||||||
import { GeistSans } from 'geist/font/sans';
|
import { GeistSans } from 'geist/font/sans';
|
||||||
import { getCart } from 'lib/shopify';
|
import { getCart } from 'lib/shopify';
|
||||||
@ -43,6 +44,7 @@ export default async function RootLayout({ children }: { children: ReactNode })
|
|||||||
<body className="bg-neutral-50 text-black selection:bg-teal-300 dark:bg-neutral-900 dark:text-white dark:selection:bg-pink-500 dark:selection:text-white">
|
<body className="bg-neutral-50 text-black selection:bg-teal-300 dark:bg-neutral-900 dark:text-white dark:selection:bg-pink-500 dark:selection:text-white">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
|
<Footer />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
174
app/page.tsx
174
app/page.tsx
@ -1,9 +1,10 @@
|
|||||||
|
import { mockProducts } from 'app/data';
|
||||||
import Error from 'app/error';
|
import Error from 'app/error';
|
||||||
import Footer from 'components/layout/footer';
|
import Grid from 'components/grid';
|
||||||
import { Search } from 'components/layout/search';
|
import { Search } from 'components/layout/search';
|
||||||
import { PriceBox } from 'components/price-box';
|
import { PriceBox } from 'components/price-box';
|
||||||
|
import ProductGridItems from 'components/product/product-grid-items';
|
||||||
import { getCollectionProducts } from 'lib/shopify';
|
import { getCollectionProducts } from 'lib/shopify';
|
||||||
import type { Product } from 'lib/shopify/types';
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
//Todo: change to proper metadata
|
//Todo: change to proper metadata
|
||||||
@ -30,157 +31,48 @@ export default async function HomePage() {
|
|||||||
}
|
}
|
||||||
alt={products[0].featuredImage.altText || 'Main product'}
|
alt={products[0].featuredImage.altText || 'Main product'}
|
||||||
fill
|
fill
|
||||||
objectFit="cover"
|
|
||||||
quality={100}
|
quality={100}
|
||||||
priority
|
priority
|
||||||
|
className="object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-20 flex w-full flex-col items-center text-lightText">
|
<div className="absolute bottom-20 flex w-full flex-col items-center text-lightText">
|
||||||
<h1 className="text-xl">{products[0].title}</h1>
|
<h1 className="text-xl">{products[0].title}</h1>
|
||||||
<span className="mb-6 mt-1 text-sm text-lightText/80">Read more</span>
|
<span className="mb-6 mt-1 text-sm text-lightText/80">Read more</span>
|
||||||
<div className="text-mainBg flex w-[384px] justify-center gap-[10px]">
|
<div className="flex w-[384px] justify-center gap-[10px] text-mainBg">
|
||||||
<PriceBox title="Box of 20" price={2460} />
|
<PriceBox title="Box of 20" price={2460} />
|
||||||
<PriceBox title="Single Cigar" price={120} />
|
<PriceBox title="Single Cigar" price={120} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Search />
|
|
||||||
</section>
|
</section>
|
||||||
|
<Search />
|
||||||
<Footer />
|
<Grid className="grid-cols-2 sm:grid-cols-4">
|
||||||
|
{mockProducts.slice(0, 4).map(({ featuredImage, id, title, handle }) => (
|
||||||
|
<ProductGridItems
|
||||||
|
key={id}
|
||||||
|
src={featuredImage.url}
|
||||||
|
title={title}
|
||||||
|
handle={handle}
|
||||||
|
ratio="2/3"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
<Grid className="grid-cols-1 sm:grid-cols-3">
|
||||||
|
{mockProducts.slice(0, 3).map(({ featuredImage, id, title, handle }) => (
|
||||||
|
<ProductGridItems
|
||||||
|
key={id}
|
||||||
|
src={featuredImage.url}
|
||||||
|
title={title}
|
||||||
|
handle={handle}
|
||||||
|
ratio="2/3"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
<Grid className="grid-cols-1 sm:grid-cols-2">
|
||||||
|
{mockProducts.slice(0, 2).map(({ featuredImage, id, title, handle }) => (
|
||||||
|
<ProductGridItems key={id} src={featuredImage.url} title={title} handle={handle} />
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//temp: for ProductGridItems test
|
|
||||||
const mockProducts: Product[] = [
|
|
||||||
{
|
|
||||||
id: 'prod_001',
|
|
||||||
handle: 'product-1',
|
|
||||||
availableForSale: true,
|
|
||||||
title: 'Product 1',
|
|
||||||
description: 'This is the description for Product 1',
|
|
||||||
descriptionHtml: '<p>This is the <strong>HTML</strong> description for Product 1</p>',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
id: 'option_001',
|
|
||||||
name: 'Size',
|
|
||||||
values: ['S', 'M', 'L']
|
|
||||||
}
|
|
||||||
],
|
|
||||||
priceRange: {
|
|
||||||
maxVariantPrice: {
|
|
||||||
amount: '100.00',
|
|
||||||
currencyCode: 'USD'
|
|
||||||
},
|
|
||||||
minVariantPrice: {
|
|
||||||
amount: '80.00',
|
|
||||||
currencyCode: 'USD'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
featuredImage: {
|
|
||||||
url: 'https://cdn.shopify.com/static/sample-images/garnished.jpeg',
|
|
||||||
altText: 'Product 1 Featured Image',
|
|
||||||
width: 500,
|
|
||||||
height: 500
|
|
||||||
},
|
|
||||||
seo: {
|
|
||||||
title: 'Product 1 SEO Title',
|
|
||||||
description: 'This is the SEO description for Product 1'
|
|
||||||
},
|
|
||||||
tags: ['tag1', 'tag2'],
|
|
||||||
updatedAt: new Date().toISOString(),
|
|
||||||
variants: [
|
|
||||||
{
|
|
||||||
id: 'variant_001',
|
|
||||||
title: 'Variant 1',
|
|
||||||
availableForSale: true,
|
|
||||||
selectedOptions: [
|
|
||||||
{
|
|
||||||
name: 'Size',
|
|
||||||
value: 'M'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
price: {
|
|
||||||
amount: '90.00',
|
|
||||||
currencyCode: 'USD'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: 'https://cdn.shopify.com/static/sample-images/garnished.jpeg',
|
|
||||||
altText: 'Product 1 Image 1',
|
|
||||||
width: 500,
|
|
||||||
height: 500
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: 'https://cdn.shopify.com/static/sample-images/garnished.jpeg',
|
|
||||||
altText: 'Product 1 Image 2',
|
|
||||||
width: 400,
|
|
||||||
height: 400
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'prod_002',
|
|
||||||
handle: 'product-2',
|
|
||||||
availableForSale: false,
|
|
||||||
title: 'Product 2',
|
|
||||||
description: 'This is the description for Product 2',
|
|
||||||
descriptionHtml: '<p>This is the <strong>HTML</strong> description for Product 2</p>',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
id: 'option_002',
|
|
||||||
name: 'Color',
|
|
||||||
values: ['Red', 'Blue', 'Green']
|
|
||||||
}
|
|
||||||
],
|
|
||||||
priceRange: {
|
|
||||||
maxVariantPrice: {
|
|
||||||
amount: '120.00',
|
|
||||||
currencyCode: 'USD'
|
|
||||||
},
|
|
||||||
minVariantPrice: {
|
|
||||||
amount: '100.00',
|
|
||||||
currencyCode: 'USD'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
featuredImage: {
|
|
||||||
url: 'https://cdn.shopify.com/static/sample-images/garnished.jpeg',
|
|
||||||
altText: 'Product 2 Featured Image',
|
|
||||||
width: 500,
|
|
||||||
height: 500
|
|
||||||
},
|
|
||||||
seo: {
|
|
||||||
title: 'Product 2 SEO Title',
|
|
||||||
description: 'This is the SEO description for Product 2'
|
|
||||||
},
|
|
||||||
tags: ['tag3', 'tag4'],
|
|
||||||
updatedAt: new Date().toISOString(),
|
|
||||||
variants: [
|
|
||||||
{
|
|
||||||
id: 'variant_002',
|
|
||||||
title: 'Variant 2',
|
|
||||||
availableForSale: false,
|
|
||||||
selectedOptions: [
|
|
||||||
{
|
|
||||||
name: 'Color',
|
|
||||||
value: 'Red'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
price: {
|
|
||||||
amount: '110.00',
|
|
||||||
currencyCode: 'USD'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: 'https://cdn.shopify.com/static/sample-images/garnished.jpeg',
|
|
||||||
altText: 'Product 2 Image 1',
|
|
||||||
width: 500,
|
|
||||||
height: 500
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Footer from 'components/layout/footer';
|
import Footer from 'components/layout/footer';
|
||||||
import Collections from 'components/layout/search/collections';
|
import Collections from 'components/layout/search/*not-in-use/collections';
|
||||||
import FilterList from 'components/layout/search/filter';
|
import FilterList from 'components/layout/search/*not-in-use/filter';
|
||||||
import { sorting } from 'lib/constants';
|
import { sorting } from 'lib/constants';
|
||||||
import ChildrenWrapper from './children-wrapper';
|
import ChildrenWrapper from './children-wrapper';
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import clsx from 'clsx';
|
|||||||
|
|
||||||
function Grid(props: React.ComponentProps<'ul'>) {
|
function Grid(props: React.ComponentProps<'ul'>) {
|
||||||
return (
|
return (
|
||||||
<ul {...props} className={clsx('grid grid-flow-row gap-4', props.className)}>
|
<ul {...props} className={clsx('grid grid-flow-row', props.className)}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
@ -10,7 +10,7 @@ function Grid(props: React.ComponentProps<'ul'>) {
|
|||||||
|
|
||||||
function GridItem(props: React.ComponentProps<'li'>) {
|
function GridItem(props: React.ComponentProps<'li'>) {
|
||||||
return (
|
return (
|
||||||
<li {...props} className={clsx('aspect-square transition-opacity', props.className)}>
|
<li {...props} className={clsx(props.className)}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
import { GridTileImage } from 'components/grid/tile';
|
|
||||||
import { getCollectionProducts } from 'lib/shopify';
|
|
||||||
import type { Product } from 'lib/shopify/types';
|
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
function ThreeItemGridItem({
|
|
||||||
item,
|
|
||||||
size,
|
|
||||||
priority
|
|
||||||
}: {
|
|
||||||
item: Product;
|
|
||||||
size: 'full' | 'half';
|
|
||||||
priority?: boolean;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={size === 'full' ? 'md:col-span-4 md:row-span-2' : 'md:col-span-2 md:row-span-1'}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
className="relative block aspect-square h-full w-full"
|
|
||||||
href={`/product/${item.handle}`}
|
|
||||||
prefetch={true}
|
|
||||||
>
|
|
||||||
<GridTileImage
|
|
||||||
src={item.featuredImage.url}
|
|
||||||
fill
|
|
||||||
sizes={
|
|
||||||
size === 'full' ? '(min-width: 768px) 66vw, 100vw' : '(min-width: 768px) 33vw, 100vw'
|
|
||||||
}
|
|
||||||
priority={priority}
|
|
||||||
alt={item.title}
|
|
||||||
label={{
|
|
||||||
position: size === 'full' ? 'center' : 'bottom',
|
|
||||||
title: item.title as string,
|
|
||||||
amount: item.priceRange.maxVariantPrice.amount,
|
|
||||||
currencyCode: item.priceRange.maxVariantPrice.currencyCode
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function ThreeItemGrid() {
|
|
||||||
// Collections that start with `hidden-*` are hidden from the search page.
|
|
||||||
const homepageItems = await getCollectionProducts({
|
|
||||||
collection: 'hidden-homepage-featured-items'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!homepageItems[0] || !homepageItems[1] || !homepageItems[2]) return null;
|
|
||||||
|
|
||||||
const [firstProduct, secondProduct, thirdProduct] = homepageItems;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="mx-auto grid max-w-screen-2xl gap-4 px-4 pb-4 md:grid-cols-6 md:grid-rows-2 lg:max-h-[calc(100vh-200px)]">
|
|
||||||
<ThreeItemGridItem size="full" item={firstProduct} priority={true} />
|
|
||||||
<ThreeItemGridItem size="half" item={secondProduct} priority={true} />
|
|
||||||
<ThreeItemGridItem size="half" item={thirdProduct} />
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,49 +1,19 @@
|
|||||||
import clsx from 'clsx';
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Label from '../label';
|
|
||||||
|
|
||||||
export function GridTileImage({
|
export function GridTileImage({
|
||||||
isInteractive = true,
|
title,
|
||||||
active,
|
|
||||||
label,
|
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
isInteractive?: boolean;
|
title?: string;
|
||||||
active?: boolean;
|
|
||||||
label?: {
|
|
||||||
title: string;
|
|
||||||
amount: string;
|
|
||||||
currencyCode: string;
|
|
||||||
position?: 'bottom' | 'center';
|
|
||||||
};
|
|
||||||
} & React.ComponentProps<typeof Image>) {
|
} & React.ComponentProps<typeof Image>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className={clsx(
|
{props.src ? <Image fill className="h-full w-full object-cover" {...props} /> : null}
|
||||||
'group flex h-full w-full items-center justify-center overflow-hidden rounded-lg border bg-white hover:border-blue-600 dark:bg-black',
|
{title ? (
|
||||||
{
|
<h3 className="absolute bottom-0 w-full p-10 text-center text-[15px] text-lightText">
|
||||||
relative: label,
|
{title}
|
||||||
'border-2 border-blue-600': active,
|
</h3>
|
||||||
'border-neutral-200 dark:border-neutral-800': !active
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{props.src ? (
|
|
||||||
<Image
|
|
||||||
className={clsx('relative h-full w-full object-contain', {
|
|
||||||
'transition duration-300 ease-in-out group-hover:scale-105': isInteractive
|
|
||||||
})}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
{label ? (
|
</>
|
||||||
<Label
|
|
||||||
title={label.title}
|
|
||||||
amount={label.amount}
|
|
||||||
currencyCode={label.currencyCode}
|
|
||||||
position={label.position}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import Grid from 'components/grid';
|
|
||||||
import { GridTileImage } from 'components/grid/tile';
|
|
||||||
import { Product } from 'lib/shopify/types';
|
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
export default function ProductGridItems({ products }: { products: Product[] }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{products.map((product) => (
|
|
||||||
<Grid.Item key={product.handle} className="animate-fadeIn">
|
|
||||||
<Link
|
|
||||||
className="relative inline-block h-full w-full"
|
|
||||||
href={`/product/${product.handle}`}
|
|
||||||
prefetch={true}
|
|
||||||
>
|
|
||||||
<GridTileImage
|
|
||||||
alt={product.title}
|
|
||||||
label={{
|
|
||||||
title: product.title,
|
|
||||||
amount: product.priceRange.maxVariantPrice.amount,
|
|
||||||
currencyCode: product.priceRange.maxVariantPrice.currencyCode
|
|
||||||
}}
|
|
||||||
src={product.featuredImage?.url}
|
|
||||||
fill
|
|
||||||
sizes="(min-width: 768px) 33vw, (min-width: 640px) 50vw, 100vw"
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</Grid.Item>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
27
components/product/product-grid-items.tsx
Normal file
27
components/product/product-grid-items.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { clsx } from 'clsx';
|
||||||
|
import Grid from 'components/grid';
|
||||||
|
import { GridTileImage } from 'components/grid/tile';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
export default function ProductGridItems({
|
||||||
|
src,
|
||||||
|
title,
|
||||||
|
handle,
|
||||||
|
ratio
|
||||||
|
}: {
|
||||||
|
src: string;
|
||||||
|
title: string;
|
||||||
|
handle: string;
|
||||||
|
ratio?: '2/3' | 'square';
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Grid.Item
|
||||||
|
key={handle}
|
||||||
|
className={clsx('relative w-full', ratio === '2/3' ? `aspect-[2/3]` : 'aspect-square')}
|
||||||
|
>
|
||||||
|
<Link className="h-full w-full" href={`/product/${handle}`} prefetch={true}>
|
||||||
|
<GridTileImage alt={title} title={title} src={src} fill />
|
||||||
|
</Link>
|
||||||
|
</Grid.Item>
|
||||||
|
);
|
||||||
|
}
|
@ -5,8 +5,8 @@ module.exports = {
|
|||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
protocol: 'https',
|
protocol: 'https',
|
||||||
hostname: 'cdn.shopify.com',
|
hostname: 'cdn.shopify.com'
|
||||||
pathname: '/s/files/**'
|
// pathname: '/s/files/**'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user