mirror of
https://github.com/vercel/commerce.git
synced 2025-05-12 12:47:50 +00:00
fix: update layout for PLP
Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
parent
39b1235c82
commit
ca37984cff
@ -10,7 +10,6 @@ import { ProductDescription } from 'components/product/product-description';
|
||||
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
|
||||
import { getProduct, getProductRecommendations } from 'lib/shopify';
|
||||
import { Image } from 'lib/shopify/types';
|
||||
import Link from 'next/link';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
@ -131,19 +130,18 @@ async function RelatedProducts({ id }: { id: string }) {
|
||||
key={product.handle}
|
||||
className="aspect-square w-full flex-none min-[475px]:w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/5"
|
||||
>
|
||||
<Link className="relative h-full w-full" href={`/product/${product.handle}`}>
|
||||
<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: 1024px) 20vw, (min-width: 768px) 25vw, (min-width: 640px) 33vw, (min-width: 475px) 50vw, 100vw"
|
||||
/>
|
||||
</Link>
|
||||
<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: 1024px) 20vw, (min-width: 768px) 25vw, (min-width: 640px) 33vw, (min-width: 475px) 50vw, 100vw"
|
||||
href={`/product/${product.handle}`}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -8,7 +8,9 @@ import YMMFilters, { YMMFiltersPlaceholder } from 'components/filters';
|
||||
import Grid from 'components/grid';
|
||||
import ProductsList from 'components/layout/products-list';
|
||||
import { getProductsInCollection } from 'components/layout/products-list/actions';
|
||||
import FiltersList from 'components/layout/search/filters/filters-list';
|
||||
import FiltersContainer, {
|
||||
FiltersListPlaceholder
|
||||
} from 'components/layout/search/filters/filters-container';
|
||||
import MobileFilters from 'components/layout/search/filters/mobile-filters';
|
||||
import SubMenu from 'components/layout/search/filters/sub-menu';
|
||||
import Header, { HeaderPlaceholder } from 'components/layout/search/header';
|
||||
@ -47,60 +49,68 @@ async function CategoryPage({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex w-full items-center justify-between gap-2 lg:justify-end">
|
||||
<MobileFilters filters={filters} menu={<SubMenu collection={params.collection} />} />
|
||||
<SortingMenu />
|
||||
</div>
|
||||
<section>
|
||||
<Grid className="pt-5 lg:grid-cols-3 lg:gap-x-8 xl:grid-cols-4">
|
||||
<aside className="hidden lg:block">
|
||||
<SubMenu collection={params.collection} />
|
||||
<h3 className="sr-only">Filters</h3>
|
||||
<FiltersList filters={filters} />
|
||||
</aside>
|
||||
<div className="lg:col-span-2 xl:col-span-3">
|
||||
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{products.length === 0 ? (
|
||||
<p className="py-3 text-lg">{`No products found in this collection`}</p>
|
||||
) : (
|
||||
<ProductsList
|
||||
initialProducts={products}
|
||||
pageInfo={pageInfo}
|
||||
page="collection"
|
||||
searchParams={searchParams}
|
||||
key={JSON.stringify(searchParams)}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Grid>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CategorySearchPage(props: {
|
||||
params: { collection: string };
|
||||
searchParams?: { [key: string]: string | string[] | undefined };
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<Suspense fallback={<BreadcrumbHome />} key={`breadcrumb-${props.params.collection}`}>
|
||||
<Breadcrumb type="collection" handle={props.params.collection} />
|
||||
</Suspense>
|
||||
</div>
|
||||
<Suspense fallback={<HeaderPlaceholder />} key={`header-${props.params.collection}`}>
|
||||
<Header collection={props.params.collection} />
|
||||
</Suspense>
|
||||
<div className="my-3">
|
||||
<div className="my-3 block lg:hidden">
|
||||
<Suspense fallback={<YMMFiltersPlaceholder />}>
|
||||
<YMMFilters />
|
||||
</Suspense>
|
||||
</div>
|
||||
<Suspense fallback={<ProductsGridPlaceholder />} key={`products-${props.params.collection}`}>
|
||||
<CategoryPage {...props} />
|
||||
</Suspense>
|
||||
<div className="mb-5 flex w-full items-center justify-between gap-2 lg:justify-end">
|
||||
<MobileFilters filters={filters} menu={<SubMenu collection={params.collection} />} />
|
||||
<SortingMenu />
|
||||
</div>
|
||||
<Grid className="grid-cols-1 sm:grid-cols-2 sm:gap-x-8 lg:grid-cols-3">
|
||||
{products.length === 0 ? (
|
||||
<p className="py-3 text-lg">{`No products found in this collection`}</p>
|
||||
) : (
|
||||
<ProductsList
|
||||
initialProducts={products}
|
||||
pageInfo={pageInfo}
|
||||
page="collection"
|
||||
searchParams={searchParams}
|
||||
key={JSON.stringify(searchParams)}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function CategorySearchPage(props: {
|
||||
params: { collection: string };
|
||||
searchParams?: { [key: string]: string | string[] | undefined };
|
||||
}) {
|
||||
return (
|
||||
<div className="grid lg:grid-cols-3 lg:gap-x-10 xl:grid-cols-4">
|
||||
<aside className="hidden lg:block">
|
||||
<div className="mb-5">
|
||||
<Suspense fallback={<YMMFiltersPlaceholder />}>
|
||||
<YMMFilters />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
<SubMenu collection={props.params.collection} />
|
||||
<h3 className="sr-only">Filters</h3>
|
||||
<Suspense fallback={<FiltersListPlaceholder />} key={`filters-${props.params.collection}`}>
|
||||
<FiltersContainer searchParams={props.searchParams} />
|
||||
</Suspense>
|
||||
</aside>
|
||||
<div className="lg:col-span-2 xl:col-span-3">
|
||||
<div className="mb-2">
|
||||
<Suspense fallback={<BreadcrumbHome />} key={`breadcrumb-${props.params.collection}`}>
|
||||
<Breadcrumb type="collection" handle={props.params.collection} />
|
||||
</Suspense>
|
||||
</div>
|
||||
<Suspense fallback={<HeaderPlaceholder />} key={`header-${props.params.collection}`}>
|
||||
<Header collection={props.params.collection} />
|
||||
</Suspense>
|
||||
|
||||
<Suspense
|
||||
fallback={<ProductsGridPlaceholder />}
|
||||
key={`products-${props.params.collection}`}
|
||||
>
|
||||
<CategoryPage {...props} />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import Footer from 'components/layout/footer';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
export default function SearchLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<div className="mx-auto mt-4 min-h-[500px] max-w-screen-2xl px-8 pb-4 lg:min-h-[800px]">
|
||||
{children}
|
||||
<div className="mx-auto mt-6 min-h-[500px] max-w-screen-2xl px-8 pb-4 lg:min-h-[800px]">
|
||||
<Suspense>{children}</Suspense>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
|
@ -1,40 +0,0 @@
|
||||
import { getCollectionProducts } from 'lib/shopify';
|
||||
import Link from 'next/link';
|
||||
import { GridTileImage } from './grid/tile';
|
||||
|
||||
export async function Carousel() {
|
||||
// Collections that start with `hidden-*` are hidden from the search page.
|
||||
const { products } = await getCollectionProducts({ collection: 'hidden-homepage-carousel' });
|
||||
|
||||
if (!products?.length) return null;
|
||||
|
||||
// Purposefully duplicating products to make the carousel loop and not run out of products on wide screens.
|
||||
const carouselProducts = [...products, ...products, ...products];
|
||||
|
||||
return (
|
||||
<div className=" w-full overflow-x-auto pb-6 pt-1">
|
||||
<ul className="flex animate-carousel gap-4">
|
||||
{carouselProducts.map((product, i) => (
|
||||
<li
|
||||
key={`${product.handle}${i}`}
|
||||
className="relative aspect-square h-[30vh] max-h-[275px] w-2/3 max-w-[475px] flex-none md:w-1/3"
|
||||
>
|
||||
<Link href={`/product/${product.handle}`} className="relative h-full w-full">
|
||||
<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: 1024px) 25vw, (min-width: 768px) 33vw, 50vw"
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -83,7 +83,7 @@ const FiltersList = ({ years, makes, models, menu, autoFocusField }: FiltersList
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex grow flex-col items-center gap-3 md:flex-row">
|
||||
<>
|
||||
<FilterField
|
||||
label="Part Type"
|
||||
onChange={onChangePartType}
|
||||
@ -124,11 +124,11 @@ const FiltersList = ({ years, makes, models, menu, autoFocusField }: FiltersList
|
||||
<Button
|
||||
onClick={onSearch}
|
||||
disabled={disabled}
|
||||
className="w-full rounded bg-secondary px-4 py-1.5 text-sm font-medium text-white data-[disabled]:cursor-not-allowed data-[hover]:bg-secondary/85 data-[disabled]:opacity-50 md:w-auto"
|
||||
className="w-full rounded bg-secondary px-4 py-1.5 text-sm font-medium text-white data-[disabled]:cursor-not-allowed data-[hover]:bg-secondary/85 data-[disabled]:opacity-50 @md:w-auto"
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -4,7 +4,7 @@ import FiltersList from './filters-list';
|
||||
|
||||
const YMMFiltersContainer = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<div className="rounded border bg-white px-6 pb-5 pt-4">
|
||||
<div className="rounded border bg-white px-6 pb-5 pt-4 @container">
|
||||
<p className="mb-3 text-xl font-semibold leading-tight tracking-tight text-neutral-700">
|
||||
Find Your Car Part
|
||||
</p>
|
||||
@ -23,7 +23,9 @@ const YMMFilters = async () => {
|
||||
|
||||
return (
|
||||
<YMMFiltersContainer>
|
||||
<FiltersList years={years} makes={makes} models={models} menu={menu} />
|
||||
<div className="flex grow flex-col items-center gap-3 @md:flex-row">
|
||||
<FiltersList years={years} makes={makes} models={models} menu={menu} />
|
||||
</div>
|
||||
</YMMFiltersContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,6 @@
|
||||
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,
|
||||
@ -16,22 +15,21 @@ function ThreeItemGridItem({
|
||||
<div
|
||||
className={size === 'full' ? 'md:col-span-4 md:row-span-2' : 'md:col-span-2 md:row-span-1'}
|
||||
>
|
||||
<Link className="aspect-square relative block h-full w-full" href={`/product/${item.handle}`}>
|
||||
<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={{
|
||||
title: item.title as string,
|
||||
amount: item.priceRange.maxVariantPrice.amount,
|
||||
currencyCode: item.priceRange.maxVariantPrice.currencyCode
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
<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={{
|
||||
title: item.title as string,
|
||||
amount: item.priceRange.maxVariantPrice.amount,
|
||||
currencyCode: item.priceRange.maxVariantPrice.currencyCode
|
||||
}}
|
||||
href={`/product/${item.handle}`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { PhotoIcon } from '@heroicons/react/24/outline';
|
||||
import { ArrowRightIcon, PhotoIcon } from '@heroicons/react/24/solid';
|
||||
import clsx from 'clsx';
|
||||
import Price from 'components/price';
|
||||
import Image from 'next/image';
|
||||
import Label from '../label';
|
||||
import Link from 'next/link';
|
||||
|
||||
export function GridTileImage({
|
||||
active,
|
||||
label,
|
||||
href,
|
||||
place = 'grid',
|
||||
...props
|
||||
}: {
|
||||
active?: boolean;
|
||||
@ -14,33 +17,84 @@ export function GridTileImage({
|
||||
amount: string;
|
||||
currencyCode: string;
|
||||
};
|
||||
place?: 'grid' | 'gallery';
|
||||
href: string;
|
||||
} & React.ComponentProps<typeof Image>) {
|
||||
return (
|
||||
<div className="group">
|
||||
<div
|
||||
className={clsx(
|
||||
'aspect-h-1 aspect-w-1 relative overflow-hidden rounded-lg bg-gray-200 group-hover:opacity-75',
|
||||
{
|
||||
'border-2 border-secondary': active,
|
||||
'border-neutral-200': !active
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.src ? (
|
||||
// eslint-disable-next-line jsx-a11y/alt-text -- `alt` is inherited from `props`, which is being enforced with TypeScript
|
||||
<Image className={clsx('h-full w-full object-cover object-center')} {...props} />
|
||||
) : (
|
||||
<div className="flex h-full flex-col rounded-b border bg-white">
|
||||
<div className="grow">
|
||||
<div className="px-4">
|
||||
<div
|
||||
className="flex h-full w-full items-center justify-center text-gray-400"
|
||||
title="Missing product image"
|
||||
className={clsx('aspect-h-1 aspect-w-1 relative overflow-hidden', {
|
||||
'border-2 border-secondary': active,
|
||||
'border-neutral-200': !active
|
||||
})}
|
||||
>
|
||||
<PhotoIcon className="size-7" />
|
||||
{props.src ? (
|
||||
// eslint-disable-next-line jsx-a11y/alt-text -- `alt` is inherited from `props`, which is being enforced with TypeScript
|
||||
<Image className={clsx('h-full w-full object-cover object-center')} {...props} />
|
||||
) : (
|
||||
<div
|
||||
className="flex h-full w-full items-center justify-center text-gray-400"
|
||||
title="Missing product image"
|
||||
>
|
||||
<PhotoIcon className="size-7" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 divide-y px-4">
|
||||
{label && (
|
||||
<h3 className="mt-4 text-sm font-semibold leading-6 text-gray-800">{label.title}</h3>
|
||||
)}
|
||||
{label && (
|
||||
<div className="flex w-full justify-end py-2">
|
||||
<Price
|
||||
className="text-lg font-medium text-gray-900"
|
||||
amount={label.amount}
|
||||
currencyCode={label.currencyCode}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{label ? (
|
||||
<Label title={label.title} amount={label.amount} currencyCode={label.currencyCode} />
|
||||
) : null}
|
||||
{place === 'grid' && (
|
||||
<Link
|
||||
href={href}
|
||||
className="flex items-center justify-center gap-3 rounded-b bg-dark py-3 text-white"
|
||||
>
|
||||
<span className="text-sm font-medium tracking-wide">More details</span>
|
||||
<ArrowRightIcon className="size-4" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const TileImage = ({
|
||||
active,
|
||||
...props
|
||||
}: {
|
||||
active?: boolean;
|
||||
} & React.ComponentProps<typeof Image>) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx('aspect-h-1 aspect-w-1 relative overflow-hidden rounded border bg-white', {
|
||||
'border-2 border-secondary': active,
|
||||
'border-neutral-200': !active
|
||||
})}
|
||||
>
|
||||
{props.src ? (
|
||||
// eslint-disable-next-line jsx-a11y/alt-text -- `alt` is inherited from `props`, which is being enforced with TypeScript
|
||||
<Image className={clsx('h-full w-full object-cover object-center')} {...props} />
|
||||
) : (
|
||||
<div
|
||||
className="flex h-full w-full items-center justify-center text-gray-400"
|
||||
title="Missing product image"
|
||||
>
|
||||
<PhotoIcon className="size-7" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,25 +0,0 @@
|
||||
import Price from './price';
|
||||
|
||||
const Label = ({
|
||||
title,
|
||||
amount,
|
||||
currencyCode
|
||||
}: {
|
||||
title: string;
|
||||
amount: string;
|
||||
currencyCode: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<h3 className="mt-4 text-sm text-gray-700">{title}</h3>
|
||||
<Price
|
||||
className="text-lg font-medium text-gray-900"
|
||||
amount={amount}
|
||||
currencyCode={currencyCode}
|
||||
currencyCodeClassName="hidden @[275px]/label:inline"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Label;
|
@ -3,7 +3,6 @@
|
||||
import Grid from 'components/grid';
|
||||
import { GridTileImage } from 'components/grid/tile';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { getProductsInCollection, searchProducts } from './actions';
|
||||
|
||||
@ -66,22 +65,21 @@ const ProductsList = ({
|
||||
{products.map((product, index) => (
|
||||
<Grid.Item
|
||||
key={product.handle}
|
||||
className="animate-fadeIn rounded-lg"
|
||||
className="animate-fadeIn"
|
||||
ref={index === products.length - 1 && _pageInfo.hasNextPage ? lastElement : undefined}
|
||||
>
|
||||
<Link className="relative inline-block h-full w-full" href={`/product/${product.handle}`}>
|
||||
<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>
|
||||
<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"
|
||||
href={`/product/${product.handle}`}
|
||||
/>
|
||||
</Grid.Item>
|
||||
))}
|
||||
</>
|
||||
|
26
components/layout/search/filters/filters-container.tsx
Normal file
26
components/layout/search/filters/filters-container.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { getProductsInCollection } from 'components/layout/products-list/actions';
|
||||
import FiltersList from './filters-list';
|
||||
|
||||
const FiltersContainer = async ({
|
||||
searchParams
|
||||
}: {
|
||||
searchParams?: { [key: string]: string | string[] | undefined };
|
||||
}) => {
|
||||
const { filters } = await getProductsInCollection({
|
||||
searchParams
|
||||
});
|
||||
|
||||
return <FiltersList filters={filters} defaultOpen={false} />;
|
||||
};
|
||||
|
||||
export const FiltersListPlaceholder = () => {
|
||||
return (
|
||||
<div className="hidden lg:flex lg:flex-col lg:gap-4">
|
||||
<div className="h-32 w-full rounded bg-gray-200" />
|
||||
<div className="h-32 w-full rounded bg-gray-200" />
|
||||
<div className="h-32 w-full rounded bg-gray-200" />
|
||||
<div className="h-32 w-full rounded bg-gray-200" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default FiltersContainer;
|
@ -42,7 +42,7 @@ const Filters = ({ filters, defaultOpen = true }: { filters: Filter[]; defaultOp
|
||||
<Disclosure
|
||||
key={id}
|
||||
as="div"
|
||||
className="flex h-auto max-h-[550px] flex-col gap-y-3 overflow-hidden pt-5"
|
||||
className="flex h-auto max-h-[300px] flex-col gap-y-3 overflow-hidden pt-5"
|
||||
defaultOpen={defaultOpen}
|
||||
>
|
||||
<DisclosureButton className="group flex items-center justify-between">
|
||||
|
@ -7,11 +7,11 @@ const SubMenu = async ({ collection }: { collection: string }) => {
|
||||
const subMenu = menu.find((item) => item.path === `/search/${collection}`)?.items || [];
|
||||
|
||||
return subMenu.length ? (
|
||||
<>
|
||||
<h3 className="sr-only">Categories</h3>
|
||||
<div className="border-t pt-4">
|
||||
<div className="text-sm font-medium text-gray-900">Manufacturers</div>
|
||||
<ul
|
||||
role="list"
|
||||
className="space-y-4 border-b border-gray-200 pb-6 text-sm font-medium text-gray-900"
|
||||
className="ml-1 mt-2 max-h-[300px] space-y-3 overflow-y-auto border-b border-gray-200 pb-6 text-sm text-gray-600"
|
||||
>
|
||||
{subMenu.map((subMenuItem) => (
|
||||
<li key={subMenuItem.title}>
|
||||
@ -21,7 +21,7 @@ const SubMenu = async ({ collection }: { collection: string }) => {
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
|
@ -1,23 +1,9 @@
|
||||
import Grid from 'components/grid';
|
||||
|
||||
const ProductsGridPlaceholder = () => {
|
||||
return (
|
||||
<section>
|
||||
<Grid className="animate-pulse pt-5 lg:grid-cols-3 lg:gap-x-8 xl:grid-cols-4">
|
||||
<aside className="hidden lg:flex lg:flex-col lg:gap-4">
|
||||
<div className="h-32 w-full rounded bg-gray-200" />
|
||||
<div className="h-32 w-full rounded bg-gray-200" />
|
||||
<div className="h-32 w-full rounded bg-gray-200" />
|
||||
<div className="h-32 w-full rounded bg-gray-200" />
|
||||
</aside>
|
||||
<div className="lg:col-span-2 xl:col-span-3">
|
||||
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{Array.from({ length: 9 }).map((_, index) => (
|
||||
<div key={index} className="h-96 w-full rounded-lg bg-gray-200" />
|
||||
))}
|
||||
</Grid>
|
||||
</div>
|
||||
</Grid>
|
||||
<section className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{Array.from({ length: 9 }).map((_, index) => (
|
||||
<div key={index} className="h-96 w-full rounded-lg bg-gray-200" />
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline';
|
||||
import { GridTileImage } from 'components/grid/tile';
|
||||
import { TileImage } from 'components/grid/tile';
|
||||
import { createUrl } from 'lib/utils';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
@ -81,11 +81,11 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
|
||||
<li key={image.src} className="h-16 w-16 md:h-20 md:w-20">
|
||||
<Link
|
||||
aria-label="Enlarge product image"
|
||||
href={createUrl(pathname, imageSearchParams)}
|
||||
scroll={false}
|
||||
className="h-full w-full"
|
||||
href={createUrl(pathname, imageSearchParams)}
|
||||
>
|
||||
<GridTileImage
|
||||
<TileImage
|
||||
alt={image.altText}
|
||||
src={image.src}
|
||||
width={80}
|
||||
|
Loading…
x
Reference in New Issue
Block a user