ecommerce / page-examples / category-pages
With Large Images And Filters Sidebar
07-with-large-images-and-filters-sidebar.jsx
'use client'
import { Fragment, useState } from 'react'
import {
Dialog,
DialogBackdrop,
DialogPanel,
Disclosure,
DisclosureButton,
DisclosurePanel,
Menu,
MenuButton,
MenuItem,
MenuItems,
Popover,
PopoverButton,
PopoverGroup,
PopoverPanel,
Tab,
TabGroup,
TabList,
TabPanel,
TabPanels,
} from '@headlessui/react'
import { Bars3Icon, MagnifyingGlassIcon, ShoppingBagIcon, XMarkIcon } from '@heroicons/react/24/outline'
import { ChevronDownIcon, FunnelIcon, MinusIcon, PlusIcon, Squares2X2Icon } from '@heroicons/react/20/solid'
const navigation = {
categories: [
{
id: 'women',
name: 'Women',
featured: [
{
name: 'New Arrivals',
href: '#',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/mega-menu-category-01.jpg',
imageAlt: 'Models sitting back to back, wearing Basic Tee in black and bone.',
},
{
name: 'Basic Tees',
href: '#',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/mega-menu-category-02.jpg',
imageAlt: 'Close up of Basic Tee fall bundle with off-white, ochre, olive, and black tees.',
},
],
sections: [
{
id: 'clothing',
name: 'Clothing',
items: [
{ name: 'Tops', href: '#' },
{ name: 'Dresses', href: '#' },
{ name: 'Pants', href: '#' },
{ name: 'Denim', href: '#' },
{ name: 'Sweaters', href: '#' },
{ name: 'T-Shirts', href: '#' },
{ name: 'Jackets', href: '#' },
{ name: 'Activewear', href: '#' },
{ name: 'Browse All', href: '#' },
],
},
{
id: 'accessories',
name: 'Accessories',
items: [
{ name: 'Watches', href: '#' },
{ name: 'Wallets', href: '#' },
{ name: 'Bags', href: '#' },
{ name: 'Sunglasses', href: '#' },
{ name: 'Hats', href: '#' },
{ name: 'Belts', href: '#' },
],
},
{
id: 'brands',
name: 'Brands',
items: [
{ name: 'Full Nelson', href: '#' },
{ name: 'My Way', href: '#' },
{ name: 'Re-Arranged', href: '#' },
{ name: 'Counterfeit', href: '#' },
{ name: 'Significant Other', href: '#' },
],
},
],
},
{
id: 'men',
name: 'Men',
featured: [
{
name: 'New Arrivals',
href: '#',
imageSrc:
'https://tailwindcss.com/plus-assets/img/ecommerce-images/product-page-04-detail-product-shot-01.jpg',
imageAlt: 'Drawstring top with elastic loop closure and textured interior padding.',
},
{
name: 'Artwork Tees',
href: '#',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/category-page-02-image-card-06.jpg',
imageAlt:
'Three shirts in gray, white, and blue arranged on table with same line drawing of hands and shapes overlapping on front of shirt.',
},
],
sections: [
{
id: 'clothing',
name: 'Clothing',
items: [
{ name: 'Tops', href: '#' },
{ name: 'Pants', href: '#' },
{ name: 'Sweaters', href: '#' },
{ name: 'T-Shirts', href: '#' },
{ name: 'Jackets', href: '#' },
{ name: 'Activewear', href: '#' },
{ name: 'Browse All', href: '#' },
],
},
{
id: 'accessories',
name: 'Accessories',
items: [
{ name: 'Watches', href: '#' },
{ name: 'Wallets', href: '#' },
{ name: 'Bags', href: '#' },
{ name: 'Sunglasses', href: '#' },
{ name: 'Hats', href: '#' },
{ name: 'Belts', href: '#' },
],
},
{
id: 'brands',
name: 'Brands',
items: [
{ name: 'Re-Arranged', href: '#' },
{ name: 'Counterfeit', href: '#' },
{ name: 'Full Nelson', href: '#' },
{ name: 'My Way', href: '#' },
],
},
],
},
],
pages: [
{ name: 'Company', href: '#' },
{ name: 'Stores', href: '#' },
],
}
const sortOptions = [
{ name: 'Most Popular', href: '#', current: true },
{ name: 'Best Rating', href: '#', current: false },
{ name: 'Newest', href: '#', current: false },
{ name: 'Price: Low to High', href: '#', current: false },
{ name: 'Price: High to Low', href: '#', current: false },
]
const subCategories = [
{ name: 'Totes', href: '#' },
{ name: 'Backpacks', href: '#' },
{ name: 'Travel Bags', href: '#' },
{ name: 'Hip Bags', href: '#' },
{ name: 'Laptop Sleeves', href: '#' },
]
const filters = [
{
id: 'color',
name: 'Color',
options: [
{ value: 'white', label: 'White', checked: false },
{ value: 'beige', label: 'Beige', checked: false },
{ value: 'blue', label: 'Blue', checked: true },
{ value: 'brown', label: 'Brown', checked: false },
{ value: 'green', label: 'Green', checked: false },
{ value: 'purple', label: 'Purple', checked: false },
],
},
{
id: 'category',
name: 'Category',
options: [
{ value: 'new-arrivals', label: 'New Arrivals', checked: false },
{ value: 'sale', label: 'Sale', checked: false },
{ value: 'travel', label: 'Travel', checked: true },
{ value: 'organization', label: 'Organization', checked: false },
{ value: 'accessories', label: 'Accessories', checked: false },
],
},
{
id: 'size',
name: 'Size',
options: [
{ value: '2l', label: '2L', checked: false },
{ value: '6l', label: '6L', checked: false },
{ value: '12l', label: '12L', checked: false },
{ value: '18l', label: '18L', checked: false },
{ value: '20l', label: '20L', checked: false },
{ value: '40l', label: '40L', checked: true },
],
},
]
const products = [
{
id: 1,
name: 'Nomad Pouch',
href: '#',
price: '$50',
availability: 'White and Black',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/category-page-07-product-01.jpg',
imageAlt: 'White fabric pouch with white zipper, black zipper pull, and black elastic loop.',
},
{
id: 2,
name: 'Zip Tote Basket',
href: '#',
price: '$140',
availability: 'Washed Black',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/category-page-07-product-02.jpg',
imageAlt: 'Front of tote bag with washed black canvas body, black straps, and tan leather handles and accents.',
},
{
id: 3,
name: 'Medium Stuff Satchel',
href: '#',
price: '$220',
availability: 'Blue',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/category-page-07-product-03.jpg',
imageAlt:
'Front of satchel with blue canvas body, black straps and handle, drawstring top, and front zipper pouch.',
},
{
id: 4,
name: 'High Wall Tote',
href: '#',
price: '$210',
availability: 'Black and Orange',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/category-page-07-product-04.jpg',
imageAlt: 'Front of zip tote bag with black canvas, black handles, and orange drawstring top.',
},
{
id: 5,
name: 'Zip Tote Basket',
href: '#',
price: '$140',
availability: 'White and black',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/category-page-07-product-05.jpg',
imageAlt: 'Front of zip tote bag with white canvas, black canvas straps and handle, and black zipper pulls.',
},
{
id: 6,
name: 'Zip High Wall Tote',
href: '#',
price: '$150',
availability: 'White and blue',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/category-page-07-product-06.jpg',
imageAlt: 'Front of zip tote bag with white canvas, blue canvas straps and handle, and front zipper pocket.',
},
{
id: 7,
name: 'Throwback Hip Bag',
href: '#',
price: '$90',
availability: 'Salmon',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/category-page-07-product-07.jpg',
imageAlt: 'Salmon orange fabric pouch with match zipper, gray zipper pull, and adjustable hip belt.',
},
{
id: 8,
name: 'Halfsize Tote',
href: '#',
price: '$210',
availability: 'Clay',
imageSrc: 'https://tailwindcss.com/plus-assets/img/ecommerce-images/category-page-07-product-08.jpg',
imageAlt: 'Front of tote with monochrome natural canvas body, straps, roll top, and handles.',
},
]
const footerNavigation = {
account: [
{ name: 'Manage Account', href: '#' },
{ name: 'Saved Items', href: '#' },
{ name: 'Orders', href: '#' },
{ name: 'Redeem Gift card', href: '#' },
],
service: [
{ name: 'Shipping & Returns', href: '#' },
{ name: 'Warranty', href: '#' },
{ name: 'FAQ', href: '#' },
{ name: 'Find a store', href: '#' },
{ name: 'Get in touch', href: '#' },
],
company: [
{ name: 'Who we are', href: '#' },
{ name: 'Press', href: '#' },
{ name: 'Careers', href: '#' },
{ name: 'Terms & Conditions', href: '#' },
{ name: 'Privacy', href: '#' },
],
connect: [
{ name: 'Facebook', href: '#' },
{ name: 'Instagram', href: '#' },
{ name: 'Pinterest', href: '#' },
],
}
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Example() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const [mobileFiltersOpen, setMobileFiltersOpen] = useState(false)
return (
<div className="bg-white">
<div>
{/* Mobile menu */}
<Dialog open={mobileMenuOpen} onClose={setMobileMenuOpen} className="relative z-40 lg:hidden">
<DialogBackdrop
transition
className="fixed inset-0 bg-black/25 transition-opacity duration-300 ease-linear data-closed:opacity-0"
/>
<div className="fixed inset-0 z-40 flex">
<DialogPanel
transition
className="relative flex w-full max-w-xs transform flex-col overflow-y-auto bg-white pb-12 shadow-xl transition duration-300 ease-in-out data-closed:-translate-x-full"
>
<div className="flex px-4 pt-5 pb-2">
<button
type="button"
onClick={() => setMobileMenuOpen(false)}
className="relative -m-2 inline-flex items-center justify-center rounded-md p-2 text-gray-400"
>
<span className="absolute -inset-0.5" />
<span className="sr-only">Close menu</span>
<XMarkIcon aria-hidden="true" className="size-6" />
</button>
</div>
{/* Links */}
<TabGroup className="mt-2">
<div className="border-b border-gray-200">
<TabList className="-mb-px flex space-x-8 px-4">
{navigation.categories.map((category) => (
<Tab
key={category.name}
className="flex-1 border-b-2 border-transparent px-1 py-4 text-base font-medium whitespace-nowrap text-gray-900 data-selected:border-indigo-600 data-selected:text-indigo-600"
>
{category.name}
</Tab>
))}
</TabList>
</div>
<TabPanels as={Fragment}>
{navigation.categories.map((category) => (
<TabPanel key={category.name} className="space-y-10 px-4 pt-10 pb-8">
<div className="grid grid-cols-2 gap-x-4">
{category.featured.map((item) => (
<div key={item.name} className="group relative text-sm">
<img
alt={item.imageAlt}
src={item.imageSrc}
className="aspect-square w-full rounded-lg bg-gray-100 object-cover group-hover:opacity-75"
/>
<a href={item.href} className="mt-6 block font-medium text-gray-900">
<span aria-hidden="true" className="absolute inset-0 z-10" />
{item.name}
</a>
<p aria-hidden="true" className="mt-1">
Shop now
</p>
</div>
))}
</div>
{category.sections.map((section) => (
<div key={section.name}>
<p id={`${category.id}-${section.id}-heading-mobile`} className="font-medium text-gray-900">
{section.name}
</p>
<ul
role="list"
aria-labelledby={`${category.id}-${section.id}-heading-mobile`}
className="mt-6 flex flex-col space-y-6"
>
{section.items.map((item) => (
<li key={item.name} className="flow-root">
<a href={item.href} className="-m-2 block p-2 text-gray-500">
{item.name}
</a>
</li>
))}
</ul>
</div>
))}
</TabPanel>
))}
</TabPanels>
</TabGroup>
<div className="space-y-6 border-t border-gray-200 px-4 py-6">
{navigation.pages.map((page) => (
<div key={page.name} className="flow-root">
<a href={page.href} className="-m-2 block p-2 font-medium text-gray-900">
{page.name}
</a>
</div>
))}
</div>
<div className="space-y-6 border-t border-gray-200 px-4 py-6">
<div className="flow-root">
<a href="#" className="-m-2 block p-2 font-medium text-gray-900">
Sign in
</a>
</div>
<div className="flow-root">
<a href="#" className="-m-2 block p-2 font-medium text-gray-900">
Create account
</a>
</div>
</div>
<div className="border-t border-gray-200 px-4 py-6">
<a href="#" className="-m-2 flex items-center p-2">
<img
alt=""
src="https://tailwindcss.com/plus-assets/img/flags/flag-canada.svg"
className="block h-auto w-5 shrink-0"
/>
<span className="ml-3 block text-base font-medium text-gray-900">CAD</span>
<span className="sr-only">, change currency</span>
</a>
</div>
</DialogPanel>
</div>
</Dialog>
<header className="relative bg-white">
<p className="flex h-10 items-center justify-center bg-indigo-600 px-4 text-sm font-medium text-white sm:px-6 lg:px-8">
Get free delivery on orders over $100
</p>
<nav aria-label="Top" className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="border-b border-gray-200">
<div className="flex h-16 items-center">
<button
type="button"
onClick={() => setMobileMenuOpen(true)}
className="relative rounded-md bg-white p-2 text-gray-400 lg:hidden"
>
<span className="absolute -inset-0.5" />
<span className="sr-only">Open menu</span>
<Bars3Icon aria-hidden="true" className="size-6" />
</button>
{/* Logo */}
<div className="ml-4 flex lg:ml-0">
<a href="#">
<span className="sr-only">Your Company</span>
<img
alt=""
src="https://tailwindcss.com/plus-assets/img/logos/mark.svg?color=indigo&shade=600"
className="h-8 w-auto"
/>
</a>
</div>
{/* Flyout menus */}
<PopoverGroup className="hidden lg:ml-8 lg:block lg:self-stretch">
<div className="flex h-full space-x-8">
{navigation.categories.map((category) => (
<Popover key={category.name} className="flex">
<div className="relative flex">
<PopoverButton className="group relative flex items-center justify-center text-sm font-medium text-gray-700 transition-colors duration-200 ease-out hover:text-gray-800 data-open:text-indigo-600">
{category.name}
<span
aria-hidden="true"
className="absolute inset-x-0 -bottom-px z-30 h-0.5 transition duration-200 ease-out group-data-open:bg-indigo-600"
/>
</PopoverButton>
</div>
<PopoverPanel
transition
className="absolute inset-x-0 top-full z-20 w-full bg-white text-sm text-gray-500 transition data-closed:opacity-0 data-enter:duration-200 data-enter:ease-out data-leave:duration-150 data-leave:ease-in"
>
{/* Presentational element used to render the bottom shadow, if we put the shadow on the actual panel it pokes out the top, so we use this shorter element to hide the top of the shadow */}
<div aria-hidden="true" className="absolute inset-0 top-1/2 bg-white shadow-sm" />
<div className="relative bg-white">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-2 gap-x-8 gap-y-10 py-16">
<div className="col-start-2 grid grid-cols-2 gap-x-8">
{category.featured.map((item) => (
<div key={item.name} className="group relative text-base sm:text-sm">
<img
alt={item.imageAlt}
src={item.imageSrc}
className="aspect-square w-full rounded-lg bg-gray-100 object-cover group-hover:opacity-75"
/>
<a href={item.href} className="mt-6 block font-medium text-gray-900">
<span aria-hidden="true" className="absolute inset-0 z-10" />
{item.name}
</a>
<p aria-hidden="true" className="mt-1">
Shop now
</p>
</div>
))}
</div>
<div className="row-start-1 grid grid-cols-3 gap-x-8 gap-y-10 text-sm">
{category.sections.map((section) => (
<div key={section.name}>
<p id={`${section.name}-heading`} className="font-medium text-gray-900">
{section.name}
</p>
<ul
role="list"
aria-labelledby={`${section.name}-heading`}
className="mt-6 space-y-6 sm:mt-4 sm:space-y-4"
>
{section.items.map((item) => (
<li key={item.name} className="flex">
<a href={item.href} className="hover:text-gray-800">
{item.name}
</a>
</li>
))}
</ul>
</div>
))}
</div>
</div>
</div>
</div>
</PopoverPanel>
</Popover>
))}
{navigation.pages.map((page) => (
<a
key={page.name}
href={page.href}
className="flex items-center text-sm font-medium text-gray-700 hover:text-gray-800"
>
{page.name}
</a>
))}
</div>
</PopoverGroup>
<div className="ml-auto flex items-center">
<div className="hidden lg:flex lg:flex-1 lg:items-center lg:justify-end lg:space-x-6">
<a href="#" className="text-sm font-medium text-gray-700 hover:text-gray-800">
Sign in
</a>
<span aria-hidden="true" className="h-6 w-px bg-gray-200" />
<a href="#" className="text-sm font-medium text-gray-700 hover:text-gray-800">
Create account
</a>
</div>
<div className="hidden lg:ml-8 lg:flex">
<a href="#" className="flex items-center text-gray-700 hover:text-gray-800">
<img
alt=""
src="https://tailwindcss.com/plus-assets/img/flags/flag-canada.svg"
className="block h-auto w-5 shrink-0"
/>
<span className="ml-3 block text-sm font-medium">CAD</span>
<span className="sr-only">, change currency</span>
</a>
</div>
{/* Search */}
<div className="flex lg:ml-6">
<a href="#" className="p-2 text-gray-400 hover:text-gray-500">
<span className="sr-only">Search</span>
<MagnifyingGlassIcon aria-hidden="true" className="size-6" />
</a>
</div>
{/* Cart */}
<div className="ml-4 flow-root lg:ml-6">
<a href="#" className="group -m-2 flex items-center p-2">
<ShoppingBagIcon
aria-hidden="true"
className="size-6 shrink-0 text-gray-400 group-hover:text-gray-500"
/>
<span className="ml-2 text-sm font-medium text-gray-700 group-hover:text-gray-800">0</span>
<span className="sr-only">items in cart, view bag</span>
</a>
</div>
</div>
</div>
</div>
</nav>
</header>
</div>
<div>
{/* Mobile filter dialog */}
<Dialog open={mobileFiltersOpen} onClose={setMobileFiltersOpen} className="relative z-40 lg:hidden">
<DialogBackdrop
transition
className="fixed inset-0 bg-black/25 transition-opacity duration-300 ease-linear data-closed:opacity-0"
/>
<div className="fixed inset-0 z-40 flex">
<DialogPanel
transition
className="relative ml-auto flex size-full max-w-xs transform flex-col overflow-y-auto bg-white pt-4 pb-6 shadow-xl transition duration-300 ease-in-out data-closed:translate-x-full"
>
<div className="flex items-center justify-between px-4">
<h2 className="text-lg font-medium text-gray-900">Filters</h2>
<button
type="button"
onClick={() => setMobileFiltersOpen(false)}
className="relative -mr-2 flex size-10 items-center justify-center rounded-md bg-white p-2 text-gray-400 hover:bg-gray-50 focus:ring-2 focus:ring-indigo-500 focus:outline-hidden"
>
<span className="absolute -inset-0.5" />
<span className="sr-only">Close menu</span>
<XMarkIcon aria-hidden="true" className="size-6" />
</button>
</div>
{/* Filters */}
<form className="mt-4 border-t border-gray-200">
<h3 className="sr-only">Categories</h3>
<ul role="list" className="px-2 py-3 font-medium text-gray-900">
{subCategories.map((category) => (
<li key={category.name}>
<a href={category.href} className="block px-2 py-3">
{category.name}
</a>
</li>
))}
</ul>
{filters.map((section) => (
<Disclosure key={section.id} as="div" className="border-t border-gray-200 px-4 py-6">
<h3 className="-mx-2 -my-3 flow-root">
<DisclosureButton className="group flex w-full items-center justify-between bg-white px-2 py-3 text-gray-400 hover:text-gray-500">
<span className="font-medium text-gray-900">{section.name}</span>
<span className="ml-6 flex items-center">
<PlusIcon aria-hidden="true" className="size-5 group-data-open:hidden" />
<MinusIcon aria-hidden="true" className="size-5 group-not-data-open:hidden" />
</span>
</DisclosureButton>
</h3>
<DisclosurePanel className="pt-6">
<div className="space-y-6">
{section.options.map((option, optionIdx) => (
<div key={option.value} className="flex gap-3">
<div className="flex h-5 shrink-0 items-center">
<div className="group grid size-4 grid-cols-1">
<input
defaultValue={option.value}
defaultChecked={option.checked}
id={`filter-mobile-${section.id}-${optionIdx}`}
name={`${section.id}[]`}
type="checkbox"
className="col-start-1 row-start-1 appearance-none rounded-sm border border-gray-300 bg-white checked:border-indigo-600 checked:bg-indigo-600 indeterminate:border-indigo-600 indeterminate:bg-indigo-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:border-gray-300 disabled:bg-gray-100 disabled:checked:bg-gray-100 forced-colors:appearance-auto"
/>
<svg
fill="none"
viewBox="0 0 14 14"
className="pointer-events-none col-start-1 row-start-1 size-3.5 self-center justify-self-center stroke-white group-has-disabled:stroke-gray-950/25"
>
<path
d="M3 8L6 11L11 3.5"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className="opacity-0 group-has-checked:opacity-100"
/>
<path
d="M3 7H11"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className="opacity-0 group-has-indeterminate:opacity-100"
/>
</svg>
</div>
</div>
<label
htmlFor={`filter-mobile-${section.id}-${optionIdx}`}
className="min-w-0 flex-1 text-gray-500"
>
{option.label}
</label>
</div>
))}
</div>
</DisclosurePanel>
</Disclosure>
))}
</form>
</DialogPanel>
</div>
</Dialog>
<main className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex items-baseline justify-between border-b border-gray-200 pt-24 pb-6">
<h1 className="text-4xl font-bold tracking-tight text-gray-900">New Arrivals</h1>
<div className="flex items-center">
<Menu as="div" className="relative inline-block text-left">
<MenuButton className="group inline-flex justify-center text-sm font-medium text-gray-700 hover:text-gray-900">
Sort
<ChevronDownIcon
aria-hidden="true"
className="-mr-1 ml-1 size-5 shrink-0 text-gray-400 group-hover:text-gray-500"
/>
</MenuButton>
<MenuItems
transition
className="absolute right-0 z-10 mt-2 w-40 origin-top-right rounded-md bg-white shadow-2xl ring-1 ring-black/5 transition focus:outline-hidden data-closed:scale-95 data-closed:transform data-closed:opacity-0 data-enter:duration-100 data-enter:ease-out data-leave:duration-75 data-leave:ease-in"
>
<div className="py-1">
{sortOptions.map((option) => (
<MenuItem key={option.name}>
<a
href={option.href}
className={classNames(
option.current ? 'font-medium text-gray-900' : 'text-gray-500',
'block px-4 py-2 text-sm data-focus:bg-gray-100 data-focus:outline-hidden',
)}
>
{option.name}
</a>
</MenuItem>
))}
</div>
</MenuItems>
</Menu>
<button type="button" className="-m-2 ml-5 p-2 text-gray-400 hover:text-gray-500 sm:ml-7">
<span className="sr-only">View grid</span>
<Squares2X2Icon aria-hidden="true" className="size-5" />
</button>
<button
type="button"
onClick={() => setMobileFiltersOpen(true)}
className="-m-2 ml-4 p-2 text-gray-400 hover:text-gray-500 sm:ml-6 lg:hidden"
>
<span className="sr-only">Filters</span>
<FunnelIcon aria-hidden="true" className="size-5" />
</button>
</div>
</div>
<section aria-labelledby="products-heading" className="pt-6 pb-24">
<h2 id="products-heading" className="sr-only">
Products
</h2>
<div className="grid grid-cols-1 gap-x-8 gap-y-10 lg:grid-cols-4">
{/* Filters */}
<form className="hidden lg:block">
<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">
{subCategories.map((category) => (
<li key={category.name}>
<a href={category.href}>{category.name}</a>
</li>
))}
</ul>
{filters.map((section) => (
<Disclosure key={section.id} as="div" className="border-b border-gray-200 py-6">
<h3 className="-my-3 flow-root">
<DisclosureButton className="group flex w-full items-center justify-between bg-white py-3 text-sm text-gray-400 hover:text-gray-500">
<span className="font-medium text-gray-900">{section.name}</span>
<span className="ml-6 flex items-center">
<PlusIcon aria-hidden="true" className="size-5 group-data-open:hidden" />
<MinusIcon aria-hidden="true" className="size-5 group-not-data-open:hidden" />
</span>
</DisclosureButton>
</h3>
<DisclosurePanel className="pt-6">
<div className="space-y-4">
{section.options.map((option, optionIdx) => (
<div key={option.value} className="flex gap-3">
<div className="flex h-5 shrink-0 items-center">
<div className="group grid size-4 grid-cols-1">
<input
defaultValue={option.value}
defaultChecked={option.checked}
id={`filter-${section.id}-${optionIdx}`}
name={`${section.id}[]`}
type="checkbox"
className="col-start-1 row-start-1 appearance-none rounded-sm border border-gray-300 bg-white checked:border-indigo-600 checked:bg-indigo-600 indeterminate:border-indigo-600 indeterminate:bg-indigo-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:border-gray-300 disabled:bg-gray-100 disabled:checked:bg-gray-100 forced-colors:appearance-auto"
/>
<svg
fill="none"
viewBox="0 0 14 14"
className="pointer-events-none col-start-1 row-start-1 size-3.5 self-center justify-self-center stroke-white group-has-disabled:stroke-gray-950/25"
>
<path
d="M3 8L6 11L11 3.5"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className="opacity-0 group-has-checked:opacity-100"
/>
<path
d="M3 7H11"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className="opacity-0 group-has-indeterminate:opacity-100"
/>
</svg>
</div>
</div>
<label htmlFor={`filter-${section.id}-${optionIdx}`} className="text-sm text-gray-600">
{option.label}
</label>
</div>
))}
</div>
</DisclosurePanel>
</Disclosure>
))}
</form>
{/* Product grid */}
<div className="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:col-span-3 lg:gap-x-8">
{products.map((product) => (
<a key={product.id} href={product.href} className="group text-sm">
<img
alt={product.imageAlt}
src={product.imageSrc}
className="aspect-square w-full rounded-lg bg-gray-100 object-cover group-hover:opacity-75"
/>
<h3 className="mt-4 font-medium text-gray-900">{product.name}</h3>
<p className="text-gray-500 italic">{product.availability}</p>
<p className="mt-2 font-medium text-gray-900">{product.price}</p>
</a>
))}
</div>
</div>
</section>
</main>
<footer aria-labelledby="footer-heading" className="bg-white">
<h2 id="footer-heading" className="sr-only">
Footer
</h2>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-2 gap-8 border-t border-gray-200 py-20 sm:gap-y-0 lg:grid-cols-4">
<div className="grid grid-cols-1 gap-y-10 lg:col-span-2 lg:grid-cols-2 lg:gap-x-8 lg:gap-y-0">
<div>
<h3 className="text-sm font-medium text-gray-900">Account</h3>
<ul role="list" className="mt-6 space-y-6">
{footerNavigation.account.map((item) => (
<li key={item.name} className="text-sm">
<a href={item.href} className="text-gray-500 hover:text-gray-600">
{item.name}
</a>
</li>
))}
</ul>
</div>
<div>
<h3 className="text-sm font-medium text-gray-900">Service</h3>
<ul role="list" className="mt-6 space-y-6">
{footerNavigation.service.map((item) => (
<li key={item.name} className="text-sm">
<a href={item.href} className="text-gray-500 hover:text-gray-600">
{item.name}
</a>
</li>
))}
</ul>
</div>
</div>
<div className="grid grid-cols-1 gap-y-10 lg:col-span-2 lg:grid-cols-2 lg:gap-x-8 lg:gap-y-0">
<div>
<h3 className="text-sm font-medium text-gray-900">Company</h3>
<ul role="list" className="mt-6 space-y-6">
{footerNavigation.company.map((item) => (
<li key={item.name} className="text-sm">
<a href={item.href} className="text-gray-500 hover:text-gray-600">
{item.name}
</a>
</li>
))}
</ul>
</div>
<div>
<h3 className="text-sm font-medium text-gray-900">Connect</h3>
<ul role="list" className="mt-6 space-y-6">
{footerNavigation.connect.map((item) => (
<li key={item.name} className="text-sm">
<a href={item.href} className="text-gray-500 hover:text-gray-600">
{item.name}
</a>
</li>
))}
</ul>
</div>
</div>
</div>
<div className="border-t border-gray-100 py-10 sm:flex sm:items-center sm:justify-between">
<div className="flex items-center justify-center text-sm text-gray-500">
<p>Shipping to Canada ($CAD)</p>
<p className="ml-3 border-l border-gray-200 pl-3">English</p>
</div>
<p className="mt-6 text-center text-sm text-gray-500 sm:mt-0">© 2021 Your Company, Inc.</p>
</div>
</div>
</footer>
</div>
</div>
)
}