mirror of
https://github.com/vercel/commerce.git
synced 2025-05-12 12:47:50 +00:00
feat: modify PLP layout
Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
parent
e0cd6ac2bd
commit
913e7a1809
@ -2,12 +2,6 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
html {
|
|
||||||
color-scheme: dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@supports (font: -apple-system-body) and (-webkit-appearance: none) {
|
@supports (font: -apple-system-body) and (-webkit-appearance: none) {
|
||||||
img[loading='lazy'] {
|
img[loading='lazy'] {
|
||||||
clip-path: inset(0.6px);
|
clip-path: inset(0.6px);
|
||||||
|
@ -2,9 +2,14 @@ import { getCollection, getCollectionProducts } from 'lib/shopify';
|
|||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
|
import Breadcrumb from 'components/breadcrumb';
|
||||||
|
import BreadcrumbHome from 'components/breadcrumb/breadcrumb-home';
|
||||||
import Grid from 'components/grid';
|
import Grid from 'components/grid';
|
||||||
import ProductGridItems from 'components/layout/product-grid-items';
|
import ProductGridItems from 'components/layout/product-grid-items';
|
||||||
|
import Filters from 'components/layout/search/filters';
|
||||||
|
import SortingMenu from 'components/layout/search/sorting-menu';
|
||||||
import { defaultSort, sorting } from 'lib/constants';
|
import { defaultSort, sorting } from 'lib/constants';
|
||||||
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
|
|
||||||
@ -33,17 +38,43 @@ export default async function CategoryPage({
|
|||||||
}) {
|
}) {
|
||||||
const { sort } = searchParams as { [key: string]: string };
|
const { sort } = searchParams as { [key: string]: string };
|
||||||
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
|
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
|
||||||
const products = await getCollectionProducts({ collection: params.collection, sortKey, reverse });
|
const productsData = getCollectionProducts({ collection: params.collection, sortKey, reverse });
|
||||||
|
const collectionData = getCollection(params.collection);
|
||||||
|
|
||||||
|
const [products, collection] = await Promise.all([productsData, collectionData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<>
|
||||||
{products.length === 0 ? (
|
<div className="mb-2">
|
||||||
<p className="py-3 text-lg">{`No products found in this collection`}</p>
|
<Suspense fallback={<BreadcrumbHome />}>
|
||||||
) : (
|
<Breadcrumb type="collection" handle={params.collection} />
|
||||||
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
</Suspense>
|
||||||
<ProductGridItems products={products} />
|
</div>
|
||||||
</Grid>
|
{collection ? (
|
||||||
)}
|
<div className="mb-1 mt-3 max-w-5xl">
|
||||||
</section>
|
<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 justify-end">
|
||||||
|
<SortingMenu />
|
||||||
|
</div>
|
||||||
|
<section className="mt-3 border-t pt-2">
|
||||||
|
{products.length === 0 ? (
|
||||||
|
<p className="py-3 text-lg">{`No products found in this collection`}</p>
|
||||||
|
) : (
|
||||||
|
<Grid className="pt-5 lg:grid-cols-3 lg:gap-x-8 xl:grid-cols-4">
|
||||||
|
<aside className="hidden lg:block">
|
||||||
|
<Filters collection={params.collection} />
|
||||||
|
</aside>
|
||||||
|
<div className="lg:col-span-2 xl:col-span-3">
|
||||||
|
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
<ProductGridItems products={products} />
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,10 @@
|
|||||||
import Footer from 'components/layout/footer';
|
import Footer from 'components/layout/footer';
|
||||||
import Collections from 'components/layout/search/collections';
|
|
||||||
import FilterList from 'components/layout/search/filter';
|
|
||||||
import { sorting } from 'lib/constants';
|
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
export default function SearchLayout({ children }: { children: React.ReactNode }) {
|
export default function SearchLayout({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<div className="mx-auto flex max-w-screen-2xl flex-col gap-8 px-4 pb-4 text-black md:flex-row dark:text-white">
|
<div className="mx-auto max-w-screen-2xl px-8 pb-4">{children}</div>
|
||||||
<div className="order-first w-full flex-none md:max-w-[125px]">
|
|
||||||
<Collections />
|
|
||||||
</div>
|
|
||||||
<div className="order-last min-h-screen w-full md:order-none">{children}</div>
|
|
||||||
<div className="order-none flex-none md:order-last md:w-[125px]">
|
|
||||||
<FilterList list={sorting} title="Sort by" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
15
components/breadcrumb/breadcrumb-home.tsx
Normal file
15
components/breadcrumb/breadcrumb-home.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList } from './breadcrumb-list';
|
||||||
|
|
||||||
|
const BreadcrumbHome = () => {
|
||||||
|
return (
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbList>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</BreadcrumbList>
|
||||||
|
</Breadcrumb>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BreadcrumbHome;
|
@ -1,4 +1,5 @@
|
|||||||
import { getProduct } from 'lib/shopify';
|
import { getCollection, getMenu, getProduct } from 'lib/shopify';
|
||||||
|
import { Menu } from 'lib/shopify/types';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
@ -14,6 +15,23 @@ type BreadcrumbProps = {
|
|||||||
handle: string;
|
handle: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findParentCollection = (menu: Menu[], collection: string): Menu | null => {
|
||||||
|
let parentCollection: Menu | null = null;
|
||||||
|
|
||||||
|
for (const item of menu) {
|
||||||
|
if (item.items.length) {
|
||||||
|
console.log({ collection, item });
|
||||||
|
const hasParent = item.items.some((subItem) => subItem.path.includes(collection));
|
||||||
|
if (hasParent) {
|
||||||
|
parentCollection = item;
|
||||||
|
} else {
|
||||||
|
parentCollection = findParentCollection(item.items, collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parentCollection;
|
||||||
|
};
|
||||||
|
|
||||||
const BreadcrumbComponent = async ({ type, handle }: BreadcrumbProps) => {
|
const BreadcrumbComponent = async ({ type, handle }: BreadcrumbProps) => {
|
||||||
const items: Array<{ href: string; title: string }> = [{ href: '/', title: 'Home' }];
|
const items: Array<{ href: string; title: string }> = [{ href: '/', title: 'Home' }];
|
||||||
|
|
||||||
@ -35,6 +53,25 @@ const BreadcrumbComponent = async ({ type, handle }: BreadcrumbProps) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'collection') {
|
||||||
|
const collectionData = getCollection(handle);
|
||||||
|
const menuData = getMenu('main-menu');
|
||||||
|
const [collection, menu] = await Promise.all([collectionData, menuData]);
|
||||||
|
if (!collection) return null;
|
||||||
|
const parentCollection = findParentCollection(menu, handle);
|
||||||
|
if (parentCollection && parentCollection.path !== `/search/${handle}`) {
|
||||||
|
items.push({
|
||||||
|
href: `${parentCollection.path}`,
|
||||||
|
title: parentCollection.title
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
title: collection.title,
|
||||||
|
href: `/search/${collection.handle}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbList>
|
<BreadcrumbList>
|
||||||
|
@ -4,9 +4,9 @@ import LogoSquare from 'components/logo-square';
|
|||||||
import Profile from 'components/profile';
|
import Profile from 'components/profile';
|
||||||
import OpenProfile from 'components/profile/open-profile';
|
import OpenProfile from 'components/profile/open-profile';
|
||||||
import { getMenu } from 'lib/shopify';
|
import { getMenu } from 'lib/shopify';
|
||||||
import { Menu } from 'lib/shopify/types';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
import MainMenu from './main-menu';
|
||||||
import MobileMenu from './mobile-menu';
|
import MobileMenu from './mobile-menu';
|
||||||
import Search, { SearchSkeleton } from './search';
|
import Search, { SearchSkeleton } from './search';
|
||||||
const { SITE_NAME } = process.env;
|
const { SITE_NAME } = process.env;
|
||||||
@ -15,7 +15,7 @@ export default async function Navbar() {
|
|||||||
const menu = await getMenu('main-menu');
|
const menu = await getMenu('main-menu');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="relative mb-4 flex items-center justify-between bg-white pb-3 pt-4 md:pb-0 dark:bg-neutral-900">
|
<nav className="relative mb-4 flex items-center justify-between bg-white pb-3 pt-4 dark:bg-neutral-900 md:pb-0">
|
||||||
<div className="block flex-none pl-4 md:hidden">
|
<div className="block flex-none pl-4 md:hidden">
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<MobileMenu menu={menu} />
|
<MobileMenu menu={menu} />
|
||||||
@ -29,7 +29,7 @@ export default async function Navbar() {
|
|||||||
className="mr-2 flex w-full items-center justify-center md:w-auto lg:mr-6"
|
className="mr-2 flex w-full items-center justify-center md:w-auto lg:mr-6"
|
||||||
>
|
>
|
||||||
<LogoSquare />
|
<LogoSquare />
|
||||||
<div className="flex-none font-league-spartan text-xl font-semibold tracking-tight text-dark md:hidden md:text-2xl lg:block lg:text-3xl lg:leading-tight dark:text-white">
|
<div className="flex-none font-league-spartan text-xl font-semibold tracking-tight text-dark dark:text-white md:hidden md:text-2xl lg:block lg:text-3xl lg:leading-tight">
|
||||||
{SITE_NAME}
|
{SITE_NAME}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -49,22 +49,7 @@ export default async function Navbar() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{menu.length ? (
|
<MainMenu menu={menu} />
|
||||||
<div className="hidden w-full items-center justify-center border-b px-4 pb-3 pt-4 md:flex">
|
|
||||||
<ul className="hidden gap-8 text-sm font-medium md:flex md:items-center lg:gap-16">
|
|
||||||
{menu.map((item: Menu) => (
|
|
||||||
<li key={item.title}>
|
|
||||||
<Link
|
|
||||||
href={item.path}
|
|
||||||
className="text-neutral-600 hover:text-black dark:text-neutral-400 dark:hover:text-neutral-300"
|
|
||||||
>
|
|
||||||
{item.title}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
|
95
components/layout/navbar/main-menu.tsx
Normal file
95
components/layout/navbar/main-menu.tsx
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Popover, Transition } from '@headlessui/react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { Menu } from 'lib/shopify/types';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
import { Fragment, useState } from 'react';
|
||||||
|
|
||||||
|
const MainMenu = ({ menu }: { menu: Menu[] }) => {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const [open, setOpen] = useState('');
|
||||||
|
|
||||||
|
return menu.length ? (
|
||||||
|
<div className="mt-2 hidden h-11 w-full border-b text-sm font-medium md:flex">
|
||||||
|
<Popover.Group as={Fragment}>
|
||||||
|
<div className="z-10 flex h-full w-full items-center justify-center gap-8 px-4 lg:gap-16">
|
||||||
|
{menu.map((item: Menu) => {
|
||||||
|
const isActiveItem =
|
||||||
|
item.path === pathname ||
|
||||||
|
item.items.some((subItem: Menu) => subItem.path === pathname);
|
||||||
|
if (!item.items.length) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={item.title}
|
||||||
|
href={item.path}
|
||||||
|
className={`flex h-full items-center ${isActiveItem ? 'text-black' : 'text-neutral-600 hover:text-black'}`}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOpen = open === item.path;
|
||||||
|
return (
|
||||||
|
<Popover key={item.title} className="relative flex h-full">
|
||||||
|
<div
|
||||||
|
className="relative flex"
|
||||||
|
onMouseOver={() => setOpen(item.path)}
|
||||||
|
onMouseLeave={() => setOpen('')}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={item.path}
|
||||||
|
className={clsx(
|
||||||
|
'relative z-10 flex items-center border-b-2 px-2 pt-px transition-colors duration-200 ease-out focus-visible:ring-0 focus-visible:ring-offset-0',
|
||||||
|
{
|
||||||
|
'border-gray-500 text-black': isOpen || isActiveItem,
|
||||||
|
'border-transparent text-neutral-600 hover:text-black':
|
||||||
|
!isOpen && !isActiveItem
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</Link>
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-out duration-200"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="transition ease-in duration-150"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
show={isOpen}
|
||||||
|
>
|
||||||
|
<Popover.Panel
|
||||||
|
static
|
||||||
|
className="absolute inset-x-0 left-1/2 top-full z-10 mt-0.5 min-w-32 max-w-sm -translate-x-1/2 transform text-sm"
|
||||||
|
>
|
||||||
|
<div className="overflow-hidden rounded-md shadow-lg ring-1 ring-black/5">
|
||||||
|
<ul className="flex flex-col space-y-2 bg-white px-4 py-3">
|
||||||
|
{item.items.map((subItem: Menu) => (
|
||||||
|
<li key={subItem.title}>
|
||||||
|
<Link
|
||||||
|
href={subItem.path}
|
||||||
|
className={`border-b ${subItem.path === pathname ? 'border-black text-black' : 'border-transparent text-neutral-600 hover:text-black'}`}
|
||||||
|
>
|
||||||
|
{subItem.title}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Popover.Panel>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Popover.Group>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainMenu;
|
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Dialog, Transition } from '@headlessui/react';
|
import { Dialog, Disclosure, Transition } from '@headlessui/react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { usePathname, useSearchParams } from 'next/navigation';
|
import { usePathname, useSearchParams } from 'next/navigation';
|
||||||
import { Fragment, Suspense, useEffect, useState } from 'react';
|
import { Fragment, Suspense, useEffect, useState } from 'react';
|
||||||
@ -35,7 +35,7 @@ export default function MobileMenu({ menu }: { menu: Menu[] }) {
|
|||||||
<button
|
<button
|
||||||
onClick={openMobileMenu}
|
onClick={openMobileMenu}
|
||||||
aria-label="Open mobile menu"
|
aria-label="Open mobile menu"
|
||||||
className="flex h-11 w-11 items-center justify-center rounded-md border border-neutral-200 text-black transition-colors md:hidden dark:border-neutral-700 dark:text-white"
|
className="flex h-11 w-11 items-center justify-center rounded-md border border-neutral-200 text-black transition-colors dark:border-neutral-700 dark:text-white md:hidden"
|
||||||
>
|
>
|
||||||
<Bars3Icon className="h-4" />
|
<Bars3Icon className="h-4" />
|
||||||
</button>
|
</button>
|
||||||
@ -80,12 +80,29 @@ export default function MobileMenu({ menu }: { menu: Menu[] }) {
|
|||||||
<ul className="flex w-full flex-col">
|
<ul className="flex w-full flex-col">
|
||||||
{menu.map((item: Menu) => (
|
{menu.map((item: Menu) => (
|
||||||
<li
|
<li
|
||||||
className="py-2 text-xl text-black transition-colors hover:text-neutral-500 dark:text-white"
|
className="py-2 text-xl text-neutral-600 transition-colors hover:text-black"
|
||||||
key={item.title}
|
key={item.title}
|
||||||
>
|
>
|
||||||
<Link href={item.path} onClick={closeMobileMenu}>
|
{item.items.length ? (
|
||||||
{item.title}
|
<Disclosure>
|
||||||
</Link>
|
<Disclosure.Button>{item.title}</Disclosure.Button>
|
||||||
|
<Disclosure.Panel className="flex flex-col space-y-2 px-3 py-2 text-lg text-neutral-600 hover:text-black">
|
||||||
|
{item.items.map((subItem: Menu) => (
|
||||||
|
<Link
|
||||||
|
key={subItem.title}
|
||||||
|
href={subItem.path}
|
||||||
|
onClick={closeMobileMenu}
|
||||||
|
>
|
||||||
|
{subItem.title}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</Disclosure.Panel>
|
||||||
|
</Disclosure>
|
||||||
|
) : (
|
||||||
|
<Link href={item.path} onClick={closeMobileMenu}>
|
||||||
|
{item.title}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
30
components/layout/search/filters/index.tsx
Normal file
30
components/layout/search/filters/index.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { getMenu } from 'lib/shopify';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
const Filters = async ({ collection }: { collection: string }) => {
|
||||||
|
const menu = await getMenu('main-menu');
|
||||||
|
const subMenu = menu.find((item) => item.path === `/search/${collection}`)?.items || [];
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{subMenu.length ? (
|
||||||
|
<>
|
||||||
|
<h3 className="sr-only">Categories</h3>
|
||||||
|
<ul
|
||||||
|
role="list"
|
||||||
|
className="space-y-4 border-b border-gray-200 pb-6 text-sm font-medium text-gray-900"
|
||||||
|
>
|
||||||
|
{subMenu.map((subMenuItem) => (
|
||||||
|
<li key={subMenuItem.title}>
|
||||||
|
<Link href={subMenuItem.path} className="hover:underline">
|
||||||
|
{subMenuItem.title}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Filters;
|
49
components/layout/search/sorting-menu/index.tsx
Normal file
49
components/layout/search/sorting-menu/index.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Menu, Transition } from '@headlessui/react';
|
||||||
|
import { ChevronDownIcon } from '@heroicons/react/20/solid';
|
||||||
|
import { sorting } from 'lib/constants';
|
||||||
|
import { Fragment } from 'react';
|
||||||
|
import SortingItem from './item';
|
||||||
|
|
||||||
|
const SortingMenu = () => {
|
||||||
|
return (
|
||||||
|
<Menu as="div" className="relative inline-block text-left">
|
||||||
|
<div>
|
||||||
|
<Menu.Button className="group inline-flex justify-center text-sm font-medium text-gray-700 hover:text-gray-900">
|
||||||
|
Sort
|
||||||
|
<ChevronDownIcon
|
||||||
|
className="-mr-1 ml-1 h-5 w-5 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</Menu.Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-out duration-100"
|
||||||
|
enterFrom="transform opacity-0 scale-95"
|
||||||
|
enterTo="transform opacity-100 scale-100"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
|
leaveTo="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Menu.Items className="absolute right-0 z-10 mt-2 w-40 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||||
|
<div className="py-1">
|
||||||
|
{sorting.map((option) => (
|
||||||
|
<Menu.Item key={option.title}>
|
||||||
|
{({ active }) => (
|
||||||
|
<div>
|
||||||
|
<SortingItem item={option} hover={active} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SortingMenu;
|
36
components/layout/search/sorting-menu/item.tsx
Normal file
36
components/layout/search/sorting-menu/item.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
|
import { SortFilterItem } from 'lib/constants';
|
||||||
|
import { createUrl } from 'lib/utils';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { usePathname, useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
|
const SortingItem = ({ item, hover }: { item: SortFilterItem; hover: boolean }) => {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const active = searchParams.get('sort') === item.slug;
|
||||||
|
const q = searchParams.get('q');
|
||||||
|
const href = createUrl(
|
||||||
|
pathname,
|
||||||
|
new URLSearchParams({
|
||||||
|
...(q && { q }),
|
||||||
|
...(item.slug && item.slug.length && { sort: item.slug })
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const DynamicTag = active ? 'p' : Link;
|
||||||
|
return (
|
||||||
|
<DynamicTag
|
||||||
|
prefetch={!active ? false : undefined}
|
||||||
|
href={href}
|
||||||
|
className={clsx('block px-4 py-2 text-sm', {
|
||||||
|
'font-medium text-gray-900': active,
|
||||||
|
'text-gray-500': !active,
|
||||||
|
'bg-gray-100': hover,
|
||||||
|
'bg-transparent': !hover
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</DynamicTag>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SortingItem;
|
@ -2,6 +2,7 @@ const plugin = require('tailwindcss/plugin');
|
|||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
darkMode: 'class',
|
||||||
content: ['./app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
|
content: ['./app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user