Skip to content

Commit 7dfad9c

Browse files
authored
Merge pull request #3581 from Shopify/icon-preview-with-dark-mode
Fix icon previewer dark mode support
2 parents 56ffb6c + 61ef487 commit 7dfad9c

File tree

7 files changed

+415
-141
lines changed

7 files changed

+415
-141
lines changed

packages/ui-extensions/docs/surfaces/admin/build-docs.mjs

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -84,22 +84,25 @@ const stylesToString = (styles) => {
8484
.join('; ');
8585
};
8686

87-
const renderJsxTemplate = async (iconPreviewData) => {
88-
const templatePath = path.join(docsPath, 'templates/jsx-render.html');
87+
/**
88+
* Renders the JSX template for the icon preview.
89+
* @returns The processed HTML string.
90+
*/
91+
const renderIconPreviewJsxTemplate = async (iconPreviewData) => {
92+
const templatePath = path.join(
93+
docsPath,
94+
'templates/icon-renderer/jsx-render.html',
95+
);
8996
const template = await fs.readFile(templatePath, 'utf-8');
9097

91-
// Read JSX code from file
9298
const jsxFilePath = path.join(docsPath, iconPreviewData.jsxFile);
9399
let jsxCode = await fs.readFile(jsxFilePath, 'utf-8');
94100

95-
// Replace __ICON_LIST__ placeholder in jsxCode
96-
// Use single quotes to avoid escaping issues in HTML attributes
97101
const iconListString = `[${iconPreviewData.icons
98102
.map((icon) => `'${icon}'`)
99103
.join(',')}]`;
100104
jsxCode = jsxCode.replace(/"__ICON_LIST__"/g, iconListString);
101105

102-
// Escape the JSX code for use in template literal
103106
const escapedJsxCode = escapeForJSTemplate(jsxCode);
104107

105108
return template
@@ -250,27 +253,37 @@ const templates = {
250253
const transformJson = async (filePath, isExtensions) => {
251254
let jsonData = JSON.parse((await fs.readFile(filePath, 'utf8')).toString());
252255

253-
for (const entry of jsonData) {
254-
if (entry.name === 'Icon' && entry.subSections) {
255-
const iconDataPath = path.join(srcPath, 'components/Icon/icon-data.json');
256-
const iconData = JSON.parse(await fs.readFile(iconDataPath, 'utf-8'));
257-
const iconPreviewData = iconData.iconPreviewData;
258-
259-
if (iconPreviewData.icons === '__AUTO_GENERATED_ICONS__') {
260-
iconPreviewData.icons = await extractIconList();
261-
}
262-
for (const subSection of entry.subSections) {
263-
if (subSection.sectionContent?.includes('{{ICON_PREVIEW_IFRAME}}')) {
264-
const renderedHtml = await renderJsxTemplate(iconPreviewData);
265-
const base64Html = Buffer.from(renderedHtml, 'utf-8').toString(
266-
'base64',
267-
);
268-
subSection.sectionContent = subSection.sectionContent.replace(
269-
/\{\{ICON_PREVIEW_IFRAME\}\}/g,
270-
`<iframe width="100%" height="490px" sandbox="allow-scripts" src="data:text/html;base64,${base64Html}"></iframe>`,
271-
);
272-
}
273-
}
256+
const iconEntry = jsonData.find(
257+
(entry) => entry.name === 'Icon' && entry.subSections,
258+
);
259+
if (iconEntry) {
260+
const iconDataPath = path.join(srcPath, 'components/Icon/icon-data.json');
261+
const iconData = JSON.parse(await fs.readFile(iconDataPath, 'utf-8'));
262+
const iconPreviewData = iconData.iconPreviewData;
263+
264+
iconPreviewData.icons = await extractIconList();
265+
266+
const subSection = iconEntry.subSections.find((section) =>
267+
section.sectionContent?.includes('{{ICON_PREVIEW_IFRAME}}'),
268+
);
269+
if (subSection) {
270+
const html = await renderIconPreviewJsxTemplate(iconPreviewData);
271+
// Converting to base64 to avoid having to escape the HTML in the JSX template.
272+
const base64Html = Buffer.from(html, 'utf-8').toString('base64');
273+
const darkModeListener = await fs.readFile(
274+
path.join(docsPath, 'templates/icon-renderer/dark-mode-listener.jsx'),
275+
'utf-8',
276+
);
277+
const base64DarkModeListener = Buffer.from(
278+
darkModeListener,
279+
'utf-8',
280+
).toString('base64');
281+
subSection.sectionContent = subSection.sectionContent.replace(
282+
/\{\{ICON_PREVIEW_IFRAME\}\}/g,
283+
`<iframe id="icon-preview-iframe" width="100%" height="580px" sandbox="allow-scripts" src="data:text/html;base64,${base64Html}"></iframe>
284+
<script src="data:text/javascript;base64,${base64DarkModeListener}"></script>
285+
`,
286+
);
274287
}
275288
}
276289

packages/ui-extensions/docs/surfaces/admin/templates/icon-preview.jsx

Lines changed: 0 additions & 110 deletions
This file was deleted.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/* eslint-disable no-undef */
2+
(function () {
3+
function setIframeTheme(isDark) {
4+
const iframe = document.querySelector('#icon-preview-iframe');
5+
if (iframe && iframe.contentWindow) {
6+
iframe.contentWindow.postMessage(
7+
{type: 'theme', mode: isDark ? 'dark' : 'light'},
8+
'*',
9+
);
10+
}
11+
}
12+
function getThemeMode() {
13+
return document.documentElement.classList.contains('Mode-Dark')
14+
? 'dark'
15+
: 'light';
16+
}
17+
const initialMode = getThemeMode();
18+
const isDark = initialMode === 'dark';
19+
// Wait for iframe to load before setting initial theme
20+
const iframe = document.querySelector('#icon-preview-iframe');
21+
if (iframe) {
22+
iframe.addEventListener('load', () => {
23+
setIframeTheme(isDark);
24+
});
25+
// If already loaded, set immediately
26+
if (iframe.contentWindow) {
27+
setIframeTheme(isDark);
28+
}
29+
}
30+
window.addEventListener('theme-mode-changed', (event) => {
31+
const themeMode = event.detail.themeMode;
32+
const isDarkMode = themeMode === 'Mode-Dark';
33+
setIframeTheme(isDarkMode);
34+
});
35+
})();

0 commit comments

Comments
 (0)