fix: update PLP display

Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
Chloe 2024-05-08 14:11:16 +07:00
parent f5a2237d43
commit 78a79a44b7
No known key found for this signature in database
GPG Key ID: CFD53CE570D42DF5
15 changed files with 138 additions and 96 deletions

View File

@ -1,4 +1,4 @@
import { getCollection, getCollectionProducts, getMenu } from 'lib/shopify';
import { getCollection, getCollectionProducts } from 'lib/shopify';
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
@ -9,6 +9,8 @@ import ProductGridItems from 'components/layout/product-grid-items';
import FiltersList from 'components/layout/search/filters/filters-list';
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';
import ProductsGridPlaceholder from 'components/layout/search/placeholder';
import SortingMenu from 'components/layout/search/sorting-menu';
import {
AVAILABILITY_FILTER_ID,
@ -90,7 +92,7 @@ const constructFilterInput = (filters: {
return results;
};
export default async function CategoryPage({
async function CategoryPage({
params,
searchParams
}: {
@ -101,42 +103,23 @@ export default async function CategoryPage({
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
const filtersInput = constructFilterInput(rest);
const productsData = getCollectionProducts({
const { products, filters } = await getCollectionProducts({
collection: params.collection,
sortKey,
reverse,
...(filtersInput.length ? { filters: filtersInput } : {})
});
const collectionData = getCollection(params.collection);
const menuData = getMenu('main-menu');
const [{ products, filters }, collection, menu] = await Promise.all([
productsData,
collectionData,
menuData
]);
return (
<>
<div className="mb-2">
<Suspense fallback={<BreadcrumbHome />}>
<Breadcrumb type="collection" handle={params.collection} />
</Suspense>
</div>
{collection ? (
<div className="mb-3 mt-3 max-w-5xl lg:mb-1">
<h1 className="text-4xl font-bold tracking-tight text-gray-900">{collection.title}</h1>
<p className="mt-2 text-base text-gray-500">{collection.description}</p>
</div>
) : null}
<div className="flex w-full items-center justify-between gap-2 lg:justify-end">
<MobileFilters collection={params.collection} filters={filters} menu={menu} />
<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 menu={menu} collection={params.collection} />
<SubMenu collection={params.collection} />
<h3 className="sr-only">Filters</h3>
<FiltersList filters={filters} />
</aside>
@ -154,3 +137,24 @@ export default async function CategoryPage({
</>
);
}
export default function CategorySearchPage(props: {
params: { collection: string };
searchParams?: { [key: string]: string | string[] | undefined };
}) {
return (
<>
<div className="mb-2">
<Suspense fallback={<BreadcrumbHome />}>
<Breadcrumb type="collection" handle={props.params.collection} />
</Suspense>
</div>
<Suspense fallback={<HeaderPlaceholder />}>
<Header collection={props.params.collection} />
</Suspense>
<Suspense fallback={<ProductsGridPlaceholder />}>
<CategoryPage {...props} />
</Suspense>
</>
);
}

View File

@ -1,11 +1,12 @@
import Footer from 'components/layout/footer';
import { Suspense } from 'react';
export default function SearchLayout({ children }: { children: React.ReactNode }) {
return (
<Suspense>
<div className="mx-auto max-w-screen-2xl px-8 pb-4">{children}</div>
<>
<div className="mx-auto min-h-[500px] max-w-screen-2xl px-8 pb-4 lg:min-h-[800px]">
{children}
</div>
<Footer />
</Suspense>
</>
);
}

View File

@ -17,12 +17,11 @@ type BreadcrumbProps = {
const findParentCollection = (menu: Menu[], collection: string): Menu | null => {
let parentCollection: Menu | null = null;
for (const item of menu) {
if (item.items.length) {
const hasParent = item.items.some((subItem) => subItem.path.includes(collection));
if (hasParent) {
parentCollection = item;
return item;
} else {
parentCollection = findParentCollection(item.items, collection);
}

View File

@ -16,7 +16,7 @@ function ThreeItemGridItem({
<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}`}>
<Link className="aspect-square relative block h-full w-full" href={`/product/${item.handle}`}>
<GridTileImage
src={item.featuredImage.url}
fill
@ -26,7 +26,6 @@ function ThreeItemGridItem({
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

View File

@ -1,49 +1,45 @@
import { PhotoIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx';
import Image from 'next/image';
import Label from '../label';
export function GridTileImage({
isInteractive = true,
active,
label,
...props
}: {
isInteractive?: boolean;
active?: boolean;
label?: {
title: string;
amount: string;
currencyCode: string;
position?: 'bottom' | 'center';
};
} & React.ComponentProps<typeof Image>) {
return (
<div
className={clsx(
'group flex h-full w-full items-center justify-center overflow-hidden rounded-lg border bg-white hover:border-secondary dark:bg-black',
{
relative: label,
'border-2 border-secondary': active,
'border-neutral-200 dark:border-neutral-800': !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('relative h-full w-full object-contain', {
'transition duration-300 ease-in-out group-hover:scale-105': isInteractive
})}
{...props}
/>
) : null}
<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 w-full items-center justify-center text-gray-400"
title="Missing product image"
>
<PhotoIcon className="size-7" />
</div>
)}
</div>
{label ? (
<Label
title={label.title}
amount={label.amount}
currencyCode={label.currencyCode}
position={label.position}
/>
<Label title={label.title} amount={label.amount} currencyCode={label.currencyCode} />
) : null}
</div>
);

View File

@ -1,32 +1,23 @@
import clsx from 'clsx';
import Price from './price';
const Label = ({
title,
amount,
currencyCode,
position = 'bottom'
currencyCode
}: {
title: string;
amount: string;
currencyCode: string;
position?: 'bottom' | 'center';
}) => {
return (
<div
className={clsx('absolute bottom-0 left-0 flex w-full px-4 pb-4 @container/label', {
'lg:px-20 lg:pb-[35%]': position === 'center'
})}
>
<div className="flex items-center rounded-full border bg-white/70 p-1 text-xs font-semibold text-black backdrop-blur-md dark:border-neutral-800 dark:bg-black/70 dark:text-white">
<h3 className="mr-4 line-clamp-2 flex-grow pl-2 leading-none tracking-tight">{title}</h3>
<Price
className="flex-none rounded-full bg-blue-600 p-2 text-white"
amount={amount}
currencyCode={currencyCode}
currencyCodeClassName="hidden @[275px]/label:inline"
/>
</div>
<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>
);
};

View File

@ -7,7 +7,7 @@ export default function ProductGridItems({ products }: { products: Product[] })
return (
<>
{products.map((product) => (
<Grid.Item key={product.handle} className="animate-fadeIn">
<Grid.Item key={product.handle} className="animate-fadeIn rounded-lg">
<Link className="relative inline-block h-full w-full" href={`/product/${product.handle}`}>
<GridTileImage
alt={product.title}

View File

@ -3,20 +3,11 @@
import { Dialog, DialogPanel, Transition, TransitionChild } from '@headlessui/react';
import { FunnelIcon } from '@heroicons/react/24/outline';
import { XMarkIcon } from '@heroicons/react/24/solid';
import { Filter, Menu } from 'lib/shopify/types';
import { Fragment, useState } from 'react';
import { Filter } from 'lib/shopify/types';
import { Fragment, ReactNode, useState } from 'react';
import Filters from './filters-list';
import SubMenu from './sub-menu';
const MobileFilters = ({
collection,
filters,
menu
}: {
collection: string;
filters: Filter[];
menu: Menu[];
}) => {
const MobileFilters = ({ filters, menu }: { filters: Filter[]; menu: ReactNode }) => {
const [openDialog, setOpenDialog] = useState(false);
return (
@ -64,7 +55,7 @@ const MobileFilters = ({
</button>
</div>
<div className="mt-4 border-t border-gray-200 px-4 pt-4">
<SubMenu collection={collection} menu={menu} />
{menu}
<Filters filters={filters} defaultOpen={false} />
</div>
</DialogPanel>

View File

@ -1,7 +1,9 @@
import { Menu } from 'lib/shopify/types';
import { getMenu } from 'lib/shopify';
import Link from 'next/link';
const SubMenu = ({ menu, collection }: { menu: Menu[]; collection: string }) => {
const SubMenu = async ({ collection }: { collection: string }) => {
const menu = await getMenu('main-menu');
const subMenu = menu.find((item) => item.path === `/search/${collection}`)?.items || [];
return subMenu.length ? (

View File

@ -0,0 +1,21 @@
import { getCollection } from 'lib/shopify';
const Header = async ({ collection }: { collection: string }) => {
const collectionData = await getCollection(collection);
return collectionData ? (
<div className="mb-3 mt-3 max-w-5xl lg:mb-1">
<h1 className="text-4xl font-bold tracking-tight text-gray-900">{collectionData.title}</h1>
<p className="mt-2 text-base text-gray-500">{collectionData.description}</p>
</div>
) : null;
};
export const HeaderPlaceholder = () => {
return (
<div className="mb-3 mt-3 max-w-5xl lg:mb-1">
<div className="h-10 w-1/2 rounded bg-gray-200" />
</div>
);
};
export default Header;

View File

@ -0,0 +1,25 @@
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>
);
};
export default ProductsGridPlaceholder;

View File

@ -28,7 +28,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
return (
<>
<div className="relative aspect-square h-full max-h-[550px] w-full overflow-hidden">
<div className="relative aspect-1 h-full max-h-[550px] w-full overflow-hidden">
{images[imageIndex] && (
<Image
className="h-full w-full object-contain"

View File

@ -35,6 +35,7 @@
"tailwind-merge": "^2.2.2"
},
"devDependencies": {
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.11",

11
pnpm-lock.yaml generated
View File

@ -40,6 +40,9 @@ dependencies:
version: 2.2.2
devDependencies:
'@tailwindcss/aspect-ratio':
specifier: ^0.4.2
version: 0.4.2(tailwindcss@3.4.1)
'@tailwindcss/container-queries':
specifier: ^0.1.1
version: 0.1.1(tailwindcss@3.4.1)
@ -688,6 +691,14 @@ packages:
tslib: 2.6.2
dev: false
/@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.1):
resolution: {integrity: sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==}
peerDependencies:
tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1'
dependencies:
tailwindcss: 3.4.1
dev: true
/@tailwindcss/container-queries@0.1.1(tailwindcss@3.4.1):
resolution: {integrity: sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==}
peerDependencies:

View File

@ -57,6 +57,7 @@ module.exports = {
}
);
}),
require('@tailwindcss/forms')
require('@tailwindcss/forms'),
require('@tailwindcss/aspect-ratio')
]
};