@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.
Files changed (45) hide show
  1. package/package.json +28 -36
  2. package/src/article/article-action-bar.tsx +4 -1
  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 +2 -2
  7. package/src/carousel/carousel.tsx +5 -2
  8. package/src/carousel/information-unit-carousel-item.tsx +1 -1
  9. package/src/content-unavailable.tsx +20 -0
  10. package/src/directoryNodes/directory-tree-context.tsx +9 -4
  11. package/src/documents/description-preview.tsx +14 -4
  12. package/src/documents/result-list-item.tsx +35 -46
  13. package/src/favorites/__tests__/favorites-hydration.test.tsx +245 -0
  14. package/src/favorites/bookmark-button.tsx +38 -20
  15. package/src/favorites/favorite-button.tsx +23 -24
  16. package/src/favorites/favorites-context.tsx +287 -0
  17. package/src/icons/file-icon.tsx +9 -26
  18. package/src/info/information-unit-metadata-grid-client.tsx +21 -21
  19. package/src/page-wrapper.tsx +1 -1
  20. package/src/renditions/html-client.tsx +8 -6
  21. package/src/renditions/html.tsx +3 -1
  22. package/src/restriction-menu/restriction-menu-item.tsx +48 -58
  23. package/src/restriction-menu/restriction-selection-command-menu.tsx +444 -0
  24. package/src/restriction-menu/restriction-selection-menu.tsx +3 -5
  25. package/src/restriction-menu/taxonomy-restriction-command-menu.tsx +111 -0
  26. package/src/restriction-menu/taxonomy-restriction-menu.tsx +1 -7
  27. package/src/results/filter-navbar.tsx +81 -76
  28. package/src/results/filter-sidebar/context.tsx +32 -0
  29. package/src/results/filter-sidebar/index.tsx +44 -35
  30. package/src/results/generic/search-results-client.tsx +5 -4
  31. package/src/results/generic/table-result-list.tsx +16 -16
  32. package/src/results/information-unit-search-results-card-list.tsx +4 -1
  33. package/src/results/pagination.tsx +43 -40
  34. package/src/search-input.tsx +4 -2
  35. package/src/toc/toc-browse-controls.tsx +2 -2
  36. package/src/toc/toc-tree-panel.tsx +19 -16
  37. package/src/article/article-content.tsx +0 -19
  38. package/src/breadcrumb.tsx +0 -124
  39. package/src/directoryNodes/tree-of-content.tsx +0 -68
  40. package/src/render-article.tsx +0 -75
  41. package/src/restriction-menu/restriction-menu-container.tsx +0 -4
  42. package/src/restriction-menu/restriction-menu.tsx +0 -4
  43. package/src/stores/__tests__/favorites-store.test.ts +0 -54
  44. package/src/stores/favorites-store.ts +0 -163
  45. /package/src/{render-article.module.css → article/render-article.module.css} +0 -0
