@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.
Files changed (50) hide show
  1. package/package.json +32 -36
  2. package/src/article/article-action-bar.tsx +12 -2
  3. package/src/{check-article-lang.tsx → article/check-article-lang.tsx} +1 -1
  4. package/src/article/render-article-highlight.tsx +108 -0
  5. package/src/article/render-article.tsx +28 -0
  6. package/src/autocomplete.tsx +7 -25
  7. package/src/blog/blog-author-card.tsx +116 -0
  8. package/src/carousel/carousel.tsx +5 -2
  9. package/src/carousel/information-unit-carousel-item.tsx +1 -1
  10. package/src/content-unavailable.tsx +20 -0
  11. package/src/directoryNodes/directory-tree-context.tsx +9 -4
  12. package/src/documents/description-preview.tsx +14 -4
  13. package/src/documents/result-list-item.tsx +40 -46
  14. package/src/favorites/__tests__/favorites-hydration.test.tsx +245 -0
  15. package/src/favorites/bookmark-button.tsx +38 -20
  16. package/src/favorites/favorite-button.tsx +23 -24
  17. package/src/favorites/favorites-context.tsx +287 -0
  18. package/src/icons/file-icon.tsx +9 -26
  19. package/src/info/information-unit-metadata-grid-client.tsx +21 -21
  20. package/src/navbar/navbar.tsx +16 -30
  21. package/src/navbar/settings.tsx +1 -1
  22. package/src/page-wrapper.tsx +3 -3
  23. package/src/renditions/html-client.tsx +8 -6
  24. package/src/renditions/html.tsx +3 -1
  25. package/src/restriction-menu/restriction-menu-item.tsx +48 -58
  26. package/src/restriction-menu/restriction-selection-command-menu.tsx +445 -0
  27. package/src/restriction-menu/restriction-selection-menu.tsx +5 -7
  28. package/src/restriction-menu/taxonomy-restriction-command-menu.tsx +111 -0
  29. package/src/restriction-menu/taxonomy-restriction-menu.tsx +19 -12
  30. package/src/results/filter-navbar.tsx +81 -76
  31. package/src/results/filter-sidebar/context.tsx +32 -0
  32. package/src/results/filter-sidebar/index.tsx +40 -35
  33. package/src/results/generic/search-results-client.tsx +5 -4
  34. package/src/results/generic/table-result-list.tsx +16 -16
  35. package/src/results/information-unit-search-results-card-list.tsx +4 -1
  36. package/src/results/information-unit-search-results-cards.tsx +169 -69
  37. package/src/results/pagination.tsx +43 -40
  38. package/src/search-input.tsx +4 -2
  39. package/src/toc/toc-breadcrumb.tsx +1 -1
  40. package/src/toc/toc-browse-controls.tsx +2 -2
  41. package/src/toc/toc-tree-panel.tsx +19 -16
  42. package/src/article/article-content.tsx +0 -19
  43. package/src/breadcrumb.tsx +0 -124
  44. package/src/directoryNodes/tree-of-content.tsx +0 -68
  45. package/src/render-article.tsx +0 -75
  46. package/src/restriction-menu/restriction-menu-container.tsx +0 -4
  47. package/src/restriction-menu/restriction-menu.tsx +0 -4
  48. package/src/stores/__tests__/favorites-store.test.ts +0 -54
  49. package/src/stores/favorites-store.ts +0 -163
  50. /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
- itemsToRender?: number;
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
- itemsToRender = 7,
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-between flex-row",
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(itemsToRender, 1);
67
+ if (fetchMode === "deferred" && !loadAll) return Math.max(maxItemsByRow, 1);
56
68
  return 100;
57
- }, [explicitPageSize, fetchMode, itemsToRender, loadAll]);
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
- <div className="flex justify-between">
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 justify-between">
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
- //onClick={() => setOpen(true)}
210
- className="md:hidden"
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
- {filters.length > 0 && (
216
- <>
217
- {filters.slice(0, 1).map((item) => (
218
- <Badge
219
- key={`${item.key}-${item?.value}`}
220
- variant="outline"
221
- className="h-8"
222
- >
223
- {item?.name ? item.name : item.key}: {item.value}
224
-
225
- {item.removable && (
226
- <Button size="xs" variant="ghost" onClick={() => {
227
- startSearchNavigation();
228
- setParams({ [item.key]: item?.default })
229
- }}>
230
- <X className="h-2" />
231
- </Button>
232
- )}
233
- </Badge>
234
- ))}
235
-
236
-
237
- {isMobile ? (
238
- <TooltipProvider>
239
- <Tooltip delayDuration={100}>
240
- <TooltipTrigger>
241
- <Badge
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={`${item.key}-${item?.value}`}
247
+ key={`grouped-filters`}
265
248
  variant="outline"
266
249
  className="h-8"
267
250
  >
268
- {item?.name ? item.name : item.key}: {item.value}
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
- </div>
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
- <div className="w-60 lg:w-80 bg-sidebar rounded-md border pb-4">
543
- <SidebarHeader className="justify-center items-end font-bold">
544
- {t("filter.filters")}
545
- <span className="text-xs text-muted-foreground leading-5">
546
- {totalItemCount} {t("results.results")}
547
- </span>
548
- </SidebarHeader>
549
- {content}
550
- </div>
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
- <Pagination pageInfo={data.pageInfo} className="pt-0" />
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 p-2 border-b items-center flex-wrap hidden md:flex">
34
- <div className="flex-1 p-2">{t("title")}</div>
35
- <div className="w-1/2 md:w-1/5 p-2 flex justify-center">{t("language")}</div>
36
- <div className="w-1/2 md:w-1/5 p-2 flex justify-center">{t("type")}</div>
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-1/2 md:w-1/5 p-2 flex justify-center">{t("files")}</div>
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 p-2">
71
+ <div className="flex-1">
72
72
  <Link href={link} className="hover:underline">{title}</Link>
73
73
  </div>
74
74
 
75
- <div className="w-1/5 md:w-1/5 flex justify-center p-2">
76
- <span className="w-8 inline-block">
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 p-2">
90
+ <div className="w-1/5 flex justify-center">
91
91
  <FileDownloadDropdown renditions={item.renditions} />
92
92
  </div>
93
93
  }