@c-rex/components 0.3.0-build.40 → 0.3.0-build.42

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.
@@ -31,6 +31,7 @@ export const RestrictionNavigationItem: FC<Props> = ({
31
31
  history: "push",
32
32
  });
33
33
  const startSearchNavigation = useSearchNavigationStore((state) => state.start);
34
+ const searchNavigationPending = useSearchNavigationStore((state) => state.pending);
34
35
 
35
36
  const { restrictionValue, shouldRemoveRestrictParam } = getRestrictionValue({
36
37
  shortId,
@@ -49,6 +50,8 @@ export const RestrictionNavigationItem: FC<Props> = ({
49
50
  <Button
50
51
  variant={selected ? "default" : "outline"}
51
52
  rounded="full"
53
+ aria-pressed={selected}
54
+ disabled={searchNavigationPending}
52
55
  onClick={() => {
53
56
  startSearchNavigation();
54
57
  if (shouldRemoveRestrictParam) {
@@ -82,6 +85,7 @@ export const RestrictionDropdownItem: FC<Props> = ({
82
85
  history: "push",
83
86
  });
84
87
  const startSearchNavigation = useSearchNavigationStore((state) => state.start);
88
+ const searchNavigationPending = useSearchNavigationStore((state) => state.pending);
85
89
 
86
90
  const { restrictionValue, shouldRemoveRestrictParam } = getRestrictionValue({
87
91
  shortId,
@@ -97,6 +101,8 @@ export const RestrictionDropdownItem: FC<Props> = ({
97
101
  <TooltipTrigger asChild>
98
102
  <Button
99
103
  variant={selected ? "default" : "ghost"}
104
+ aria-pressed={selected}
105
+ disabled={searchNavigationPending}
100
106
  onClick={() => {
101
107
  if (onClick) {
102
108
  onClick();
@@ -141,16 +147,16 @@ function getRestrictionValue({
141
147
 
142
148
  if (currentRestrict && multipleSelection) {
143
149
  if (selected) {
144
- const restrictionsLength = currentRestrict.split(",").length;
145
- //if there is only one restriction, we can remove the whole restrict param
146
- if (restrictionsLength === 1) {
150
+ const eqIndex = currentRestrict.indexOf("=");
151
+ if (eqIndex === -1) {
147
152
  shouldRemoveRestrictParam = true;
148
153
  } else {
149
- restrictParam = currentRestrict.replace(`${shortId}`, "").replace(",,", ",").replace(/(^,)|(,$)/g, "");
150
-
151
- // Remove the restrict param if nothing remains after '='
152
- if (/^[^=]+=$/.test(restrictParam)) {
154
+ const fieldPrefix = currentRestrict.slice(0, eqIndex + 1);
155
+ const ids = currentRestrict.slice(eqIndex + 1).split(",").filter((id) => id !== shortId);
156
+ if (ids.length === 0) {
153
157
  shouldRemoveRestrictParam = true;
158
+ } else {
159
+ restrictParam = fieldPrefix + ids.join(",");
154
160
  }
155
161
  }
156
162
  } else {
@@ -0,0 +1,51 @@
1
+ type FetchMode = "all" | "deferred";
2
+
3
+ export type ResolvedMenuState = {
4
+ restrict: string[];
5
+ resolvedPageSize: number;
6
+ hasMoreItems: boolean;
7
+ };
8
+
9
+ export function resolveMenuState({
10
+ queryRestrict = [],
11
+ onlyUsedEntries = true,
12
+ fetchMode = "deferred",
13
+ loadAll = false,
14
+ maxItemsByRow,
15
+ explicitPageSize,
16
+ itemCount,
17
+ totalItemCount,
18
+ }: {
19
+ queryRestrict?: string[];
20
+ onlyUsedEntries?: boolean;
21
+ fetchMode?: FetchMode;
22
+ loadAll?: boolean;
23
+ maxItemsByRow: number;
24
+ explicitPageSize?: number;
25
+ itemCount: number;
26
+ totalItemCount?: number;
27
+ }): ResolvedMenuState {
28
+ const restrict = onlyUsedEntries
29
+ ? ["hasInformationUnits=true", ...queryRestrict]
30
+ : queryRestrict;
31
+
32
+ const resolvedPageSize = explicitPageSize
33
+ ? explicitPageSize
34
+ : fetchMode === "deferred" && !loadAll
35
+ ? Math.max(maxItemsByRow, 1)
36
+ : 100;
37
+
38
+ const hasMoreFromServer =
39
+ typeof totalItemCount === "number" ? totalItemCount > itemCount : false;
40
+ const allItemsLoaded =
41
+ typeof totalItemCount === "number" && itemCount >= totalItemCount;
42
+ const hasMoreItems =
43
+ hasMoreFromServer ||
44
+ (!explicitPageSize &&
45
+ !allItemsLoaded &&
46
+ fetchMode === "deferred" &&
47
+ !loadAll &&
48
+ itemCount >= resolvedPageSize);
49
+
50
+ return { restrict, resolvedPageSize, hasMoreItems };
51
+ }
@@ -6,6 +6,7 @@ import { FC, ReactNode, useMemo, useState } from "react";
6
6
  import * as ComponentOptions from "../generated/client-components";
7
7
  import { Skeleton } from "@c-rex/ui/skeleton";
8
8
  import { RestrictionSelectionMenu } from "./restriction-selection-menu";
9
+ import { resolveMenuState } from "./restriction-menu-logic";
9
10
  import { DEVICE_OPTIONS } from "@c-rex/constants";
10
11
 
11
12
  type GenericRequestData = {
@@ -62,18 +63,21 @@ export const TaxonomyRestrictionMenu: FC<TaxonomyRestrictionMenuProps> = ({
62
63
  }) => {
63
64
  const [loadAll, setLoadAll] = useState(false);
64
65
  const RequestComponent = ComponentOptions[requestType] as unknown as FC<GenericRequestProps>;
65
- const queryRestrict = queryParams?.Restrict || [];
66
- const restrict = onlyUsedEntries ? ["hasInformationUnits=true", ...queryRestrict] : queryRestrict;
66
+ const queryRestrict = useMemo(() => queryParams?.Restrict || [], [queryParams?.Restrict]);
67
67
  const explicitPageSize =
68
68
  Number.isFinite(Number(queryParams?.PageSize)) && Number(queryParams?.PageSize) > 0
69
69
  ? Number(queryParams?.PageSize)
70
70
  : undefined;
71
71
  const maxItemsByRow = useMemo(() => Math.max(...Object.values(itemsByRow)), [itemsByRow]);
72
- const resolvedPageSize = useMemo(() => {
73
- if (explicitPageSize) return explicitPageSize;
74
- if (fetchMode === "deferred" && !loadAll) return Math.max(maxItemsByRow, 1);
75
- return 100;
76
- }, [explicitPageSize, fetchMode, maxItemsByRow, loadAll]);
72
+ const { restrict, resolvedPageSize } = useMemo(() => resolveMenuState({
73
+ queryRestrict,
74
+ onlyUsedEntries,
75
+ fetchMode,
76
+ loadAll,
77
+ maxItemsByRow,
78
+ explicitPageSize,
79
+ itemCount: 0,
80
+ }), [queryRestrict, onlyUsedEntries, fetchMode, loadAll, maxItemsByRow, explicitPageSize]);
77
81
  const requestedFields = Array.isArray(queryParams?.Fields) ? queryParams.Fields : undefined;
78
82
  const resolvedFields = useMemo(() => {
79
83
  const baseFields = requestedFields && requestedFields.length > 0 ? requestedFields : ["labels"];
@@ -102,9 +106,16 @@ export const TaxonomyRestrictionMenu: FC<TaxonomyRestrictionMenuProps> = ({
102
106
  if (!data) return null;
103
107
 
104
108
  const itemCount = data.items?.length || 0;
105
- const totalItemCount = data.pageInfo?.totalItemCount;
106
- const hasMoreFromServer = typeof totalItemCount === "number" ? totalItemCount > itemCount : false;
107
- const hasMoreItems = hasMoreFromServer || (explicitPageSize ? false : (fetchMode === "deferred" && !loadAll && itemCount >= resolvedPageSize));
109
+ const { hasMoreItems } = resolveMenuState({
110
+ queryRestrict,
111
+ onlyUsedEntries,
112
+ fetchMode,
113
+ loadAll,
114
+ maxItemsByRow,
115
+ explicitPageSize,
116
+ itemCount,
117
+ totalItemCount: data.pageInfo?.totalItemCount,
118
+ });
108
119
 
109
120
  return (
110
121
  <RestrictionSelectionMenu
@@ -47,6 +47,7 @@ export const FilterNavbar: FC<FilterNavbarProps> = ({
47
47
  const { setIsMobileFiltersOpen } = useFilterSidebarState();
48
48
  const restrictionList = useRestrictionStore((state) => state.restrictionList);
49
49
  const startSearchNavigation = useSearchNavigationStore((state) => state.start);
50
+ const searchNavigationPending = useSearchNavigationStore((state) => state.pending);
50
51
  const effectiveFacetPresentation = facetPresentation || facetLabelOverrides;
51
52
  const [params, setParams] = useQueryStates({
52
53
  language: parseAsString.withDefault(useSearchSettingsStore.getState().language || ''),
@@ -207,7 +208,7 @@ export const FilterNavbar: FC<FilterNavbarProps> = ({
207
208
  }
208
209
 
209
210
  return (
210
- <div className="pb-4 flex flex-wrap gap-1">
211
+ <div className={`pb-4 flex flex-wrap gap-1 transition-opacity duration-150${searchNavigationPending ? " opacity-60 pointer-events-none" : ""}`}>
211
212
  {hasMobileFilters && (
212
213
  <Button
213
214
  size="sm"
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
 
3
- import { FC, useEffect, useMemo, useState } from "react";
3
+ import { FC, useMemo, useState } from "react";
4
4
  import { useLocale, useTranslations } from 'next-intl'
5
- import { Check, ChevronDown, ChevronRight } from "lucide-react"
5
+ import { Check, ChevronDown, ChevronRight, Loader2 } from "lucide-react"
6
6
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@c-rex/ui/collapsible";
7
7
  import { useBreakpoint } from "@c-rex/ui/hooks";
8
8
  import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@c-rex/ui/sheet";
@@ -76,6 +76,7 @@ const FilterSidebar: FC<FilterSidebarProps> = ({
76
76
  shallow: false,
77
77
  });
78
78
  const startSearchNavigation = useSearchNavigationStore((state) => state.start);
79
+ const searchNavigationPending = useSearchNavigationStore((state) => state.pending);
79
80
  const effectiveFacetPresentation = facetPresentation || facetLabelOverrides;
80
81
 
81
82
  const filteredTags = useMemo(() => {
@@ -105,6 +106,7 @@ const FilterSidebar: FC<FilterSidebarProps> = ({
105
106
  };
106
107
 
107
108
  const onClickHandler = (key: string, item: FilterItem) => {
109
+ if (!item.active && item.hits === 0) return;
108
110
  startSearchNavigation();
109
111
  if (item.active) {
110
112
  setParams(removeFilterItem(key, item, params.filter))
@@ -171,20 +173,24 @@ const FilterSidebar: FC<FilterSidebarProps> = ({
171
173
 
172
174
  return (
173
175
  <>
174
- {visibleItems.map((item) => (
175
- <SidebarMenuSubItem key={item.shortId}>
176
- <SidebarMenuSubButton
177
- className="cursor-pointer !py-1.5"
178
- data-taxonomy-id={item.taxonomyId}
179
- data-taxonomy-short-id={item.shortId}
180
- isActive={item.active}
181
- onClick={() => onClickHandler(key, item)}
182
- >
183
- {item.label} ({item.hits}/{item.total})
184
- {item.active && <Check className="ml-2" />}
185
- </SidebarMenuSubButton>
186
- </SidebarMenuSubItem>
187
- ))}
176
+ {visibleItems.map((item) => {
177
+ const isDisabled = !item.active && item.hits === 0;
178
+ return (
179
+ <SidebarMenuSubItem key={item.shortId}>
180
+ <SidebarMenuSubButton
181
+ className={isDisabled ? "!py-1 opacity-40 cursor-not-allowed" : "cursor-pointer !py-1"}
182
+ data-taxonomy-id={item.taxonomyId}
183
+ data-taxonomy-short-id={item.shortId}
184
+ isActive={item.active}
185
+ aria-disabled={isDisabled}
186
+ onClick={() => onClickHandler(key, item)}
187
+ >
188
+ {item.label} ({item.hits}/{item.total})
189
+ {item.active && <Check className="ml-2" />}
190
+ </SidebarMenuSubButton>
191
+ </SidebarMenuSubItem>
192
+ );
193
+ })}
188
194
  {(hasMoreItems || canShowLess) && (
189
195
  <SidebarMenuSubItem>
190
196
  <div className="flex items-center gap-3 px-2">
@@ -222,20 +228,24 @@ const FilterSidebar: FC<FilterSidebarProps> = ({
222
228
 
223
229
  return (
224
230
  <>
225
- {visibleItems.map((item) => (
226
- <SidebarMenuSubItem key={item.shortId}>
227
- <SidebarMenuSubButton
228
- className="cursor-pointer !py-1.5"
229
- data-taxonomy-id={item.taxonomyId}
230
- data-taxonomy-short-id={item.shortId}
231
- isActive={item.active}
232
- onClick={() => onClickHandler(facetKey, item)}
233
- >
234
- {item.label} ({item.hits}/{item.total})
235
- {item.active && <Check className="ml-2" />}
236
- </SidebarMenuSubButton>
237
- </SidebarMenuSubItem>
238
- ))}
231
+ {visibleItems.map((item) => {
232
+ const isDisabled = !item.active && item.hits === 0;
233
+ return (
234
+ <SidebarMenuSubItem key={item.shortId}>
235
+ <SidebarMenuSubButton
236
+ className={isDisabled ? "!py-1 opacity-40 cursor-not-allowed" : "cursor-pointer !py-1"}
237
+ data-taxonomy-id={item.taxonomyId}
238
+ data-taxonomy-short-id={item.shortId}
239
+ isActive={item.active}
240
+ aria-disabled={isDisabled}
241
+ onClick={() => onClickHandler(facetKey, item)}
242
+ >
243
+ {item.label} ({item.hits}/{item.total})
244
+ {item.active && <Check className="ml-2" />}
245
+ </SidebarMenuSubButton>
246
+ </SidebarMenuSubItem>
247
+ );
248
+ })}
239
249
  {(hasMoreItems || canShowLess) && (
240
250
  <SidebarMenuSubItem>
241
251
  <div className="flex items-center gap-3 px-2">
@@ -298,7 +308,7 @@ const FilterSidebar: FC<FilterSidebarProps> = ({
298
308
  <span className={toggleSlotClass} aria-hidden="true" />
299
309
  )}
300
310
  <SidebarMenuSubButton
301
- className={item.isStructural ? "!py-1.5 cursor-default text-muted-foreground" : "cursor-pointer !py-1.5"}
311
+ className={item.isStructural ? "!py-1 cursor-default text-muted-foreground" : "cursor-pointer !py-1"}
302
312
  data-taxonomy-id={item.taxonomyId}
303
313
  data-taxonomy-short-id={item.shortId}
304
314
  isActive={item.active}
@@ -320,7 +330,7 @@ const FilterSidebar: FC<FilterSidebarProps> = ({
320
330
  };
321
331
 
322
332
  const content = (
323
- <SidebarContent className="!gap-0 capitalize" suppressHydrationWarning>
333
+ <SidebarContent className={`!gap-1.5 p-1.5 capitalize${searchNavigationPending ? " opacity-60 pointer-events-none" : ""}`} suppressHydrationWarning>
324
334
  {Object.entries(filteredTags).map(([key, value]) => {
325
335
  const items = value as ResolvedFilterItem[];
326
336
  const hasTaxonomy = Boolean(resolveFacetTaxonomy(key, facetTaxonomies));
@@ -337,21 +347,21 @@ const FilterSidebar: FC<FilterSidebarProps> = ({
337
347
  <div
338
348
  key={key}
339
349
  data-filter-group-key={key}
340
- className="border-b border-border/40 pb-1 mb-1 last:border-b-0 last:mb-0"
350
+ className="rounded-md border border-border/40 overflow-hidden bg-background/50"
341
351
  >
342
352
  {!hasSectionLabels && (
343
353
  <Collapsible defaultOpen className="py-0 group/collapsible">
344
- <SidebarGroup className="!p-1">
345
- <SidebarGroupLabel asChild className="hover:bg-sidebar-accent text-sidebar-accent-foreground text-sm font-bold">
346
- <CollapsibleTrigger className="!h-8 capitalize">
354
+ <SidebarGroup className="!p-0">
355
+ <SidebarGroupLabel asChild className="hover:bg-sidebar-accent text-sidebar-accent-foreground text-xs font-bold bg-muted/30">
356
+ <CollapsibleTrigger className="!h-7 capitalize rounded-none px-2">
347
357
  {propertyLabel}
348
358
  <ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
349
359
  </CollapsibleTrigger>
350
360
  </SidebarGroupLabel>
351
361
  <CollapsibleContent>
352
- <SidebarGroupContent>
353
- <SidebarMenu className="!gap-0.5">
354
- <SidebarMenuSub className="!ml-2 !pl-1.5 !py-0 !gap-0.5">
362
+ <SidebarGroupContent className="pb-1">
363
+ <SidebarMenu className="!gap-0">
364
+ <SidebarMenuSub className="!ml-1.5 !pl-1 !py-0.5 !gap-0">
355
365
  {!hierarchicalFacet && renderFacetItems(key, items)}
356
366
  {hierarchicalFacet && visibleRoots.map((item) => renderHierarchyNode(key, item, hierarchy!.children, 0))}
357
367
  {hierarchicalFacet && (hasMoreRoots || canShowLessRoots) && (
@@ -411,22 +421,22 @@ const FilterSidebar: FC<FilterSidebarProps> = ({
411
421
  <Collapsible
412
422
  defaultOpen
413
423
  key={`${key}:${sectionLabel}`}
414
- className="py-0 group/collapsible"
424
+ className="py-0 group/collapsible border-t border-border/30 first:border-t-0"
415
425
  >
416
- <SidebarGroup className="!p-1">
426
+ <SidebarGroup className="!p-0">
417
427
  <SidebarGroupLabel
418
428
  asChild
419
- className="hover:bg-sidebar-accent text-sidebar-accent-foreground text-sm font-bold"
429
+ className="hover:bg-sidebar-accent text-sidebar-accent-foreground text-xs font-bold bg-muted/30"
420
430
  >
421
- <CollapsibleTrigger className="!h-8">
431
+ <CollapsibleTrigger className="!h-7 rounded-none px-2">
422
432
  {sectionLabel}
423
433
  <ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
424
434
  </CollapsibleTrigger>
425
435
  </SidebarGroupLabel>
426
436
  <CollapsibleContent>
427
- <SidebarGroupContent>
428
- <SidebarMenu className="!gap-0.5">
429
- <SidebarMenuSub className="!ml-2 !pl-1.5 !py-0 !gap-0.5">
437
+ <SidebarGroupContent className="pb-1">
438
+ <SidebarMenu className="!gap-0">
439
+ <SidebarMenuSub className="!ml-1.5 !pl-1 !py-0.5 !gap-0">
430
440
  {shouldRenderSectionHierarchy && !hasNestedGroups &&
431
441
  visibleSectionRoots.map((item) => renderHierarchyNode(key, item, sectionHierarchy!.children, 0))}
432
442
  {shouldRenderSectionHierarchy && !hasNestedGroups && (hasMoreSectionRoots || canShowLessSectionRoots) && (
@@ -500,7 +510,10 @@ const FilterSidebar: FC<FilterSidebarProps> = ({
500
510
  className="w-[calc(100vw-2rem)] max-w-sm overflow-y-auto !px-2 !pt-6 lg:hidden"
501
511
  >
502
512
  <SheetHeader className="justify-center items-end font-bold">
503
- <SheetTitle>{t("filter.filters")}</SheetTitle>
513
+ <SheetTitle className="flex items-center gap-2">
514
+ {t("filter.filters")}
515
+ {searchNavigationPending && <Loader2 className="size-3.5 animate-spin text-muted-foreground" />}
516
+ </SheetTitle>
504
517
  <span className="text-xs text-muted-foreground leading-5">
505
518
  {totalItemCount} {t("results.results")}
506
519
  </span>
@@ -509,9 +522,12 @@ const FilterSidebar: FC<FilterSidebarProps> = ({
509
522
  </SheetContent>
510
523
  </Sheet>
511
524
 
512
- <div className="hidden w-60 rounded-md border bg-sidebar pb-4 lg:block lg:w-80">
525
+ <div className="hidden w-56 rounded-md border bg-sidebar pb-2 lg:block lg:w-72">
513
526
  <SidebarHeader className="justify-center items-end font-bold">
514
- {t("filter.filters")}
527
+ <span className="flex items-center gap-2">
528
+ {t("filter.filters")}
529
+ {searchNavigationPending && <Loader2 className="size-3.5 animate-spin text-muted-foreground" />}
530
+ </span>
515
531
  <span className="text-xs text-muted-foreground leading-5">
516
532
  {totalItemCount} {t("results.results")}
517
533
  </span>
@@ -184,7 +184,7 @@ const SearchResultsBody = <TItem extends CommonItemsModel>({
184
184
  excludeProperties={facetExcludeProperties}
185
185
  />
186
186
 
187
- <div className="flex-1 flex flex-col gap-4">
187
+ <div className={`flex-1 flex flex-col gap-4 transition-opacity duration-150${isLoading ? " opacity-60 pointer-events-none" : ""}`}>
188
188
  {Number(data.pageInfo.pageCount) > 1 && (
189
189
  <Pagination pageInfo={data.pageInfo} className="pt-0" />
190
190
  )}
@@ -34,6 +34,7 @@ export const InformationUnitSearchResultsCardList: FC<InformationUnitSearchResul
34
34
  descriptionFragmentSubjectIds = [],
35
35
  }) => {
36
36
  const t = useTranslations();
37
+ const tTypes = useTranslations("itemTypes");
37
38
 
38
39
  return (
39
40
  <div className="flex-1">
@@ -91,7 +92,7 @@ export const InformationUnitSearchResultsCardList: FC<InformationUnitSearchResul
91
92
  </span>
92
93
 
93
94
  <div>
94
- <Badge>{itemType}</Badge>
95
+ <Badge>{tTypes(itemType.toLowerCase() as any) ?? itemType}</Badge>
95
96
  </div>
96
97
  <span className="text-sm">
97
98
  <HtmlRendition
@@ -5,7 +5,7 @@ import { FC, useEffect, useRef, useState } from "react";
5
5
  import Link from "next/link";
6
6
  import { cn, formatDateToLocale } from "@c-rex/utils";
7
7
  import { Badge } from "@c-rex/ui/badge";
8
- import { useLocale } from "next-intl";
8
+ import { useLocale, useTranslations } from "next-intl";
9
9
  import { TopicsResponseItem } from "@c-rex/interfaces";
10
10
  import { Card } from "@c-rex/ui/card";
11
11
  import { useQueryState } from "nuqs";
@@ -28,6 +28,7 @@ const InformationUnitSearchResultCard: FC<{
28
28
  index: number;
29
29
  query: string | null;
30
30
  }> = ({ item, index, query }) => {
31
+ const t = useTranslations();
31
32
  const locale = useLocale();
32
33
  const date = formatDateToLocale(item.created!, locale);
33
34
  const cardRef = useRef<HTMLDivElement>(null);
@@ -67,7 +68,7 @@ const InformationUnitSearchResultCard: FC<{
67
68
  fetch(url)
68
69
  .then(r => r.json())
69
70
  .then(vcard => ({ name: vcard.fullName || "", photo: vcard.photos?.[0]?.source || null }))
70
- .catch(() => null)
71
+ .catch((): { name: string; photo: string | null } | null => null)
71
72
  )
72
73
  ).then(results => {
73
74
  setCardData(prev => ({
@@ -170,8 +171,8 @@ const InformationUnitSearchResultCard: FC<{
170
171
  </div>
171
172
 
172
173
  {!item.disabled && (
173
- <Link href={item.link} className="absolute inset-0">
174
- <span className="sr-only">View article</span>
174
+ <Link href={item.link} className="absolute inset-0" aria-label={t("results.viewArticle")}>
175
+ <span className="sr-only">{t("results.viewArticle")}</span>
175
176
  </Link>
176
177
  )}
177
178
  </Card>
@@ -18,7 +18,9 @@ const IconsToFileExtension: Record<string, React.ReactNode> = {
18
18
  };
19
19
 
20
20
  const InformationUnitSearchResultsTable: FC<InformationUnitSearchResultsTableProps> = ({ items }) => {
21
- const t = useTranslations("results")
21
+ const t = useTranslations("results");
22
+ const tRoot = useTranslations();
23
+ const tTypes = useTranslations("itemTypes");
22
24
  const [query] = useQueryState("search");
23
25
 
24
26
 
@@ -55,7 +57,7 @@ const InformationUnitSearchResultsTable: FC<InformationUnitSearchResultsTablePro
55
57
 
56
58
  <div className="w-4/5 md:w-1/5 p-2">
57
59
  <Badge variant="secondary">
58
- {item.type}
60
+ {tTypes(item.type.toLowerCase() as any) ?? item.type}
59
61
  </Badge>
60
62
  </div>
61
63
 
@@ -78,12 +80,12 @@ const InformationUnitSearchResultsTable: FC<InformationUnitSearchResultsTablePro
78
80
  <DropdownMenuContent>
79
81
  <DropdownMenuItem>
80
82
  <a href={item.files[fileKey].view} target="_blank" rel="noreferrer" className="flex items-center">
81
- <Eye className="mr-2" /> Open {/*TODO: use i18n functions*/}
83
+ <Eye className="mr-2" /> {tRoot("open")}
82
84
  </a>
83
85
  </DropdownMenuItem>
84
86
  <DropdownMenuItem>
85
87
  <a href={item.files[fileKey].download} target="_blank" rel="noreferrer" className="flex items-center">
86
- <CloudDownload className="mr-2" /> Download {/*TODO: use i18n functions*/}
88
+ <CloudDownload className="mr-2" /> {tRoot("download")}
87
89
  </a>
88
90
  </DropdownMenuItem>
89
91
  </DropdownMenuContent>
@@ -14,6 +14,7 @@ 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";
17
+ import { useSearchNavigationStore } from "../stores/search-navigation-store";
17
18
 
18
19
  interface PaginationProps {
19
20
  pageInfo: ResultContainerPageInfoModel;
@@ -67,6 +68,7 @@ export const Pagination: FC<PaginationProps> = ({ pageInfo, className }) => {
67
68
  const t = useTranslations("results");
68
69
  const pathname = usePathname();
69
70
  const searchParams = useSearchParams();
71
+ const searchNavigationPending = useSearchNavigationStore((state) => state.pending);
70
72
 
71
73
  const pageNumber = pageInfo.pageNumber || 1;
72
74
  const pageCount = pageInfo.pageCount || 1;
@@ -78,7 +80,7 @@ export const Pagination: FC<PaginationProps> = ({ pageInfo, className }) => {
78
80
  const search = currentSearch.length > 0 ? `?${currentSearch}` : "";
79
81
 
80
82
  return (
81
- <div className={cn("flex flex-col gap-3 py-4", className)}>
83
+ <div className={cn("flex flex-col gap-3 py-4 transition-opacity duration-150", searchNavigationPending ? "opacity-60 pointer-events-none" : "", className)}>
82
84
  <PaginationUI className="py-4 items-center justify-center sm:justify-between">
83
85
  <span className="hidden sm:block text-sm text-muted-foreground">
84
86
  {t("paginationResults", {
@@ -25,8 +25,6 @@ export type SearchSettingsState = {
25
25
  wildcard: WildCardType,
26
26
  operator: OperatorType,
27
27
  like: boolean,
28
- facetExcludeProperties?: string[],
29
- metadataExcludeProperties?: string[],
30
28
  }
31
29
 
32
30
  export type SearchSettingsStore = SearchSettingsState & {
@@ -38,8 +36,6 @@ export const defaultSearchSettings: SearchSettingsState = {
38
36
  wildcard: WILD_CARD_OPTIONS.BOTH,
39
37
  operator: OPERATOR_OPTIONS.OR,
40
38
  like: false,
41
- facetExcludeProperties: [],
42
- metadataExcludeProperties: [],
43
39
  }
44
40
 
45
41
  /**