@c-rex/components 0.1.38 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -73
- package/package.json +250 -218
- package/src/article/article-action-bar.tsx +110 -110
- package/src/article/article-content.tsx +18 -46
- package/src/autocomplete.tsx +201 -201
- package/src/breadcrumb.tsx +124 -124
- package/src/carousel/carousel.tsx +353 -353
- package/src/check-article-lang.tsx +47 -47
- package/src/directoryNodes/directory-tree-context.tsx +388 -0
- package/src/directoryNodes/tree-of-content.tsx +68 -67
- package/src/documents/result-list.tsx +124 -127
- package/src/favorites/bookmark-button.tsx +97 -94
- package/src/favorites/favorite-button.tsx +137 -120
- package/src/footer/footer-shell.tsx +52 -0
- package/src/footer/footer.tsx +7 -0
- package/src/footer/legal-links-block.tsx +25 -0
- package/src/footer/organization-contact-block.tsx +94 -0
- package/src/footer/social-links-block.tsx +38 -0
- package/src/footer/types.ts +10 -0
- package/src/footer/vcard-footer.tsx +72 -0
- package/src/generated/client-components.tsx +1366 -1350
- package/src/generated/create-client-request.tsx +116 -113
- package/src/generated/create-server-request.tsx +70 -61
- package/src/generated/create-suggestions-request.tsx +55 -55
- package/src/generated/server-components.tsx +1056 -1056
- package/src/generated/suggestions.tsx +302 -299
- package/src/icons/file-icon.tsx +8 -8
- package/src/icons/flag-icon.tsx +15 -15
- package/src/icons/loading.tsx +11 -11
- package/src/icons/social-icon.tsx +24 -0
- package/src/info/info-card.tsx +43 -0
- package/src/info/{info-table.tsx → information-unit-metadata-grid.tsx} +157 -168
- package/src/info/shared.tsx +49 -25
- package/src/navbar/language-switcher/content-language-switch.tsx +92 -92
- package/src/navbar/language-switcher/shared.tsx +33 -33
- package/src/navbar/language-switcher/ui-language-switch.tsx +37 -37
- package/src/navbar/navbar.tsx +157 -152
- package/src/navbar/settings.tsx +62 -62
- package/src/navbar/sign-in-out-btns.tsx +35 -35
- package/src/navbar/user-menu.tsx +60 -60
- package/src/page-wrapper.tsx +54 -31
- package/src/render-article.module.css +155 -0
- package/src/render-article.tsx +75 -68
- package/src/renditions/file-download.tsx +83 -83
- package/src/renditions/html.tsx +64 -64
- package/src/renditions/image/container.tsx +54 -54
- package/src/renditions/image/rendition.tsx +55 -55
- package/src/restriction-menu/restriction-menu-container.tsx +117 -53
- package/src/restriction-menu/restriction-menu-item.tsx +155 -147
- package/src/restriction-menu/restriction-menu.tsx +341 -156
- package/src/results/dialog-filter.tsx +166 -166
- package/src/results/empty.tsx +15 -15
- package/src/results/filter-navbar.tsx +294 -261
- package/src/results/filter-sidebar/__tests__/utils.test.ts +129 -0
- package/src/results/filter-sidebar/index.tsx +270 -126
- package/src/results/filter-sidebar/utils.ts +196 -164
- package/src/results/generic/table-result-list.tsx +97 -99
- package/src/results/{table-with-images.tsx → information-unit-search-results-card-list.tsx} +125 -127
- package/src/results/{cards.tsx → information-unit-search-results-cards.tsx} +99 -99
- package/src/results/{table.tsx → information-unit-search-results-table.tsx} +104 -104
- package/src/results/pagination.tsx +81 -81
- package/src/results/summary.ts +30 -0
- package/src/results/utils.ts +54 -54
- package/src/search-input.tsx +70 -70
- package/src/share-button.tsx +49 -49
- package/src/stores/favorites-store.ts +88 -88
- package/src/stores/highlight-store.ts +15 -15
- package/src/stores/language-store.ts +14 -14
- package/src/stores/restriction-store.ts +11 -11
- package/src/stores/search-settings-store.ts +68 -64
- package/src/info/set-available-versions.tsx +0 -19
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Tags } from "@c-rex/interfaces";
|
|
2
|
+
import {
|
|
3
|
+
clearData,
|
|
4
|
+
memoizeFilteredTags,
|
|
5
|
+
resolveLabelByLanguage,
|
|
6
|
+
updateFilterParam,
|
|
7
|
+
removeFilterItem,
|
|
8
|
+
} from "../utils";
|
|
9
|
+
|
|
10
|
+
const createTags = (): Tags => ({
|
|
11
|
+
informationSubjects: {
|
|
12
|
+
labels: ["Information Subjects"],
|
|
13
|
+
items: [
|
|
14
|
+
{
|
|
15
|
+
hits: 3,
|
|
16
|
+
total: 10,
|
|
17
|
+
labels: [
|
|
18
|
+
{ language: "en", value: "Translation" },
|
|
19
|
+
{ language: "de", value: "Uebersetzung" },
|
|
20
|
+
],
|
|
21
|
+
shortId: "subA",
|
|
22
|
+
id: "https://example.org/subA",
|
|
23
|
+
} as unknown as Tags[string]["items"][number],
|
|
24
|
+
{
|
|
25
|
+
hits: 0,
|
|
26
|
+
total: 5,
|
|
27
|
+
labels: [{ language: "en", value: "Collection" }],
|
|
28
|
+
shortId: "subB",
|
|
29
|
+
id: "https://example.org/subB",
|
|
30
|
+
} as unknown as Tags[string]["items"][number],
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
packages: {
|
|
34
|
+
labels: ["Packages"],
|
|
35
|
+
items: [
|
|
36
|
+
{
|
|
37
|
+
hits: 2,
|
|
38
|
+
total: 2,
|
|
39
|
+
labels: [{ language: "en", value: "Default Package" }],
|
|
40
|
+
shortId: "pkgA",
|
|
41
|
+
id: "https://example.org/pkgA",
|
|
42
|
+
} as unknown as Tags[string]["items"][number],
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("filter-sidebar utils", () => {
|
|
48
|
+
it("resolveLabelByLanguage prefers exact language, then base, then fallback", () => {
|
|
49
|
+
const labels = [
|
|
50
|
+
{ language: "en", value: "English Label" },
|
|
51
|
+
{ language: "de", value: "German Label" },
|
|
52
|
+
{ value: "Neutral Label" },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
expect(resolveLabelByLanguage(labels, "de-DE")).toBe("German Label");
|
|
56
|
+
expect(resolveLabelByLanguage(labels, "en-US")).toBe("English Label");
|
|
57
|
+
expect(resolveLabelByLanguage([{ value: "Neutral Label" }], "fr-FR")).toBe("Neutral Label");
|
|
58
|
+
expect(resolveLabelByLanguage([], "en")).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("clearData includes zero-hit entries by default", () => {
|
|
62
|
+
const tags = createTags();
|
|
63
|
+
const result = clearData(tags, { uiLanguage: "en" });
|
|
64
|
+
|
|
65
|
+
expect(result.informationSubjects).toHaveLength(2);
|
|
66
|
+
expect(result.informationSubjects?.map((item) => item.shortId)).toEqual(["subA", "subB"]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("clearData excludes zero-hit entries when includeZeroHits is false", () => {
|
|
70
|
+
const tags = createTags();
|
|
71
|
+
const result = clearData(tags, { uiLanguage: "en", includeZeroHits: false });
|
|
72
|
+
|
|
73
|
+
expect(result.informationSubjects).toHaveLength(1);
|
|
74
|
+
expect(result.informationSubjects?.[0]?.shortId).toBe("subA");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("clearData applies facet overrides and stores taxonomyId", () => {
|
|
78
|
+
const tags = createTags();
|
|
79
|
+
const result = clearData(tags, {
|
|
80
|
+
uiLanguage: "en",
|
|
81
|
+
labelOverrides: {
|
|
82
|
+
informationSubjects: {
|
|
83
|
+
subA: {
|
|
84
|
+
label: "Overridden Label",
|
|
85
|
+
groupLabel: "Subject Group",
|
|
86
|
+
sectionLabel: "Process",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(result.informationSubjects?.[0]).toMatchObject({
|
|
93
|
+
label: "Overridden Label",
|
|
94
|
+
groupLabel: "Subject Group",
|
|
95
|
+
sectionLabel: "Process",
|
|
96
|
+
taxonomyId: "https://example.org/subA",
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("memoizeFilteredTags marks active filters and active package", () => {
|
|
101
|
+
const tags = createTags();
|
|
102
|
+
const result = memoizeFilteredTags(tags, "informationSubjects.shortId=subA", "pkgA", {
|
|
103
|
+
uiLanguage: "en",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(result.informationSubjects?.find((item) => item.shortId === "subA")?.active).toBe(true);
|
|
107
|
+
expect(result.informationSubjects?.find((item) => item.shortId === "subB")?.active).toBe(false);
|
|
108
|
+
expect(result.packages?.find((item) => item.shortId === "pkgA")?.active).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("updateFilterParam and removeFilterItem keep query behavior stable", () => {
|
|
112
|
+
expect(updateFilterParam("packages", { shortId: "pkgA", hits: 0, total: 0, label: "", active: false }, null)).toEqual({
|
|
113
|
+
packages: "pkgA",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(updateFilterParam("informationSubjects", { shortId: "subA", hits: 0, total: 0, label: "", active: false }, null)).toEqual({
|
|
117
|
+
filter: "informationSubjects.shortId=subA",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(
|
|
121
|
+
removeFilterItem(
|
|
122
|
+
"informationSubjects",
|
|
123
|
+
{ shortId: "subA", hits: 0, total: 0, label: "", active: false },
|
|
124
|
+
"informationSubjects.shortId=subA,packages.shortId=pkgA"
|
|
125
|
+
)
|
|
126
|
+
).toEqual({ filter: "packages.shortId=pkgA" });
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
@@ -1,126 +1,270 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { FC, useMemo } from "react";
|
|
4
|
-
import { useTranslations } from 'next-intl'
|
|
5
|
-
import { Check, ChevronDown } from "lucide-react"
|
|
6
|
-
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@c-rex/ui/collapsible";
|
|
7
|
-
import {
|
|
8
|
-
SidebarContent,
|
|
9
|
-
SidebarGroup,
|
|
10
|
-
SidebarGroupContent,
|
|
11
|
-
SidebarGroupLabel,
|
|
12
|
-
SidebarHeader,
|
|
13
|
-
SidebarMenu,
|
|
14
|
-
SidebarMenuSub,
|
|
15
|
-
SidebarMenuSubButton,
|
|
16
|
-
SidebarMenuSubItem
|
|
17
|
-
} from "@c-rex/ui/sidebar";
|
|
18
|
-
import { parseAsString, useQueryStates } from "nuqs";
|
|
19
|
-
import { memoizeFilteredTags, removeFilterItem, updateFilterParam } from "./utils";
|
|
20
|
-
import { FilterItem, Tags } from "@c-rex/interfaces";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export interface FilterSidebarProps {
|
|
24
|
-
tags?: Tags
|
|
25
|
-
totalItemCount?: number
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { FC, useMemo } from "react";
|
|
4
|
+
import { useLocale, useTranslations } from 'next-intl'
|
|
5
|
+
import { Check, ChevronDown } from "lucide-react"
|
|
6
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@c-rex/ui/collapsible";
|
|
7
|
+
import {
|
|
8
|
+
SidebarContent,
|
|
9
|
+
SidebarGroup,
|
|
10
|
+
SidebarGroupContent,
|
|
11
|
+
SidebarGroupLabel,
|
|
12
|
+
SidebarHeader,
|
|
13
|
+
SidebarMenu,
|
|
14
|
+
SidebarMenuSub,
|
|
15
|
+
SidebarMenuSubButton,
|
|
16
|
+
SidebarMenuSubItem
|
|
17
|
+
} from "@c-rex/ui/sidebar";
|
|
18
|
+
import { parseAsString, useQueryStates } from "nuqs";
|
|
19
|
+
import { applyFacetPropertyVisibility, FacetLabelOverrides, memoizeFilteredTags, removeFilterItem, ResolvedFilterItem, resolveLabelByLanguage, updateFilterParam } from "./utils";
|
|
20
|
+
import { FilterItem, Tags } from "@c-rex/interfaces";
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export interface FilterSidebarProps {
|
|
24
|
+
tags?: Tags
|
|
25
|
+
totalItemCount?: number
|
|
26
|
+
facetLabelOverrides?: FacetLabelOverrides
|
|
27
|
+
includeZeroHits?: boolean
|
|
28
|
+
includeProperties?: string[]
|
|
29
|
+
excludeProperties?: string[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
//TODO; check layout on mobile
|
|
34
|
+
export const FilterSidebar: FC<FilterSidebarProps> = ({
|
|
35
|
+
tags,
|
|
36
|
+
totalItemCount,
|
|
37
|
+
facetLabelOverrides,
|
|
38
|
+
includeZeroHits = true,
|
|
39
|
+
includeProperties,
|
|
40
|
+
excludeProperties,
|
|
41
|
+
}) => {
|
|
42
|
+
const t = useTranslations();
|
|
43
|
+
const locale = useLocale();
|
|
44
|
+
const [params, setParams] = useQueryStates({
|
|
45
|
+
packages: parseAsString,
|
|
46
|
+
filter: parseAsString,
|
|
47
|
+
}, {
|
|
48
|
+
history: 'push',
|
|
49
|
+
shallow: false,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const filteredTags = useMemo(() => {
|
|
53
|
+
const resolved = memoizeFilteredTags(tags, params.filter, params.packages, {
|
|
54
|
+
uiLanguage: locale,
|
|
55
|
+
labelOverrides: facetLabelOverrides,
|
|
56
|
+
includeZeroHits,
|
|
57
|
+
});
|
|
58
|
+
return applyFacetPropertyVisibility(resolved, includeProperties, excludeProperties);
|
|
59
|
+
}, [tags, params.filter, params.packages, locale, facetLabelOverrides, includeZeroHits, includeProperties, excludeProperties]);
|
|
60
|
+
|
|
61
|
+
const onClickHandler = (key: string, item: FilterItem) => {
|
|
62
|
+
if (item.active) {
|
|
63
|
+
setParams(removeFilterItem(key, item, params.filter))
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setParams(updateFilterParam(key, item, params.filter))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const resolvePropertyLabel = (propertyKey: string): string => {
|
|
71
|
+
const rawLabels = (tags?.[propertyKey]?.labels as unknown[] | undefined) || [];
|
|
72
|
+
const normalized = rawLabels
|
|
73
|
+
.map((item) => {
|
|
74
|
+
if (typeof item === "string") {
|
|
75
|
+
return { value: item };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (item && typeof item === "object") {
|
|
79
|
+
const labelObj = item as { language?: string; value?: string };
|
|
80
|
+
if (typeof labelObj.value === "string") {
|
|
81
|
+
return { language: labelObj.language, value: labelObj.value };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return undefined;
|
|
86
|
+
})
|
|
87
|
+
.filter((item): item is { language?: string; value: string } => Boolean(item));
|
|
88
|
+
|
|
89
|
+
return resolveLabelByLanguage(normalized, locale) || propertyKey;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const content = (
|
|
93
|
+
<SidebarContent className="!gap-0" suppressHydrationWarning>
|
|
94
|
+
|
|
95
|
+
{Object.entries(filteredTags).map(([key, value]) => {
|
|
96
|
+
const items = value as ResolvedFilterItem[];
|
|
97
|
+
const hasSectionLabels = items.some((item) => Boolean(item.sectionLabel));
|
|
98
|
+
const propertyLabel = resolvePropertyLabel(key);
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div
|
|
102
|
+
key={key}
|
|
103
|
+
data-filter-group-key={key}
|
|
104
|
+
className="border-b border-border/40 pb-1 mb-1 last:border-b-0 last:mb-0"
|
|
105
|
+
>
|
|
106
|
+
{!hasSectionLabels && (
|
|
107
|
+
<Collapsible defaultOpen className="py-0 group/collapsible">
|
|
108
|
+
<SidebarGroup className="!p-1">
|
|
109
|
+
<SidebarGroupLabel asChild className="hover:bg-sidebar-accent text-sidebar-accent-foreground text-sm font-bold">
|
|
110
|
+
<CollapsibleTrigger className="!h-8">
|
|
111
|
+
{propertyLabel}
|
|
112
|
+
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
|
113
|
+
</CollapsibleTrigger>
|
|
114
|
+
</SidebarGroupLabel>
|
|
115
|
+
<CollapsibleContent>
|
|
116
|
+
<SidebarGroupContent>
|
|
117
|
+
<SidebarMenu className="!gap-0.5">
|
|
118
|
+
<SidebarMenuSub className="!ml-2 !pl-1.5 !py-0 !gap-0.5">
|
|
119
|
+
{items.map((item) => (
|
|
120
|
+
<SidebarMenuSubItem key={item.shortId}>
|
|
121
|
+
<SidebarMenuSubButton
|
|
122
|
+
className="cursor-pointer !py-1.5"
|
|
123
|
+
data-taxonomy-id={item.taxonomyId}
|
|
124
|
+
data-taxonomy-short-id={item.shortId}
|
|
125
|
+
isActive={item.active}
|
|
126
|
+
onClick={() => onClickHandler(key, item)}
|
|
127
|
+
>
|
|
128
|
+
{item.label} ({item.hits}/{item.total})
|
|
129
|
+
{item.active && <Check className="ml-2" />}
|
|
130
|
+
</SidebarMenuSubButton>
|
|
131
|
+
</SidebarMenuSubItem>
|
|
132
|
+
))}
|
|
133
|
+
</SidebarMenuSub>
|
|
134
|
+
</SidebarMenu>
|
|
135
|
+
</SidebarGroupContent>
|
|
136
|
+
</CollapsibleContent>
|
|
137
|
+
</SidebarGroup>
|
|
138
|
+
</Collapsible>
|
|
139
|
+
)}
|
|
140
|
+
|
|
141
|
+
{hasSectionLabels && (
|
|
142
|
+
<>
|
|
143
|
+
<div className="sr-only">{propertyLabel}</div>
|
|
144
|
+
{Object.entries(
|
|
145
|
+
items.reduce<Record<string, ResolvedFilterItem[]>>((acc, item) => {
|
|
146
|
+
const sectionLabel = item.sectionLabel || "Other";
|
|
147
|
+
if (!acc[sectionLabel]) acc[sectionLabel] = [];
|
|
148
|
+
acc[sectionLabel].push(item);
|
|
149
|
+
return acc;
|
|
150
|
+
}, {})
|
|
151
|
+
)
|
|
152
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
153
|
+
.map(([sectionLabel, sectionItems]) => {
|
|
154
|
+
const hasNestedGroups = sectionItems.some((item) => Boolean(item.groupLabel));
|
|
155
|
+
return (
|
|
156
|
+
<Collapsible
|
|
157
|
+
defaultOpen
|
|
158
|
+
key={`${key}:${sectionLabel}`}
|
|
159
|
+
className="py-0 group/collapsible"
|
|
160
|
+
>
|
|
161
|
+
<SidebarGroup className="!p-1">
|
|
162
|
+
<SidebarGroupLabel
|
|
163
|
+
asChild
|
|
164
|
+
className="hover:bg-sidebar-accent text-sidebar-accent-foreground text-sm font-bold"
|
|
165
|
+
>
|
|
166
|
+
<CollapsibleTrigger className="!h-8">
|
|
167
|
+
{sectionLabel}
|
|
168
|
+
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
|
169
|
+
</CollapsibleTrigger>
|
|
170
|
+
</SidebarGroupLabel>
|
|
171
|
+
<CollapsibleContent>
|
|
172
|
+
<SidebarGroupContent>
|
|
173
|
+
<SidebarMenu className="!gap-0.5">
|
|
174
|
+
<SidebarMenuSub className="!ml-2 !pl-1.5 !py-0 !gap-0.5">
|
|
175
|
+
{!hasNestedGroups &&
|
|
176
|
+
sectionItems.map((item) => (
|
|
177
|
+
<SidebarMenuSubItem key={item.shortId}>
|
|
178
|
+
<SidebarMenuSubButton
|
|
179
|
+
className="cursor-pointer !py-1.5"
|
|
180
|
+
data-taxonomy-id={item.taxonomyId}
|
|
181
|
+
data-taxonomy-short-id={item.shortId}
|
|
182
|
+
isActive={item.active}
|
|
183
|
+
onClick={() => onClickHandler(key, item)}
|
|
184
|
+
>
|
|
185
|
+
{item.label} ({item.hits}/{item.total})
|
|
186
|
+
{item.active && <Check className="ml-2" />}
|
|
187
|
+
</SidebarMenuSubButton>
|
|
188
|
+
</SidebarMenuSubItem>
|
|
189
|
+
))}
|
|
190
|
+
|
|
191
|
+
{hasNestedGroups &&
|
|
192
|
+
Object.entries(
|
|
193
|
+
sectionItems.reduce<Record<string, ResolvedFilterItem[]>>((acc, item) => {
|
|
194
|
+
const groupLabel = item.groupLabel || "Other";
|
|
195
|
+
if (!acc[groupLabel]) acc[groupLabel] = [];
|
|
196
|
+
acc[groupLabel].push(item);
|
|
197
|
+
return acc;
|
|
198
|
+
}, {})
|
|
199
|
+
)
|
|
200
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
201
|
+
.map(([groupLabel, groupedItems]) => (
|
|
202
|
+
<div key={groupLabel} className="pb-0.5">
|
|
203
|
+
<div className="px-2 py-1 text-xs font-semibold text-muted-foreground uppercase tracking-wide">
|
|
204
|
+
{groupLabel}
|
|
205
|
+
</div>
|
|
206
|
+
{groupedItems.map((item) => (
|
|
207
|
+
<SidebarMenuSubItem key={item.shortId}>
|
|
208
|
+
<SidebarMenuSubButton
|
|
209
|
+
className="cursor-pointer !py-1.5"
|
|
210
|
+
data-taxonomy-id={item.taxonomyId}
|
|
211
|
+
data-taxonomy-short-id={item.shortId}
|
|
212
|
+
isActive={item.active}
|
|
213
|
+
onClick={() => onClickHandler(key, item)}
|
|
214
|
+
>
|
|
215
|
+
{item.label} ({item.hits}/{item.total})
|
|
216
|
+
{item.active && <Check className="ml-2" />}
|
|
217
|
+
</SidebarMenuSubButton>
|
|
218
|
+
</SidebarMenuSubItem>
|
|
219
|
+
))}
|
|
220
|
+
</div>
|
|
221
|
+
))}
|
|
222
|
+
</SidebarMenuSub>
|
|
223
|
+
</SidebarMenu>
|
|
224
|
+
</SidebarGroupContent>
|
|
225
|
+
</CollapsibleContent>
|
|
226
|
+
</SidebarGroup>
|
|
227
|
+
</Collapsible>
|
|
228
|
+
);
|
|
229
|
+
})}
|
|
230
|
+
</>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
})}
|
|
235
|
+
</SidebarContent>
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
/*
|
|
239
|
+
if (isMobile) {
|
|
240
|
+
return (
|
|
241
|
+
|
|
242
|
+
<SheetContent
|
|
243
|
+
side="left"
|
|
244
|
+
className="!pt-6 !px-2 w-[400px] overflow-y-auto"
|
|
245
|
+
>
|
|
246
|
+
<SheetHeader className="justify-center items-end font-bold">
|
|
247
|
+
{t("filter.filters")}
|
|
248
|
+
<span className="text-xs text-muted-foreground leading-5">
|
|
249
|
+
{totalItemCount} {t("results.results")}
|
|
250
|
+
</span>
|
|
251
|
+
</SheetHeader>
|
|
252
|
+
{content}
|
|
253
|
+
</SheetContent>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
*/
|
|
257
|
+
if (Object.entries(filteredTags).length === 0) return null;
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div className="w-60 lg:w-80 bg-sidebar rounded-md border pb-4">
|
|
261
|
+
<SidebarHeader className="justify-center items-end font-bold">
|
|
262
|
+
{t("filter.filters")}
|
|
263
|
+
<span className="text-xs text-muted-foreground leading-5">
|
|
264
|
+
{totalItemCount} {t("results.results")}
|
|
265
|
+
</span>
|
|
266
|
+
</SidebarHeader>
|
|
267
|
+
{content}
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
};
|