Skip to content

Commit e0fb9ac

Browse files
authored
Support image frame (#3695)
1 parent f3e4041 commit e0fb9ac

File tree

4 files changed

+88
-35
lines changed

4 files changed

+88
-35
lines changed

bun.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@
304304
"react-dom": "^19.0.0",
305305
},
306306
"catalog": {
307-
"@gitbook/api": "0.143.1",
307+
"@gitbook/api": "0.143.2",
308308
"bidc": "^0.0.2",
309309
},
310310
"packages": {
@@ -676,7 +676,7 @@
676676

677677
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
678678

679-
"@gitbook/api": ["@gitbook/api@0.143.1", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-5k7PnMe9W8EhmSejqayCbAIIJDGB4C2m+o6+dD+asmlv+6jE/LqoxuAvbP8o+kG83tMnbsr5I5d73B/9cAYbag=="],
679+
"@gitbook/api": ["@gitbook/api@0.143.2", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-ZIQwIgX+artl0o20ftnzeJyM9icwQ6pFeM55AG/lEdeFbMuUGxTfSnZ90+8Aou8l/3ELR3/tOkU2SAvyKc+ATw=="],
680680

681681
"@gitbook/browser-types": ["@gitbook/browser-types@workspace:packages/browser-types"],
682682

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.143.1",
37+
"@gitbook/api": "0.143.2",
3838
"bidc": "^0.0.2"
3939
}
4040
},

packages/gitbook/src/components/DocumentView/Caption.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export function Caption(
2424
wrapperStyle?: ClassValue;
2525
block: DocumentBlockImage | DocumentBlockDrawing | DocumentBlockEmbed | DocumentBlockFile;
2626
withBorder?: boolean;
27+
withFrame?: boolean;
2728
} & DocumentContextProps
2829
) {
2930
const {
@@ -33,6 +34,7 @@ export function Caption(
3334
context,
3435
fit = false,
3536
withBorder = false,
37+
withFrame = false,
3638
wrapperStyle = [
3739
'relative',
3840
'overflow-hidden',
@@ -44,6 +46,7 @@ export function Caption(
4446
withBorder
4547
? 'rounded-corners:rounded-sm circular-corners:rounded-2xl after:border-tint-subtle after:border after:rounded circular-corners:after:rounded-2xl rounded-corners:after:rounded-sm dark:after:mix-blend-plus-lighter after:pointer-events-none'
4648
: null,
49+
withFrame && 'p-2',
4750
],
4851
style,
4952
} = props;
@@ -62,7 +65,14 @@ export function Caption(
6265
return (
6366
<picture className={tcls('relative', style)}>
6467
<div className={tcls(wrapperStyle, 'mx-auto')}>{children}</div>
65-
<figcaption className={tcls('text-sm', 'text-center', 'mt-2', 'text-tint')}>
68+
<figcaption
69+
className={tcls(
70+
'text-xs',
71+
'text-center',
72+
'text-tint',
73+
withFrame ? 'mt-0.5 mb-2.5' : 'mt-2'
74+
)}
75+
>
6676
<Inlines
6777
nodes={captionParagraph.nodes}
6878
document={document}

packages/gitbook/src/components/DocumentView/Images.tsx

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import type { DocumentContext } from './DocumentView';
1111
export function Images(props: BlockProps<DocumentBlockImages>) {
1212
const { document, block, style, context, isEstimatedOffscreen } = props;
1313

14-
const isMultipleImages = block.nodes.length > 1;
15-
const { align = 'center' } = block.data;
14+
const hasMultipleImages = block.nodes.length > 1;
15+
const { align = 'center', withFrame } = block.data;
1616

1717
return (
1818
<div
@@ -24,7 +24,15 @@ export function Images(props: BlockProps<DocumentBlockImages>) {
2424
align === 'center' && 'justify-center',
2525
align === 'right' && 'justify-end',
2626
align === 'left' && 'justify-start',
27-
isMultipleImages && ['grid', 'grid-flow-col']
27+
hasMultipleImages && ['grid', 'grid-flow-col'],
28+
withFrame && [
29+
'rounded-2xl',
30+
'border',
31+
'border-[rgb(234,235,238)]',
32+
'dark:border-[rgb(45,50,58)]',
33+
'relative',
34+
'overflow-hidden',
35+
]
2836
)}
2937
>
3038
{block.nodes.map((node: any, _i: number) => (
@@ -36,6 +44,7 @@ export function Images(props: BlockProps<DocumentBlockImages>) {
3644
siblings={block.nodes.length}
3745
context={context}
3846
isEstimatedOffscreen={isEstimatedOffscreen}
47+
withFrame={withFrame}
3948
/>
4049
))}
4150
</div>
@@ -62,8 +71,9 @@ async function ImageBlock(props: {
6271
context: DocumentContext;
6372
siblings: number;
6473
isEstimatedOffscreen: boolean;
74+
withFrame?: boolean;
6575
}) {
66-
const { block, context, isEstimatedOffscreen } = props;
76+
const { block, context, isEstimatedOffscreen, withFrame } = props;
6777

6878
const [src, darkSrc] = await Promise.all([
6979
context.contentContext ? resolveContentRef(block.data.ref, context.contentContext) : null,
@@ -77,33 +87,66 @@ async function ImageBlock(props: {
7787
}
7888

7989
return (
80-
<Caption {...props} fit>
81-
<Image
82-
alt={block.data.alt ?? ''}
83-
sizes={imageBlockSizes}
84-
resize={context.contentContext?.imageResizer}
85-
sources={{
86-
light: {
87-
src: src.href,
88-
size: src.file?.dimensions,
89-
},
90-
dark: darkSrc
91-
? {
92-
src: darkSrc.href,
93-
size: darkSrc.file?.dimensions,
94-
}
95-
: null,
96-
}}
97-
priority={isEstimatedOffscreen ? 'lazy' : 'high'}
98-
preload
99-
zoom
100-
inlineStyle={{
101-
maxWidth: '100%',
102-
width: getImageDimension(block.data.width, undefined),
103-
height: getImageDimension(block.data.height, 'auto'),
104-
}}
105-
/>
106-
</Caption>
90+
<div className={tcls('relative', 'overflow-hidden')}>
91+
{/* Frame grid */}
92+
{withFrame && (
93+
<div
94+
className={tcls(
95+
'absolute',
96+
'-top-0.5',
97+
'-left-0.5',
98+
'right-px',
99+
'bottom-px',
100+
'opacity-40',
101+
'dark:opacity-[0.1]',
102+
'bg-[length:24px_24px,24px_24px]',
103+
'bg-[linear-gradient(to_right,_rgb(234,235,238)_1px,_transparent_1px),linear-gradient(to_bottom,_rgb(234,235,238)_1px,_transparent_1px)]',
104+
'dark:bg-[linear-gradient(to_right,_rgb(122,128,139)_1px,_transparent_1px),linear-gradient(to_bottom,_rgb(122,128,139)_1px,_transparent_1px)]',
105+
'bg-repeat'
106+
)}
107+
/>
108+
)}
109+
110+
{/* Shadow overlay */}
111+
{withFrame && (
112+
<div
113+
className={tcls(
114+
'pointer-events-none absolute inset-0 rounded-2xl',
115+
'shadow-[inset_0_0_10px_10px_rgba(255,255,255,0.9)]',
116+
'dark:shadow-[inset_0_0_10px_10px_rgb(29,29,29)]'
117+
)}
118+
/>
119+
)}
120+
121+
<Caption {...props} fit>
122+
<Image
123+
alt={block.data.alt ?? ''}
124+
sizes={imageBlockSizes}
125+
resize={context.contentContext?.imageResizer}
126+
sources={{
127+
light: {
128+
src: src.href,
129+
size: src.file?.dimensions,
130+
},
131+
dark: darkSrc
132+
? {
133+
src: darkSrc.href,
134+
size: darkSrc.file?.dimensions,
135+
}
136+
: null,
137+
}}
138+
priority={isEstimatedOffscreen ? 'lazy' : 'high'}
139+
preload
140+
zoom
141+
inlineStyle={{
142+
maxWidth: '100%',
143+
width: getImageDimension(block.data.width, undefined),
144+
height: getImageDimension(block.data.height, 'auto'),
145+
}}
146+
style={withFrame ? 'rounded-xl' : undefined}
147+
/>
148+
</Caption>
149+
</div>
107150
);
108151
}
109152

0 commit comments

Comments
 (0)