@c-rex/components 0.3.0-build.28 → 0.3.0-build.30

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.
@@ -0,0 +1,368 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { useLocale, useTranslations } from "next-intl";
5
+ import Link from "next/link";
6
+ import { Card, CardContent, CardHeader, CardTitle } from "@c-rex/ui/card";
7
+ import { Table, TableBody, TableCell, TableRow } from "@c-rex/ui/table";
8
+ import { Flag } from "../icons/flag-icon";
9
+ import { BookmarkButton } from "../favorites/bookmark-button";
10
+ import { renderMetadataDisplayValues } from "./shared";
11
+ import { InformationUnitsGetAllClient } from "../generated/client-components";
12
+ import type {
13
+ CommonItemsModel,
14
+ InformationUnitModel,
15
+ LiteralModel,
16
+ ObjectRefModel,
17
+ RenditionModel,
18
+ } from "@c-rex/interfaces";
19
+ import {
20
+ INFORMATION_UNIT_PROPERTY_PRESENTATION,
21
+ type InformationUnitPropertyKey,
22
+ } from "@c-rex/services/metadata-presentation-config";
23
+ import { resolveMetadataDisplayProperties } from "@c-rex/services/metadata-view-profile";
24
+ import {
25
+ extractCountryCodeFromLanguage,
26
+ getFileRenditionGroups,
27
+ resolvePreferredLanguage,
28
+ sortAndDeduplicateLanguages,
29
+ } from "@c-rex/utils";
30
+
31
+ type MetadataDisplayRow = {
32
+ key: InformationUnitPropertyKey;
33
+ label: string;
34
+ labelSource: "translationKey" | "direct";
35
+ values: string[];
36
+ valueType?: "text" | "language" | "rendition";
37
+ renditions?: RenditionModel[];
38
+ };
39
+
40
+ type Props = {
41
+ title: string;
42
+ linkPattern: string;
43
+ data: CommonItemsModel;
44
+ metadataIncludeProperties?: Array<keyof InformationUnitModel>;
45
+ metadataExcludeProperties?: Array<keyof InformationUnitModel>;
46
+ showBookmarkButton?: boolean;
47
+ showFileRenditions?: boolean;
48
+ embedded?: boolean;
49
+ };
50
+
51
+ const resolveLiteralLabel = (labels?: LiteralModel[] | null, uiLanguage?: string): string | undefined => {
52
+ if (!labels || labels.length === 0) return undefined;
53
+ const language = uiLanguage || "en";
54
+ const preferred = resolvePreferredLanguage(
55
+ labels.map((item) => item.language || ""),
56
+ language
57
+ );
58
+ if (preferred) {
59
+ const exact = labels.find((item) => item.language === preferred)?.value;
60
+ if (exact) return exact;
61
+ }
62
+ return labels.find((item) => item.value)?.value || undefined;
63
+ };
64
+
65
+ const resolveObjectRefLabel = (item: ObjectRefModel, uiLanguage: string): string | undefined => {
66
+ return resolveLiteralLabel(item.labels || [], uiLanguage) || item.shortId || item.id || undefined;
67
+ };
68
+
69
+ const asObjectRefArray = (value: unknown): ObjectRefModel[] => {
70
+ if (!Array.isArray(value)) return [];
71
+ return value.filter((item): item is ObjectRefModel => Boolean(item && typeof item === "object"));
72
+ };
73
+
74
+ const asLiteralArray = (value: unknown): LiteralModel[] => {
75
+ if (!Array.isArray(value)) return [];
76
+ return value.filter((item): item is LiteralModel => Boolean(item && typeof item === "object"));
77
+ };
78
+
79
+ const buildMetadataDisplayRows = (
80
+ data: CommonItemsModel,
81
+ uiLanguage: string,
82
+ includeProperties: InformationUnitPropertyKey[]
83
+ ): MetadataDisplayRow[] => {
84
+ const preferredTitle = resolveLiteralLabel(data.titles || [], uiLanguage) || resolveLiteralLabel(data.labels || [], uiLanguage);
85
+ const rows: MetadataDisplayRow[] = [];
86
+
87
+ for (const key of includeProperties) {
88
+ const config = INFORMATION_UNIT_PROPERTY_PRESENTATION[key];
89
+ if (!config?.metadataDisplay?.supported) continue;
90
+
91
+ if (key === "labels") continue;
92
+
93
+ if (key === "titles") {
94
+ if (preferredTitle) {
95
+ rows.push({
96
+ key,
97
+ label: key,
98
+ labelSource: "translationKey",
99
+ values: [preferredTitle],
100
+ valueType: "text",
101
+ });
102
+ }
103
+ continue;
104
+ }
105
+
106
+ const value = (data as InformationUnitModel)[key];
107
+ if (value == null) continue;
108
+
109
+ if (key === "languages") {
110
+ const languages = sortAndDeduplicateLanguages((value as string[]) || []);
111
+ if (languages.length > 0) {
112
+ rows.push({
113
+ key,
114
+ label: key,
115
+ labelSource: "translationKey",
116
+ values: languages,
117
+ valueType: "language",
118
+ });
119
+ }
120
+ continue;
121
+ }
122
+
123
+ if (config.valueKind === "scalar") {
124
+ rows.push({
125
+ key,
126
+ label: key,
127
+ labelSource: "translationKey",
128
+ values: [String(value)],
129
+ valueType: "text",
130
+ });
131
+ continue;
132
+ }
133
+
134
+ if (config.valueKind === "stringArray") {
135
+ const values = Array.from(new Set((value as string[]).map((item) => String(item)).filter(Boolean)));
136
+ if (values.length > 0) {
137
+ rows.push({
138
+ key,
139
+ label: key,
140
+ labelSource: "translationKey",
141
+ values,
142
+ valueType: "text",
143
+ });
144
+ }
145
+ continue;
146
+ }
147
+
148
+ if (config.valueKind === "literalArray") {
149
+ const preferred = resolveLiteralLabel(asLiteralArray(value), uiLanguage);
150
+ if (preferred) {
151
+ rows.push({
152
+ key,
153
+ label: key,
154
+ labelSource: "translationKey",
155
+ values: [preferred],
156
+ valueType: "text",
157
+ });
158
+ }
159
+ continue;
160
+ }
161
+
162
+ if (config.valueKind === "objectRef") {
163
+ const label = resolveObjectRefLabel(value as ObjectRefModel, uiLanguage);
164
+ if (label) {
165
+ rows.push({
166
+ key,
167
+ label: key,
168
+ labelSource: "translationKey",
169
+ values: [label],
170
+ valueType: "text",
171
+ });
172
+ }
173
+ continue;
174
+ }
175
+
176
+ if (config.valueKind === "objectRefArray") {
177
+ const refs = asObjectRefArray(value);
178
+ if (refs.length === 0) continue;
179
+
180
+ const values = Array.from(
181
+ new Set(
182
+ refs
183
+ .map((ref) => resolveObjectRefLabel(ref, uiLanguage))
184
+ .filter((label): label is string => Boolean(label))
185
+ )
186
+ ).sort((a, b) => a.localeCompare(b));
187
+
188
+ if (values.length === 0) continue;
189
+
190
+ rows.push({
191
+ key,
192
+ label: key,
193
+ labelSource: "translationKey",
194
+ values,
195
+ valueType: "text",
196
+ });
197
+ continue;
198
+ }
199
+
200
+ if (config.valueKind === "renditionArray") {
201
+ const renditions = Array.isArray(value)
202
+ ? value.filter((item): item is RenditionModel => Boolean(item && typeof item === "object"))
203
+ : [];
204
+ if (renditions.length === 0) continue;
205
+ if (getFileRenditionGroups({ renditions }).length === 0) continue;
206
+
207
+ rows.push({
208
+ key,
209
+ label: "files",
210
+ labelSource: "translationKey",
211
+ values: [],
212
+ valueType: "rendition",
213
+ renditions,
214
+ });
215
+ }
216
+ }
217
+
218
+ return rows;
219
+ };
220
+
221
+ const normalizeVersionItems = (items: CommonItemsModel[], uiLanguage: string) => {
222
+ const uniqueByShortId = new Map<string, { shortId: string; language: string }>();
223
+
224
+ items.forEach((item) => {
225
+ const shortId = item.shortId?.trim();
226
+ if (!shortId) return;
227
+ const language = resolvePreferredLanguage(item.languages, uiLanguage);
228
+ if (!language) return;
229
+ uniqueByShortId.set(shortId, { shortId, language });
230
+ });
231
+
232
+ return Array.from(uniqueByShortId.values()).sort((a, b) => {
233
+ const languageOrder = a.language.localeCompare(b.language);
234
+ if (languageOrder !== 0) return languageOrder;
235
+ return a.shortId.localeCompare(b.shortId);
236
+ });
237
+ };
238
+
239
+ const AvailableInRow = ({
240
+ versionOfShortId,
241
+ currentShortId,
242
+ linkPattern,
243
+ }: {
244
+ versionOfShortId?: string | null;
245
+ currentShortId?: string | null;
246
+ linkPattern: string;
247
+ }) => {
248
+ const t = useTranslations();
249
+ const locale = useLocale();
250
+
251
+ if (!versionOfShortId) return null;
252
+
253
+ return (
254
+ <InformationUnitsGetAllClient
255
+ queryParams={{
256
+ Restrict: [`versionOf.shortId=${versionOfShortId}`],
257
+ Fields: ["shortId", "languages", "labels", "versionOf"],
258
+ PageNumber: 1,
259
+ PageSize: 200,
260
+ Sort: ["languages"],
261
+ }}
262
+ >
263
+ {({ data }) => {
264
+ const versions = normalizeVersionItems(data?.items || [], locale)
265
+ .filter((item) => item.shortId !== currentShortId);
266
+
267
+ if (versions.length === 0) return null;
268
+
269
+ return (
270
+ <TableRow className="min-h-12">
271
+ <TableCell className="font-medium w-28 pl-4">
272
+ <h4 className="text-sm font-medium">{t("availableIn")}</h4>
273
+ </TableCell>
274
+ <TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
275
+ {versions.map((item) => (
276
+ <span className="w-8 block border" key={item.shortId}>
277
+ <Link
278
+ href={linkPattern.replace("{shortId}", item.shortId)}
279
+ title={item.language}
280
+ >
281
+ <Flag countryCode={extractCountryCodeFromLanguage(item.language)} />
282
+ </Link>
283
+ </span>
284
+ ))}
285
+ </TableCell>
286
+ </TableRow>
287
+ );
288
+ }}
289
+ </InformationUnitsGetAllClient>
290
+ );
291
+ };
292
+
293
+ export const InformationUnitMetadataGridClient = ({
294
+ title,
295
+ data,
296
+ embedded = false,
297
+ linkPattern,
298
+ metadataIncludeProperties,
299
+ metadataExcludeProperties,
300
+ showBookmarkButton = false,
301
+ showFileRenditions = true,
302
+ }: Props) => {
303
+ const t = useTranslations();
304
+ const locale = useLocale();
305
+
306
+ const metadataRows = useMemo(() => {
307
+ const includeProperties = resolveMetadataDisplayProperties({
308
+ includeProperties: metadataIncludeProperties as string[] | undefined,
309
+ excludeProperties: showFileRenditions
310
+ ? (metadataExcludeProperties as string[] | undefined)
311
+ : [...(metadataExcludeProperties || []), "renditions"],
312
+ });
313
+
314
+ return buildMetadataDisplayRows(data, locale, includeProperties);
315
+ }, [data, locale, metadataExcludeProperties, metadataIncludeProperties, showFileRenditions]);
316
+
317
+ const cardContent = (
318
+ <CardContent className="space-y-3 !p-0">
319
+ <Table className="table-fixed">
320
+ <TableBody>
321
+ {showBookmarkButton && (
322
+ <TableRow className="min-h-12">
323
+ <TableCell className="font-medium w-32 pl-4 align-top">
324
+ <h4 className="text-sm font-medium">{t("favorites")}</h4>
325
+ </TableCell>
326
+ <TableCell className="min-h-12 pt-3 text-xs text-muted-foreground align-top break-words whitespace-normal">
327
+ <BookmarkButton shortId={data.shortId!} />
328
+ </TableCell>
329
+ </TableRow>
330
+ )}
331
+
332
+ {metadataRows.map((row) => (
333
+ <TableRow key={`${row.key}:${row.label}`} className="min-h-12">
334
+ <TableCell className="font-medium w-32 pl-4 align-top">
335
+ <h4 className="text-sm font-medium capitalize">
336
+ {row.labelSource === "translationKey" ? t(row.label) : row.label}
337
+ </h4>
338
+ </TableCell>
339
+ <TableCell className="min-h-12 text-xs text-muted-foreground align-top break-words whitespace-normal [&_*]:break-words">
340
+ {renderMetadataDisplayValues(row, locale)}
341
+ </TableCell>
342
+ </TableRow>
343
+ ))}
344
+
345
+ <AvailableInRow
346
+ versionOfShortId={data.versionOf?.shortId}
347
+ currentShortId={data.shortId}
348
+ linkPattern={linkPattern}
349
+ />
350
+ </TableBody>
351
+ </Table>
352
+ </CardContent>
353
+ );
354
+
355
+ if (embedded) return cardContent;
356
+
357
+ return (
358
+ <Card className="!p-0 !gap-0">
359
+ <CardHeader>
360
+ <CardTitle className="text-lg flex justify-between items-end">
361
+ {title}
362
+ {showBookmarkButton && <BookmarkButton shortId={data.shortId!} />}
363
+ </CardTitle>
364
+ </CardHeader>
365
+ {cardContent}
366
+ </Card>
367
+ );
368
+ };
@@ -0,0 +1,77 @@
1
+ "use client";
2
+
3
+ import type { CommonItemsModel } from "@c-rex/interfaces";
4
+ import { ImageRenditionContainer } from "../renditions/image/container";
5
+ import { DocumentsGetByIdClient } from "../generated/client-components";
6
+ import { resolveInformationUnitPreviewFragmentShortId } from "./information-unit-fragment-ids";
7
+
8
+ type Props = {
9
+ item?: (CommonItemsModel & { informationUnits?: CommonItemsModel["informationUnits"] }) | null;
10
+ imageFragmentSubjectIds?: string[];
11
+ loadImage?: boolean;
12
+ emptyImageStyle?: string;
13
+ skeletonStyle?: string;
14
+ imageStyle?: string;
15
+ };
16
+
17
+ export const InformationUnitPreviewImage = ({
18
+ item,
19
+ imageFragmentSubjectIds = [],
20
+ loadImage = true,
21
+ emptyImageStyle,
22
+ skeletonStyle,
23
+ imageStyle,
24
+ }: Props) => {
25
+ const initialFragmentShortId = loadImage
26
+ ? resolveInformationUnitPreviewFragmentShortId(item, imageFragmentSubjectIds)
27
+ : undefined;
28
+
29
+ if (!loadImage) {
30
+ return (
31
+ <ImageRenditionContainer
32
+ emptyImageStyle={emptyImageStyle}
33
+ skeletonStyle={skeletonStyle}
34
+ imageStyle={imageStyle}
35
+ />
36
+ );
37
+ }
38
+
39
+ if (initialFragmentShortId || !item?.shortId) {
40
+ return (
41
+ <ImageRenditionContainer
42
+ fragmentShortId={initialFragmentShortId}
43
+ emptyImageStyle={emptyImageStyle}
44
+ skeletonStyle={skeletonStyle}
45
+ imageStyle={imageStyle}
46
+ />
47
+ );
48
+ }
49
+
50
+ return (
51
+ <DocumentsGetByIdClient
52
+ pathParams={{ id: item.shortId }}
53
+ queryParams={{
54
+ // TODO(IDS): Temporary generic fallback for information-unit preview images.
55
+ // Remove this per-card detail lookup once list-based teaser/search requests can
56
+ // safely switch back to embedded `informationUnits` without causing unacceptable
57
+ // IDS load. Keep this in sync with all callers that intentionally removed the
58
+ // embedded relation from their list requests, especially `apps/cdp/app/page.tsx`.
59
+ Fields: ["informationUnits"],
60
+ Embed: ["informationUnits"],
61
+ }}
62
+ >
63
+ {({ data }) => {
64
+ const fragmentShortId = resolveInformationUnitPreviewFragmentShortId(data, imageFragmentSubjectIds);
65
+
66
+ return (
67
+ <ImageRenditionContainer
68
+ fragmentShortId={fragmentShortId}
69
+ emptyImageStyle={emptyImageStyle}
70
+ skeletonStyle={skeletonStyle}
71
+ imageStyle={imageStyle}
72
+ />
73
+ );
74
+ }}
75
+ </DocumentsGetByIdClient>
76
+ );
77
+ };
@@ -1,7 +1,7 @@
1
1
  import { ComponentProps, ReactNode } from "react";
