-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Story CSS bleeds into all other stories #16016
Comments
Hi @Bilge
I am guessing now but I am thinking maybe the app in question is not a SPA and you have global styles that you want to apply for one page but not another; and you get away with this because of the full-page refresh involved in navigating around? If that's the case, then this is not the use case SB was designed for. However we can probably figure out a solution using a decorator. |
Yes, I have never worked on an SPA. All of your assumptions are correct. |
I'm working on an SPA using vanilla JS, and using the HTML framework of Storybook. My styles are not always loaded in the component itself. So I'd also like to be able to import the styles for a single component into a story without those styles bleeding into other stories. |
@Bilge so the simplest solution that is probably closest to the non-SPA experience would be to throw in a
@dwhieb I am curious as to how you import styles in your app and then "un-import" them when you browse to a different page. Can you tell me a bit more about that? |
@tmeasday All the styles needed for individual pages are scoped to that page, and that page's CSS is loaded dynamically when the page loads. That said, I think my actual issue was that in my LESS code I was often applying a class to a certain kind of element (e.g. I was basically trying to avoid cluttering my HTML with CSS classes and it backfired 😬 |
This should really be tagged with bug, in case tags matter. |
I wish we had a solution for this that does not require modifying every single story to do a location reload. This should be supported by the system by some mechanism. This issue has stopped me from using Storybook since I filed it over a year ago, which is very disappointing. |
@Bilge the decorator I recommended above would just be defined once in |
@tmeasday I see. Nevertheless, wouldn't your proposed workaround also change the behaviour of Storybook in that it would forget state when switching between stories, unlike normal, where it actually remembers the state of each story as you change controls and switch between stories? |
@Bilge it might work OK as the (args) state is recorded in the URL and usually re-inits OK. I'm not quite sure how we could make it work that CSS needs to be reset each time you change story otherwise though? |
i dont know if our use cases match, but i solved this issue using raw-loader and sass-loader:
|
Hi all... I'm sorry but I'm going through every thread however I still didn't find any workable solution yet. Am I missing something? |
Hi @tmeasday I was giving a try to your solution: let storyId;
const reloadDecorator = (storyFn, context) => {
if (storyId && context.id !== storyId) {
document.location.reload();
}
storyId = context.id;
return storyFn();
} as global decorator in the preview.js. // https://storybook.js.org/docs/react/writing-stories/parameters#global-parameters
import '../stories/govUkStyle.css';
const tokenContext = require.context(
'!!raw-loader!../src',
true,
/.\.(css|less|scss|svg)$/
);
const tokenFiles = tokenContext.keys().map(function (filename) {
return { filename: filename, content: tokenContext(filename).default };
});
export const parameters = {
// https://storybook.js.org/docs/react/essentials/actions#automatically-matching-args
actions: { argTypesRegex: '^on.*' },
designToken: {
files: tokenFiles
},
options: {
storySort: {
order: ['DCXLibrary',
[
'Introduction',
'Utils',
'Form',['Select', ['documentation', 'live', 'Default', 'Design-System','Class-Based']],
'CopyToClipboard',
'Details',
'Tabs',
'Table',
'Changelog'
]
]
},
}
};
let storyId;
export const decorators = [
(storyFn, context) => {
if (storyId && context.id !== storyId) {
document.location.reload();
}
console.log('is it called???');
storyId = context.id;
return storyFn();
}
]; It works perfectly fine in the canvas section but as soon as you go in the Docs tab I got an infinite loop to reload |
Hmm, yes I can see that would be a problem. I suppose for Docs pages you would probably want to check the Keep in mind a couple restrictions with that:
An alternative would be to ensure you iframe the docs stories, via |
so just for other in case they have the same challenge... this is how I fix it: //It will allow to refresh the iframe all the time you move from one story to another - buggy ... it doesn't work
let storyId;
let storyTitle;
export const decorators = [
(storyFn, context) => {
console.log('context.title:',context.title);
console.log('storyTitle:',storyTitle);
if (storyTitle && context.title !== storyTitle) {
document.location.reload();
console.log('first')
} else if(storyId && context.id !== storyId && context.title !== storyTitle) {
document.location.reload();
console.log('second')
}
storyId = context.id;
storyTitle = context.title;
return storyFn();
}
]; I do appreciate is not the best solution but at least it works for me. |
OK, let's keep this open and report back if you see further problems @daniele-zurico. I guess having a feature flag for "story isolation mode" or similar might make sense for this use case. Can folks keep upvoting the top comment on this ticket to get it on the radar? The more I think about it, the more I think just rendering docs stories in an iframe would make sense in such a mode btw. |
I'm using it with svelte-kit, whose component css is ostensibly isolated per component automatically, but in storybook it's not. I figured this out when I copied the example Button to another directory to play with it, while still having the original as a reference. I cleared out the CSS after a while, and the button was still styled as the storybook default button. Doing so in the opposite direction, that is - clearing out the css file for the original button and leaving it for my copy, does de-style one of the buttons. The fact that this happens, and also is dependent on however storybook decides to load components (I'm guessing breadth-first in directory listing order) makes for a very non-isolated environment for testing. The reason this should be a bug, and not just a support question, is that storybook's whole idea is to give a place and a method for developing components in isolation before combining them - something which is thusly not the actual case. |
So this isn't the most ideal approach but it works for my unique scenario. First, some context. I'm using Storybook as a tool to preview some really simple, static HTML files that we crank out often. I wanted a way to view kind of a historical list of designs so stakeholders can scroll through and review them at their leisure. So no need for fancy things I've used in the past like args, props, etc. Very basic static HTML. Unfortunately in my use case, we have a lot of styles applied to things like The first thing I did was look into how to build custom decorators. I created one that wraps my story in a container that has the
Then I import it into my
Then in my imported component stylesheets, I wrap my styles in that ID. So something like:
In my setup, I have a custom bash script that lets a user scaffold out a new page quickly. In the process, I have a command that runs Like I said, not ideal, but it got me across the finish line. This is a unique case because we don't have the luxury of CSS scoping like we do in Vue and React components. I considered using CSS modules too, but not everything has an ID or class applied to it (and we can't modify the HTML structure at our leisure). Hope this helps someone! |
@bloqhead That sounds truly, truly awful. Thanks for sharing. |
@Bilge it is most definitely not ideal, haha. Fortunately, there isn't anything we have to modify outside of the CSS. So while it would otherwise be a brittle setup, it works for this weird use case. |
Really needing this. We make some 'headless' compononents, like in 'headless ui' without styling. |
This is how we fixed it: We use a decorator which encapsulates the story in an empty elements shadowRoot, called : the decorator export function withShadowRoot(storyFn: StoryFn, csss: string = '') {
const element = document.createElement('storybook-shadow-root');
const shadow = element.attachShadow({ mode: 'open' });
const sheet = new CSSStyleSheet();
sheet.replaceSync(csss);
shadow.adoptedStyleSheets = [sheet];
element.appendChild(shadow);
render(storyFn(), this.shadow);
return html`${element}`;
} you can use it like this: import story_style from 'styles/mystory.css?inline';
export default {
component: 'render-in-shadow',
decorators: [story => withShadowRoot(story, story_style)],
}; |
If someone wants a React solution here you are: Create a ShadowRootContainer container that renders a children component inside a shadowroot with a styles tag that includes the styles you need. import { useLayoutEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
export type ShadowRootContainerProps = {
children: JSX.Element | JSX.Element[];
css: string;
};
const ShadowRootContainer = ({
children,
css,
}: ShadowRootContainerProps) => {
const containerRef = useRef(null);
const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);
useLayoutEffect(() => {
if (containerRef.current) {
const container = containerRef.current as HTMLElement;
const shadowRootElement = shadowRoot || container.attachShadow({ mode: 'open' });
const style = document.createElement('style');
const existingStyle = shadowRootElement.querySelector('style');
if (existingStyle) {
shadowRootElement.removeChild(existingStyle);
}
style.innerHTML = css;
shadowRootElement.appendChild(style);
setShadowRoot(shadowRootElement);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [children, css]);
return (
<div ref={containerRef}>
{
shadowRoot && ReactDOM.createPortal(
children,
shadowRoot,
)
}
</div>
);
};
export default ShadowRootContainer; Then you can use within a custom render function on storybook or you can use a decorator like this import { StoryFn } from '@storybook/react';
import ShadowRootContainer from './ShadowRootContainer';
const withShadowRoot = (css: string) => (StoryFn: StoryFn) => (
<ShadowRootContainer css={css}>
<StoryFn />
</ShadowRootContainer>
);
export default withShadowRoot; And your decorator will look like this: import styles from './NavigationBarLines.scss?inline'; // use any import you need
decorators: [withShadowRoot(styles.toString())], // You can change it to use any other type of styling solution |
@Chofito and another solution: export function reactStory(Story, csss: string = '') {
const container = document.createElement('div');
const shadow = container.attachShadow({ mode: "open" });
const sheet = new CSSStyleSheet();
sheet.replaceSync(csss);
shadow.adoptedStyleSheets = [sheet];
createRoot(shadow).render(<Story />);
return container;
} import { reactStory } from '../../../.storybook/decorators';
import style from '@component/styles.css?inline';
const meta = {
decorators: [story => reactStory(story, style)],
}; this also works if you have a storybookwebcomponent repository and want to render react components in stories. ( because of testing react wrappers we have created ) |
@tmeasday I can see that everyone is trying to implement a custom solution here so it's quite spread as "challenge". I was wondering if the Storybook team is taking in consideration to improve/add this feature and by when |
Just ran into this myself. I for example am using the same storybook application for 2 design systems and the styles from design system 1 are bleeding into design system 2 and I have not found a clean way to circumvent this. It would be good to prevent the styles bleeding into stories that it's not relevant to. |
I ran into this problem as well and ended up writing a decorator that simply overrides CSS variables (without any fancy shadowRoot etc). However, I wrote a Medium story about this issue and covered 3 possible workarounds (including the shadowRoot solution that was suggested here), so if you're trying to show off different themes in different stories, have a read here: |
Describe the bug
If any Story imports CSS, all other stories are forced to adopt the same CSS.
To Reproduce
Both Story1 and Story2 (and any/all other stories) have both
foo.less
andbar.less
loaded.System
Additional context
Probably this only happens in the HTML "framework", as such a glaring bug would have been discovered before now otherwise.
CSS is loaded inline with
style-loader
.The text was updated successfully, but these errors were encountered: