Skip to content

Commit 5f6853d

Browse files
Support new object-fit property on individual card cover image (#3594)
1 parent 17dd382 commit 5f6853d

File tree

4 files changed

+85
-46
lines changed

4 files changed

+85
-46
lines changed

bun.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@
295295
"react-dom": "^19.0.0",
296296
},
297297
"catalog": {
298-
"@gitbook/api": "^0.138.0",
298+
"@gitbook/api": "^0.139.0",
299299
"bidc": "^0.0.2",
300300
},
301301
"packages": {
@@ -661,7 +661,7 @@
661661

662662
"@fortawesome/fontawesome-svg-core": ["@fortawesome/[email protected]", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
663663

664-
"@gitbook/api": ["@gitbook/api@0.138.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-6jYH1R5IpmbFj3qUyDGuUBaABCinKQcqNCtTKtql0MSPaFp9KVnqyBsIaq/s9HohjCpyU1/EoddwzAaJWHWkkw=="],
664+
"@gitbook/api": ["@gitbook/api@0.139.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-6VqN4BVvOdaRng2xz5wh0gj6NJmJJZwtswKZ6g9RMWedp2UkeMXi+kcO3fBo2VYJsxhIgbtWikqGEePgHTx67g=="],
665665

666666
"@gitbook/browser-types": ["@gitbook/browser-types@workspace:packages/browser-types"],
667667

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"workspaces": {
3535
"packages": ["packages/*"],
3636
"catalog": {
37-
"@gitbook/api": "^0.138.0",
37+
"@gitbook/api": "^0.139.0",
3838
"bidc": "^0.0.2"
3939
}
4040
},

packages/gitbook/src/components/DocumentView/Table/RecordCard.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Image } from '@/components/utils';
33
import { type ResolvedContentRef, resolveContentRef } from '@/lib/references';
44
import { tcls } from '@/lib/tailwind';
55
import {
6+
CardsImageObjectFit,
67
type ContentRef,
78
type DocumentTableViewCards,
89
SiteInsightsLinkPosition,
@@ -25,8 +26,12 @@ export async function RecordCard(
2526
: null;
2627

2728
const [lightCover, darkCover, target] = await Promise.all([
28-
light && context.contentContext ? resolveContentRef(light, context.contentContext) : null,
29-
dark && context.contentContext ? resolveContentRef(dark, context.contentContext) : null,
29+
light.contentRef && context.contentContext
30+
? resolveContentRef(light.contentRef, context.contentContext)
31+
: null,
32+
dark.contentRef && context.contentContext
33+
? resolveContentRef(dark.contentRef, context.contentContext)
34+
: null,
3035
targetRef && context.contentContext
3136
? resolveContentRef(targetRef, context.contentContext)
3237
: null,
@@ -35,6 +40,10 @@ export async function RecordCard(
3540
const darkCoverIsSquareOrPortrait = isSquareOrPortrait(darkCover);
3641
const lightCoverIsSquareOrPortrait = isSquareOrPortrait(lightCover);
3742

43+
const darkObjectFit = dark.objectFit ? `dark:${getObjectFitClass(dark.objectFit)}` : '';
44+
const lightObjectFit = light.objectFit ? getObjectFitClass(light.objectFit) : '';
45+
const objectFits = `${lightObjectFit} ${darkObjectFit}`;
46+
3847
const body = (
3948
<div
4049
className={tcls(
@@ -94,7 +103,7 @@ export async function RecordCard(
94103
'min-w-0',
95104
'w-full',
96105
'h-full',
97-
'object-cover',
106+
'bg-tint-subtle',
98107
lightCoverIsSquareOrPortrait || darkCoverIsSquareOrPortrait
99108
? [
100109
lightCoverIsSquareOrPortrait
@@ -104,7 +113,8 @@ export async function RecordCard(
104113
? 'dark:min-[432px]:aspect-video dark:min-[432px]:h-auto'
105114
: '',
106115
].filter(Boolean)
107-
: ['h-auto', 'aspect-video']
116+
: ['h-auto', 'aspect-video'],
117+
objectFits
108118
)}
109119
priority={isOffscreen ? 'lazy' : 'high'}
110120
preload
@@ -190,3 +200,19 @@ function isSquareOrPortrait(contentRef: ResolvedContentRef | null) {
190200

191201
return file.dimensions?.width / file.dimensions?.height <= 1;
192202
}
203+
204+
/**
205+
* Get the CSS class for object-fit based on the objectFit value.
206+
*/
207+
function getObjectFitClass(objectFit: CardsImageObjectFit): string {
208+
switch (objectFit) {
209+
case CardsImageObjectFit.Contain:
210+
return 'object-contain';
211+
case CardsImageObjectFit.Fill:
212+
return 'object-fill';
213+
case CardsImageObjectFit.Cover:
214+
return 'object-cover';
215+
default:
216+
throw new Error(`Unsupported object fit: ${objectFit}`);
217+
}
218+
}

packages/gitbook/src/components/DocumentView/Table/utils.ts

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type {
2+
CardsImageObjectFit,
23
ContentRef,
34
ContentRefFile,
45
ContentRefURL,
56
DocumentTableDefinition,
7+
DocumentTableImageRecord,
68
DocumentTableRecord,
79
DocumentTableViewCards,
810
} from '@gitbook/api';
@@ -21,55 +23,66 @@ export function getRecordValue<T extends number | string | boolean | string[] |
2123

2224
/**
2325
* Get the covers for a record card.
24-
* Returns both the light and dark covers.
26+
* Returns both the light and dark covers with their content refs and optional object fit.
2527
* The light cover is a string or a content ref (image or files column type).
2628
* The dark cover is a content ref (image column type).
2729
*/
2830
export function getRecordCardCovers(
2931
record: DocumentTableRecord,
3032
view: DocumentTableViewCards
31-
): { [key in 'light' | 'dark']: ContentRefFile | ContentRefURL | null } {
33+
): {
34+
[key in 'light' | 'dark']: {
35+
contentRef: ContentRefFile | ContentRefURL | null;
36+
objectFit?: CardsImageObjectFit;
37+
};
38+
} {
39+
const lightValue = view.coverDefinition
40+
? (getRecordValue(record, view.coverDefinition) as DocumentTableImageRecord | string[])
41+
: null;
42+
43+
const darkValue = view.coverDefinitionDark
44+
? (getRecordValue(record, view.coverDefinitionDark) as DocumentTableImageRecord)
45+
: null;
46+
3247
return {
33-
light: (() => {
34-
if (!view.coverDefinition) {
35-
return null;
36-
}
37-
38-
const value = getRecordValue(record, view.coverDefinition) as
39-
| ContentRefFile
40-
| ContentRefURL
41-
| string[];
42-
43-
if (Array.isArray(value)) {
44-
if (value.length === 0) {
45-
return null;
46-
}
47-
48-
if (typeof value[0] === 'string') {
49-
return { kind: 'file', file: value[0] };
50-
}
51-
}
52-
53-
return value as ContentRefFile | ContentRefURL;
54-
})(),
55-
dark: (() => {
56-
if (!view.coverDefinitionDark) {
57-
return null;
58-
}
59-
60-
const value = getRecordValue(record, view.coverDefinitionDark) as
61-
| ContentRefFile
62-
| ContentRefURL;
63-
64-
if (!value) {
65-
return null;
66-
}
67-
68-
return value;
69-
})(),
48+
light: processCoverValue(lightValue),
49+
dark: processCoverValue(darkValue),
7050
};
7151
}
7252

53+
/**
54+
* Process a cover value and return the content ref and object fit.
55+
*/
56+
function processCoverValue(value: DocumentTableImageRecord | string[] | null | undefined): {
57+
contentRef: ContentRefFile | ContentRefURL | null;
58+
objectFit?: CardsImageObjectFit;
59+
} {
60+
if (!value) {
61+
return { contentRef: null };
62+
}
63+
64+
if (Array.isArray(value)) {
65+
if (value.length === 0) {
66+
return { contentRef: null };
67+
}
68+
69+
if (typeof value[0] === 'string') {
70+
return { contentRef: { kind: 'file', file: value[0] } };
71+
}
72+
}
73+
74+
// Check if it's the new schema with objectFit
75+
if (value && typeof value === 'object' && 'ref' in value && 'objectFit' in value) {
76+
return {
77+
contentRef: value.ref,
78+
objectFit: value.objectFit,
79+
};
80+
}
81+
82+
// It's a direct ContentRef
83+
return { contentRef: value as ContentRefFile | ContentRefURL };
84+
}
85+
7386
/**
7487
* Get the text alignment for a column.
7588
*/

0 commit comments

Comments
 (0)