Skip to content

Commit 66ee7f1

Browse files
lennessyyclaude
andauthored
Add dark mode image support to CaptionedImage (#4457)
* Add dark mode image support to CaptionedImage component Both light and dark images are rendered in the DOM and preloaded by the browser so theme switching is instant with no loading delay. Replaces all ThemedImage usages with the new srcDark prop. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Restore link wrapper around Nexus decision tree image The original had the image wrapped in a link to open the full SVG in a new tab. Restores that behavior instead of replacing it with zoom. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix preview links for long branch names by reading Vercel comment Instead of constructing the preview URL from the branch name (which breaks when Vercel truncates long subdomains), poll for the Vercel bot comment and extract the actual preview URL. Falls back to the constructed URL if the comment doesn't appear within 3 minutes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Skip Vercel comment polling when branch name is short enough Only poll for the Vercel bot comment when the constructed subdomain exceeds 63 characters (the DNS label limit where Vercel truncates). Short branch names use the constructed URL directly with no delay. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a89b2c4 commit 66ee7f1

8 files changed

Lines changed: 103 additions & 61 deletions

File tree

.github/workflows/docs-preview-links.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
with:
2727
node-version: '20'
2828

29-
- name: Compute preview base URL
29+
- name: Get preview URL from Vercel comment
3030
id: preview-url
3131
uses: actions/github-script@v7
3232
with:

COMPONENTS.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,21 @@ Usage:
137137

138138
Images are normally stored in the '/static' folder in `img` or `diagrams`.
139139

140+
### Dark mode images
141+
142+
To provide a separate image for dark mode, use the `srcDark` prop:
143+
144+
```
145+
<CaptionedImage
146+
src="/diagrams/my-diagram.svg"
147+
srcDark="/diagrams/my-diagram-dark.svg"
148+
title="My diagram"
149+
alt="Description of the diagram"
150+
/>
151+
```
152+
153+
When `srcDark` is provided, both images are rendered in the DOM and the browser loads both upfront. CSS toggles visibility based on the active theme, so switching between light and dark mode is instant with no loading delay. When `srcDark` is omitted, the component renders a single image as usual.
154+
140155
### Zooming images
141156

142157
When images are complex and may not render in a readable fashion on normal monitors, you can enable a minimal form of zooming by setting the `zoom` prop to true:

docs/develop/go/best-practices/data-handling/index.mdx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,17 @@ tags:
1313
- Data Converters
1414
---
1515

16-
import ThemedImage from '@theme/ThemedImage';
16+
import { CaptionedImage } from '@site/src/components';
1717

1818
All data sent to and from the Temporal Service passes through the **Data Converter**. The Data Converter has three
1919
layers that handle different concerns:
2020

21-
<figure>
22-
<ThemedImage
23-
alt="The Flow of Data through a Data Converter"
24-
sources={{
25-
light: '/diagrams/data-converter-flow-with-external-storage.svg',
26-
dark: '/diagrams/data-converter-flow-dark.svg',
27-
}}
28-
/>
29-
<figcaption>The Flow of Data through a Data Converter</figcaption>
30-
</figure>
21+
<CaptionedImage
22+
src="/diagrams/data-converter-flow-with-external-storage.svg"
23+
srcDark="/diagrams/data-converter-flow-dark.svg"
24+
title="The Flow of Data through a Data Converter"
25+
alt="The Flow of Data through a Data Converter"
26+
/>
3127

3228
Of these three layers, only the PayloadConverter is required. Temporal uses a default PayloadConverter that handles JSON
3329
serialization. The PayloadCodec and ExternalStorage layers are optional. You only need to customize these layers when

docs/develop/python/best-practices/data-handling/index.mdx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,17 @@ tags:
1313
- Data Converters
1414
---
1515

16-
import ThemedImage from '@theme/ThemedImage';
16+
import { CaptionedImage } from '@site/src/components';
1717

1818
All data sent to and from the Temporal Service passes through the **Data Converter**. The Data Converter has three
1919
layers that handle different concerns:
2020

21-
<figure>
22-
<ThemedImage
23-
alt="The Flow of Data through a Data Converter"
24-
sources={{
25-
light: '/diagrams/data-converter-flow-with-external-storage.svg',
26-
dark: '/diagrams/data-converter-flow-dark.svg',
27-
}}
28-
/>
29-
<figcaption>The Flow of Data through a Data Converter</figcaption>
30-
</figure>
21+
<CaptionedImage
22+
src="/diagrams/data-converter-flow-with-external-storage.svg"
23+
srcDark="/diagrams/data-converter-flow-dark.svg"
24+
title="The Flow of Data through a Data Converter"
25+
alt="The Flow of Data through a Data Converter"
26+
/>
3127

3228
Of these three layers, only the PayloadConverter is required. Temporal uses a default PayloadConverter that handles JSON
3329
serialization. The PayloadCodec and ExternalStorage layers are optional. You only need to customize these layers when

docs/encyclopedia/data-conversion/external-storage.mdx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ tags:
1919
- Data Converters
2020
---
2121

22-
import ThemedImage from '@theme/ThemedImage';
22+
import { CaptionedImage } from '@site/src/components';
2323

2424
:::info Release, stability, and dependency info
2525

@@ -72,16 +72,12 @@ For SDK-specific usage guides, see:
7272
During [Data Conversion](/dataconversion), External Storage sits at the end of the pipeline, after both the
7373
[Payload Converter](/payload-converter) and the [Payload Codec](/payload-codec):
7474

75-
<figure>
76-
<ThemedImage
77-
alt="The Flow of Data through a Data Converter"
78-
sources={{
79-
light: '/diagrams/data-converter-flow-with-external-storage.svg',
80-
dark: '/diagrams/data-converter-flow-dark.svg',
81-
}}
82-
/>
83-
<figcaption>The Flow of Data through a Data Converter</figcaption>
84-
</figure>
75+
<CaptionedImage
76+
src="/diagrams/data-converter-flow-with-external-storage.svg"
77+
srcDark="/diagrams/data-converter-flow-dark.svg"
78+
title="The Flow of Data through a Data Converter"
79+
alt="The Flow of Data through a Data Converter"
80+
/>
8581

8682
When a Temporal Client sends a payload that exceeds the configured size threshold, the storage driver uploads the
8783
payload to your external store and replaces it with a lightweight reference. Payloads below the threshold stay inline in

docs/evaluate/development-production-features/temporal-nexus.mdx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ keywords:
3232
---
3333

3434
import { RelatedReadContainer, RelatedReadItem } from '@site/src/components';
35-
import ThemedImage from '@theme/ThemedImage';
35+
import { CaptionedImage } from '@site/src/components';
3636

3737
:::tip SUPPORT, STABILITY, and DEPENDENCY INFO
3838

@@ -78,13 +78,10 @@ Use the following decision tree to help determine if Nexus is right for your use
7878

7979
<div style={{textAlign: 'center', margin: '2rem 0'}}>
8080
<a href="/diagrams/nexusadoptionlight.svg" target="_blank" rel="noopener noreferrer">
81-
<ThemedImage
81+
<CaptionedImage
82+
src="/diagrams/nexusadoptionlight.svg"
83+
srcDark="/diagrams/nexusadoptiondark.svg"
8284
alt="Should I use Nexus? Decision tree"
83-
sources={{
84-
light: '/diagrams/nexusadoptionlight.svg',
85-
dark: '/diagrams/nexusadoptiondark.svg',
86-
}}
87-
style={{maxWidth: '100%', cursor: 'pointer'}}
8885
/>
8986
</a>
9087
</div>

src/components/images/CaptionedImage.js

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,60 @@
22
import React, { useState } from "react";
33
import styles from "./CaptionedImage.module.css";
44

5-
const CaptionedImage = ({ src, alt, title, width, inset = "0%", zoom = false }) => {
5+
const CaptionedImage = ({ src, srcDark, alt, title, width, inset = "0%", zoom = false }) => {
66
const [isZoomed, setIsZoomed] = useState(false);
7-
const isSVG = src.endsWith(".svg");
87

98
const toggleZoom = () => {
10-
if (zoom) { // Only toggle zoom if zoom prop is true
9+
if (zoom) {
1110
setIsZoomed(!isZoomed);
1211
}
1312
};
1413

14+
const imgStyle = {
15+
width: isZoomed && zoom ? "auto" : "100%",
16+
height: "auto",
17+
maxWidth: isZoomed && zoom ? "none" : "100%",
18+
maxHeight: isZoomed && zoom ? "none" : "auto",
19+
objectFit: "contain",
20+
transition: "transform 0.3s ease-in-out",
21+
};
22+
1523
return (
1624
<div
1725
className={styles.imageContainer}
1826
style={{
1927
width: width || "auto",
2028
paddingLeft: inset,
2129
paddingRight: inset,
22-
cursor: zoom ? "pointer" : "default", // Change cursor if zoom is enabled
23-
position: "relative", // Keep the container in its normal position
24-
display: "inline-block", // Keep image in line with text
30+
cursor: zoom ? "pointer" : "default",
31+
position: "relative",
32+
display: "inline-block",
2533
}}
26-
onClick={toggleZoom} // Toggle zoom on image container click if zoom is true
34+
onClick={toggleZoom}
2735
>
28-
<img
29-
src={src}
30-
alt={alt || title}
31-
className={styles.image}
32-
style={{
33-
width: isZoomed && zoom ? "auto" : "100%", // Zoomed image behavior (only if zoom is true)
34-
height: isZoomed && zoom ? "auto" : "auto", // Keep height as auto to preserve aspect ratio
35-
maxWidth: isZoomed && zoom ? "none" : "100%", // Allow the image to exceed its container when zoomed (only if zoom is true)
36-
maxHeight: isZoomed && zoom ? "none" : "auto", // Allow the image to exceed its container when zoomed (only if zoom is true)
37-
objectFit: "contain", // Preserve aspect ratio for zoomed image
38-
transition: "transform 0.3s ease-in-out", // Smooth zoom transition
39-
}}
40-
/>
36+
{srcDark ? (
37+
<>
38+
<img
39+
src={src}
40+
alt={alt || title}
41+
className={`${styles.image} ${styles.lightOnly}`}
42+
style={imgStyle}
43+
/>
44+
<img
45+
src={srcDark}
46+
alt={alt || title}
47+
className={`${styles.image} ${styles.darkOnly}`}
48+
style={imgStyle}
49+
/>
50+
</>
51+
) : (
52+
<img
53+
src={src}
54+
alt={alt || title}
55+
className={styles.image}
56+
style={imgStyle}
57+
/>
58+
)}
4159
{title && <p className={styles.title}>{title}</p>}
4260
</div>
4361
);

src/components/images/CaptionedImage.module.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,27 @@
1616
padding-left: 8px;
1717
padding-right: 8px;
1818
}
19+
20+
.lightOnly {
21+
visibility: visible;
22+
position: relative;
23+
}
24+
25+
.darkOnly {
26+
visibility: hidden;
27+
position: absolute;
28+
top: 0;
29+
left: 0;
30+
}
31+
32+
:global([data-theme='dark']) .lightOnly {
33+
visibility: hidden;
34+
position: absolute;
35+
top: 0;
36+
left: 0;
37+
}
38+
39+
:global([data-theme='dark']) .darkOnly {
40+
visibility: visible;
41+
position: relative;
42+
}

0 commit comments

Comments
 (0)