Skip to content

Commit d3af3f0

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

File tree

2 files changed

+62
-10
lines changed

2 files changed

+62
-10
lines changed

src/components/Tags/Tags.tsx

+34-5
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,39 @@ 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+
// Generate URL based on tag state:
28+
// - If active: remove this tag from URL
29+
// - If inactive: add this tag to existing active tags
30+
const remainingTags = isActive
31+
? activeTags.filter((t) => t !== tag)
32+
: [...activeTags, tag];
33+
34+
const searchParams = new URLSearchParams();
35+
if (remainingTags.length) {
36+
remainingTags.forEach((tag) => {
37+
searchParams.append("tag", encodeURIComponent(tag));
38+
});
39+
}
40+
const href = remainingTags.length ? `/?${searchParams.toString()}` : "/";
41+
1942
return (
2043
<Link
2144
{...props}
2245
className={cn(styles.tag, className, isActive && styles.active)}
23-
href={isActive ? "/" : `/?tag=${encodeURIComponent(tag)}`}
46+
href={href}
2447
>
2548
<Icon className={cn(styles.icon)} />
2649
<span className={styles.label}>{tag}</span>
@@ -30,17 +53,23 @@ export function Tag({ tag, isActive, className, ...props }: TagProps) {
3053

3154
interface TagsProps {
3255
tags: string[];
33-
activeTag?: string;
56+
activeTags: string[];
3457
className?: string;
3558
}
3659

37-
export function Tags({ tags, activeTag, className }: TagsProps) {
60+
export function Tags({ tags, activeTags, className }: TagsProps) {
3861
const label = getLabel("filterByTag");
3962
return (
4063
<div className={cn(styles.tags, className)}>
4164
{!!label && <h3>{label}</h3>}
4265
{tags.map((tag) => (
43-
<Tag key={tag} tag={tag} isActive={activeTag == tag} scroll={false} />
66+
<Tag
67+
key={tag}
68+
tag={tag}
69+
isActive={activeTags.includes(tag)}
70+
activeTags={activeTags}
71+
scroll={false}
72+
/>
4473
))}
4574
</div>
4675
);

src/pages/index.tsx

+28-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,32 @@ 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+
// Parse active tags from URL query parameters
34+
const activeTags = Array.isArray(tagQuery)
35+
? tagQuery.map((tag) => decodeURIComponent(tag))
36+
: tagQuery
37+
? [decodeURIComponent(tagQuery)]
38+
: [];
39+
40+
// Remove empty tag parameter from URL
41+
if (tagQuery === "") {
42+
router.replace("/", undefined, { shallow: true });
43+
}
44+
45+
// Filter items based on selected tags
46+
const items = getItems(undefined, true).filter((item) => {
47+
// Show all items if no tags are selected
48+
if (!activeTags.length) return true;
49+
50+
// Handle potentially undefined tags with a default empty array
51+
const itemTags = item.tags ?? [];
52+
53+
// Show item only if it has all selected tags (AND logic)
54+
return (
55+
itemTags.length > 0 && activeTags.every((tag) => itemTags.includes(tag))
56+
);
57+
});
3558

3659
return (
3760
<>
@@ -65,7 +88,7 @@ const Home: CustomPage = () => {
6588
return (
6689
getToggle("showTagFilter") &&
6790
tags.length > 0 && (
68-
<Tags key={section} tags={tags} activeTag={tag} />
91+
<Tags key={section} tags={tags} activeTags={activeTags} />
6992
)
7093
);
7194
case "list":

0 commit comments

Comments
 (0)