Skip to content

Commit

Permalink
fix(docs): add back post styles to storybook iframes (#3580)
Browse files Browse the repository at this point in the history
Co-authored-by: Philipp Gfeller <[email protected]>
  • Loading branch information
alizedebray and gfellerph authored Sep 27, 2024
1 parent 0a1f308 commit d09aa9a
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export class PostAccordion {

@Listen('postToggle')
collapseToggleHandler(event: CustomEvent<boolean>) {
event.stopPropagation();

const toggledItem = event.target as HTMLElement;
const closestParentAccordion = toggledItem.closest('post-accordion');

Expand Down
2 changes: 1 addition & 1 deletion packages/documentation/.storybook/addons/addons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
top: -5px;
right: 0;
padding: post.$size-mini;
background-color: var(--post-light);
background-color: post.$white;
border: post.$border-width solid post.$border-color;
border-radius: post.$border-radius;
font-size: post.$font-size-sm;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,101 +1,146 @@
import React, { useEffect, useState } from 'react';
import { IconButton, WithTooltip } from '@storybook/components';

const STYLESHEET_ID = 'preview-stylesheet';
const THEMES = ['Post'] as const;
const CHANNELS = ['External', 'Internal'] as const;
const MODES = ['Light', 'Dark'] as const;

/*
* Stylesheets
*/
const getStylesheetUrl = (theme: string, channel: string) => {
return `/styles/${theme.toLowerCase()}-${channel.toLowerCase()}.css`;
};
const possibleStylesheets = THEMES.flatMap(theme => {
return CHANNELS.map(channel => getStylesheetUrl(theme, channel));
});

/*
* Backgrounds
*/
const backgroundClasses: { [key in (typeof MODES)[number]]: string } = {
Light: 'bg-white',
Dark: 'bg-dark',
};
const getBackgroundClass = (mode: string) => {
return mode in backgroundClasses ? backgroundClasses[mode] : '';
};
const possibleBackgrounds = MODES.map(mode => getBackgroundClass(mode));

/*
* Local storage access
*/
const STORAGE_KEY_PREFIX = 'swisspost-documentation';
const THEMES = ['Post'];
const CHANNELS = ['External', 'Internal'];
const MODES = ['Light', 'Dark'];
const store = (key: string, value: string) => {
return localStorage.setItem(`${STORAGE_KEY_PREFIX}-${key}`, value);
};
const stored = (key: string): string => {
return localStorage.getItem(`${STORAGE_KEY_PREFIX}-${key}`);
};

/*
* Helpers
*/
const debounce = <T extends unknown[]>(callback: (...args: T) => void, timeout: number) => {
let timer;
return (...args: T) => {
clearTimeout(timer);
timer = setTimeout(() => {
callback(...args);
}, timeout);
};
};

function StylesSwitcher() {
const [currentTheme, setCurrentTheme] = useState<string>(
localStorage.getItem(`${STORAGE_KEY_PREFIX}-theme`) || THEMES[0],
);
const [currentChannel, setCurrentChannel] = useState<string>(
localStorage.getItem(`${STORAGE_KEY_PREFIX}-channel`) || CHANNELS[0],
);
const [currentMode, setCurrentMode] = useState<string>(
localStorage.getItem(`${STORAGE_KEY_PREFIX}-mode`) || MODES[0],
);
let observer: MutationObserver;

/**
* Sets the 'data-color-mode' attribute and preview stylesheet when the addon initializes
*/
useEffect(() => {
setPreviewStylesheet();
setDataColorModeAttribute();
});
const [currentTheme, setCurrentTheme] = useState<string>(stored('theme') || THEMES[0]);
const [currentChannel, setCurrentChannel] = useState<string>(stored('channel') || CHANNELS[0]);
const [currentMode, setCurrentMode] = useState<string>(stored('mode') || MODES[0]);

const [preview, setPreview] = useState<Document>();
const [stories, setStories] = useState<NodeListOf<Element>>();

/**
* Sets the stylesheet matching the selected theme and channel in the preview document head
* Retrieves the preview document after the first rendering
*/
const setPreviewStylesheet = () => {
const preview = getPreviewDocument();
const previewHead = preview && preview.querySelector('head');
useEffect(() => {
const previewIFrame = document.querySelector('iframe#storybook-preview-iframe');

if (!previewHead) return;
if (!previewIFrame) return;

let stylesheetLink = previewHead.querySelector(`#${STYLESHEET_ID}`);
previewIFrame.addEventListener('load', () => {
setPreview((previewIFrame as HTMLIFrameElement).contentWindow.document);
});
}, []);

if (!stylesheetLink) {
stylesheetLink = document.createElement('link');
stylesheetLink.setAttribute('rel', 'stylesheet');
stylesheetLink.setAttribute('id', STYLESHEET_ID);
previewHead.appendChild(stylesheetLink);
}
/**
* Retrieves all the stories when the preview content changes
*/
useEffect(() => {
if (!preview || observer) return;

stylesheetLink.setAttribute(
'href',
`/styles/${currentTheme.toLowerCase()}-${currentChannel.toLowerCase()}.css`,
observer = new MutationObserver(
debounce(() => {
setStories(preview.querySelectorAll('.sbdocs-preview, .sb-main-padded'));
}, 200),
);
};

observer.observe(preview.body, { childList: true, subtree: true });
}, [preview]);

/**
* Sets the 'data-color-mode' attribute of the preview body to match the selected mode
* Sets the expected stylesheet in the preview head when the theme or channel changes
*/
const setDataColorModeAttribute = () => {
const preview = getPreviewDocument();
useEffect(() => {
if (!preview) return;

const mode = currentMode.toLowerCase();
const storyContainers = preview.querySelectorAll('.sbdocs-preview, .sb-main-padded');
storyContainers.forEach(storyContainer => {
storyContainer.classList.remove('bg-light', 'bg-dark');
storyContainer.classList.add(`bg-${mode}`);
storyContainer.setAttribute('data-color-mode', mode);
possibleStylesheets.forEach(stylesheet => {
const stylesheetLink = preview.head.querySelector(`link[href="${stylesheet}"]`);
if (stylesheetLink) stylesheetLink.remove();
});
};

preview.head.insertAdjacentHTML(
'beforeend',
`<link rel="stylesheet" href="${getStylesheetUrl(currentTheme, currentChannel)}" />`,
);
}, [preview, currentTheme, currentChannel]);

/**
* Returns the Document contained in the preview iframe
* Sets the expected 'data-color-mode' attribute on all story containers when the mode changes
*/
const getPreviewDocument = (): Document | undefined => {
const preview = document.querySelector('#storybook-preview-iframe');
return preview && (preview as HTMLIFrameElement).contentWindow.document;
};
useEffect(() => {
if (!stories) return;

stories.forEach(story => {
story.classList.remove(...possibleBackgrounds);
story.classList.add(getBackgroundClass(currentMode));
story.setAttribute('data-color-mode', currentMode.toLowerCase());
});
}, [stories, currentMode]);

/**
* Applies selected theme and registers it to the local storage
*/
const applyTheme = (theme: string) => {
store('theme', theme);
setCurrentTheme(theme);
localStorage.setItem(`${STORAGE_KEY_PREFIX}-theme`, theme);
};

/**
* Applies selected channel and registers it to the local storage
*/
const applyChannel = (channel: string) => {
store('channel', channel);
setCurrentChannel(channel);
localStorage.setItem(`${STORAGE_KEY_PREFIX}-channel`, channel);
};

/**
* Applies selected mode and registers it to the local storage
*/
const applyMode = (mode: string) => {
store('mode', mode);
setCurrentMode(mode);
localStorage.setItem(`${STORAGE_KEY_PREFIX}-mode`, mode);
};

return (
Expand Down
1 change: 1 addition & 0 deletions packages/documentation/.storybook/preview-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="stylesheet" href="/styles/post-external.css" />

<meta name="design-system-settings" data-post-icon-base="/post-icons" />
<meta http-equiv="Content-Security-Policy" content="connect-src 'self' *.coveo.com *.post.ch" />
Expand Down
4 changes: 4 additions & 0 deletions packages/documentation/.storybook/styles/preview.scss
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@
overflow-x: hidden;
}

.sbdocs-preview:not([data-color-mode]) {
display: none;
}

.fake-content {
position: relative;
min-height: calc(1.6rem * 8);
Expand Down
1 change: 1 addition & 0 deletions packages/styles/src/components/card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
// Outline with card-group margin trick prevents double/jumping (on hover) borders
outline: 1px solid card.$card-border-color;
box-shadow: commons.$box-shadow-lg;
color: card.$card-color;

&.product-card,
&.card-button {
Expand Down
1 change: 1 addition & 0 deletions packages/styles/src/variables/components/_card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ $card-inner-border-radius: calc(
) !default; // Design System
$card-cap-bg: color.$light !default;
$card-bg: color.$white !default;
$card-color: color.$black !default;

$card-img-overlay-padding: $card-spacer-x !default;

Expand Down
45 changes: 38 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit d09aa9a

Please sign in to comment.