mirror of
https://github.com/vercel/commerce.git
synced 2025-05-12 12:47:50 +00:00
feat: add selected filter list
Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
parent
c8f8fd2adc
commit
161ac5a164
@ -20,8 +20,6 @@ import {
|
||||
} from 'lib/constants';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function generateMetadata({
|
||||
params
|
||||
}: {
|
||||
|
@ -3,8 +3,6 @@ import ProductGridItems from 'components/layout/product-grid-items';
|
||||
import { defaultSort, sorting } from 'lib/constants';
|
||||
import { getProducts } from 'lib/shopify';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Search',
|
||||
description: 'Search for products in the store.'
|
||||
|
@ -5,6 +5,7 @@ import clsx from 'clsx';
|
||||
import { Filter } from 'lib/shopify/types';
|
||||
import { createUrl } from 'lib/utils';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import SelectedList from './selected-list';
|
||||
|
||||
const Filters = ({ filters, defaultOpen = true }: { filters: Filter[]; defaultOpen?: boolean }) => {
|
||||
const router = useRouter();
|
||||
@ -31,43 +32,47 @@ const Filters = ({ filters, defaultOpen = true }: { filters: Filter[]; defaultOp
|
||||
};
|
||||
|
||||
return (
|
||||
<form onChange={handleChange} className="space-y-5 divide-y divide-gray-200 border-b pb-3">
|
||||
{filters.map(({ label, id, values }) => (
|
||||
<Disclosure
|
||||
key={id}
|
||||
as="div"
|
||||
className="flex h-auto max-h-[550px] flex-col gap-y-3 overflow-hidden pt-5"
|
||||
defaultOpen={defaultOpen}
|
||||
>
|
||||
<DisclosureButton className="group flex items-center justify-between">
|
||||
<div className="text-sm font-medium text-gray-900">{label}</div>
|
||||
<ChevronDownIcon className="size-4 group-data-[open]:rotate-180" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel className="flex-grow space-y-3 overflow-auto pb-1 pl-1 pt-2">
|
||||
{values.map(({ id: valueId, label, count, value }) => (
|
||||
<label
|
||||
key={valueId}
|
||||
htmlFor={valueId}
|
||||
className={clsx('flex items-center gap-2 text-sm text-gray-600', {
|
||||
'cursor-not-allowed opacity-50': count === 0
|
||||
})}
|
||||
>
|
||||
<input
|
||||
id={valueId}
|
||||
name={id}
|
||||
defaultChecked={searchParams.getAll(id).includes(String(value))}
|
||||
type="checkbox"
|
||||
value={String(value)}
|
||||
className="h-4 w-4 rounded border-gray-300 text-secondary focus:ring-secondary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={count === 0}
|
||||
/>
|
||||
<span>{`${label} (${count})`}</span>
|
||||
</label>
|
||||
))}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
))}
|
||||
</form>
|
||||
<>
|
||||
<SelectedList filters={filters} />
|
||||
<form onChange={handleChange} className="space-y-5 divide-y divide-gray-200 border-b pb-3">
|
||||
{filters.map(({ label, id, values }) => (
|
||||
<Disclosure
|
||||
key={id}
|
||||
as="div"
|
||||
className="flex h-auto max-h-[550px] flex-col gap-y-3 overflow-hidden pt-5"
|
||||
defaultOpen={defaultOpen}
|
||||
>
|
||||
<DisclosureButton className="group flex items-center justify-between">
|
||||
<div className="text-sm font-medium text-gray-900">{label}</div>
|
||||
<ChevronDownIcon className="size-4 group-data-[open]:rotate-180" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel className="flex-grow space-y-3 overflow-auto pb-1 pl-1 pt-2">
|
||||
{values.map(({ id: valueId, label, count, value }) => (
|
||||
<label
|
||||
key={valueId}
|
||||
htmlFor={valueId}
|
||||
className={clsx('flex items-center gap-2 text-sm text-gray-600', {
|
||||
'cursor-not-allowed opacity-50': count === 0
|
||||
})}
|
||||
>
|
||||
<input
|
||||
id={valueId}
|
||||
name={id}
|
||||
checked={searchParams.getAll(id).includes(String(value))}
|
||||
type="checkbox"
|
||||
value={String(value)}
|
||||
className="h-4 w-4 rounded border-gray-300 text-secondary focus:ring-secondary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={count === 0}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
<span>{`${label} (${count})`}</span>
|
||||
</label>
|
||||
))}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
))}
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,73 @@
|
||||
const SelectedList = () => {
|
||||
return <div>SelectedList</div>;
|
||||
'use client';
|
||||
|
||||
import { XMarkIcon } from '@heroicons/react/16/solid';
|
||||
import { Filter } from 'lib/shopify/types';
|
||||
import { createUrl } from 'lib/utils';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
|
||||
const SelectedList = ({ filters }: { filters: Filter[] }) => {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const selectedFilters = filters.flatMap(({ id, values }) => {
|
||||
const selectedValues = searchParams.getAll(id);
|
||||
if (selectedValues.length === 0) return [];
|
||||
return values
|
||||
.filter(({ value }) => selectedValues.includes(String(value)))
|
||||
.map(({ id: valueId, value, label }) => ({
|
||||
valueId,
|
||||
value,
|
||||
id,
|
||||
label
|
||||
}));
|
||||
});
|
||||
|
||||
const handleClear = (id: string, value: string) => {
|
||||
const newSearchParams = new URLSearchParams(searchParams);
|
||||
const selectedValues = newSearchParams.getAll(id);
|
||||
selectedValues.splice(selectedValues.indexOf(value), 1);
|
||||
newSearchParams.delete(id);
|
||||
for (const value of selectedValues) {
|
||||
newSearchParams.append(id, value);
|
||||
}
|
||||
|
||||
router.replace(createUrl(pathname, newSearchParams), { scroll: false });
|
||||
};
|
||||
|
||||
const handleClearAll = () => {
|
||||
const sort = searchParams.get('sort');
|
||||
const collection = searchParams.get('collection');
|
||||
const q = searchParams.get('q');
|
||||
const newSearchParams = new URLSearchParams({
|
||||
...(sort && { sort }),
|
||||
...(collection && { collection }),
|
||||
...(q && { q })
|
||||
});
|
||||
router.replace(createUrl(pathname, newSearchParams), { scroll: false });
|
||||
};
|
||||
|
||||
return selectedFilters.length ? (
|
||||
<div className="mt-5 flex flex-wrap gap-2 text-xs">
|
||||
{selectedFilters.map((filter) => (
|
||||
<div
|
||||
key={filter.valueId}
|
||||
className="flex items-center justify-between gap-2 rounded-full border bg-gray-100 px-2 py-1"
|
||||
>
|
||||
{filter.label}
|
||||
<button onClick={() => handleClear(filter.id, String(filter.value))}>
|
||||
<XMarkIcon className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
onClick={handleClearAll}
|
||||
className="ml-2 tracking-wide text-secondary/80 underline hover:text-secondary"
|
||||
>
|
||||
Clear all
|
||||
</button>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default SelectedList;
|
||||
|
Loading…
x
Reference in New Issue
Block a user