@c-rex/components 0.3.0-build.35 → 0.3.0-build.36
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 +28 -36
- package/src/article/article-action-bar.tsx +4 -1
- 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 +2 -2
- 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 +35 -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/page-wrapper.tsx +1 -1
- 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 +444 -0
- package/src/restriction-menu/restriction-selection-menu.tsx +3 -5
- package/src/restriction-menu/taxonomy-restriction-command-menu.tsx +111 -0
- package/src/restriction-menu/taxonomy-restriction-menu.tsx +1 -7
- package/src/results/filter-navbar.tsx +81 -76
- package/src/results/filter-sidebar/context.tsx +32 -0
- package/src/results/filter-sidebar/index.tsx +44 -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/pagination.tsx +43 -40
- package/src/search-input.tsx +4 -2
- 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
|
@@ -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,
|
|
@@ -64,6 +69,10 @@ export const FilterSidebar: FC<FilterSidebarProps> = ({
|
|
|
64
69
|
});
|
|
65
70
|
const startSearchNavigation = useSearchNavigationStore((state) => state.start);
|
|
66
71
|
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
console.log(isMobile, isMobileFiltersOpen)
|
|
74
|
+
}, [isMobile, isMobileFiltersOpen]);
|
|
75
|
+
|
|
67
76
|
const filteredTags = useMemo(() => {
|
|
68
77
|
const resolved = memoizeFilteredTags(tags, params.filter, params.packages, {
|
|
69
78
|
uiLanguage: locale,
|
|
@@ -379,8 +388,7 @@ export const FilterSidebar: FC<FilterSidebarProps> = ({
|
|
|
379
388
|
};
|
|
380
389
|
|
|
381
390
|
const content = (
|
|
382
|
-
<SidebarContent className="!gap-0" suppressHydrationWarning>
|
|
383
|
-
|
|
391
|
+
<SidebarContent className="!gap-0 capitalize" suppressHydrationWarning>
|
|
384
392
|
{Object.entries(filteredTags).map(([key, value]) => {
|
|
385
393
|
const items = value as ResolvedFilterItem[];
|
|
386
394
|
const hasTaxonomy = Boolean(facetTaxonomies?.[key]);
|
|
@@ -403,7 +411,7 @@ export const FilterSidebar: FC<FilterSidebarProps> = ({
|
|
|
403
411
|
<Collapsible defaultOpen className="py-0 group/collapsible">
|
|
404
412
|
<SidebarGroup className="!p-1">
|
|
405
413
|
<SidebarGroupLabel asChild className="hover:bg-sidebar-accent text-sidebar-accent-foreground text-sm font-bold">
|
|
406
|
-
<CollapsibleTrigger className="!h-8">
|
|
414
|
+
<CollapsibleTrigger className="!h-8 capitalize">
|
|
407
415
|
{propertyLabel}
|
|
408
416
|
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
|
409
417
|
</CollapsibleTrigger>
|
|
@@ -517,36 +525,37 @@ export const FilterSidebar: FC<FilterSidebarProps> = ({
|
|
|
517
525
|
</SidebarContent>
|
|
518
526
|
)
|
|
519
527
|
|
|
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
528
|
if (Object.entries(filteredTags).length === 0) return null;
|
|
540
529
|
|
|
541
530
|
return (
|
|
542
|
-
|
|
543
|
-
<
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
531
|
+
<>
|
|
532
|
+
<Sheet open={isMobile && isMobileFiltersOpen} onOpenChange={setIsMobileFiltersOpen}>
|
|
533
|
+
<SheetContent
|
|
534
|
+
side="left"
|
|
535
|
+
className="w-[calc(100vw-2rem)] max-w-sm overflow-y-auto !px-2 !pt-6 lg:hidden"
|
|
536
|
+
>
|
|
537
|
+
<SheetHeader className="justify-center items-end font-bold">
|
|
538
|
+
<SheetTitle>{t("filter.filters")}</SheetTitle>
|
|
539
|
+
<span className="text-xs text-muted-foreground leading-5">
|
|
540
|
+
{totalItemCount} {t("results.results")}
|
|
541
|
+
</span>
|
|
542
|
+
</SheetHeader>
|
|
543
|
+
{content}
|
|
544
|
+
</SheetContent>
|
|
545
|
+
</Sheet>
|
|
546
|
+
|
|
547
|
+
<div className="hidden w-60 rounded-md border bg-sidebar pb-4 lg:block lg:w-80">
|
|
548
|
+
<SidebarHeader className="justify-center items-end font-bold">
|
|
549
|
+
{t("filter.filters")}
|
|
550
|
+
<span className="text-xs text-muted-foreground leading-5">
|
|
551
|
+
{totalItemCount} {t("results.results")}
|
|
552
|
+
</span>
|
|
553
|
+
</SidebarHeader>
|
|
554
|
+
{content}
|
|
555
|
+
</div>
|
|
556
|
+
</>
|
|
551
557
|
);
|
|
552
558
|
};
|
|
559
|
+
|
|
560
|
+
export { FilterSidebarProvider } from "./context";
|
|
561
|
+
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
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FC } from "react";
|
|
2
|
+
import { useTranslations } from "next-intl";
|
|
2
3
|
import { CommonItemsModel } from "@c-rex/interfaces";
|
|
3
4
|
import { FileStack } from "lucide-react";
|
|
4
5
|
import { FileDownloadDropdown } from "../renditions/file-download";
|
|
@@ -32,6 +33,8 @@ export const InformationUnitSearchResultsCardList: FC<InformationUnitSearchResul
|
|
|
32
33
|
imageFragmentSubjectIds = [],
|
|
33
34
|
descriptionFragmentSubjectIds = [],
|
|
34
35
|
}) => {
|
|
36
|
+
const t = useTranslations();
|
|
37
|
+
|
|
35
38
|
return (
|
|
36
39
|
<div className="flex-1">
|
|
37
40
|
{items.map((item, index) => {
|
|
@@ -115,7 +118,7 @@ export const InformationUnitSearchResultsCardList: FC<InformationUnitSearchResul
|
|
|
115
118
|
</Button>
|
|
116
119
|
</TooltipTrigger>
|
|
117
120
|
<TooltipContent>
|
|
118
|
-
|
|
121
|
+
{t("availableIn")}: {multipleVersions.join(", ")}
|
|
119
122
|
</TooltipContent>
|
|
120
123
|
</Tooltip>
|
|
121
124
|
)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import { FC
|
|
3
|
+
import { FC } from "react";
|
|
4
4
|
import {
|
|
5
5
|
Pagination as PaginationUI,
|
|
6
6
|
PaginationContent,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
PaginationNext,
|
|
11
11
|
PaginationPrevious,
|
|
12
12
|
} from "@c-rex/ui/pagination"
|
|
13
|
-
import {
|
|
13
|
+
import { usePathname, useSearchParams } from "next/navigation";
|
|
14
14
|
import { ResultContainerPageInfoModel } from "@c-rex/interfaces";
|
|
15
15
|
import { useTranslations } from "next-intl";
|
|
16
16
|
import { cn } from "@c-rex/utils";
|
|
@@ -22,6 +22,30 @@ interface PaginationProps {
|
|
|
22
22
|
|
|
23
23
|
type PaginationToken = number | "ellipsis-left" | "ellipsis-right";
|
|
24
24
|
|
|
25
|
+
const isValidPageNumber = (pageNumber: number, currentPage: number, pageCount: number) => (
|
|
26
|
+
pageNumber >= 1
|
|
27
|
+
&& pageNumber <= pageCount
|
|
28
|
+
&& pageNumber !== currentPage
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
export const buildPageHref = (
|
|
32
|
+
pathname: string,
|
|
33
|
+
currentSearch: string,
|
|
34
|
+
pageNumber: number,
|
|
35
|
+
currentPage: number,
|
|
36
|
+
pageCount: number,
|
|
37
|
+
) => {
|
|
38
|
+
if (!isValidPageNumber(pageNumber, currentPage, pageCount)) {
|
|
39
|
+
return `${pathname}${currentSearch}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const searchParams = new URLSearchParams(currentSearch);
|
|
43
|
+
searchParams.set("page", String(pageNumber));
|
|
44
|
+
|
|
45
|
+
const queryString = searchParams.toString();
|
|
46
|
+
return queryString.length > 0 ? `${pathname}?${queryString}` : pathname;
|
|
47
|
+
};
|
|
48
|
+
|
|
25
49
|
const buildPaginationTokens = (currentPage: number, pageCount: number): PaginationToken[] => {
|
|
26
50
|
if (pageCount <= 7) {
|
|
27
51
|
return Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
@@ -41,27 +65,8 @@ const buildPaginationTokens = (currentPage: number, pageCount: number): Paginati
|
|
|
41
65
|
export const Pagination: FC<PaginationProps> = ({ pageInfo, className }) => {
|
|
42
66
|
const disabledClass = "opacity-50 pointer-events-none";
|
|
43
67
|
const t = useTranslations("results");
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
parseAsInteger.withOptions({
|
|
47
|
-
history: 'push',
|
|
48
|
-
shallow: false,
|
|
49
|
-
})
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
const onChangePage = (event: MouseEvent<HTMLAnchorElement>, pageNumber: number) => {
|
|
53
|
-
event.preventDefault();
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
pageNumber < 1
|
|
57
|
-
|| (pageInfo.pageCount !== undefined && pageNumber > pageInfo.pageCount)
|
|
58
|
-
|| pageNumber === pageInfo.pageNumber
|
|
59
|
-
) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
void setPage(pageNumber);
|
|
64
|
-
}
|
|
68
|
+
const pathname = usePathname();
|
|
69
|
+
const searchParams = useSearchParams();
|
|
65
70
|
|
|
66
71
|
const pageNumber = pageInfo.pageNumber || 1;
|
|
67
72
|
const pageCount = pageInfo.pageCount || 1;
|
|
@@ -69,32 +74,25 @@ export const Pagination: FC<PaginationProps> = ({ pageInfo, className }) => {
|
|
|
69
74
|
const firstItemOnPage = pageInfo.firstItemOnPage || 0;
|
|
70
75
|
const lastItemOnPage = pageInfo.lastItemOnPage || 0;
|
|
71
76
|
const tokens = buildPaginationTokens(pageNumber, pageCount);
|
|
77
|
+
const currentSearch = searchParams.toString();
|
|
78
|
+
const search = currentSearch.length > 0 ? `?${currentSearch}` : "";
|
|
72
79
|
|
|
73
80
|
return (
|
|
74
81
|
<div className={cn("flex flex-col gap-3 py-4", className)}>
|
|
75
|
-
<
|
|
76
|
-
<span>
|
|
82
|
+
<PaginationUI className="py-4 items-center justify-center sm:justify-between">
|
|
83
|
+
<span className="hidden sm:block text-sm text-muted-foreground">
|
|
77
84
|
{t("paginationResults", {
|
|
78
85
|
first: firstItemOnPage,
|
|
79
86
|
last: lastItemOnPage,
|
|
80
87
|
total: totalItemCount,
|
|
81
88
|
})}
|
|
82
89
|
</span>
|
|
83
|
-
<span>
|
|
84
|
-
{t("paginationPage", {
|
|
85
|
-
page: pageNumber,
|
|
86
|
-
pageCount,
|
|
87
|
-
})}
|
|
88
|
-
</span>
|
|
89
|
-
</div>
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
<PaginationContent className="justify-start">
|
|
91
|
+
<PaginationContent className="justify-between sm:justify-start w-full sm:w-auto">
|
|
93
92
|
<PaginationItem>
|
|
94
93
|
<PaginationPrevious
|
|
95
|
-
href=
|
|
94
|
+
href={buildPageHref(pathname, search, pageNumber - 1, pageNumber, pageCount)}
|
|
96
95
|
className={pageNumber === 1 ? disabledClass : ""}
|
|
97
|
-
onClick={(event) => onChangePage(event, pageNumber - 1)}
|
|
98
96
|
/>
|
|
99
97
|
</PaginationItem>
|
|
100
98
|
|
|
@@ -102,9 +100,8 @@ export const Pagination: FC<PaginationProps> = ({ pageInfo, className }) => {
|
|
|
102
100
|
typeof token === "number" ? (
|
|
103
101
|
<PaginationItem key={token}>
|
|
104
102
|
<PaginationLink
|
|
105
|
-
href=
|
|
103
|
+
href={buildPageHref(pathname, search, token, pageNumber, pageCount)}
|
|
106
104
|
isActive={token === pageNumber}
|
|
107
|
-
onClick={(event) => onChangePage(event, token)}
|
|
108
105
|
>
|
|
109
106
|
{token}
|
|
110
107
|
</PaginationLink>
|
|
@@ -118,12 +115,18 @@ export const Pagination: FC<PaginationProps> = ({ pageInfo, className }) => {
|
|
|
118
115
|
|
|
119
116
|
<PaginationItem>
|
|
120
117
|
<PaginationNext
|
|
121
|
-
href=
|
|
122
|
-
onClick={(event) => onChangePage(event, pageNumber + 1)}
|
|
118
|
+
href={buildPageHref(pathname, search, pageNumber + 1, pageNumber, pageCount)}
|
|
123
119
|
className={pageNumber === pageCount ? disabledClass : ""}
|
|
124
120
|
/>
|
|
125
121
|
</PaginationItem>
|
|
126
122
|
</PaginationContent>
|
|
123
|
+
|
|
124
|
+
<span className="hidden sm:block text-sm text-muted-foreground">
|
|
125
|
+
{t("paginationPage", {
|
|
126
|
+
page: pageNumber,
|
|
127
|
+
pageCount,
|
|
128
|
+
})}
|
|
129
|
+
</span>
|
|
127
130
|
</PaginationUI>
|
|
128
131
|
</div>
|
|
129
132
|
)
|