Skip to content

Commit

Permalink
Merge pull request #4622 from nm123github/addon-cssresources
Browse files Browse the repository at this point in the history
Resources addon to dynamically add/remove css
  • Loading branch information
ndelangen authored Nov 26, 2018
2 parents 0ff675d + ca0f135 commit cd02d36
Show file tree
Hide file tree
Showing 16 changed files with 369 additions and 1 deletion.
1 change: 1 addition & 0 deletions ADDONS_SUPPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
|[links](addons/links) |+|+|+|+|+|+|+| |+|+|+|
|[notes](addons/notes) |+|+*|+|+|+|+|+| |+|+|+|
|[options](addons/options) |+|+|+|+|+|+|+| |+|+|+|
|[cssresources](addons/cssresources) |+| |+|+|+|+|+|+|+|+|+|
|[storyshots](addons/storyshots) |+|+|+|+| | |+| |+|+| |
|[storysource](addons/storysource) |+| |+|+|+|+|+|+|+|+|+|
|[viewport](addons/viewport) |+| |+|+|+|+|+|+|+|+|+|
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ For additional help, join us [in our Discord](https://discord.gg/sMFvFsG) or [Sl
| [actions](addons/actions/) | Log actions as users interact with components in the Storybook UI |
| [backgrounds](addons/backgrounds/) | Let users choose backgrounds in the Storybook UI |
| [centered](addons/centered/) | Center the alignment of your components within the Storybook UI |
| [cssresources](addons/cssresources/) | Dynamically add/remove css resources to the component iframe |
| [events](addons/events/) | Interactively fire events to components that respond to EventEmitter |
| [graphql](addons/graphql/) | Query a GraphQL server within Storybook stories |
| [google-analytics](addons/google-analytics) | Reports google analytics on stories |
Expand Down
63 changes: 63 additions & 0 deletions addons/cssresources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Storybook Addon Cssresources

Storybook Addon Cssresources to switch between css resources at runtime for your story [Storybook](https://storybook.js.org).

[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)

![Storybook Addon Cssresources Demo](docs/demo.gif)

## Installation

```sh
yarn add -D @storybook/addon-cssresources
```

## Configuration

Then create a file called `addons.js` in your storybook config.

Add following content to it:

```js
import '@storybook/addon-cssresources/register';
```

## Usage

You need add the all the css resources at compile time using the `withCssResources` decorator. They can be added globally or per story. You can then choose which ones to load from the cssresources addon ui:

```js
// Import from @storybook/X where X is your framework
import { configure, addDecorator, storiesOf } from '@storybook/react';
import { withCssResources } from '@storybook/addon-cssresources';

// global
addDecorator(
withCssResources({
cssresources: [{
name: `bluetheme`,
code: `<style>body { background-color: lightblue; }</style>`,
picked: false,
},
],
})
);

// per story
storiesOf('Addons|Cssresources', module)
.addDecorator(
withCssResources({
cssresources: [{
name: `fontawesome`,
code: `<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"></link>`,
picked: true,
}, {
name: `whitetheme`,
code: `<style>.fa { color: #fff }</style>`,
picked: true,
},
],
})
)
.add('Camera Icon', () => <i className="fa fa-camera-retro"> Camera Icon</i>);
```
Binary file added addons/cssresources/docs/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions addons/cssresources/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@storybook/addon-cssresources",
"version": "4.1.0-alpha.8",
"description": "A storybook addon to switch between css resources at runtime for your story",
"keywords": [
"addon",
"cssresources",
"react",
"storybook"
],
"homepage": "https://storybook.js.org",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/storybooks/storybook.git"
},
"license": "MIT",
"author": "nm123github",
"main": "dist/index.js",
"jsnext:main": "src/index.js",
"scripts": {
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@emotion/styled": "0.10.6",
"@storybook/addons": "4.1.0-alpha.8",
"@storybook/components": "4.1.0-alpha.8",
"@storybook/core-events": "4.1.0-alpha.8",
"global": "^4.3.2",
"prop-types": "^15.6.2",
"react-syntax-highlighter": "^10.0.0",
"util-deprecate": "^1.0.2"
},
"peerDependencies": {
"react": "*"
}
}
1 change: 1 addition & 0 deletions addons/cssresources/register.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('./dist/register.js');
147 changes: 147 additions & 0 deletions addons/cssresources/src/CssResourcePanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { document, DOMParser } from 'global';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { monoFonts } from '@storybook/components';
import styled from '@emotion/styled';

import css from 'react-syntax-highlighter/dist/esm/languages/prism/css';
import { darcula } from 'react-syntax-highlighter/dist/esm/styles/prism';
import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light';

SyntaxHighlighter.registerLanguage('css', css);

const storybookIframe = 'storybook-preview-iframe';
const addedElAttr = 'addedbycssresourcesaddon';

// Taken from StoryPanel.js
const highlighterTheme = {
...darcula,
'pre[class*="language-"]': {
...darcula['pre[class*="language-"]'],
margin: 'auto',
width: 'auto',
height: 'auto',
minHeight: '100%',
overflow: 'hidden',
boxSizing: 'border-box',
display: 'flex',
fontFamily: monoFonts.fontFamily,
fontSize: 'inherit',
},
'code[class*="language-"]': {
...darcula['code[class*="language-"]'],
margin: 0,
fontFamily: 'inherit',
},
};

const PanelWrapper = styled.div({
position: 'absolute',
top: '10px',
left: '10px',
fontFamily: monoFonts.fontFamily,
});

export default class CssResourcePanel extends Component {
constructor(props) {
super(props);
this.state = { cssresources: `` };
this.onAddCssresources = this.onAddCssresources.bind(this);
this.parser = new DOMParser();
}

componentDidMount() {
const { channel } = this.props;
this.iframe = document.getElementById(storybookIframe);
if (!this.iframe) {
throw new Error('Cannot find Storybook iframe');
}
channel.on('storybook/resources/add_cssresources', this.onAddCssresources);
}

componentWillUnmount() {
const { channel } = this.props;
channel.removeListener('storybook/resources/add_cssresources', this.onAddCssresources);
}

onAddCssresources(cssresources) {
this.loadCssresources(cssresources.filter(res => res.picked));
this.setState(prevState => ({ ...prevState, cssresources }));
}

handleChange(i, { target }) {
const { channel } = this.props;
const { cssresources = [] } = this.state;
cssresources[i].picked = target.checked;
channel.emit('storybook/resources/add_cssresources', cssresources);
}

loadCssresources(cssresources) {
// remove previously added elements!
this.iframe.contentDocument.head.querySelectorAll(`[${addedElAttr}]`).forEach(eL => {
if (eL) {
eL.parentNode.removeChild(eL);
}
});

// add new elements!
const str = cssresources.map(res => res.code).join('');
const parsedHtml = this.parser.parseFromString(str, 'text/html');
const elements = parsedHtml.querySelectorAll('head > *');
elements.forEach(eL => {
// add addedElAttr to css elements
eL.setAttribute(addedElAttr, '');
this.iframe.contentDocument.head.appendChild(eL);
});
}

render() {
const { cssresources = [] } = this.state;
const { active } = this.props;

if (!active) {
return null;
}

return (
<PanelWrapper className="addon-cssresources-container">
{cssresources &&
cssresources.map(({ name, code, picked }, i) => (
<div key={name} style={{ padding: 10 }}>
<div style={{ fontSize: '1.1em', marginBottom: 10 }}>
<input
id={`cssresource${i}`}
style={{ fontSize: '1.5em' }}
type="checkbox"
checked={picked}
onChange={this.handleChange.bind(this, i)}
/>
<label htmlFor={`cssresource${i}`}>#{name}</label>
</div>
<SyntaxHighlighter
language="css"
style={highlighterTheme}
customStyle={{ opacity: picked ? 1 : 0.5 }}
>
{code}
</SyntaxHighlighter>
</div>
))}
</PanelWrapper>
);
}
}

CssResourcePanel.propTypes = {
active: PropTypes.bool.isRequired,
channel: PropTypes.shape({
on: PropTypes.func,
emit: PropTypes.func,
removeListener: PropTypes.func,
}).isRequired,
api: PropTypes.shape({
onStory: PropTypes.func,
getQueryParam: PropTypes.func,
setQueryParams: PropTypes.func,
}).isRequired,
};
23 changes: 23 additions & 0 deletions addons/cssresources/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import addons, { makeDecorator } from '@storybook/addons';

export const withCssResources = makeDecorator({
name: 'withCssResources',
parameterName: 'cssresources',
skipIfNoParametersOrOptions: true,
allowDeprecatedUsage: false,
wrapper: (getStory, context, { options, parameters }) => {
const channel = addons.getChannel();
const storyOptions = parameters || options;

if (!Array.isArray(storyOptions) && !Array.isArray(storyOptions.cssresources)) {
throw new Error('The `cssresources` parameter needs to be an Array');
}

channel.emit(
'storybook/resources/add_cssresources',
Array.isArray(storyOptions) ? storyOptions : storyOptions.cssresources
);

return getStory(context);
},
});
12 changes: 12 additions & 0 deletions addons/cssresources/src/register.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import addons from '@storybook/addons';
import CssResourcePanel from './CssResourcePanel';

addons.register('storybook/cssresources', api => {
const channel = addons.getChannel();
addons.addPanel('storybook/cssresources/panel', {
title: 'Cssresources',
// eslint-disable-next-line react/prop-types
render: ({ active }) => <CssResourcePanel channel={channel} api={api} active={active} />,
});
});
1 change: 1 addition & 0 deletions addons/info/src/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2880,6 +2880,7 @@ exports[`addon Info should render component description 1`] = `
</div>
</div>
<div
id="story-root"
style={Object {}}
>
<div>
Expand Down
1 change: 1 addition & 0 deletions examples/official-storybook/addons.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import '@storybook/addon-events/register';
import '@storybook/addon-notes/register';
import '@storybook/addon-options/register';
import '@storybook/addon-knobs/register';
import '@storybook/addon-cssresources/register';
import '@storybook/addon-backgrounds/register';
import '@storybook/addon-a11y/register';
import '@storybook/addon-jest/register';
Expand Down
15 changes: 15 additions & 0 deletions examples/official-storybook/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { configure, addDecorator } from '@storybook/react';
import { themes } from '@storybook/components';
import { withOptions } from '@storybook/addon-options';
import { configureViewport, INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
import { withCssResources } from '@storybook/addon-cssresources';

import 'react-chromatic/storybook-addon';
import addHeadWarning from './head-warning';
Expand Down Expand Up @@ -34,6 +35,20 @@ addDecorator(
})
);

addDecorator(
withCssResources({
cssresources: [
{
name: `bluetheme`,
code: `<style>body {
background-color: lightblue;
}</style>`,
picked: false,
},
],
})
);

addDecorator(
(story, { kind }) =>
kind === 'Core|Errors' ? (
Expand Down
1 change: 1 addition & 0 deletions examples/official-storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@storybook/addon-actions": "4.1.0-alpha.8",
"@storybook/addon-backgrounds": "4.1.0-alpha.8",
"@storybook/addon-centered": "4.1.0-alpha.8",
"@storybook/addon-cssresources": "4.1.0-alpha.8",
"@storybook/addon-events": "4.1.0-alpha.8",
"@storybook/addon-graphql": "4.1.0-alpha.8",
"@storybook/addon-info": "4.1.0-alpha.8",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Storyshots Addons|Cssresources Camera Icon 1`] = `
<i
class="fa fa-camera-retro"
>
Camera Icon
</i>
`;

exports[`Storyshots Addons|Cssresources Primary Large Button 1`] = `
<button
class="btn btn-lg btn-primary"
type="button"
>
Primary Large Button
</button>
`;
Loading

0 comments on commit cd02d36

Please sign in to comment.