mirror of
https://github.com/vercel/commerce.git
synced 2025-05-13 13:17:51 +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';
|
} from 'lib/constants';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params
|
params
|
||||||
}: {
|
}: {
|
||||||
|
@ -3,8 +3,6 @@ import ProductGridItems from 'components/layout/product-grid-items';
|
|||||||
import { defaultSort, sorting } from 'lib/constants';
|
import { defaultSort, sorting } from 'lib/constants';
|
||||||
import { getProducts } from 'lib/shopify';
|
import { getProducts } from 'lib/shopify';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: 'Search',
|
title: 'Search',
|
||||||
description: 'Search for products in the store.'
|
description: 'Search for products in the store.'
|
||||||
|
@ -5,6 +5,7 @@ import clsx from 'clsx';
|
|||||||
import { Filter } from 'lib/shopify/types';
|
import { Filter } from 'lib/shopify/types';
|
||||||
import { createUrl } from 'lib/utils';
|
import { createUrl } from 'lib/utils';
|
||||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
import SelectedList from './selected-list';
|
||||||
|
|
||||||
const Filters = ({ filters, defaultOpen = true }: { filters: Filter[]; defaultOpen?: boolean }) => {
|
const Filters = ({ filters, defaultOpen = true }: { filters: Filter[]; defaultOpen?: boolean }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -31,43 +32,47 @@ const Filters = ({ filters, defaultOpen = true }: { filters: Filter[]; defaultOp
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onChange={handleChange} className="space-y-5 divide-y divide-gray-200 border-b pb-3">
|
<>
|
||||||
{filters.map(({ label, id, values }) => (
|
<SelectedList filters={filters} />
|
||||||
<Disclosure
|
<form onChange={handleChange} className="space-y-5 divide-y divide-gray-200 border-b pb-3">
|
||||||
key={id}
|
{filters.map(({ label, id, values }) => (
|
||||||
as="div"
|
<Disclosure
|
||||||
className="flex h-auto max-h-[550px] flex-col gap-y-3 overflow-hidden pt-5"
|
key={id}
|
||||||
defaultOpen={defaultOpen}
|
as="div"
|
||||||
>
|
className="flex h-auto max-h-[550px] flex-col gap-y-3 overflow-hidden pt-5"
|
||||||
<DisclosureButton className="group flex items-center justify-between">
|
defaultOpen={defaultOpen}
|
||||||
<div className="text-sm font-medium text-gray-900">{label}</div>
|
>
|
||||||
<ChevronDownIcon className="size-4 group-data-[open]:rotate-180" />
|
<DisclosureButton className="group flex items-center justify-between">
|
||||||
</DisclosureButton>
|
<div className="text-sm font-medium text-gray-900">{label}</div>
|
||||||
<DisclosurePanel className="flex-grow space-y-3 overflow-auto pb-1 pl-1 pt-2">
|
<ChevronDownIcon className="size-4 group-data-[open]:rotate-180" />
|
||||||
{values.map(({ id: valueId, label, count, value }) => (
|
</DisclosureButton>
|
||||||
<label
|
<DisclosurePanel className="flex-grow space-y-3 overflow-auto pb-1 pl-1 pt-2">
|
||||||
key={valueId}
|
{values.map(({ id: valueId, label, count, value }) => (
|
||||||
htmlFor={valueId}
|
<label
|
||||||
className={clsx('flex items-center gap-2 text-sm text-gray-600', {
|
key={valueId}
|
||||||
'cursor-not-allowed opacity-50': count === 0
|
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}
|
<input
|
||||||
defaultChecked={searchParams.getAll(id).includes(String(value))}
|
id={valueId}
|
||||||
type="checkbox"
|
name={id}
|
||||||
value={String(value)}
|
checked={searchParams.getAll(id).includes(String(value))}
|
||||||
className="h-4 w-4 rounded border-gray-300 text-secondary focus:ring-secondary disabled:cursor-not-allowed disabled:opacity-50"
|
type="checkbox"
|
||||||
disabled={count === 0}
|
value={String(value)}
|
||||||
/>
|
className="h-4 w-4 rounded border-gray-300 text-secondary focus:ring-secondary disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
<span>{`${label} (${count})`}</span>
|
disabled={count === 0}
|
||||||
</label>
|
onChange={() => {}}
|
||||||
))}
|
/>
|
||||||
</DisclosurePanel>
|
<span>{`${label} (${count})`}</span>
|
||||||
</Disclosure>
|
</label>
|
||||||
))}
|
))}
|
||||||
</form>
|
</DisclosurePanel>
|
||||||
|
</Disclosure>
|
||||||
|
))}
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,73 @@
|
|||||||
const SelectedList = () => {
|
'use client';
|
||||||
return <div>SelectedList</div>;
|
|
||||||
|
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;
|
export default SelectedList;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user