├── .babelrc ├── .gitignore ├── .npmignore ├── .storybook ├── addons.js ├── config.js └── webpack.config.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── diagram.png ├── info.gif └── info2.gif ├── examples ├── Button.js ├── index.js └── style.css ├── npm-debug.log ├── package.json ├── press ├── article.md └── article.pdf ├── register.js ├── src ├── index.js └── register.js ├── typings └── index.d.ts └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ] 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | dist 4 | npm-debug.log 5 | .cache -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | node_modules 3 | examples 4 | .storybook -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions */ 2 | 3 | import '@storybook/addon-actions/register'; 4 | import '@storybook/addon-links/register'; 5 | import * as CodeAddon from '../src/register'; 6 | CodeAddon.setTabs([ 7 | { label: 'Css', type: 'css' }, 8 | { label: 'JavaScript', type: 'js' } 9 | ]); -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions */ 2 | 3 | import { configure } from '@storybook/react'; 4 | 5 | function loadStories() { 6 | require('../examples'); 7 | } 8 | 9 | configure(loadStories, module); 10 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | module: { 4 | rules: [ 5 | { 6 | test: /\.tsx$/, 7 | loader: "raw-loader" 8 | }, 9 | { 10 | test: /\.css?$/, 11 | loaders: ['style-loader', 'raw-loader'] 12 | }], 13 | } 14 | 15 | }; -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #Version 0.0.1 2 | 3 | Initial release 4 | 5 | #Version 0.1.0 6 | 7 | - Fix typings 8 | - Fix imports in register 9 | 10 | #Version 0.1.1 11 | - Fix typings 12 | - Add README.MD 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 SOFTVISION University 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # storybook-addon-code 2 | ![React Storybook code addon](./assets/info.gif) 3 | 4 | 5 | A Storybook addon enabling to show off code samples in the Storybook panel for your stories in [Storybook](https://storybook.js.org). 6 | 7 | ### Getting Started 8 | 9 | ```sh 10 | npm i --save-dev storybook-addon-code 11 | ``` 12 | ### Usage 13 | 14 | Create a file called `addons.js` in your storybook config. 15 | 16 | Add following content to it: 17 | 18 | ```js 19 | import * as CodeAddon from '../src/register'; 20 | CodeAddon.setTabs( 21 | [{ label: 'Sass', type: 'sass' }, {label: 'TypeScript', type: 'typescript'}] 22 | ); 23 | ``` 24 | setTab function accept and object like {label: 'Sass', type:'sass'} or if you want to have multiple tabs you can pass an array with multiple objects. The label will pe displayed in the Storybook panel. 25 | 26 | 27 | Then write your stories like this: 28 | 29 | ```js 30 | import { storiesOf } from '@storybook/react'; 31 | import withCode from 'storybook-addon-code'; 32 | import Button from './Button'; 33 | 34 | const styleFile = require('raw-loader!./style.scss'); 35 | const typescriptFile = require('./test.tsx'); 36 | 37 | storiesOf('Button', module) 38 | .addDecorator(withCode(typescriptFile, 'typescript')) 39 | .addDecorator(withCode(styleFile, 'sass')) 40 | .add('with text', () => 41 | 42 | ) 43 | ``` 44 | ### Available list of format's for withCode function 45 | 1. clike (withCode(YourCFile, 'clike')) 46 | 2. css (withCode(YourCssFile, 'css')) 47 | 3. html (withCode(YourHtmlFile, 'html')) 48 | 4. js | javascript (withCode(YourJavascriptFile, 'js')) 49 | 5. markup (withCode(YourMarkupFile, 'js')) 50 | 6. mathml (withCode(YourMatHmlFile, 'mathml')) 51 | 7. sass (withCode(YourSassFile, 'sass')) 52 | 8. svg (withCode(YourSvgFile, 'svg')) 53 | 9. ts (withCode(YourTsFile, 'ts')) 54 | 10. typescript (withCode(YourTypescriptFile, 'typescript')) 55 | 11. xml (withCode(YourXmlFile, 'xml')) 56 | 57 | > Have a look at [this example](examples/index.js) stories to learn more about the `withCode` API 58 | 59 | 60 | -------------------------------------------------------------------------------- /assets/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOFTVISION-University/storybook-addon-code/b56323c2ff1ac670c32c25844650a9ff265af663/assets/diagram.png -------------------------------------------------------------------------------- /assets/info.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOFTVISION-University/storybook-addon-code/b56323c2ff1ac670c32c25844650a9ff265af663/assets/info.gif -------------------------------------------------------------------------------- /assets/info2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOFTVISION-University/storybook-addon-code/b56323c2ff1ac670c32c25844650a9ff265af663/assets/info2.gif -------------------------------------------------------------------------------- /examples/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Button = (props) => 4 | ( 5 | 8 | ) -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { storiesOf } from '@storybook/react'; 4 | import { action } from '@storybook/addon-actions'; 5 | import withCode from '../src'; 6 | import './style.css'; 7 | import { Button } from './Button'; 8 | const style = require('!raw-loader!./style.css'); 9 | const javascriptCode = require('!raw-loader!./Button.js'); 10 | 11 | storiesOf('Button', module) 12 | .addDecorator(withCode(javascriptCode, 'js')) 13 | .addDecorator(withCode(style, 'css')) 14 | .add('with text', () => 15 | 16 | ) 17 | .add('with some emoji', () => ); 18 | -------------------------------------------------------------------------------- /examples/style.css: -------------------------------------------------------------------------------- 1 | 2 | .button { 3 | color: blue; 4 | } 5 | -------------------------------------------------------------------------------- /npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ '/x/s/node-v6.7.0-linux-x64/bin/node', 3 | 1 verbose cli '/x/s/node/bin/npm', 4 | 1 verbose cli 'run', 5 | 1 verbose cli 'commit', 6 | 1 verbose cli '-m', 7 | 1 verbose cli 'Add article' ] 8 | 2 info using npm@3.10.3 9 | 3 info using node@v6.7.0 10 | 4 verbose stack Error: missing script: commit 11 | 4 verbose stack at run (/x/s/node-v6.7.0-linux-x64/lib/node_modules/npm/lib/run-script.js:151:19) 12 | 4 verbose stack at /x/s/node-v6.7.0-linux-x64/lib/node_modules/npm/lib/run-script.js:61:5 13 | 4 verbose stack at /x/s/node-v6.7.0-linux-x64/lib/node_modules/npm/node_modules/read-package-json/read-json.js:356:5 14 | 4 verbose stack at checkBinReferences_ (/x/s/node-v6.7.0-linux-x64/lib/node_modules/npm/node_modules/read-package-json/read-json.js:320:45) 15 | 4 verbose stack at final (/x/s/node-v6.7.0-linux-x64/lib/node_modules/npm/node_modules/read-package-json/read-json.js:354:3) 16 | 4 verbose stack at then (/x/s/node-v6.7.0-linux-x64/lib/node_modules/npm/node_modules/read-package-json/read-json.js:124:5) 17 | 4 verbose stack at /x/s/node-v6.7.0-linux-x64/lib/node_modules/npm/node_modules/read-package-json/read-json.js:311:12 18 | 4 verbose stack at /x/s/node-v6.7.0-linux-x64/lib/node_modules/npm/node_modules/graceful-fs/graceful-fs.js:78:16 19 | 4 verbose stack at tryToString (fs.js:455:3) 20 | 4 verbose stack at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:442:12) 21 | 5 verbose cwd /home/cosminionascu/workspace/storybook-addon 22 | 6 error Linux 4.4.0-92-generic 23 | 7 error argv "/x/s/node-v6.7.0-linux-x64/bin/node" "/x/s/node/bin/npm" "run" "commit" "-m" "Add article" 24 | 8 error node v6.7.0 25 | 9 error npm v3.10.3 26 | 10 error missing script: commit 27 | 11 error If you need help, you may report this error at: 28 | 11 error 29 | 12 verbose exit [ 1, true ] 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storybook-addon-code", 3 | "version": "0.1.3", 4 | "description": "A storybook addon that display any type of code highlighted with Prismjs", 5 | "keywords": [ 6 | "addon", 7 | "storybook", 8 | "code" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/SOFTVISION-University/storybook-addon-code.git" 13 | }, 14 | "main": "./dist/index.js", 15 | "types": "./typings/index.d.ts", 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1", 18 | "storybook": "start-storybook -p 6006", 19 | "build-storybook": "build-storybook", 20 | "build": "npm run clean & babel -d ./dist ./src", 21 | "clean": "rm -rf ./dist", 22 | "prepublish": "npm run build" 23 | }, 24 | "author": "SoftVision", 25 | "license": "MIT", 26 | "devDependencies": { 27 | "@babel/cli": "^7.1.5", 28 | "@babel/core": "^7.1.5", 29 | "@babel/preset-env": "^7.1.5", 30 | "@babel/preset-react": "^7.0.0", 31 | "@storybook/addon-actions": "4.0.0", 32 | "@storybook/addon-links": "4.0.0", 33 | "@storybook/react": "4.0.0", 34 | "babel-loader": "^8.0.4", 35 | "raw-loader": "^0.5.1", 36 | "react": "^16.3.2", 37 | "react-dom": "^16.3.2", 38 | "style-loader": "^0.23.1" 39 | }, 40 | "dependencies": { 41 | "@storybook/addons": "4.0.0", 42 | "prismjs": "^1.15.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /press/article.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | [React Storybook](https://storybook.js.org/) is a javascript library for React, React Native and Vue js where the engineers can develop,design and test the UI components outside your app in an isolated environment.In the recent React project we needed the advantages that Storybook offer and we decided to use it. 4 | The major advantage was that this allows us to develop UI components outside the app and allow other people in our team to work on them. 5 | In this article we are offering the details of a solution we created for a need that is not covered by React Storybook - offering a preview of the styling code used inside various components. 6 | 7 | React Storybook ,for those who don't know, has many features including:: 8 | 1. Completely isolate the environment for your components 9 | 2. HMR — hot module replacement 10 | 3. Clean and fast user interface 11 | 4. Multiple add-ons like: 12 | + [Accessibility](https://github.com/joscha/storybook-addon-i18n-tools) 13 | + [CSS regression](https://github.com/tsuyoshiwada/storybook-chrome-screenshot) 14 | + [Snapshot testing of React components](https://github.com/storybooks/storybook/tree/master/addons/storyshots) 15 | + [Knobs allow you to edit React props dynamically](https://github.com/storybooks/storybook/tree/master/addons/knobs) 16 | 17 | Just visit the [React Storybook](https://storybook.js.org/addons/addon-gallery/) for full list of features. 18 | 19 | # Why another add-on 20 | 21 | Recently, on our current, project we come up with a needed to display the CSS code samples inside the Storybook panel along with the React component. After doing our fair share of research into existing add-ons mentioned earlier we've come up empty. 22 | So we decided to create our own add-on called [storybook-addon-code](https://github.com/SOFTVISION-University/storybook-addon-code) that not only displays CSS code but support other languages like javascript, HTML, typescript etc. 23 | 24 | # How it works 25 | 26 | Before we get started, you need to install react storybook and storybook-addon-code. 27 | 28 | In your Storybook config folder create a addons.js and add the following code: 29 | 30 | ```js 31 | import '@storybook/addon-actions/register'; 32 | import * as CodeAddon from '../src/register'; 33 | CodeAddon.setTabs([ 34 | { label: 'Css', type: 'css' }, 35 | { label: 'JavaScript', type: 'js' } 36 | ]); 37 | ``` 38 | ### Note: 39 | ``setTab`` function accept and object like ``{label: 'Sass', type:'sass'}`` or if you want to have multiple tabs you can pass an array with multiple objects. The label will pe displayed in the Storybook panel. 40 | 41 | 42 | Then write your stories like this: 43 | ```js 44 | import { storiesOf } from '@storybook/react'; 45 | import withCode from 'storybook-addon-code'; 46 | import Button from './Button'; 47 | 48 | const styleFile = require('raw-loader!./style.scss'); 49 | const typescriptFile = require('./test.tsx'); 50 | 51 | storiesOf('Button', module) 52 | .addDecorator(withCode(typescriptFile, 'typescript')) 53 | .addDecorator(withCode(styleFile, 'sass')) 54 | .add('with text', () => 55 | 56 | ) 57 | ``` 58 | ![React Storybook code addon](../assets/info2.gif) 59 | 60 | # Supported languages 61 | The plugin has a wide variety of built-in support for common Web programming languages. The most common built-in languages includes: 62 | 63 | + css 64 | + html 65 | + javascript 66 | + scss 67 | + mathml 68 | + sass 69 | + svg 70 | + typescript 71 | + xml 72 | 73 | # Anatomy of a Storybook plugin 74 | 75 | It turns out developing our custom React Storybook plugin wasn’t that all complicated. You can too, very easily, build your own plugins if you feel the need to. 76 | 77 | On a high level, this is how we've build our own: 78 | 1. We've created a top level React component ``Code Addon`` that gets displayed in Storybook panel and shows off the code samples. 79 | 2. Created a decorator function ``withCode``.This function is used to decorate any stories that need source code info being displayed along side them. ``withCode`` has 2 parameter, source code of the file to display and the type of the file. When is called the function emit a message through a channel that is created dynamically based on the language type. 80 | 4. ``Code Addon`` component listens to that specific channel name and calls a function that renders a text with the received code properly formatted. 81 | 5.We used [PrismJS](http://prismjs.com/) for code highlighting and because it can be used with various code samples like CSS, Typescript, Javascript, HTML etc. 82 | 83 | ![React Storybook code addon](../assets/diagram.png) 84 | 85 | For more details on how to build a Storybook addon, access the official [tutorial](https://storybook.js.org/addons/writing-addons/) . 86 | 87 | 88 | 89 | # Future development 90 | We would like to see at least 2 features added to it in the near and medium term: 91 | 92 | 1. Support for displaying markdown content alongside the component. Usefull for displaying extra info alongside the component being showcased, it would allow us to get rid of an extra plugin like [withReadme](https://github.com/tuchk4/storybook-readme). 93 | 2. Extend the built in language collection with additional languages 94 | We are opened to [Pull Requests](https://github.com/SOFTVISION-University/storybook-addon-code), so please don’t hesitate to get involved! 95 | 96 | If you like this plugin and find it useful in your day to day work please don’t hesitate to give us a little [Github](https://github.com/SOFTVISION-University/storybook-addon-code) star or tweet about it! 97 | -------------------------------------------------------------------------------- /press/article.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOFTVISION-University/storybook-addon-code/b56323c2ff1ac670c32c25844650a9ff265af663/press/article.pdf -------------------------------------------------------------------------------- /register.js: -------------------------------------------------------------------------------- 1 | require('./dist/register'); -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import addons from '@storybook/addons'; 3 | 4 | const withCode = function(code, type , storyFn = null) { 5 | const emitAddCode = ({ kind, story }) => { 6 | addons.getChannel().emit(`soft/code/add_${type}`, { code, type }); 7 | }; 8 | return (storyFn, { kind, story }) => { 9 | emitAddCode({ kind, story }); 10 | return storyFn(); 11 | }; 12 | }; 13 | 14 | export default withCode; -------------------------------------------------------------------------------- /src/register.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import addons from '@storybook/addons'; 3 | import Prism from 'prismjs'; 4 | import 'prismjs/components/prism-typescript.js'; 5 | import 'prismjs/components/prism-sass.js'; 6 | 7 | import 'prismjs/themes/prism.css'; 8 | 9 | class Code extends React.Component { 10 | constructor(props, context) { 11 | super(props, context); 12 | this.state = {code: ''}; 13 | this.channelName = `soft/code/add_${props.type}`; 14 | this.onSelectTab = this.onSelectTab.bind(this); 15 | } 16 | 17 | onSelectTab({code, type}) { 18 | const formattedCode = type && code && Prism.highlight(code, Prism.languages[type]); 19 | 20 | this.setState({code: formattedCode}); 21 | } 22 | 23 | componentDidMount() { 24 | const { channel, api } = this.props; 25 | channel.on(this.channelName, this.onSelectTab); 26 | 27 | this.stopListeningOnStory = api.onStory(() => { 28 | this.onSelectTab(''); 29 | }); 30 | } 31 | 32 | render() { 33 | const { code } = this.state; 34 | const { type } = this.props; 35 | return ( 36 |
{ 37 | code ? 38 |
39 |             
40 |               
41 | 42 |
: 43 |

No {type} code Found

44 | } 45 |
46 | ); 47 | } 48 | 49 | componentWillUnmount() { 50 | if(this.stopListeningOnStory) { 51 | this.stopListeningOnStory(); 52 | } 53 | 54 | this.unmounted = true; 55 | const { channel, api } = this.props; 56 | channel.removeListener(this.channelName, this.onSelectTab); 57 | } 58 | } 59 | 60 | const registerTab = ({label, type}) => { 61 | addons.register(`soft/code/add_${type}`, (api) => { 62 | addons.addPanel(`soft/${type}/panel`, { 63 | title: label, 64 | render: () => ( 65 | 66 | ) 67 | }) 68 | }) 69 | } 70 | export const setTabs = (tabs) => { 71 | const tabsToRender = [].concat(tabs); 72 | tabsToRender.forEach((t) => registerTab(t)); 73 | } -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare function withCode(code: string, type: string): any; 3 | export default withCode; --------------------------------------------------------------------------------