feat: add selected filter list

Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
Chloe 2024-05-07 15:37:53 +07:00
parent c8f8fd2adc
commit 161ac5a164
No known key found for this signature in database
GPG Key ID: CFD53CE570D42DF5
4 changed files with 112 additions and 43 deletions

View File

@ -20,8 +20,6 @@ import {
} from 'lib/constants';
import { Suspense } from 'react';
export const runtime = 'edge';
export async function generateMetadata({
params
}: {

View File

@ -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.'

View File

@ -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>
</>
);
};

View File

@ -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;