2
2
  import { NavBar } from './navbar/navbar';
3
3
  import { MultiSidebarProvider } from "@c-rex/ui/sidebar";
4
- import { RestrictionMenuContainer } from "./restriction-menu/restriction-menu-container";
4
+ import { TaxonomyRestrictionMenu } from "./restriction-menu/taxonomy-restriction-menu";
5
5
  import { Footer } from "./footer/footer";
6
6
 
7
7
  type Props = {
@@ -9,8 +9,8 @@ type Props = {
9
9
  showRestrictMenu?: boolean;
10
10
  showFooter?: boolean;
11
11
  renderFooter?: () => ReactNode;
12
- renderRestrictionMenu?: (props: ComponentProps<typeof RestrictionMenuContainer>) => ReactNode;
13
- } & Partial<ComponentProps<typeof NavBar>> & Partial<ComponentProps<typeof RestrictionMenuContainer>>;
12
+ renderRestrictionMenu?: (props: ComponentProps<typeof TaxonomyRestrictionMenu>) => ReactNode;
13
+ } & Partial<ComponentProps<typeof NavBar>> & Partial<ComponentProps<typeof TaxonomyRestrictionMenu>>;
14
14
 
15
15
  export const PageWrapper = ({
16
16
  children,
@@ -30,7 +30,7 @@ export const PageWrapper = ({
30
30
  navigationMenuListClassName,
31
31
  ...props
32
32
  }: Props) => {
33
- const restrictionMenuProps: ComponentProps<typeof RestrictionMenuContainer> = {
33
+ const restrictionMenuProps: ComponentProps<typeof TaxonomyRestrictionMenu> = {
34
34
  restrictField: restrictField ?? "informationSubjects",
35
35
  requestType: requestType ?? "InformationSubjectsGetAllClient",
36
36
  itemsToRender,
@@ -48,7 +48,7 @@ export const PageWrapper = ({
48
48
  {showRestrictMenu && (
49
49
  <div className="flex-1 container pt-6">
50
50
  {renderRestrictionMenu ? renderRestrictionMenu(restrictionMenuProps) : (
51
- <RestrictionMenuContainer {...restrictionMenuProps} />
51
+ <TaxonomyRestrictionMenu {...restrictionMenuProps} />
52
52
  )}
53
53
  </div>
54
54
  )}
@@ -1,6 +1,8 @@
1
+ "use client";
2
+
1
3
  import { FC } from "react";
2
4
  import { RenditionModel } from "@c-rex/interfaces";
3
- import { getTranslations } from "next-intl/server";
5
+ import { useTranslations } from "next-intl";
4
6
  import {
5
7
  DropdownMenu,
6
8
  DropdownMenuContent,
@@ -18,11 +20,11 @@ interface FileDownloadDropdown {
18
20
  buttonVariant?: "ghost" | "outline";
19
21
  }
20
22
 
21
- export const FileDownloadDropdown: FC<FileDownloadDropdown> = async ({
23
+ export const FileDownloadDropdown: FC<FileDownloadDropdown> = ({
22
24
  renditions,
23
25
  buttonVariant = "ghost",
24
26
  }) => {
25
- const t = await getTranslations();
27
+ const t = useTranslations();
26
28
 
27
29
  if (renditions == null || renditions.length == 0) return null;
28
30
  const groups = getFileRenditionGroups({ renditions });
@@ -0,0 +1,99 @@
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { FragmentsGetByIdClient } from "../generated/client-components";
5
+ import type { RenditionModel } from "@c-rex/interfaces";
6
+ import { RenderArticle } from "../render-article";
7
+
8
+ type HtmlRenditionClientProps = {
9
+ htmlFormats?: string[];
10
+ fragmentShortId?: string;
11
+ renditions?: RenditionModel[] | null;
12
+ };
13
+
14
+ const EMPTY = <div>No rendition available</div>;
15
+
16
+ const findHtmlViewUrl = (
17
+ renditions?: RenditionModel[] | null,
18
+ htmlFormats: string[] = ["application/xhtml+xml", "application/html", "text/html"]
19
+ ) => {
20
+ if (!renditions || renditions.length === 0) return undefined;
21
+ const allowed = new Set(htmlFormats.map((item) => item.toLowerCase()));
22
+ const rendition = renditions.find((item) => allowed.has((item.format || "").toLowerCase()));
23
+ return rendition?.links?.find((item) => item.rel === "view")?.href;
24
+ };
25
+
26
+ const HtmlFromRenditions = ({
27
+ renditions,
28
+ htmlFormats,
29
+ }: {
30
+ renditions?: RenditionModel[] | null;
31
+ htmlFormats?: string[];
32
+ }) => {
33
+ const [htmlContent, setHtmlContent] = useState<string | null>(null);
34
+ const [hasError, setHasError] = useState(false);
35
+ const viewUrl = useMemo(() => findHtmlViewUrl(renditions, htmlFormats), [htmlFormats, renditions]);
36
+
37
+ useEffect(() => {
38
+ let cancelled = false;
39
+ if (!viewUrl) {
40
+ setHtmlContent(null);
41
+ setHasError(false);
42
+ return;
43
+ }
44
+
45
+ setHasError(false);
46
+ setHtmlContent(null);
47
+
48
+ fetch(viewUrl)
49
+ .then((res) => res.text())
50
+ .then((html) => {
51
+ if (cancelled) return;
52
+ const parsed = new DOMParser().parseFromString(html, "text/html");
53
+ setHtmlContent(parsed.body?.innerHTML || "");
54
+ })
55
+ .catch(() => {
56
+ if (cancelled) return;
57
+ setHasError(true);
58
+ });
59
+
60
+ return () => {
61
+ cancelled = true;
62
+ };
63
+ }, [viewUrl]);
64
+
65
+ if (!viewUrl || hasError) return EMPTY;
66
+ if (htmlContent == null) return <div className="text-muted-foreground text-sm">Loading content...</div>;
67
+
68
+ return <RenderArticle htmlContent={htmlContent} />;
69
+ };
70
+
71
+ export const HtmlRenditionClient = ({
72
+ fragmentShortId,
73
+ htmlFormats = ["application/xhtml+xml", "application/html", "text/html"],
74
+ renditions,
75
+ }: HtmlRenditionClientProps) => {
76
+ if (renditions !== undefined) {
77
+ return <HtmlFromRenditions renditions={renditions} htmlFormats={htmlFormats} />;
78
+ }
79
+
80
+ if (!fragmentShortId) return EMPTY;
81
+
82
+ return (
83
+ <FragmentsGetByIdClient
84
+ pathParams={{ id: fragmentShortId }}
85
+ queryParams={{
86
+ Fields: ["titles", "renditions"],
87
+ Embed: ["renditions"],
88
+ Links: true,
89
+ }}
90
+ >
91
+ {({ data, isLoading }) => {
92
+ if (isLoading && !data) {
93
+ return <div className="text-muted-foreground text-sm">Loading content...</div>;
94
+ }
95
+ return <HtmlFromRenditions renditions={data?.renditions} htmlFormats={htmlFormats} />;
96
+ }}
97
+ </FragmentsGetByIdClient>
98
+ );
99
+ };
@@ -12,12 +12,14 @@ interface ImageContainerProps {
12
12
  emptyImageStyle?: string;
13
13
  skeletonStyle?: string;
14
14
  imageStyle?: string;
15
+ containerStyle?: string;
15
16
  }
16
17
 
17
18
  export const ImageRenditionContainer: FC<ImageContainerProps> = ({
18
19
  fragmentShortId,
19
20
  emptyImageStyle,
20
21
  imageStyle,
22
+ containerStyle,
21
23
  imageFormats = ["image/svg+xml", "image/gif", "image/png", "image/jpeg", "image/jpg"],
22
24
  skeletonStyle
23
25
  }) => {
@@ -35,23 +37,27 @@ export const ImageRenditionContainer: FC<ImageContainerProps> = ({
35
37
  isLoading ? (
36
38
  <Skeleton className={cn("w-full h-full", skeletonStyle)} />
37
39
  ) : (
38
- <ImageRendition
39
- key={data?.shortId}
40
- items={data ? [data] : []}
41
- formats={imageFormats}
42
- emptyImageStyle={emptyImageStyle}
43
- imageStyle={imageStyle}
44
- />
40
+ <div className={cn("w-full h-full flex items-start justify-center overflow-hidden", containerStyle)}>
41
+ <ImageRendition
42
+ key={data?.shortId}
43
+ items={data ? [data] : []}
44
+ formats={imageFormats}
45
+ emptyImageStyle={emptyImageStyle}
46
+ imageStyle={imageStyle}
47
+ />
48
+ </div>
45
49
  )
46
50
  )}
47
51
  </FragmentsGetByIdClient>
48
52
  ) : (
49
- <ImageRendition
50
- items={[]}
51
- formats={imageFormats}
52
- emptyImageStyle={emptyImageStyle}
53
- imageStyle={imageStyle}
54
- />
53
+ <div className={cn("w-full h-full flex items-start justify-center overflow-hidden", containerStyle)}>
54
+ <ImageRendition
55
+ items={[]}
56
+ formats={imageFormats}
57
+ emptyImageStyle={emptyImageStyle}
58
+ imageStyle={imageStyle}
59
+ />
60
+ </div>
55
61
  )
56
62
  )
57
63
  }
@@ -51,6 +51,11 @@ export const ImageRendition: FC<ImageRenditionProps> = ({
51
51
  "Document image";
52
52
 
53
53
  return (
54
- <img src={src} alt={alt} loading="eager" className={cn(imageStyle, "w-full")} />
54
+ <img
55
+ src={src}
56
+ alt={alt}
57
+ loading="eager"
58
+ className={cn("max-w-full", imageStyle)}
59
+ />
55
60
  );
56
61
  }