@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c-rex/components",
3
- "version": "0.3.0-build.35",
3
+ "version": "0.3.0-build.36",
4
4
  "files": [
5
5
  "src"
6
6
  ],
@@ -21,10 +21,6 @@
21
21
  "types": "./src/generated/suggestions.tsx",
22
22
  "import": "./src/generated/suggestions.tsx"
23
23
  },
24
- "./breadcrumb": {
25
- "types": "./src/breadcrumb.tsx",
26
- "import": "./src/breadcrumb.tsx"
27
- },
28
24
  "./navbar": {
29
25
  "types": "./src/navbar/navbar.tsx",
30
26
  "import": "./src/navbar/navbar.tsx"
@@ -62,12 +58,12 @@
62
58
  "import": "./src/info/info-card.tsx"
63
59
  },
64
60
  "./check-article-lang": {
65
- "types": "./src/check-article-lang.tsx",
66
- "import": "./src/check-article-lang.tsx"
61
+ "types": "./src/article/check-article-lang.tsx",
62
+ "import": "./src/article/check-article-lang.tsx"
67
63
  },
68
64
  "./render-article": {
69
- "types": "./src/render-article.tsx",
70
- "import": "./src/render-article.tsx"
65
+ "types": "./src/article/render-article.tsx",
66
+ "import": "./src/article/render-article.tsx"
71
67
  },
72
68
  "./article-action-bar": {
73
69
  "types": "./src/article/article-action-bar.tsx",
@@ -98,17 +94,13 @@
98
94
  "import": "./src/renditions/html-client.tsx"
99
95
  },
100
96
  "./image-rendition-container": {
101
- "types": "./src/renditions/container.ts",
102
- "import": "./src/renditions/container.ts"
97
+ "types": "./src/renditions/image/container.tsx",
98
+ "import": "./src/renditions/image/container.tsx"
103
99
  },
104
100
  "./autocomplete": {
105
101
  "types": "./src/autocomplete.tsx",
106
102
  "import": "./src/autocomplete.tsx"
107
103
  },
108
- "./result-container": {
109
- "types": "./src/result-container.tsx",
110
- "import": "./src/result-container.tsx"
111
- },
112
104
  "./dialog-filter": {
113
105
  "types": "./src/results/dialog-filter.tsx",
114
106
  "import": "./src/results/dialog-filter.tsx"
@@ -117,6 +109,10 @@
117
109
  "types": "./src/results/pagination.tsx",
118
110
  "import": "./src/results/pagination.tsx"
119
111
  },
112
+ "./information-unit-search-results-card-list": {
113
+ "types": "./src/results/information-unit-search-results-card-list.tsx",
114
+ "import": "./src/results/information-unit-search-results-card-list.tsx"
115
+ },
120
116
  "./information-unit-search-results-cards": {
121
117
  "types": "./src/results/information-unit-search-results-cards.tsx",
122
118
  "import": "./src/results/information-unit-search-results-cards.tsx"
@@ -141,10 +137,10 @@
141
137
  "types": "./src/stores/highlight-store.ts",
142
138
  "import": "./src/stores/highlight-store.ts"
143
139
  },
144
- "./favorites-store": {
145
- "types": "./src/stores/favorites-store.ts",
146
- "import": "./src/stores/favorites-store.ts"
147
- },
140
+ "./favorites-context": {
141
+ "types": "./src/favorites/favorites-context.tsx",
142
+ "import": "./src/favorites/favorites-context.tsx"
143
+ },
148
144
  "./search-settings-store": {
149
145
  "types": "./src/stores/search-settings-store.ts",
150
146
  "import": "./src/stores/search-settings-store.ts"
@@ -177,14 +173,6 @@
177
173
  "types": "./src/renditions/image/rendition.tsx",
178
174
  "import": "./src/renditions/image/rendition.tsx"
179
175
  },
180
- "./server-image-rendition": {
181
- "types": "./src/renditions/image/server-image-rendition.tsx",
182
- "import": "./src/renditions/image/server-image-rendition.tsx"
183
- },
184
- "./article-skeleton": {
185
- "types": "./src/article/article-skeleton.tsx",
186
- "import": "./src/article/article-skeleton.tsx"
187
- },
188
176
  "./documents-result-list": {
189
177
  "types": "./src/documents/result-list.tsx",
190
178
  "import": "./src/documents/result-list.tsx"
@@ -201,14 +189,6 @@
201
189
  "types": "./src/carousel/carousel.tsx",
202
190
  "import": "./src/carousel/carousel.tsx"
203
191
  },
