@c-rex/components 0.3.0-build.35 → 0.3.0-build.38
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/package.json +32 -36
- package/src/article/article-action-bar.tsx +12 -2
- package/src/{check-article-lang.tsx → article/check-article-lang.tsx} +1 -1
- package/src/article/render-article-highlight.tsx +108 -0
- package/src/article/render-article.tsx +28 -0
- package/src/autocomplete.tsx +7 -25
- package/src/blog/blog-author-card.tsx +116 -0
- package/src/carousel/carousel.tsx +5 -2
- package/src/carousel/information-unit-carousel-item.tsx +1 -1
- package/src/content-unavailable.tsx +20 -0
- package/src/directoryNodes/directory-tree-context.tsx +9 -4
- package/src/documents/description-preview.tsx +14 -4
- package/src/documents/result-list-item.tsx +40 -46
- package/src/favorites/__tests__/favorites-hydration.test.tsx +245 -0
- package/src/favorites/bookmark-button.tsx +38 -20
- package/src/favorites/favorite-button.tsx +23 -24
- package/src/favorites/favorites-context.tsx +287 -0
- package/src/icons/file-icon.tsx +9 -26
- package/src/info/information-unit-metadata-grid-client.tsx +21 -21
- package/src/navbar/navbar.tsx +16 -30
- package/src/navbar/settings.tsx +1 -1
- package/src/page-wrapper.tsx +3 -3
- package/src/renditions/html-client.tsx +8 -6
- package/src/renditions/html.tsx +3 -1
- package/src/restriction-menu/restriction-menu-item.tsx +48 -58
- package/src/restriction-menu/restriction-selection-command-menu.tsx +445 -0
- package/src/restriction-menu/restriction-selection-menu.tsx +5 -7
- package/src/restriction-menu/taxonomy-restriction-command-menu.tsx +111 -0
- package/src/restriction-menu/taxonomy-restriction-menu.tsx +19 -12
- package/src/results/filter-navbar.tsx +81 -76
- package/src/results/filter-sidebar/context.tsx +32 -0
- package/src/results/filter-sidebar/index.tsx +40 -35
- package/src/results/generic/search-results-client.tsx +5 -4
- package/src/results/generic/table-result-list.tsx +16 -16
- package/src/results/information-unit-search-results-card-list.tsx +4 -1
- package/src/results/information-unit-search-results-cards.tsx +169 -69
- package/src/results/pagination.tsx +43 -40
- package/src/search-input.tsx +4 -2
- package/src/toc/toc-breadcrumb.tsx +1 -1
- package/src/toc/toc-browse-controls.tsx +2 -2
- package/src/toc/toc-tree-panel.tsx +19 -16
- package/src/article/article-content.tsx +0 -19
- package/src/breadcrumb.tsx +0 -124
- package/src/directoryNodes/tree-of-content.tsx +0 -68
- package/src/render-article.tsx +0 -75
- package/src/restriction-menu/restriction-menu-container.tsx +0 -4
- package/src/restriction-menu/restriction-menu.tsx +0 -4
- package/src/stores/__tests__/favorites-store.test.ts +0 -54
- package/src/stores/favorites-store.ts +0 -163
- /package/src/{render-article.module.css → article/render-article.module.css} +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { DomainEntityModel } from "@c-rex/interfaces";
|
|
4
|
+
import { FC, ReactNode, useMemo, useState } from "react";
|
|
5
|
+
import * as ComponentOptions from "../generated/client-components";
|
|
6
|
+
import { Skeleton } from "@c-rex/ui/skeleton";
|
|
7
|
+
import { RestrictionSelectionCommandMenu } from "./restriction-selection-command-menu";
|
|
8
|
+
|
|
9
|
+
type GenericRequestData = {
|
|
10
|
+
items?: DomainEntityModel[];
|
|
11
|
+
pageInfo?: {
|
|
12
|
+
totalItemCount?: number;
|
|
13
|
+
};
|
|
14
|
+
} | null | undefined;
|
|
15
|
+
type GenericQueryParams = Record<string, unknown> & { Restrict?: string[]; PageSize?: number; Fields?: string[] };
|
|
16
|
+
type GenericRequestProps = {
|
|
17
|
+
queryParams?: GenericQueryParams;
|
|
18
|
+
children: (props: { data?: GenericRequestData; isLoading?: boolean }) => ReactNode;
|
|
19
|
+
};
|
|
20
|
+
type RestrictionMenuFetchMode = "all" | "deferred";
|
|
21
|
+
|
|
22
|
+
export type TaxonomyRestrictionCommandMenuProps = {
|
|
23
|
+
restrictField: string;
|
|
24
|
+
navigationMenuListClassName?: string;
|
|
25
|
+
itemsToRender?: number;
|
|
26
|
+
requestType: keyof typeof ComponentOptions;
|
|
27
|
+
onlyUsedEntries?: boolean;
|
|
28
|
+
enableHierarchy?: boolean;
|
|
29
|
+
fetchMode?: RestrictionMenuFetchMode;
|
|
30
|
+
showAllWhenEmpty?: boolean;
|
|
31
|
+
queryParams?: GenericQueryParams;
|
|
32
|
+
stripLabelPrefix?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const TaxonomyRestrictionCommandMenu: FC<TaxonomyRestrictionCommandMenuProps> = ({
|
|
36
|
+
queryParams,
|
|
37
|
+
restrictField,
|
|
38
|
+
itemsToRender = 7,
|
|
39
|
+
requestType,
|
|
40
|
+
onlyUsedEntries = true,
|
|
41
|
+
enableHierarchy = false,
|
|
42
|
+
fetchMode = "deferred",
|
|
43
|
+
showAllWhenEmpty = true,
|
|
44
|
+
navigationMenuListClassName = "items-center justify-start gap-4 flex-row",
|
|
45
|
+
stripLabelPrefix,
|
|
46
|
+
}) => {
|
|
47
|
+
const [loadAll, setLoadAll] = useState(false);
|
|
48
|
+
const RequestComponent = ComponentOptions[requestType] as unknown as FC<GenericRequestProps>;
|
|
49
|
+
const queryRestrict = queryParams?.Restrict || [];
|
|
50
|
+
const restrict = onlyUsedEntries ? ["hasInformationUnits=true", ...queryRestrict] : queryRestrict;
|
|
51
|
+
const explicitPageSize =
|
|
52
|
+
Number.isFinite(Number(queryParams?.PageSize)) && Number(queryParams?.PageSize) > 0
|
|
53
|
+
? Number(queryParams?.PageSize)
|
|
54
|
+
: undefined;
|
|
55
|
+
const resolvedPageSize = useMemo(() => {
|
|
56
|
+
if (explicitPageSize) return explicitPageSize;
|
|
57
|
+
if (fetchMode === "deferred" && !loadAll) return Math.max(itemsToRender, 1);
|
|
58
|
+
return 100;
|
|
59
|
+
}, [explicitPageSize, fetchMode, itemsToRender, loadAll]);
|
|
60
|
+
const requestedFields = Array.isArray(queryParams?.Fields) ? queryParams.Fields : undefined;
|
|
61
|
+
const resolvedFields = useMemo(() => {
|
|
62
|
+
const baseFields = requestedFields && requestedFields.length > 0 ? requestedFields : ["labels"];
|
|
63
|
+
if (!enableHierarchy) return baseFields;
|
|
64
|
+
const withParents = new Set(baseFields);
|
|
65
|
+
withParents.add("parents");
|
|
66
|
+
return Array.from(withParents);
|
|
67
|
+
}, [enableHierarchy, requestedFields]);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<RequestComponent
|
|
71
|
+
queryParams={{
|
|
72
|
+
...queryParams,
|
|
73
|
+
Fields: resolvedFields,
|
|
74
|
+
Links: true,
|
|
75
|
+
Sort: ["labels.value"],
|
|
76
|
+
PageSize: resolvedPageSize,
|
|
77
|
+
Restrict: restrict,
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{({ data, isLoading }) => {
|
|
81
|
+
if (isLoading) return (
|
|
82
|
+
<Skeleton className="w-full h-9 rounded-full" />
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!data) return null;
|
|
86
|
+
|
|
87
|
+
const itemCount = data.items?.length || 0;
|
|
88
|
+
const totalItemCount = data.pageInfo?.totalItemCount;
|
|
89
|
+
const hasMoreFromServer = typeof totalItemCount === "number" ? totalItemCount > itemCount : false;
|
|
90
|
+
const hasMoreItems = hasMoreFromServer || (explicitPageSize ? false : (fetchMode === "deferred" && !loadAll && itemCount >= resolvedPageSize));
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<RestrictionSelectionCommandMenu
|
|
94
|
+
restrictField={restrictField}
|
|
95
|
+
items={data.items || []}
|
|
96
|
+
enableHierarchy={enableHierarchy}
|
|
97
|
+
hasMoreItems={hasMoreItems}
|
|
98
|
+
showAllWhenEmpty={showAllWhenEmpty}
|
|
99
|
+
onRequestMore={() => {
|
|
100
|
+
if (fetchMode === "deferred" && !explicitPageSize) {
|
|
101
|
+
setLoadAll(true);
|
|
102
|
+
}
|
|
103
|
+
}}
|
|
104
|
+
navigationMenuListClassName={navigationMenuListClassName}
|
|
105
|
+
stripLabelPrefix={stripLabelPrefix}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}}
|
|
109
|
+
</RequestComponent>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
@@ -5,6 +5,7 @@ import { FC, ReactNode, useMemo, useState } from "react";
|
|
|
5
5
|
import * as ComponentOptions from "../generated/client-components";
|
|
6
6
|
import { Skeleton } from "@c-rex/ui/skeleton";
|
|
7
7
|
import { RestrictionSelectionMenu } from "./restriction-selection-menu";
|
|
8
|
+
import { DEVICE_OPTIONS } from "@c-rex/constants";
|
|
8
9
|
|
|
9
10
|
type GenericRequestData = {
|
|
10
11
|
items?: DomainEntityModel[];
|
|
@@ -22,7 +23,11 @@ type RestrictionMenuFetchMode = "all" | "deferred";
|
|
|
22
23
|
export type TaxonomyRestrictionMenuProps = {
|
|
23
24
|
restrictField: string;
|
|
24
25
|
navigationMenuListClassName?: string;
|
|
25
|
-
|
|
26
|
+
itemsByRow?: {
|
|
27
|
+
[DEVICE_OPTIONS.MOBILE]: number;
|
|
28
|
+
[DEVICE_OPTIONS.TABLET]: number;
|
|
29
|
+
[DEVICE_OPTIONS.DESKTOP]: number;
|
|
30
|
+
};
|
|
26
31
|
requestType: keyof typeof ComponentOptions;
|
|
27
32
|
onlyUsedEntries?: boolean;
|
|
28
33
|
enableHierarchy?: boolean;
|
|
@@ -31,16 +36,22 @@ export type TaxonomyRestrictionMenuProps = {
|
|
|
31
36
|
queryParams?: GenericQueryParams;
|
|
32
37
|
};
|
|
33
38
|
|
|
39
|
+
const DEFAULT_ITEMS_BY_ROW = {
|
|
40
|
+
[DEVICE_OPTIONS.MOBILE]: 7,
|
|
41
|
+
[DEVICE_OPTIONS.TABLET]: 7,
|
|
42
|
+
[DEVICE_OPTIONS.DESKTOP]: 7,
|
|
43
|
+
};
|
|
44
|
+
|
|
34
45
|
export const TaxonomyRestrictionMenu: FC<TaxonomyRestrictionMenuProps> = ({
|
|
35
46
|
queryParams,
|
|
36
47
|
restrictField,
|
|
37
|
-
|
|
48
|
+
itemsByRow = DEFAULT_ITEMS_BY_ROW,
|
|
38
49
|
requestType,
|
|
39
50
|
onlyUsedEntries = true,
|
|
40
51
|
enableHierarchy = false,
|
|
41
52
|
fetchMode = "deferred",
|
|
42
53
|
showAllWhenEmpty = true,
|
|
43
|
-
navigationMenuListClassName = "items-center justify-
|
|
54
|
+
navigationMenuListClassName = "items-center justify-start gap-4 flex-row",
|
|
44
55
|
}) => {
|
|
45
56
|
const [loadAll, setLoadAll] = useState(false);
|
|
46
57
|
const RequestComponent = ComponentOptions[requestType] as unknown as FC<GenericRequestProps>;
|
|
@@ -50,11 +61,12 @@ export const TaxonomyRestrictionMenu: FC<TaxonomyRestrictionMenuProps> = ({
|
|
|
50
61
|
Number.isFinite(Number(queryParams?.PageSize)) && Number(queryParams?.PageSize) > 0
|
|
51
62
|
? Number(queryParams?.PageSize)
|
|
52
63
|
: undefined;
|
|
64
|
+
const maxItemsByRow = useMemo(() => Math.max(...Object.values(itemsByRow)), [itemsByRow]);
|
|
53
65
|
const resolvedPageSize = useMemo(() => {
|
|
54
66
|
if (explicitPageSize) return explicitPageSize;
|
|
55
|
-
if (fetchMode === "deferred" && !loadAll) return Math.max(
|
|
67
|
+
if (fetchMode === "deferred" && !loadAll) return Math.max(maxItemsByRow, 1);
|
|
56
68
|
return 100;
|
|
57
|
-
}, [explicitPageSize, fetchMode,
|
|
69
|
+
}, [explicitPageSize, fetchMode, maxItemsByRow, loadAll]);
|
|
58
70
|
const requestedFields = Array.isArray(queryParams?.Fields) ? queryParams.Fields : undefined;
|
|
59
71
|
const resolvedFields = useMemo(() => {
|
|
60
72
|
const baseFields = requestedFields && requestedFields.length > 0 ? requestedFields : ["labels"];
|
|
@@ -77,13 +89,7 @@ export const TaxonomyRestrictionMenu: FC<TaxonomyRestrictionMenuProps> = ({
|
|
|
77
89
|
>
|
|
78
90
|
{({ data, isLoading }) => {
|
|
79
91
|
if (isLoading) return (
|
|
80
|
-
<
|
|
81
|
-
<Skeleton className="w-12 h-9 rounded-full" />
|
|
82
|
-
{Array(itemsToRender).fill(0).map((_, index) => (
|
|
83
|
-
<Skeleton key={`skeleton-${index}`} className="w-28 h-9 rounded-full" />
|
|
84
|
-
))}
|
|
85
|
-
<Skeleton className="w-20 h-9 rounded-full" />
|
|
86
|
-
</div>
|
|
92
|
+
<Skeleton className="w-full h-9 rounded-full" />
|
|
87
93
|
);
|
|
88
94
|
|
|
89
95
|
if (!data) return null;
|
|
@@ -100,6 +106,7 @@ export const TaxonomyRestrictionMenu: FC<TaxonomyRestrictionMenuProps> = ({
|
|
|
100
106
|
enableHierarchy={enableHierarchy}
|
|
101
107
|
hasMoreItems={hasMoreItems}
|
|
102
108
|
showAllWhenEmpty={showAllWhenEmpty}
|
|
109
|
+
itemsByRow={itemsByRow}
|
|
103
110
|
onRequestMore={() => {
|
|
104
111
|
if (fetchMode === "deferred" && !explicitPageSize) {
|
|
105
112
|
setLoadAll(true);
|
|
@@ -6,9 +6,11 @@ import { Button } from "@c-rex/ui/button";
|
|
|
6
6
|
import { Funnel, X } from "lucide-react";
|
|
7
7
|
import { Badge } from "@c-rex/ui/badge";
|
|
8
8
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@c-rex/ui/tooltip";
|
|
9
|
-
import { OPERATOR_OPTIONS } from "@c-rex/constants";
|
|
9
|
+
import { DEVICE_OPTIONS, OPERATOR_OPTIONS } from "@c-rex/constants";
|
|
10
10
|
import { Tags } from "@c-rex/interfaces";
|
|
11
|
+
import { useBreakpoint } from "@c-rex/ui/hooks";
|
|
11
12
|
import { applyFacetPropertyVisibility, FacetLabelOverrides, memoizeFilteredTags } from "./filter-sidebar/utils";
|
|
13
|
+
import { useFilterSidebarState } from "./filter-sidebar/context";
|
|
12
14
|
import { useSearchSettingsStore } from "../stores/search-settings-store";
|
|
13
15
|
import { useRestrictionStore } from "../stores/restriction-store";
|
|
14
16
|
import { useSearchNavigationStore } from "../stores/search-navigation-store";
|
|
@@ -38,6 +40,9 @@ export const FilterNavbar: FC<FilterNavbarProps> = ({
|
|
|
38
40
|
}) => {
|
|
39
41
|
const t = useTranslations()
|
|
40
42
|
const locale = useLocale()
|
|
43
|
+
const device = useBreakpoint();
|
|
44
|
+
const isMobile = device !== null && device === DEVICE_OPTIONS.MOBILE
|
|
45
|
+
const { setIsMobileFiltersOpen } = useFilterSidebarState();
|
|
41
46
|
const restrictionList = useRestrictionStore((state) => state.restrictionList);
|
|
42
47
|
const startSearchNavigation = useSearchNavigationStore((state) => state.start);
|
|
43
48
|
const [params, setParams] = useQueryStates({
|
|
@@ -59,8 +64,6 @@ export const FilterNavbar: FC<FilterNavbarProps> = ({
|
|
|
59
64
|
history: 'push',
|
|
60
65
|
shallow: false,
|
|
61
66
|
});
|
|
62
|
-
const isMobile = false
|
|
63
|
-
|
|
64
67
|
|
|
65
68
|
const filteredTags = useMemo(() => {
|
|
66
69
|
const resolved = memoizeFilteredTags(tags, params.filter, params.packages, {
|
|
@@ -70,6 +73,7 @@ export const FilterNavbar: FC<FilterNavbarProps> = ({
|
|
|
70
73
|
});
|
|
71
74
|
return applyFacetPropertyVisibility(resolved, includeProperties, excludeProperties);
|
|
72
75
|
}, [tags, params.filter, params.packages, locale, facetLabelOverrides, includeZeroHits, includeProperties, excludeProperties]);
|
|
76
|
+
const hasMobileFilters = Object.entries(filteredTags).length > 0;
|
|
73
77
|
|
|
74
78
|
const humanizeMetadataKey = (key: string): string =>
|
|
75
79
|
key
|
|
@@ -193,99 +197,100 @@ export const FilterNavbar: FC<FilterNavbarProps> = ({
|
|
|
193
197
|
})
|
|
194
198
|
|
|
195
199
|
return filters
|
|
196
|
-
}, [filteredTags, params]);
|
|
200
|
+
}, [filteredTags, params, resolveSectionLabelByKey, restrictionList, t]);
|
|
197
201
|
|
|
198
202
|
if (filters == null || filters.length === 0) {
|
|
199
203
|
return null;
|
|
200
204
|
}
|
|
201
205
|
|
|
202
206
|
return (
|
|
203
|
-
<div className="pb-4 flex
|
|
204
|
-
|
|
205
|
-
<div className="flex flex-wrap gap-2">
|
|
207
|
+
<div className="pb-4 flex flex-wrap gap-1">
|
|
208
|
+
{hasMobileFilters && (
|
|
206
209
|
<Button
|
|
207
210
|
size="sm"
|
|
208
211
|
variant="secondary"
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
className="lg:hidden"
|
|
213
|
+
aria-label={t("filter.filters")}
|
|
214
|
+
onClick={() => setIsMobileFiltersOpen(true)}
|
|
211
215
|
>
|
|
212
216
|
<Funnel className="h-2" />
|
|
213
217
|
</Button>
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
key={`grouped-filters`}
|
|
243
|
-
variant="outline"
|
|
244
|
-
className="h-8"
|
|
245
|
-
>
|
|
246
|
-
+{filters.length - 1} {t("filter.filters")}
|
|
247
|
-
</Badge>
|
|
248
|
-
|
|
249
|
-
</TooltipTrigger>
|
|
250
|
-
|
|
251
|
-
<TooltipContent>
|
|
252
|
-
{filters.slice(1).map((item) => {
|
|
253
|
-
const label = item?.name ? item.name : item.key
|
|
254
|
-
const returnString = `${label}: ${item.value}`;
|
|
255
|
-
return <div className="capitalize" key={returnString}>{returnString}</div>;
|
|
256
|
-
})}
|
|
257
|
-
</TooltipContent>
|
|
258
|
-
</Tooltip>
|
|
259
|
-
</TooltipProvider>
|
|
260
|
-
) : (
|
|
261
|
-
<>
|
|
262
|
-
{filters.slice(1).map((item) => (
|
|
218
|
+
)}
|
|
219
|
+
|
|
220
|
+
{filters.length > 0 && (
|
|
221
|
+
<>
|
|
222
|
+
{filters.slice(0, 1).map((item) => (
|
|
223
|
+
<Badge
|
|
224
|
+
key={`${item.key}-${item?.value}`}
|
|
225
|
+
variant="outline"
|
|
226
|
+
className="h-8"
|
|
227
|
+
>
|
|
228
|
+
{item?.name ? item.name : item.key}: {item.value}
|
|
229
|
+
|
|
230
|
+
{item.removable && (
|
|
231
|
+
<Button size="xs" variant="ghost" onClick={() => {
|
|
232
|
+
startSearchNavigation();
|
|
233
|
+
setParams({ [item.key]: item?.default })
|
|
234
|
+
}}>
|
|
235
|
+
<X className="h-2" />
|
|
236
|
+
</Button>
|
|
237
|
+
)}
|
|
238
|
+
</Badge>
|
|
239
|
+
))}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
{isMobile && filters.length > 1 ? (
|
|
243
|
+
<TooltipProvider>
|
|
244
|
+
<Tooltip delayDuration={100}>
|
|
245
|
+
<TooltipTrigger>
|
|
263
246
|
<Badge
|
|
264
|
-
key={
|
|
247
|
+
key={`grouped-filters`}
|
|
265
248
|
variant="outline"
|
|
266
249
|
className="h-8"
|
|
267
250
|
>
|
|
268
|
-
{
|
|
269
|
-
|
|
270
|
-
{item.removable && (
|
|
271
|
-
<Button size="xs" variant="ghost" onClick={() => {
|
|
272
|
-
startSearchNavigation();
|
|
273
|
-
setParams({ [item.key]: item?.default })
|
|
274
|
-
}}>
|
|
275
|
-
<X className="h-2" />
|
|
276
|
-
</Button>
|
|
277
|
-
)}
|
|
251
|
+
+{filters.length - 1} {t("filter.filters")}
|
|
278
252
|
</Badge>
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
253
|
+
|
|
254
|
+
</TooltipTrigger>
|
|
255
|
+
|
|
256
|
+
<TooltipContent>
|
|
257
|
+
{filters.slice(1).map((item) => {
|
|
258
|
+
const label = item?.name ? item.name : item.key
|
|
259
|
+
const returnString = `${label}: ${item.value}`;
|
|
260
|
+
return <div className="capitalize" key={returnString}>{returnString}</div>;
|
|
261
|
+
})}
|
|
262
|
+
</TooltipContent>
|
|
263
|
+
</Tooltip>
|
|
264
|
+
</TooltipProvider>
|
|
265
|
+
) : (
|
|
266
|
+
<>
|
|
267
|
+
{filters.slice(1).map((item) => (
|
|
268
|
+
<Badge
|
|
269
|
+
key={`${item.key}-${item?.value}`}
|
|
270
|
+
variant="outline"
|
|
271
|
+
className="h-8"
|
|
272
|
+
>
|
|
273
|
+
{item?.name ? item.name : item.key}: {item.value}
|
|
274
|
+
|
|
275
|
+
{item.removable && (
|
|
276
|
+
<Button size="xs" variant="ghost" onClick={() => {
|
|
277
|
+
startSearchNavigation();
|
|
278
|
+
setParams({ [item.key]: item?.default })
|
|
279
|
+
}}>
|
|
280
|
+
<X className="h-2" />
|
|
281
|
+
</Button>
|
|
282
|
+
)}
|
|
283
|
+
</Badge>
|
|
284
|
+
))}
|
|
285
|
+
</>
|
|
286
|
+
)}
|
|
287
|
+
</>
|
|
288
|
+
)}
|
|
285
289
|
|
|
286
290
|
<Button
|
|
287
291
|
size="sm"
|
|
288
292
|
variant="secondary"
|
|
293
|
+
className="ml-auto"
|
|
289
294
|
onClick={() => {
|
|
290
295
|
startSearchNavigation();
|
|
291
296
|
setParams({ filter: null, packages: null })
|
|
@@ -294,6 +299,6 @@ export const FilterNavbar: FC<FilterNavbarProps> = ({
|
|
|
294
299
|
{t("reset")}
|
|
295
300
|
</Button>
|
|
296
301
|
|
|
297
|
-
</div>
|
|
302
|
+
</div >
|
|
298
303
|
);
|
|
299
304
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { FC, ReactNode, createContext, useContext, useMemo, useState } from "react";
|
|
4
|
+
|
|
5
|
+
type FilterSidebarContextValue = {
|
|
6
|
+
isMobileFiltersOpen: boolean
|
|
7
|
+
setIsMobileFiltersOpen: (open: boolean) => void
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const noop = () => undefined;
|
|
11
|
+
|
|
12
|
+
const FilterSidebarContext = createContext<FilterSidebarContextValue>({
|
|
13
|
+
isMobileFiltersOpen: false,
|
|
14
|
+
setIsMobileFiltersOpen: noop,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const FilterSidebarProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
|
18
|
+
const [isMobileFiltersOpen, setIsMobileFiltersOpen] = useState(false);
|
|
19
|
+
|
|
20
|
+
const value = useMemo(() => ({
|
|
21
|
+
isMobileFiltersOpen,
|
|
22
|
+
setIsMobileFiltersOpen,
|
|
23
|
+
}), [isMobileFiltersOpen]);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<FilterSidebarContext.Provider value={value}>
|
|
27
|
+
{children}
|
|
28
|
+
</FilterSidebarContext.Provider>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const useFilterSidebarState = () => useContext(FilterSidebarContext);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { FC, useMemo, useState } from "react";
|
|
3
|
+
import { FC, useEffect, useMemo, useState } from "react";
|
|
4
4
|
import { useLocale, useTranslations } from 'next-intl'
|
|
5
5
|
import { Check, ChevronDown, ChevronRight } from "lucide-react"
|
|
6
6
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@c-rex/ui/collapsible";
|
|
7
|
+
import { useBreakpoint } from "@c-rex/ui/hooks";
|
|
8
|
+
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@c-rex/ui/sheet";
|
|
7
9
|
import {
|
|
8
10
|
SidebarContent,
|
|
9
11
|
SidebarGroup,
|
|
@@ -30,6 +32,8 @@ import {
|
|
|
30
32
|
import { FilterItem, Tags } from "@c-rex/interfaces";
|
|
31
33
|
import type { TaxonomyResult } from "@c-rex/services/read-models";
|
|
32
34
|
import { useSearchNavigationStore } from "../../stores/search-navigation-store";
|
|
35
|
+
import { DEVICE_OPTIONS } from "@c-rex/constants";
|
|
36
|
+
import { useFilterSidebarState } from "./context";
|
|
33
37
|
|
|
34
38
|
|
|
35
39
|
export interface FilterSidebarProps {
|
|
@@ -42,9 +46,7 @@ export interface FilterSidebarProps {
|
|
|
42
46
|
excludeProperties?: string[]
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
//TODO; check layout on mobile
|
|
47
|
-
export const FilterSidebar: FC<FilterSidebarProps> = ({
|
|
49
|
+
const FilterSidebar: FC<FilterSidebarProps> = ({
|
|
48
50
|
tags,
|
|
49
51
|
totalItemCount,
|
|
50
52
|
facetLabelOverrides,
|
|
@@ -55,6 +57,9 @@ export const FilterSidebar: FC<FilterSidebarProps> = ({
|
|
|
55
57
|
}) => {
|
|
56
58
|
const t = useTranslations();
|
|
57
59
|
const locale = useLocale();
|
|
60
|
+
const device = useBreakpoint();
|
|
61
|
+
const isMobile = device !== null && (device === DEVICE_OPTIONS.MOBILE || device === DEVICE_OPTIONS.TABLET);
|
|
62
|
+
const { isMobileFiltersOpen, setIsMobileFiltersOpen } = useFilterSidebarState();
|
|
58
63
|
const [params, setParams] = useQueryStates({
|
|
59
64
|
packages: parseAsString,
|
|
60
65
|
filter: parseAsString,
|
|
@@ -379,8 +384,7 @@ export const FilterSidebar: FC<FilterSidebarProps> = ({
|
|
|
379
384
|
};
|
|
380
385
|
|
|
381
386
|
const content = (
|
|
382
|
-
<SidebarContent className="!gap-0" suppressHydrationWarning>
|
|
383
|
-
|
|
387
|
+
<SidebarContent className="!gap-0 capitalize" suppressHydrationWarning>
|
|
384
388
|
{Object.entries(filteredTags).map(([key, value]) => {
|
|
385
389
|
const items = value as ResolvedFilterItem[];
|
|
386
390
|
const hasTaxonomy = Boolean(facetTaxonomies?.[key]);
|
|
@@ -403,7 +407,7 @@ export const FilterSidebar: FC<FilterSidebarProps> = ({
|
|
|
403
407
|
<Collapsible defaultOpen className="py-0 group/collapsible">
|
|
404
408
|
<SidebarGroup className="!p-1">
|
|
405
409
|
<SidebarGroupLabel asChild className="hover:bg-sidebar-accent text-sidebar-accent-foreground text-sm font-bold">
|
|
406
|
-
<CollapsibleTrigger className="!h-8">
|
|
410
|
+
<CollapsibleTrigger className="!h-8 capitalize">
|
|
407
411
|
{propertyLabel}
|
|
408
412
|
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
|
409
413
|
</CollapsibleTrigger>
|
|
@@ -517,36 +521,37 @@ export const FilterSidebar: FC<FilterSidebarProps> = ({
|
|
|
517
521
|
</SidebarContent>
|
|
518
522
|
)
|
|
519
523
|
|
|
520
|
-
/*
|
|
521
|
-
if (isMobile) {
|
|
522
|
-
return (
|
|
523
|
-
|
|
524
|
-
<SheetContent
|
|
525
|
-
side="left"
|
|
526
|
-
className="!pt-6 !px-2 w-[400px] overflow-y-auto"
|
|
527
|
-
>
|
|
528
|
-
<SheetHeader className="justify-center items-end font-bold">
|
|
529
|
-
{t("filter.filters")}
|
|
530
|
-
<span className="text-xs text-muted-foreground leading-5">
|
|
531
|
-
{totalItemCount} {t("results.results")}
|
|
532
|
-
</span>
|
|
533
|
-
</SheetHeader>
|
|
534
|
-
{content}
|
|
535
|
-
</SheetContent>
|
|
536
|
-
)
|
|
537
|
-
}
|
|
538
|
-
*/
|
|
539
524
|
if (Object.entries(filteredTags).length === 0) return null;
|
|
540
525
|
|
|
541
526
|
return (
|
|
542
|
-
|
|
543
|
-
<
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
527
|
+
<>
|
|
528
|
+
<Sheet open={isMobile && isMobileFiltersOpen} onOpenChange={setIsMobileFiltersOpen}>
|
|
529
|
+
<SheetContent
|
|
530
|
+
side="left"
|
|
531
|
+
className="w-[calc(100vw-2rem)] max-w-sm overflow-y-auto !px-2 !pt-6 lg:hidden"
|
|
532
|
+
>
|
|
533
|
+
<SheetHeader className="justify-center items-end font-bold">
|
|
534
|
+
<SheetTitle>{t("filter.filters")}</SheetTitle>
|
|
535
|
+
<span className="text-xs text-muted-foreground leading-5">
|
|
536
|
+
{totalItemCount} {t("results.results")}
|
|
537
|
+
</span>
|
|
538
|
+
</SheetHeader>
|
|
539
|
+
{content}
|
|
540
|
+
</SheetContent>
|
|
541
|
+
</Sheet>
|
|
542
|
+
|
|
543
|
+
<div className="hidden w-60 rounded-md border bg-sidebar pb-4 lg:block lg:w-80">
|
|
544
|
+
<SidebarHeader className="justify-center items-end font-bold">
|
|
545
|
+
{t("filter.filters")}
|
|
546
|
+
<span className="text-xs text-muted-foreground leading-5">
|
|
547
|
+
{totalItemCount} {t("results.results")}
|
|
548
|
+
</span>
|
|
549
|
+
</SidebarHeader>
|
|
550
|
+
{content}
|
|
551
|
+
</div>
|
|
552
|
+
</>
|
|
551
553
|
);
|
|
552
554
|
};
|
|
555
|
+
|
|
556
|
+
export { FilterSidebarProvider } from "./context";
|
|
557
|
+
export { FilterSidebar };
|
|
@@ -156,9 +156,7 @@ const SearchResultsBody = <TItem extends CommonItemsModel>({
|
|
|
156
156
|
/>
|
|
157
157
|
|
|
158
158
|
<div className="flex-1 flex flex-col gap-4">
|
|
159
|
-
<Pagination pageInfo={data.pageInfo} className="pt-0" />
|
|
160
159
|
<Empty />
|
|
161
|
-
<Pagination pageInfo={data.pageInfo} />
|
|
162
160
|
</div>
|
|
163
161
|
</div>
|
|
164
162
|
</div>
|
|
@@ -183,7 +181,10 @@ const SearchResultsBody = <TItem extends CommonItemsModel>({
|
|
|
183
181
|
/>
|
|
184
182
|
|
|
185
183
|
<div className="flex-1 flex flex-col gap-4">
|
|
186
|
-
|
|
184
|
+
{Number(data.pageInfo.pageCount) > 1 && (
|
|
185
|
+
<Pagination pageInfo={data.pageInfo} className="pt-0" />
|
|
186
|
+
)}
|
|
187
|
+
|
|
187
188
|
<GenericTableResultList
|
|
188
189
|
items={data.items}
|
|
189
190
|
query={search}
|
|
@@ -217,7 +218,7 @@ export const GenericSearchResultsClient = <
|
|
|
217
218
|
const t = useTranslations();
|
|
218
219
|
|
|
219
220
|
return (
|
|
220
|
-
<div className="container">
|
|
221
|
+
<div className="flex-1 container">
|
|
221
222
|
<div className="flex gap-6 py-6">
|
|
222
223
|
<div className="flex-1">
|
|
223
224
|
<SuggestionsComponent
|
|
@@ -30,12 +30,12 @@ export const GenericTableResultList: FC<Props> = ({
|
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
32
|
<div className="rounded-md border mb-6 last:border-b-0">
|
|
33
|
-
<div className="font-bold text-sm
|
|
34
|
-
<div className="flex-1
|
|
35
|
-
<div className="w-
|
|
36
|
-
<div className="w-
|
|
33
|
+
<div className="font-bold text-sm px-2 py-4 gap-2 border-b items-center flex">
|
|
34
|
+
<div className="flex-1">{t("title")}</div>
|
|
35
|
+
<div className="w-28 flex justify-center">{t("type")}</div>
|
|
36
|
+
<div className="w-20 flex justify-center">{t("language")}</div>
|
|
37
37
|
{showFilesColumn &&
|
|
38
|
-
<div className="w-
|
|
38
|
+
<div className="w-8 flex justify-center">{t("files")}</div>
|
|
39
39
|
}
|
|
40
40
|
</div>
|
|
41
41
|
|
|
@@ -63,31 +63,31 @@ export const GenericTableResultList: FC<Props> = ({
|
|
|
63
63
|
return (
|
|
64
64
|
<div
|
|
65
65
|
className={cn(
|
|
66
|
-
"min-h-12 c-rex-result-item flex flex-wrap items-center border-b",
|
|
66
|
+
"min-h-12 c-rex-result-item flex flex-wrap items-center border-b p-2 gap-2",
|
|
67
67
|
`c-rex-result-${itemType.toLowerCase()}`,
|
|
68
68
|
)}
|
|
69
69
|
key={item.shortId}
|
|
70
70
|
>
|
|
71
|
-
<div className="flex-1
|
|
71
|
+
<div className="flex-1">
|
|
72
72
|
<Link href={link} className="hover:underline">{title}</Link>
|
|
73
73
|
</div>
|
|
74
74
|
|
|
75
|
-
<div className="w-
|
|
76
|
-
<
|
|
75
|
+
<div className="w-28 flex justify-center">
|
|
76
|
+
<Badge variant="secondary">
|
|
77
|
+
{itemType}
|
|
78
|
+
</Badge>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div className="w-20 flex justify-center">
|
|
82
|
+
<span className="w-8 inline-block">
|
|
77
83
|
{countryCode.length > 0 && (
|
|
78
84
|
<Flag countryCode={countryCode} />
|
|
79
85
|
)}
|
|
80
86
|
</span>
|
|
81
87
|
</div>
|
|
82
88
|
|
|
83
|
-
<div className="w-4/5 md:w-1/5 p-2 flex justify-center">
|
|
84
|
-
<Badge>
|
|
85
|
-
{itemType}
|
|
86
|
-
</Badge>
|
|
87
|
-
</div>
|
|
88
|
-
|
|
89
89
|
{showFilesColumn &&
|
|
90
|
-
<div className="w-1/5 flex justify-center
|
|
90
|
+
<div className="w-1/5 flex justify-center">
|
|
91
91
|
<FileDownloadDropdown renditions={item.renditions} />
|
|
92
92
|
</div>
|
|
93
93
|
}
|