@@ -0,0 +1,287 @@
1
+ "use client";
2
+
3
+ import { Favorite } from "@c-rex/types";
4
+ import { getLocalStorageJson, removeLocalStorageItem, setLocalStorageJson } from "@c-rex/utils";
5
+ import { ReactNode, createContext, useContext, useEffect, useState } from "react";
6
+
7
+ type FavoriteDocumentState = { topics: Favorite[]; label?: string };
8
+ type FavoriteDocumentsMap = Record<string, FavoriteDocumentState>;
9
+
10
+ export type FavoritesState = {
11
+ favorites: Favorite[];
12
+ documents: FavoriteDocumentsMap;
13
+ };
14
+
15
+ export type FavoritesActions = {
16
+ favoriteTopic: (documentId: string, id: string, label: string, color: string) => void;
17
+ unfavoriteTopic: (documentId: string, id: string) => void;
18
+ favoriteDocument: (id: string, label: string) => void;
19
+ unfavoriteDocument: (id: string) => void;
20
+ };
21
+
22
+ export type FavoritesContextValue = FavoritesState & FavoritesActions & {
23
+ isHydrated: boolean;
24
+ };
25
+
26
+ type PersistedFavoritesState = FavoritesState | {
27
+ state?: FavoritesState;
28
+ version?: number;
29
+ };
30
+
31
+ export const FAVORITES_STORAGE_KEY = "c-rex-favorites";
32
+
33
+ export const defaultFavoritesState: FavoritesState = {
34
+ documents: {},
35
+ favorites: [],
36
+ };
37
+
38
+ export const loadFavoritesState = (fallbackState: FavoritesState = defaultFavoritesState): FavoritesState => {
39
+ const persisted = getLocalStorageJson<PersistedFavoritesState>(FAVORITES_STORAGE_KEY);
40
+
41
+ if (!persisted) {
42
+ return fallbackState;
43
+ }
44
+
45
+ if (!isFavoritesState(persisted)) {
46
+ return persisted.state ?? fallbackState;
47
+ }
48
+
49
+ return persisted;
50
+ };
51
+
52
+ export const persistFavoritesState = (state: FavoritesState): void => {
53
+ setLocalStorageJson(FAVORITES_STORAGE_KEY, state);
54
+ };
55
+
56
+ export const clearFavoritesState = (): void => {
57
+ removeLocalStorageItem(FAVORITES_STORAGE_KEY);
58
+ };
59
+
60
+ export const applyFavoriteTopic = (state: FavoritesState, documentId: string, id: string, label: string, color: string): FavoritesState => {
61
+ const documents = favoriteTopic(state.documents, documentId, id, label, color);
62
+ const favorites = upsertFavorites(state.favorites, [
63
+ { id, label, color },
64
+ { id: documentId, label: state.documents[documentId]?.label || "", color: "" },
65
+ ]);
66
+
67
+ if (documents === state.documents && favorites === state.favorites) {
68
+ return state;
69
+ }
70
+
71
+ return { ...state, documents, favorites };
72
+ };
73
+
74
+ export const applyUnfavoriteTopic = (state: FavoritesState, documentId: string, id: string): FavoritesState => ({
75
+ ...state,
76
+ documents: unfavoriteTopic(state.documents, documentId, id),
77
+ favorites: state.favorites.filter((topic) => topic.id !== id),
78
+ });
79
+
80
+ export const applyFavoriteDocument = (state: FavoritesState, id: string, label: string): FavoritesState => {
81
+ const documents = upsertDocument(state.documents, id, label);
82
+ const favorites = upsertFavorites(state.favorites, [{ id, label, color: "" }]);
83
+
84
+ if (documents === state.documents && favorites === state.favorites) {
85
+ return state;
86
+ }
87
+
88
+ return {
89
+ ...state,
90
+ documents,
91
+ favorites,
92
+ };
93
+ };
94
+
95
+ export const applyUnfavoriteDocument = (state: FavoritesState, id: string): FavoritesState => {
96
+ const documentsCopy = { ...state.documents };
97
+ if (!documentsCopy[id]) {
98
+ return state;
99
+ }
100
+
101
+ const favoritesToRemove = documentsCopy[id]?.topics.map((topic) => topic.id) || [];
102
+ const newFavorites = state.favorites.filter((fav) => fav.id !== id && !favoritesToRemove.includes(fav.id));
103
+ delete documentsCopy[id];
104
+
105
+ return {
106
+ ...state,
107
+ documents: documentsCopy,
108
+ favorites: newFavorites,
109
+ };
110
+ };
111
+
112
+ const FavoritesContext = createContext<FavoritesContextValue | undefined>(undefined);
113
+
114
+ type FavoritesProviderProps = {
115
+ children: ReactNode;
116
+ initialState?: FavoritesState;
117
+ };
118
+
119
+ export const FavoritesProvider = ({
120
+ children,
121
+ initialState = defaultFavoritesState,
122
+ }: FavoritesProviderProps) => {
123
+ const [state, setState] = useState<FavoritesState>(initialState);
124
+ const [isHydrated, setIsHydrated] = useState(false);
125
+
126
+ useEffect(() => {
127
+ setState(loadFavoritesState(initialState));
128
+ setIsHydrated(true);
129
+
130
+ const handleStorageChange = () => {
131
+ setState(loadFavoritesState(initialState));
132
+ };
133
+
134
+ window.addEventListener("storage", handleStorageChange);
135
+ return () => window.removeEventListener("storage", handleStorageChange);
136
+ }, [initialState]);
137
+
138
+ const favoriteTopic = (documentId: string, id: string, label: string, color: string) => {
139
+ setState((currentState) => {
140
+ const nextState = applyFavoriteTopic(currentState, documentId, id, label, color);
141
+ persistFavoritesState(nextState);
142
+ return nextState;
143
+ });
144
+ };
145
+
146
+ const unfavoriteTopic = (documentId: string, id: string) => {
147
+ setState((currentState) => {
148
+ const nextState = applyUnfavoriteTopic(currentState, documentId, id);
149
+ persistFavoritesState(nextState);
150
+ return nextState;
151
+ });
152
+ };
153
+
154
+ const favoriteDocument = (id: string, label: string) => {
155
+ setState((currentState) => {
156
+ const nextState = applyFavoriteDocument(currentState, id, label);
157
+ persistFavoritesState(nextState);
158
+ return nextState;
159
+ });
160
+ };
161
+
162
+ const unfavoriteDocument = (id: string) => {
163
+ setState((currentState) => {
164
+ const nextState = applyUnfavoriteDocument(currentState, id);
165
+ persistFavoritesState(nextState);
166
+ return nextState;
167
+ });
168
+ };
169
+
170
+ const contextValue: FavoritesContextValue = {
171
+ ...state,
172
+ isHydrated,
173
+ favoriteTopic,
174
+ unfavoriteTopic,
175
+ favoriteDocument,
176
+ unfavoriteDocument,
177
+ };
178
+
179
+ return (
180
+ <FavoritesContext.Provider value={contextValue}>
181
+ {children}
182
+ </FavoritesContext.Provider>
183
+ );
184
+ };
185
+
186
+ export const useFavorites = <T,>(selector: (state: FavoritesContextValue) => T): T => {
187
+ const favoritesContext = useContext(FavoritesContext);
188
+
189
+ if (!favoritesContext) {
190
+ throw new Error("useFavorites must be used within FavoritesProvider");
191
+ }
192
+
193
+ return selector(favoritesContext);
194
+ };
195
+
196
+ const favoriteTopic = (documents: FavoriteDocumentsMap, documentId: string, id: string, label: string, color: string): FavoriteDocumentsMap => {
197
+ const currentDocument = documents[documentId];
198
+ const currentTopics = currentDocument?.topics ?? [];
199
+ if (currentTopics.some((topic) => topic.id === id)) {
200
+ return documents;
201
+ }
202
+
203
+ return {
204
+ ...documents,
205
+ [documentId]: {
206
+ ...currentDocument,
207
+ topics: [...currentTopics, { id, label, color }],
208
+ },
209
+ };
210
+ };
211
+
212
+ const unfavoriteTopic = (documents: FavoriteDocumentsMap, documentId: string, id: string): FavoriteDocumentsMap => {
213
+ const currentDocument = documents[documentId];
214
+ if (!currentDocument) {
215
+ return documents;
216
+ }
217
+
218
+ return {
219
+ ...documents,
220
+ [documentId]: {
221
+ ...currentDocument,
222
+ topics: currentDocument.topics.filter((topic) => topic.id !== id),
223
+ },
224
+ };
225
+ };
226
+
227
+ const upsertDocument = (documents: FavoriteDocumentsMap, documentId: string, label: string): FavoriteDocumentsMap => {
228
+ const currentDocument = documents[documentId];
229
+
230
+ if (!currentDocument) {
231
+ return {
232
+ ...documents,
233
+ [documentId]: { topics: [], label },
234
+ };
235
+ }
236
+
237
+ if (currentDocument.label === label) {
238
+ return documents;
239
+ }
240
+
241
+ return {
242
+ ...documents,
243
+ [documentId]: {
244
+ ...currentDocument,
245
+ label,
246
+ },
247
+ };
248
+ };
249
+
250
+ const upsertFavorites = (favorites: Favorite[], entries: Favorite[]): Favorite[] => {
251
+ let nextFavorites = favorites;
252
+
253
+ for (const entry of entries) {
254
+ nextFavorites = upsertFavorite(nextFavorites, entry);
255
+ }
256
+
257
+ return nextFavorites;
258
+ };
259
+
260
+ const upsertFavorite = (favorites: Favorite[], entry: Favorite): Favorite[] => {
261
+ const index = favorites.findIndex((favorite) => favorite.id === entry.id);
262
+ if (index === -1) {
263
+ return [...favorites, entry];
264
+ }
265
+
266
+ const current = favorites[index];
267
+ const next = {
268
+ ...current,
269
+ label: current.label || entry.label,
270
+ color: current.color || entry.color,
271
+ };
272
+
273
+ if (
274
+ current.label === next.label &&
275
+ current.color === next.color
276
+ ) {
277
+ return favorites;
278
+ }
279
+
280
+ const copy = [...favorites];
281
+ copy[index] = next;
282
+ return copy;
283
+ };
284
+
285
+ const isFavoritesState = (value: PersistedFavoritesState): value is FavoritesState => (
286
+ "favorites" in value && "documents" in value
287
+ );
@@ -34,34 +34,25 @@ const getGenericDisplayCode = (mimeType: string) => {
34
34
 
35
35
  const GenericFileTypeIcon = ({ code }: { code: string }) => (
36
36
  <svg
37
- viewBox="0 0 24 24"
37
+ strokeWidth="0"
38
+ viewBox="0 0 256 256"
38
39
  className="size-5 text-primary"
39
40
  aria-hidden="true"
40
- fill="none"
41
+ fill="currentColor"
41
42
  xmlns="http://www.w3.org/2000/svg"
42
43
  >
43
44
  <path
44
- d="M14 2H7a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8l-5-6Z"
45
- stroke="currentColor"
46
- strokeWidth="1.5"
47
- fill="currentColor"
48
- fillOpacity="0.08"
49
- />
50
- <path
51
- d="M14 2v6h5"
52
- stroke="currentColor"
53
- strokeWidth="1.5"
54
- strokeLinecap="round"
55
- strokeLinejoin="round"
45
+ d="M36,112V40A16,16,0,0,1,52,24h100a8,8,0,0,1,5.66,2.34l56,56A8,8,0,0,1,216,88v24a8,8,0,0,1-16,0V96H152a8,8,0,0,1-8-8V40H52v72a8,8,0,0,1-16,0ZM160,80h28.69L160,51.31Z"
56
46
  />
57
47
  <text
58
- x="12"
59
- y="17.25"
48
+ x="128"
49
+ y="220"
60
50
  textAnchor="middle"
61
- fontSize="5.2"
51
+ fontSize="110"
62
52
  fontWeight="700"
63
53
  fill="currentColor"
64
- letterSpacing="0.4"
54
+ fontFamily="inherit"
55
+ letterSpacing={code.length >= 4 ? -2 : 0}
65
56
  >
66
57
  {code}
67
58
  </text>
@@ -69,13 +60,5 @@ const GenericFileTypeIcon = ({ code }: { code: string }) => (
69
60
  );
70
61
 
71
62
  export const FileIcon = ({ extension }: { extension: string }) => {
72
- if (extension === "application/pdf") {
73
- return <PiFilePdf className="text-primary" />;
74
- }
75
-
76
- if (extension === "application/zip") {
77
- return <PiFileZip className="text-primary" />;
78
- }
79
-
80
63
  return <GenericFileTypeIcon code={getGenericDisplayCode(extension)} />;
81
64
  };
@@ -344,7 +344,7 @@ export const InformationUnitMetadataGridClient = ({
344
344
 
345
345
  const cardContent = (
346
346
  <CardContent className="space-y-3 !p-0">
347
- <Table className="table-fixed">
347
+ <Table className="table-fixed overflow-ellipsis">
348
348
  <TableBody>
349
349
  {showBookmarkButton && (
350
350
  <TableRow className="min-h-12">
@@ -374,26 +374,26 @@ export const InformationUnitMetadataGridClient = ({
374
374
  availableInVersions
375
375
  .filter((item) => item.shortId !== data.shortId)
376
376
  .length > 0 ? (
377
- <TableRow className="min-h-12">
378
- <TableCell className="font-medium w-28 pl-4">
379
- <h4 className="text-sm font-medium">{t("availableIn")}</h4>
380
- </TableCell>
381
- <TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
382
- {availableInVersions
383
- .filter((item) => item.shortId !== data.shortId)
384
- .map((item) => (
385
- <span className="w-8 block border" key={item.shortId}>
386
- <Link
387
- href={linkPattern.replace("{shortId}", item.shortId)}
388
- title={item.language}
389
- >
390
- <Flag countryCode={extractCountryCodeFromLanguage(item.language)} />
391
- </Link>
392
- </span>
393
- ))}
394
- </TableCell>
395
- </TableRow>
396
- ) : null
377
+ <TableRow className="min-h-12">
378
+ <TableCell className="font-medium w-28 pl-4">
379
+ <h4 className="text-sm font-medium">{t("availableIn")}</h4>
380
+ </TableCell>
381
+ <TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
382
+ {availableInVersions
383
+ .filter((item) => item.shortId !== data.shortId)
384
+ .map((item) => (
385
+ <span className="w-8 block border" key={item.shortId}>
386
+ <Link
387
+ href={linkPattern.replace("{shortId}", item.shortId)}
388
+ title={item.language}
389
+ >
390
+ <Flag countryCode={extractCountryCodeFromLanguage(item.language)} />
391
+ </Link>
392
+ </span>
393
+ ))}
394
+ </TableCell>
395
+ </TableRow>
396
+ ) : null
397
397
  ) : (
398
398
  <AvailableInRow
399
399
  versionOfShortId={data.versionOf?.shortId}
@@ -46,7 +46,7 @@ export const PageWrapper = ({
46
46
  <NavBar showInput={showInput} showPkgFilter={showPkgFilter} {...props} />
47
47
 
48
48
  {showRestrictMenu && (
49
- <div className="flex-1 container pt-6">
49
+ <div className="container pt-6">
50
50
  {renderRestrictionMenu ? renderRestrictionMenu(restrictionMenuProps) : (
51
51
  <TaxonomyRestrictionMenu {...restrictionMenuProps} />
52
52
  )}
@@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from "react";
4
4
  import { FragmentsGetByIdClient } from "../generated/client-components";
5
5
  import type { RenditionModel } from "@c-rex/interfaces";
6
6
  import { RenderArticle } from "../render-article";
7
+ import { useTranslations } from "next-intl";
7
8
 
8
9
  type HtmlRenditionClientProps = {
9
10
  htmlFormats?: string[];
@@ -11,8 +12,6 @@ type HtmlRenditionClientProps = {
11
12
  renditions?: RenditionModel[] | null;
12
13
  };
13
14
 
14
- const EMPTY = <div>No rendition available</div>;
15
-
16
15
  const findHtmlViewUrl = (
17
16
  renditions?: RenditionModel[] | null,
18
17
  htmlFormats: string[] = ["application/xhtml+xml", "application/html", "text/html"]
@@ -30,6 +29,7 @@ const HtmlFromRenditions = ({
30
29
  renditions?: RenditionModel[] | null;
31
30
  htmlFormats?: string[];
32
31
  }) => {
32
+ const t = useTranslations();
33
33
  const [htmlContent, setHtmlContent] = useState<string | null>(null);
34
34
  const [hasError, setHasError] = useState(false);
35
35
  const viewUrl = useMemo(() => findHtmlViewUrl(renditions, htmlFormats), [htmlFormats, renditions]);
@@ -62,8 +62,8 @@ const HtmlFromRenditions = ({
62
62
  };
63
63
  }, [viewUrl]);
64
64
 
65
- if (!viewUrl || hasError) return EMPTY;
66
- if (htmlContent == null) return <div className="text-muted-foreground text-sm">Loading content...</div>;
65
+ if (!viewUrl || hasError) return <div>{t("noRenditionAvailable")}</div>;
66
+ if (htmlContent == null) return <div className="text-muted-foreground text-sm">{t("loadingContent")}</div>;
67
67
 
68
68
  return <RenderArticle htmlContent={htmlContent} />;
69
69
  };
@@ -73,11 +73,13 @@ export const HtmlRenditionClient = ({
73
73
  htmlFormats = ["application/xhtml+xml", "application/html", "text/html"],
74
74
  renditions,
75
75
  }: HtmlRenditionClientProps) => {
76
+ const t = useTranslations();
77
+
76
78
  if (renditions !== undefined) {
77
79
  return <HtmlFromRenditions renditions={renditions} htmlFormats={htmlFormats} />;
78
80
  }
79
81
 
80
- if (!fragmentShortId) return EMPTY;
82
+ if (!fragmentShortId) return <div>{t("noRenditionAvailable")}</div>;
81
83
 
82
84
  return (
83
85
  <FragmentsGetByIdClient
@@ -90,7 +92,7 @@ export const HtmlRenditionClient = ({
90
92
  >
91
93
  {({ data, isLoading }) => {
92
94
  if (isLoading && !data) {
93
- return <div className="text-muted-foreground text-sm">Loading content...</div>;
95
+ return <div className="text-muted-foreground text-sm">{t("loadingContent")}</div>;
94
96
  }
95
97
  return <HtmlFromRenditions renditions={data?.renditions} htmlFormats={htmlFormats} />;
96
98
  }}
@@ -3,6 +3,7 @@ import * as cheerio from "cheerio"
3
3
  import { RenditionModel } from "@c-rex/interfaces";
4
4
  import { fragmentsGetByIdServer } from "@c-rex/services/server-requests";
5
5
  import { call } from "@c-rex/utils";
6
+ import { getTranslations } from "next-intl/server";
6
7
 
7
8
  interface HtmlRenditionProps {
8
9
  htmlFormats?: string[]
@@ -23,7 +24,8 @@ export const HtmlRendition: FC<HtmlRenditionProps> = async ({
23
24
  render = defaultRender,
24
25
  renditions,
25
26
  }) => {
26
- const empty = <div>No rendition available</div>;
27
+ const t = await getTranslations();
28
+ const empty = <div>{t("noRenditionAvailable")}</div>;
27
29
 
28
30
  if (renditions == undefined) {
29
31
  if (fragmentShortId) {
@@ -38,47 +38,30 @@ export const RestrictionNavigationItem: FC<Props> = ({
38
38
  currentRestrict: restrict,
39
39
  });
40
40
 
41
- const labelStyle = {
42
- display: 'inline-block',
43
- maxWidth: 100,
44
- overflow: 'hidden',
45
- textOverflow: 'ellipsis',
46
- whiteSpace: 'nowrap',
47
- verticalAlign: 'middle',
48
- } as React.CSSProperties;
49
-
50
- const needsTooltip = label.length > 12;
51
-
52
- const labelNode = needsTooltip ? (
53
- <TooltipProvider delayDuration={300}>
54
- <Tooltip>
55
- <TooltipTrigger asChild>
56
- <span style={labelStyle}>{label}</span>
57
- </TooltipTrigger>
58
- <TooltipContent>{label}</TooltipContent>
59
- </Tooltip>
60
- </TooltipProvider>
61
- ) : (
62
- <span style={labelStyle}>{label}</span>
63
- );
64
-
65
41
  return (
66
42
  <NavigationMenuItem key={shortId} asChild>
67
- <Button
68
- variant={selected ? "default" : "outline"}
69
- rounded="full"
70
- onClick={() => {
71
- startSearchNavigation();
72
- if (shouldRemoveRestrictParam) {
73
- setRestrict(null);
74
- } else {
75
- setRestrict(restrictionValue);
76
- }
77
- }}
78
- className="cursor-pointer"
79
- >
80
- {labelNode}
81
- </Button>
43
+ <TooltipProvider delayDuration={300}>
44
+ <Tooltip>
45
+ <TooltipTrigger asChild>
46
+ <Button
47
+ variant={selected ? "default" : "outline"}
48
+ rounded="full"
49
+ onClick={() => {
50
+ startSearchNavigation();
51
+ if (shouldRemoveRestrictParam) {
52
+ setRestrict(null);
53
+ } else {
54
+ setRestrict(restrictionValue);
55
+ }
56
+ }}
57
+ className="cursor-pointer"
58
+ >
59
+ {label}
60
+ </Button>
61
+ </TooltipTrigger>
62
+ <TooltipContent>{label}</TooltipContent>
63
+ </Tooltip>
64
+ </TooltipProvider>
82
65
  </NavigationMenuItem>
83
66
  );
84
67
  };
@@ -104,25 +87,32 @@ export const RestrictionDropdownItem: FC<Props> = ({
104
87
  });
105
88
 
106
89
  return (
107
- <Button
108
- variant={selected ? "default" : "ghost"}
109
- onClick={() => {
110
- if (onClick) {
111
- onClick();
112
- return;
113
- }
114
- startSearchNavigation();
115
- if (shouldRemoveRestrictParam) {
116
- setRestrict(null);
117
- } else {
118
- setRestrict(restrictionValue);
119
- }
120
- }}
121
- rounded="full"
122
- className="text-left text-wrap !h-auto min-h-10 w-full !justify-start cursor-pointer"
123
- >
124
- {label}
125
- </Button>
90
+ <TooltipProvider delayDuration={300}>
91
+ <Tooltip>
92
+ <TooltipTrigger asChild>
93
+ <Button
94
+ variant={selected ? "default" : "ghost"}
95
+ onClick={() => {
96
+ if (onClick) {
97
+ onClick();
98
+ return;
99
+ }
100
+ startSearchNavigation();
101
+ if (shouldRemoveRestrictParam) {
102
+ setRestrict(null);
103
+ } else {
104
+ setRestrict(restrictionValue);
105
+ }
106
+ }}
107
+ rounded="full"
108
+ className="text-left text-wrap !h-auto min-h-10 w-full !justify-start cursor-pointer"
109
+ >
110
+ {label}
111
+ </Button>
112
+ </TooltipTrigger>
113
+ <TooltipContent>{label}</TooltipContent>
114
+ </Tooltip>
115
+ </TooltipProvider>
126
116
  );
127
117
  };
128
118