Skip to content

Commit 120b49a

Browse files
committed
feat: add multi-tag filtering with AND logic
1 parent 864ab22 commit 120b49a

File tree

2 files changed

+50
-10
lines changed

2 files changed

+50
-10
lines changed

src/components/Tags/Tags.tsx

+29-5
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,34 @@ import { cn } from "@/lib/utils";
1111
type TagProps = {
1212
tag: string;
1313
isActive?: boolean;
14+
activeTags?: string[];
1415
} & Omit<LinkProps, "href"> &
1516
ComponentPropsWithoutRef<"a">;
1617

17-
export function Tag({ tag, isActive, className, ...props }: TagProps) {
18+
export function Tag({
19+
tag,
20+
isActive,
21+
activeTags = [],
22+
className,
23+
...props
24+
}: TagProps) {
1825
const Icon = isActive ? IconRemove : IconTag;
26+
27+
// Update URL based on tag state
28+
const remainingTags = isActive
29+
? activeTags.filter((t) => t !== tag)
30+
: [...activeTags, tag];
31+
32+
const searchParams = new URLSearchParams(
33+
remainingTags.map((tag) => ["tag", encodeURIComponent(tag)]),
34+
);
35+
const href = searchParams.toString() ? `/?${searchParams}` : "/";
36+
1937
return (
2038
<Link
2139
{...props}
2240
className={cn(styles.tag, className, isActive && styles.active)}
23-
href={isActive ? "/" : `/?tag=${encodeURIComponent(tag)}`}
41+
href={href}
2442
>
2543
<Icon className={cn(styles.icon)} />
2644
<span className={styles.label}>{tag}</span>
@@ -30,17 +48,23 @@ export function Tag({ tag, isActive, className, ...props }: TagProps) {
3048

3149
interface TagsProps {
3250
tags: string[];
33-
activeTag?: string;
51+
activeTags: string[];
3452
className?: string;
3553
}
3654

37-
export function Tags({ tags, activeTag, className }: TagsProps) {
55+
export function Tags({ tags, activeTags, className }: TagsProps) {
3856
const label = getLabel("filterByTag");
3957
return (
4058
<div className={cn(styles.tags, className)}>
4159
{!!label && <h3>{label}</h3>}
4260
{tags.map((tag) => (
43-
<Tag key={tag} tag={tag} isActive={activeTag == tag} scroll={false} />
61+
<Tag
62+
key={tag}
63+
tag={tag}
64+
isActive={activeTags.includes(tag)}
65+
activeTags={activeTags}
66+
scroll={false}
67+
/>
4468
))}
4569
</div>
4670
);

src/pages/index.tsx

+21-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { CustomPage } from "@/pages/_app";
2020

2121
const Home: CustomPage = () => {
2222
const router = useRouter();
23-
const tag = router.query.tag as string | undefined;
23+
const tagQuery = router.query.tag;
2424
const appName = getAppName();
2525
const metaDescription = getLabel("metaDescription");
2626
const chartConfig = getChartConfig();
@@ -29,9 +29,25 @@ const Home: CustomPage = () => {
2929
const rings = getRings();
3030
const quadrants = getQuadrants();
3131
const tags = getTags();
32-
const items = getItems(undefined, true).filter(
33-
(item) => !tag || item.tags?.includes(tag),
34-
);
32+
33+
// Convert URL tag parameter(s) to array of decoded tags
34+
const activeTags = (Array.isArray(tagQuery) ? tagQuery : [tagQuery])
35+
.filter((tag): tag is string => Boolean(tag))
36+
.map((tag) => decodeURIComponent(tag));
37+
38+
// Clean up URL if needed
39+
if (tagQuery === "") {
40+
router.replace("/", undefined, { shallow: true });
41+
}
42+
43+
// Filter items by selected tags (AND logic)
44+
const items = getItems(undefined, true).filter((item) => {
45+
if (!activeTags.length) return true;
46+
const itemTags = item.tags ?? [];
47+
return (
48+
itemTags.length > 0 && activeTags.every((tag) => itemTags.includes(tag))
49+
);
50+
});
3551

3652
return (
3753
<>
@@ -65,7 +81,7 @@ const Home: CustomPage = () => {
6581
return (
6682
getToggle("showTagFilter") &&
6783
tags.length > 0 && (
68-
<Tags key={section} tags={tags} activeTag={tag} />
84+
<Tags key={section} tags={tags} activeTags={activeTags} />
6985
)
7086
);
7187
case "list":

0 commit comments

Comments
 (0)