204
- "./restriction-menu": {
205
- "types": "./src/restriction-menu/restriction-menu.tsx",
206
- "import": "./src/restriction-menu/restriction-menu.tsx"
207
- },
208
- "./restriction-menu-container": {
209
- "types": "./src/restriction-menu/restriction-menu-container.tsx",
210
- "import": "./src/restriction-menu/restriction-menu-container.tsx"
211
- },
212
192
  "./restriction-menu-item": {
213
193
  "types": "./src/restriction-menu/restriction-menu-item.tsx",
214
194
  "import": "./src/restriction-menu/restriction-menu-item.tsx"
@@ -217,6 +197,10 @@
217
197
  "types": "./src/search-input.tsx",
218
198
  "import": "./src/search-input.tsx"
219
199
  },
200
+ "./content-unavailable": {
201
+ "types": "./src/content-unavailable.tsx",
202
+ "import": "./src/content-unavailable.tsx"
203
+ },
220
204
  "./footer": {
221
205
  "types": "./src/footer/footer.tsx",
222
206
  "import": "./src/footer/footer.tsx"
@@ -240,7 +224,15 @@
240
224
  "./taxonomy-restriction-menu": {
241
225
  "types": "./src/restriction-menu/taxonomy-restriction-menu.tsx",
242
226
  "import": "./src/restriction-menu/taxonomy-restriction-menu.tsx"
243
- }
227
+ },
228
+ "./restriction-selection-command-menu": {
229
+ "types": "./src/restriction-menu/restriction-selection-command-menu.tsx",
230
+ "import": "./src/restriction-menu/restriction-selection-command-menu.tsx"
231
+ },
232
+ "./taxonomy-restriction-command-menu": {
233
+ "types": "./src/restriction-menu/taxonomy-restriction-command-menu.tsx",
234
+ "import": "./src/restriction-menu/taxonomy-restriction-command-menu.tsx"
235
+ }
244
236
  },
245
237
  "scripts": {
246
238
  "storybook": "storybook dev -p 6006",
@@ -10,6 +10,7 @@ import { cn } from "@c-rex/utils";
10
10
  import { useTranslations } from "next-intl";
11
11
  import { FavoriteButton } from "../favorites/favorite-button";
12
12
  import { ResultTypes } from "@c-rex/types";
13
+ import { ShareButton } from "../share-button";
13
14
  type Props = {
14
15
  id: string;
15
16
  articleType: ResultTypes;
@@ -31,7 +32,7 @@ export const ArticleActionBar: FC<Props> = ({ id, articleType, favoriteLabel })
31
32
  }, [open]);
32
33
 
33
34
  return (
34
- <div className="w-9 flex gap-2 transition-all duration-300 flex-row z-20 items-end md:flex-col md:rounded-2xl md:sticky md:top-24 md:self-start">
35
+ <div className="w-9 flex gap-2 transition-all duration-300 z-20 items-end flex-col sticky top-40 md:top-24 self-start">
35
36
 
36
37
  <SidebarTrigger side="right" />
37
38
 
@@ -41,6 +42,8 @@ export const ArticleActionBar: FC<Props> = ({ id, articleType, favoriteLabel })
41
42
  label={favoriteLabel}
42
43
  />
43
44
 
45
+ <ShareButton />
46
+
44
47
  <Button
45
48
  variant="ghost"
46
49
  size="icon"
@@ -5,7 +5,7 @@ import { useAppConfig } from "@c-rex/contexts/config-provider";
5
5
  import { AvailableVersionsInterface } from "@c-rex/interfaces";
6
6
  import { toast } from "sonner"
7
7
  import { useTranslations } from "next-intl"
8
- import { useSearchSettingsStore } from "./stores/search-settings-store";
8
+ import { useSearchSettingsStore } from "../stores/search-settings-store";
9
9
 
10
10
  interface Props {
11
11
  availableVersions: AvailableVersionsInterface[]
@@ -0,0 +1,108 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import { useQueryState } from "nuqs";
5
+ import { useHighlight } from "@c-rex/contexts/highlight-provider";
6
+ import { useHighlightStore } from "../stores/highlight-store";
7
+
8
+ const EXCLUDED_TAGS = new Set(["MARK", "SCRIPT", "STYLE", "NOSCRIPT", "TEXTAREA"]);
9
+
10
+ export const extractHighlightTerms = (query: string | null): string[] => {
11
+ if (!query) return [];
12
+
13
+ return query
14
+ .split(/[+ ]/)
15
+ .map((term) => term.trim())
16
+ .filter(Boolean);
17
+ };
18
+
19
+ const applyHighlightToElement = (container: HTMLElement, terms: string[]) => {
20
+ if (terms.length === 0) return;
21
+
22
+ const escapedTerms = terms.map((term) =>
23
+ term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
24
+ );
25
+ const regex = new RegExp(`(${escapedTerms.join("|")})`, "gi");
26
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
27
+ const textNodes: Text[] = [];
28
+
29
+ let currentNode = walker.nextNode();
30
+ while (currentNode) {
31
+ const parent = currentNode.parentElement;
32
+ const textContent = currentNode.textContent ?? "";
33
+
34
+ if (
35
+ parent &&
36
+ !EXCLUDED_TAGS.has(parent.tagName) &&
37
+ textContent.trim().length > 0
38
+ ) {
39
+ const hasMatch = regex.test(textContent);
40
+ regex.lastIndex = 0;
41
+
42
+ if (hasMatch) {
43
+ textNodes.push(currentNode as Text);
44
+ }
45
+ }
46
+
47
+ currentNode = walker.nextNode();
48
+ }
49
+
50
+ textNodes.forEach((textNode) => {
51
+ const textContent = textNode.textContent ?? "";
52
+ const parts = textContent.split(regex);
53
+
54
+ if (parts.length <= 1) return;
55
+
56
+ const fragment = document.createDocumentFragment();
57
+
58
+ parts.forEach((part) => {
59
+ if (!part) return;
60
+
61
+ const isMatch = regex.test(part);
62
+ regex.lastIndex = 0;
63
+
64
+ if (isMatch) {
65
+ const mark = document.createElement("mark");
66
+ mark.className = "bg-yellow-200";
67
+ mark.textContent = part;
68
+ fragment.appendChild(mark);
69
+ return;
70
+ }
71
+
72
+ fragment.appendChild(document.createTextNode(part));
73
+ });
74
+
75
+ textNode.parentNode?.replaceChild(fragment, textNode);
76
+ });
77
+ };
78
+
79
+ type RenderArticleHighlightProps = {
80
+ containerId: string
81
+ htmlContent: string
82
+ };
83
+
84
+ export const RenderArticleHighlight = ({
85
+ containerId,
86
+ htmlContent,
87
+ }: RenderArticleHighlightProps) => {
88
+ const [query] = useQueryState("q");
89
+ const enableHighlight = useHighlightStore((state) => state.enable);
90
+ const { registerContainer, refreshMarks } = useHighlight();
91
+
92
+ useEffect(() => {
93
+ const container = document.getElementById(containerId) as HTMLElement | null;
94
+ if (!container) return;
95
+
96
+ registerContainer(container);
97
+ container.innerHTML = htmlContent;
98
+
99
+ if (enableHighlight) {
100
+ const terms = extractHighlightTerms(query);
101
+ applyHighlightToElement(container, terms);
102
+ }
103
+
104
+ refreshMarks();
105
+ }, [containerId, enableHighlight, htmlContent, query, refreshMarks, registerContainer]);
106
+
107
+ return null;
108
+ };
@@ -0,0 +1,28 @@
1
+ import { useId } from "react";
2
+ import styles from "./render-article.module.css";
3
+ import { RenderArticleHighlight } from "./render-article-highlight";
4
+
5
+ type Props = {
6
+ htmlContent: string;
7
+ contentLang?: string;
8
+ };
9
+
10
+ export const RenderArticle = ({ htmlContent, contentLang }: Props) => {
11
+ const containerId = useId().replace(/:/g, "");
12
+
13
+ return (
14
+ <>
15
+ <main
16
+ id={containerId}
17
+ data-content-scope="dita"
18
+ lang={contentLang}
19
+ className={`ids-content ids-content--dita-ot ${styles.idsContent}`}
20
+ dangerouslySetInnerHTML={{ __html: htmlContent }}
21
+ />
22
+ <RenderArticleHighlight
23
+ containerId={containerId}
24
+ htmlContent={htmlContent}
25
+ />
26
+ </>
27
+ );
28
+ };
@@ -209,7 +209,7 @@ export const AutoComplete = ({
209
209
  <InputGroupButton
210
210
  size="icon-xs"
211
211
  variant="ghost"
212
- aria-label="Clear search"
212
+ aria-label={t("clearSearch")}
213
213
  onClick={clearSearch}
214
214
  >
215
215
  <X className="size-3" />
@@ -246,7 +246,7 @@ export const AutoComplete = ({
246
246
  ))}
247
247
  </>
248
248
  ) : (
249
- <li className="px-4 py-2">No suggestions.</li>
249
+ <li className="px-4 py-2">{t("noSuggestions")}</li>
250
250
  )}
251
251
  </>
252
252
  )}
@@ -298,6 +298,7 @@ export const Carousel: FC<Props> = ({
298
298
  <div ref={containerRef} className={cn("w-full flex items-center")}>
299
299
  {(arrows && data && data.length > 0) && <CarouselPrev />}
300
300
  <div className={cn("flex-1 overflow-hidden relative flex items-center")}>
301
+ {/*TODO: update loading position*/}
301
302
  {loadByPages && loading && data && data.length > 0 && (
302
303
  <div className="absolute right-3 top-3 z-20 rounded-full bg-background/95 p-2 text-muted-foreground shadow-sm">
303
304
  <LoaderCircle className="h-3.5 w-3.5 animate-spin" />
@@ -327,7 +328,10 @@ export const Carousel: FC<Props> = ({
327
328
  data.map((item: CommonItemsModel, index: number) => (
328
329
  <div
329
330
  key={item.shortId}
330
- className={`flex-shrink-0 flex-grow-0 flex justify-center`}
331
+ className={cn(
332
+ 'flex-shrink-0 flex-grow-0 flex justify-center',
333
+ loading && "opacity-40"
334
+ )}
331
335
  style={{ width: `${100 / slidesToShow}%` }}
332
336
  >
333
337
  <RenderComponent
@@ -454,7 +458,6 @@ const CarouselIndicators: FC<{ className?: string }> = ({ className }) => {
454
458
  );
455
459
  })}
456
460
  </ol>
457
- {pagedMode && loading && <LoaderCircle className="h-3.5 w-3.5 animate-spin text-muted-foreground" />}
458
461
  </div>
459
462
  );
460
463
  };
@@ -65,7 +65,7 @@ export const InformationUnitCarouselItem: FC<Props> = ({
65
65
  <Tooltip>
66
66
  <TooltipTrigger asChild>
67
67
  <div className="h-44 overflow-hidden">
68
- <span className="line-clamp-5 text-lg font-semibold group-hover:underline">
68
+ <span className="line-clamp-5 text-lg font-semibold group-hover:underline max-w-full overflow-ellipsis [overflow-wrap:anywhere] hyphens-auto">
69
69
  {title}
70
70
  </span>
71
71
  </div>
@@ -0,0 +1,20 @@
1
+ import { ComponentProps, FC } from "react";
2
+ import { PageWrapper } from "@c-rex/components/page-wrapper";
3
+ import { getTranslations } from "next-intl/server";
4
+
5
+
6
+ type Props = Omit<ComponentProps<typeof PageWrapper>, "children">;
7
+
8
+ export const ContentUnavailable: FC<Props> = async ({ ...props }) => {
9
+ const t = await getTranslations();
10
+
11
+ return (
12
+ <PageWrapper {...props}>
13
+ <div className="flex flex-1 justify-center items-center p-4">
14
+ <div className="rounded-md border p-4 text-sm text-muted-foreground">
15
+ {t("contentTemporarilyUnavailableRetry")}
16
+ </div>
17
+ </div>
18
+ </PageWrapper>
19
+ )
20
+ }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { createContext, useContext, useEffect, useMemo, useState, type FC, type PropsWithChildren } from "react";
4
+ import { useTranslations } from "next-intl";
4
5
  import { useRouter } from "next/navigation";
5
6
  import type { TreeOfContent } from "@c-rex/interfaces";
6
7
  import {
@@ -232,6 +233,7 @@ type DirectoryTreeSidebarMenuProps = {
232
233
  export const DirectoryTreeSidebarMenu: FC<DirectoryTreeSidebarMenuProps> = ({
233
234
  onNavigateNode,
234
235
  }) => {
236
+ const t = useTranslations();
235
237
  const { tree, isLoading, expandActivePathOnLoad } = useDirectoryTree();
236
238
  const [expandedNodeIds, setExpandedNodeIds] = useState<Set<string>>(new Set());
237
239
 
@@ -266,7 +268,8 @@ export const DirectoryTreeSidebarMenu: FC<DirectoryTreeSidebarMenuProps> = ({
266
268
  <Skeleton className="w-auto h-10 mb-2 ml-8" />
267
269
  <Skeleton className="w-auto h-10 mb-2 ml-8" />
268
270
  <Skeleton className="w-auto h-10 mb-2" />
269
- <div className="px-2 pt-1 text-xs text-muted-foreground">Inhaltsverzeichnis wird geladen...</div>
271
+ {/*TODO: need this?*/}
272
+ <div className="px-2 pt-1 text-xs text-muted-foreground">{t("loadingTableOfContents")}</div>
270
273
  </div>
271
274
  );
272
275
  }
@@ -302,7 +305,7 @@ export const DirectoryTreeSidebarMenu: FC<DirectoryTreeSidebarMenuProps> = ({
302
305
  {node.children?.length ? (
303
306
  <button
304
307
  type="button"
305
- aria-label={expandedNodeIds.has(node.id) ? "Collapse section" : "Expand section"}
308
+ aria-label={expandedNodeIds.has(node.id) ? t("collapseSection") : t("expandSection")}
306
309
  className="h-8 w-8 inline-flex items-center justify-center text-muted-foreground hover:text-foreground"
307
310
  onClick={() => toggleNode(node.id)}
308
311
  >
@@ -335,11 +338,13 @@ type DirectoryTreeBreadcrumbProps = {
335
338
 
336
339
  export const DirectoryTreeBreadcrumb: FC<DirectoryTreeBreadcrumbProps> = ({
337
340
  lang,
338
- homeLabel = "Home",
341
+ homeLabel,
339
342
  onNavigateHome,
340
343
  onNavigateNodeId,
341
344
  }) => {
345
+ const t = useTranslations();
342
346
  const { tree, isLoading } = useDirectoryTree();
347
+ const resolvedHomeLabel = homeLabel ?? t("home");
343
348
  const router = useRouter();
344
349
  const items = generateBreadcrumbItems(tree);
345
350
 
@@ -362,7 +367,7 @@ export const DirectoryTreeBreadcrumb: FC<DirectoryTreeBreadcrumbProps> = ({
362
367
  <BreadcrumbList>
363
368
  <BreadcrumbItem>
364
369
  <button type="button" onClick={navigateHome}>
365
- {homeLabel}
370
+ {resolvedHomeLabel}
366
371
  </button>
367
372
  </BreadcrumbItem>
368
373
  <BreadcrumbSeparator />
@@ -2,8 +2,10 @@
2
2
 
3
3
  import { FC, useEffect, useState } from "react";
4
4
  import { FragmentsGetByIdClient } from "../generated/client-components";
5
+ import { Skeleton } from "@c-rex/ui/skeleton";
5
6
  import { ExpandableSummary } from "./expandable-summary";
6
7
  import type { RenditionModel } from "@c-rex/interfaces";
8
+ import { useTranslations } from "next-intl";
7
9
 
8
10
  interface Props {
9
11
  fragmentShortId?: string;
@@ -48,6 +50,7 @@ const DocumentDescriptionPreviewContent: FC<{
48
50
  isLoading,
49
51
  hasError,
50
52
  }) => {
53
+ const t = useTranslations();
51
54
  const [text, setText] = useState<string | null>(null);
52
55
  const [loadingText, setLoadingText] = useState(false);
53
56
 
@@ -84,15 +87,20 @@ const DocumentDescriptionPreviewContent: FC<{
84
87
  }, [title, viewHref]);
85
88
 
86
89
  if (hasError) {
87
- return <span className="text-muted-foreground">No rendition available</span>;
90
+ return <span className="text-muted-foreground">{t("noRenditionAvailable")}</span>;
88
91
  }
89
92
 
90
93
  if (isLoading || loadingText) {
91
- return <span className="text-muted-foreground">Loading rendition...</span>;
94
+ return (
95
+ <div className="flex flex-col gap-1 pt-1">
96
+ <Skeleton className="h-4 w-full" />
97
+ <Skeleton className="h-4 w-5/6" />
98
+ </div>
99
+ );
92
100
  }
93
101
 
94
102
  if (!text) {
95
- return <span className="text-muted-foreground">No rendition available</span>;
103
+ return <span className="text-muted-foreground">{t("noRenditionAvailable")}</span>;
96
104
  }
97
105
 
98
106
  return <ExpandableSummary text={text} />;
@@ -102,8 +110,10 @@ export const DocumentDescriptionPreview: FC<Props> = ({
102
110
  fragmentShortId,
103
111
  title,
104
112
  }) => {
113
+ const t = useTranslations();
114
+
105
115
  if (!fragmentShortId) {
106
- return <span className="text-muted-foreground">No rendition available</span>;
116
+ return <span className="text-muted-foreground">{t("noRenditionAvailable")}</span>;
107
117
  }
108
118
 
109
119
  return (