diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..1b6241f1f98f --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules +*.log +.idea +npm-shrinkwrap.json +dist +.tern-port +*.DS_Store +*.lerna_backup +build +packages/examples/automated-* +yarn.lock diff --git a/.storybook/addons.js b/.storybook/addons.js new file mode 100644 index 000000000000..f782d05c1b5e --- /dev/null +++ b/.storybook/addons.js @@ -0,0 +1,3 @@ +import { register } from './notes_addon'; +register(); +require('@kadira/storybook/addons'); diff --git a/.storybook/config.js b/.storybook/config.js new file mode 100644 index 000000000000..7bffa1dfcee3 --- /dev/null +++ b/.storybook/config.js @@ -0,0 +1,9 @@ +import { configure } from '@kadira/storybook'; +import 'bootstrap/dist/css/bootstrap.css'; +import '../src/index.css'; + +function loadStories() { + require('../src/stories'); +} + +configure(loadStories, module); diff --git a/.storybook/notes_addon.js b/.storybook/notes_addon.js new file mode 100644 index 000000000000..9f2329163082 --- /dev/null +++ b/.storybook/notes_addon.js @@ -0,0 +1,78 @@ +import React from 'react'; +import addons from '@kadira/storybook-addons'; + +const styles = { + notesPanel: { + margin: 10, + fontFamily: '-apple-system, ".SFNSText-Regular", "San Francisco", Roboto, "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif', + fontSize: 14, + color: '#444', + width: '100%', + overflow: 'auto', + } +}; + +export class WithNote extends React.Component { + render() { + const { children, note } = this.props; + // This is to make sure, we'll always call this at the end of the eventloop. + // So we know that, panel will get cleared, before we render the note. + setTimeout(() => { + addons.getChannel().emit('kadira/notes/add_note', note); + }, 0); + return children; + } +} + +class Notes extends React.Component { + constructor(...args) { + super(...args); + this.state = {text: ''}; + this.onAddNote = this.onAddNote.bind(this); + } + + onAddNote(text) { + this.setState({text}); + } + + componentDidMount() { + const { channel, api } = this.props; + channel.on('kadira/notes/add_note', this.onAddNote); + + this.stopListeningOnStory = api.onStory(() => { + this.setState({text: ''}); + }); + } + + componentWillUnmount() { + if(this.stopListeningOnStory) { + this.stopListeningOnStory(); + } + + this.unmounted = true; + const { channel, api } = this.props; + channel.removeListener('kadira/notes/add_note', this.onAddNote); + } + + render() { + const { text } = this.state; + const textAfterFormatted = text? text.trim().replace(/\n/g, '
') : ""; + + return ( +
+
+
+ ); + } +} + +export function register() { + addons.register('kadira/notes', (api) => { + addons.addPanel('kadira/notes/panel', { + title: 'Notes', + render: () => ( + + ), + }) + }) +} diff --git a/README.md b/README.md index 4905551a9767..f42a3fba327f 100644 --- a/README.md +++ b/README.md @@ -1 +1,19 @@ -# storybooks.github.io \ No newline at end of file +# Website for [storybooks.js.org](https://storybooks.js.org) + +This is the source for [storybooks.js.org](https://storybooks.js.org). This is a [Create React App](https://github.com/facebookincubator/create-react-app) based site. + +This site has also has the documentation for Storybook. + +### Usage + +``` +npm i +npm start +``` + +### Edit Documentation + +This website carries the documentation for Storybook.
+Documentation is located at: [`src/docs`](/src/docs) directory. + +It's written in Markdown and ES6. So, it supports goodies of both worlds. diff --git a/app.json b/app.json new file mode 100644 index 000000000000..bc1535ec1749 --- /dev/null +++ b/app.json @@ -0,0 +1,20 @@ +{ + "name": "storybooks.js.org", + "scripts": { + }, + "env": { + }, + "formation": { + "web": { + "quantity": 1 + } + }, + "addons": [ + + ], + "buildpacks": [ + { + "url": "https://github.com/mars/create-react-app-buildpack.git" + } + ] +} diff --git a/package.json b/package.json new file mode 100644 index 000000000000..90aa1e74b439 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "storybooks.js.org", + "version": "0.0.1", + "private": true, + "devDependencies": { + "@kadira/storybook": "^2.35.3", + "react-scripts": "^0.9.5" + }, + "dependencies": { + "@kadira/storybook-addons": "^1.3.1", + "airbnb-js-shims": "^1.0.1", + "bootstrap": "^3.3.7", + "common-tags": "^1.3.1", + "highlight.js": "^9.6.0", + "marked": "^0.3.6", + "prop-types": "^15.5.7", + "react": "^15.5.4", + "react-dom": "^15.5.4", + "react-helmet": "^3.1.0", + "react-router": "^2.6.1", + "react-router-scroll": "^0.3.2", + "reflexbox": "^2.1.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "eject": "react-scripts eject", + "storybook": "start-storybook -p 9009", + "build-storybook": "build-storybook" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 000000000000..31b707d749bd Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 000000000000..3ef0d1035145 --- /dev/null +++ b/public/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/src/components/Docs/Container/index.js b/src/components/Docs/Container/index.js new file mode 100644 index 000000000000..91bf9d76d7cc --- /dev/null +++ b/src/components/Docs/Container/index.js @@ -0,0 +1,93 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Nav from '../Nav'; +import NavDropdown from '../Nav/dropdown'; +import Content from '../Content'; +import './style.css'; + +class Container extends React.Component { + renderTopNav(cat) { + const { selectedCatId } = this.props; + const path = `/docs/${cat.id}`; + + if (selectedCatId === cat.id) { + return
  • {cat.title}
  • ; + } + + return
  • {cat.title}
  • ; + } + + render() { + const { + categories, + selectedCatId, + sections, + selectedItem, + selectedSectionId, + selectedItemId, + } = this.props; + + const gitHubRepoUrl = 'https://github.com/storybooks/storybooks.github.io'; + const docPath = `${selectedCatId}/${selectedSectionId}/${selectedItemId}`; + const gitHubRepoDocUrl = `${gitHubRepoUrl}/tree/master/src/docs/${docPath}.js`; + + return ( +
    +
    +
    +
      + {categories.map(this.renderTopNav.bind(this))} +
    +
    +
    + +
    +
    +
    +
    + +
    + + + +
    + +
    +
    +
    + ); + } +} + +Container.propTypes = { + categories: PropTypes.array, + selectedCatId: PropTypes.string, + sections: PropTypes.array, + selectedItem: PropTypes.object, + selectedSectionId: PropTypes.string, + selectedItemId: PropTypes.string, +}; + +export default Container; diff --git a/src/components/Docs/Container/style.css b/src/components/Docs/Container/style.css new file mode 100644 index 000000000000..23604750df99 --- /dev/null +++ b/src/components/Docs/Container/style.css @@ -0,0 +1,59 @@ +#docs-container { + border-top: 1px solid #F4F4F4; + border-bottom: 1px solid #F4F4F4; + margin: 10px 0; + padding: 0; +} + +#docs-container ul.top-nav { + margin: 20px 0 0; + padding: 0; + list-style: none; + font-size: 13px; +} + +#docs-container ul.top-nav li { + display: inline-block; + background-color: #eee; + margin-right: 10px; + padding: 6px 20px; + cursor: pointer; + border-bottom: 1px solid #ccc; +} + +#docs-container ul.top-nav li.selected { + background-color: #eb5d88; + color: #FFF; +} + +#docs-container ul.top-nav li a { + font-size: 13px; +} + +#docs-container .nav-dropdown { + display: none; +} + +#docs-container .nav-dropdown select { + width: 100%; +} + +#docs-container .nav-dropdown div { + margin: 0 0 5px; +} + +@media only screen and (max-width: 998px) { + #docs-container .nav { + display: none; + } + + #docs-container .content { + border-left: none; + margin: 15px 0; + padding: 0; + } + + #docs-container .nav-dropdown { + display: block; + } +} diff --git a/src/components/Docs/Content/index.js b/src/components/Docs/Content/index.js new file mode 100644 index 000000000000..2f8d533e87f5 --- /dev/null +++ b/src/components/Docs/Content/index.js @@ -0,0 +1,40 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Highlight from '../../../lib/highlight.js'; +import marked from 'marked'; +import 'highlight.js/styles/github-gist.css'; +import './style.css'; + +marked.setOptions({ + renderer: new marked.Renderer(), + gfm: true, + tables: true, + breaks: true, + pedantic: false, + sanitize: false, + smartLists: true, + smartypants: false, +}); + +const DocsContent = ({ title, content, editUrl }) => ( +
    +
    +

    {title}

    +

    Edit this page

    + +
    + + {marked(content)} + +
    +
    +
    +); + +DocsContent.propTypes = { + title: PropTypes.string, + content: PropTypes.string.isRequired, + editUrl: PropTypes.string, +}; + +export default DocsContent; diff --git a/src/components/Docs/Content/style.css b/src/components/Docs/Content/style.css new file mode 100644 index 000000000000..7053dec1a5f3 --- /dev/null +++ b/src/components/Docs/Content/style.css @@ -0,0 +1,96 @@ +#docs-content .content { + font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif; + font-size: 17px; + line-height: 25px; + margin: 40px 0; + max-width: 700px; + -webkit-font-smoothing: antialiased; +} + +#docs-content .title { + font-size: 25px; + color: rgb(0, 0, 0); + margin-bottom: 0; + line-height: 28px; + font-weight: 800; +} + +#docs-content a.edit-link { + color: #E25E5E !important; + font-size: 11px; + text-transform: uppercase; + font-weight: bold; +} + +#docs-content .markdown div, +#docs-content .markdown p, +#docs-content .markdown li, +#docs-content .markdown a { + font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif; + font-size: 16px; + line-height: 26px; + color: #333; +} + +#docs-content .markdown img { + max-width: 100%; +} + +#docs-content .markdown h2 { + font-size: 20px; + margin: 30px 0 10px 0; +} + +#docs-content .markdown h3 { + font-size: 18px; + margin: 30px 0 10px 0; + font-weight: 800; +} + +#docs-content .markdown a, +#docs-content .markdown a:visited, +#docs-content .markdown a:hover, +#docs-content .markdown a:active { + text-decoration: none; + color: #1E88E5; +} + +#docs-content .markdown a:hover { + text-decoration: underline; +} + +#docs-content .markdown blockquote { + padding: 10px 20px; + margin: 10px 0; + font-size: 16px; + border-left: 5px solid #B2CDA9; + color: #444; +} + +#docs-content .markdown blockquote p { + margin: 0px; +} + +#docs-content .markdown code { + font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; + padding: 2px 5px; + margin: 0; + font-size: 85%; + background-color: rgba(0,0,0,0.04); + border-radius: 2px; + color: #333; +} + +#docs-content .markdown pre { + border: 0; + border-radius: 0; + padding: 0; +} + +#docs-content .markdown pre code { + font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 14px; + line-height: 20px; + background-color: #f7f7f7; + padding: 10px 12px; +} diff --git a/src/components/Docs/Nav/dropdown.js b/src/components/Docs/Nav/dropdown.js new file mode 100644 index 000000000000..25b24a07025e --- /dev/null +++ b/src/components/Docs/Nav/dropdown.js @@ -0,0 +1,64 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { browserHistory } from 'react-router'; +import './style.css'; + +class Nav extends React.Component { + renderHeadingOpts(section) { + return ; + } + + renderNavOpts(nav) { + return ; + } + + changeRoute(selectedCatId, selectedSectionId, selectedItemId) { + const url = `/docs/${selectedCatId}/${selectedSectionId}/${selectedItemId}`; + browserHistory.push(url); + } + + handleHeadingChange(evt) { + const { selectedCatId } = this.props; + this.changeRoute(selectedCatId, evt.target.value, ''); + } + + handleNavChange(evt) { + const { selectedCatId, sections, selectedSection } = this.props; + const selectedSectionId = selectedSection || sections[0].id; + this.changeRoute(selectedCatId, selectedSectionId, evt.target.value); + } + + render() { + const { sections, selectedSection, selectedItem } = this.props; + const selectedSectionId = selectedSection || sections[0].id; + const selectedItemId = selectedItem || sections[0].items[0].id; + + const selectedSectionData = sections.find(section => section.id === selectedSectionId); + const navs = selectedSectionData.items; + + return ( +
    +
    + +
    + +
    + +
    +
    + ); + } +} + +Nav.propTypes = { + selectedCatId: PropTypes.string, + sections: PropTypes.array, + selectedSection: PropTypes.string, + selectedItem: PropTypes.string, +}; + +export default Nav; diff --git a/src/components/Docs/Nav/index.js b/src/components/Docs/Nav/index.js new file mode 100644 index 000000000000..35d8906cd3a1 --- /dev/null +++ b/src/components/Docs/Nav/index.js @@ -0,0 +1,50 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import './style.css'; + +class Nav extends React.Component { + renderNavItem(section, item) { + const { selectedCatId, selectedSectionId, selectedItemId } = this.props; + const cssClass = section.id === selectedSectionId && item.id === selectedItemId + ? 'selected' + : ''; + + const url = `/docs/${selectedCatId}/${section.id}/${item.id}`; + + return ( +
  • + {item.title} +
  • + ); + } + + renderNavSections(section) { + return ( +
    +

    {section.heading}

    +
      + {section.items.map(this.renderNavItem.bind(this, section))} +
    +
    + ); + } + + render() { + const { sections } = this.props; + + return ( + + ); + } +} + +Nav.propTypes = { + selectedCatId: PropTypes.string, + sections: PropTypes.array, + selectedItem: PropTypes.string, + prefix: PropTypes.string, +}; + +export default Nav; diff --git a/src/components/Docs/Nav/style.css b/src/components/Docs/Nav/style.css new file mode 100644 index 000000000000..4c18f28c12cc --- /dev/null +++ b/src/components/Docs/Nav/style.css @@ -0,0 +1,43 @@ +#nav { + color: #444; + margin: 40px 0; + padding: 0 15px 0 0; + border-right: 1px solid #ececec; + -webkit-font-smoothing: antialiased; +} + +#nav .col-xs-12 { + margin: 0; + padding: 0; +} + +#nav h3 { + font-family: "Open Sans"; + font-size: 18px; + font-weight: 600; + margin: 10px 0; + padding: 0; + color: #000; +} + +#nav ul { + margin: 0 0 30px 0; + padding: 0; + list-style: none; +} + +#nav ul li { + margin: 8px 0; +} + +#nav ul li a { + font-size: 14px; + line-height: 16px; + padding: 0; + color: #444; +} + +#nav ul li a.selected { + color: #E25E5E; + font-weight: 600; +} diff --git a/src/components/Docs/Navigation/index.js b/src/components/Docs/Navigation/index.js new file mode 100644 index 000000000000..54c6a91bf3d8 --- /dev/null +++ b/src/components/Docs/Navigation/index.js @@ -0,0 +1,59 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import styles from './styles'; +import { Link } from 'react-router'; + +class Navigation extends React.Component { + renderHeading(caption) { + const style = { + ...styles.h3, + }; + + return

    {caption}

    ; + } + + renderItem(section, item) { + const { prefix = '', selectedSection, selectedItem } = this.props; + const href = `${prefix}/docs/${section.id}/${item.id}`; + + let style = styles.item; + if (selectedSection === section.id && selectedItem === item.id) { + style = styles.selectedItem; + } + + return ( + + {item.title} + + ); + } + + render() { + const { sections } = this.props; + return ( +
    + {sections.map(section => ( +
    + {this.renderHeading(section.heading)} +
      + {section.items.map(item => ( +
    • + {this.renderItem(section, item)} +
    • + ))} +
    +
    + ))} +
    + ); + } +} + +Navigation.propTypes = { + sections: PropTypes.array, + selectedSection: PropTypes.string, + selectedItem: PropTypes.string, + prefix: PropTypes.string, +}; + +export default Navigation; diff --git a/src/components/Docs/Navigation/styles.js b/src/components/Docs/Navigation/styles.js new file mode 100644 index 000000000000..f1ab365cf157 --- /dev/null +++ b/src/components/Docs/Navigation/styles.js @@ -0,0 +1,40 @@ +import theme from '../../theme'; + +const styles = { + container: { + ...theme.base, + ...theme.text, + borderRight: '1px solid #ECECEC', + marginRight: 30, + }, + + h3: { + color: '#000', + margin: '25px 0 7px 0', + padding: 0, + fontSize: 20, + }, + + ul: { + margin: 0, + padding: 0, + listStyle: 'none', + }, + + li: { + margin: '8px 0', + lineHeight: '25px', + }, + + item: { + ...theme.text, + textDecoration: 'none', + }, +}; + +styles.selectedItem = { + ...styles.item, + fontWeight: 600, +}; + +export default styles; diff --git a/src/components/Docs/index.js b/src/components/Docs/index.js new file mode 100644 index 000000000000..1dcacc043bdf --- /dev/null +++ b/src/components/Docs/index.js @@ -0,0 +1,50 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Helmet from 'react-helmet'; +import Header from '../Homepage/Header'; +import Container from './Container'; +import Footer from '../Homepage/Footer'; +import './style.css'; + +class Docs extends React.Component { + render() { + const { + categories, + selectedCatId, + sections, + selectedItem, + selectedSectionId, + selectedItemId, + } = this.props; + + const selectedCat = categories.find(cat => cat.id === selectedCatId); + const headTitle = `${selectedItem.title} - ${selectedCat.title}`; + + return ( +
    + +
    + +
    +
    + ); + } +} + +Docs.propTypes = { + categories: PropTypes.array, + selectedCatId: PropTypes.string, + sections: PropTypes.array, + selectedItem: PropTypes.object, + selectedSectionId: PropTypes.string, + selectedItemId: PropTypes.string, +}; + +export default Docs; diff --git a/src/components/Docs/style.css b/src/components/Docs/style.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/components/Homepage/Demo/images/demo.gif b/src/components/Homepage/Demo/images/demo.gif new file mode 100644 index 000000000000..e427df3345d4 Binary files /dev/null and b/src/components/Homepage/Demo/images/demo.gif differ diff --git a/src/components/Homepage/Demo/index.js b/src/components/Homepage/Demo/index.js new file mode 100644 index 000000000000..e0b426a25c48 --- /dev/null +++ b/src/components/Homepage/Demo/index.js @@ -0,0 +1,15 @@ +import React from 'react'; +import demoImg from './images/demo.gif'; +import './style.css'; + +const Demo = () => ( +
    +
    +
    + +
    +
    +
    +); + +export default Demo; diff --git a/src/components/Homepage/Demo/style.css b/src/components/Homepage/Demo/style.css new file mode 100644 index 000000000000..481072f2fe28 --- /dev/null +++ b/src/components/Homepage/Demo/style.css @@ -0,0 +1,11 @@ +.demo-img { + width: 800px; + border: 5px solid #444; +} + +@media only screen and (max-width: 800px) { + .demo-img { + width: 90%; + border: 2px solid #444; + } +} diff --git a/src/components/Homepage/Featured/images/airbnb.png b/src/components/Homepage/Featured/images/airbnb.png new file mode 100644 index 000000000000..7485843aea9d Binary files /dev/null and b/src/components/Homepage/Featured/images/airbnb.png differ diff --git a/src/components/Homepage/Featured/images/rb.png b/src/components/Homepage/Featured/images/rb.png new file mode 100644 index 000000000000..5fe90c28a530 Binary files /dev/null and b/src/components/Homepage/Featured/images/rb.png differ diff --git a/src/components/Homepage/Featured/images/rnw.png b/src/components/Homepage/Featured/images/rnw.png new file mode 100644 index 000000000000..38d6a0534101 Binary files /dev/null and b/src/components/Homepage/Featured/images/rnw.png differ diff --git a/src/components/Homepage/Featured/index.js b/src/components/Homepage/Featured/index.js new file mode 100644 index 000000000000..9a5c78434c46 --- /dev/null +++ b/src/components/Homepage/Featured/index.js @@ -0,0 +1,48 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import './style.css'; + +class Featured extends React.Component { + renderFeaturedItem(ftItem) { + return ( +
    +
    + +
    + +
    +
    +
    +
    +

    + + {ftItem.storybook.name} + +

    + source +
    +
    + ); + } + + render() { + const { featuredStorybooks } = this.props; + return ( + + ); + } +} + +Featured.propTypes = { + featuredStorybooks: PropTypes.array, +}; + +export default Featured; diff --git a/src/components/Homepage/Featured/style.css b/src/components/Homepage/Featured/style.css new file mode 100644 index 000000000000..b1043b8480c7 --- /dev/null +++ b/src/components/Homepage/Featured/style.css @@ -0,0 +1,77 @@ +#featured { + margin: 25px auto 35px; + max-width: 600px; +} + +#featured hr { + margin: 50px auto 0; +} + +#featured h2 { + text-align: center; + text-transform: uppercase; + font-size: 24px; + font-weight: 700; + margin-bottom: 35px; +} + +#featured .reponame { + font-size: 14px; +} + +#featured a.reponame { + color: #6f5151; +} + +.ft-logo { + width: 45px; + height: 45px; +} + +.ft-sbooks { + padding-right: 0 !important; + padding-left: 0 !important; +} + +.ft-sbooks .desc { + padding-left: 0 !important; +} + +.ft-sbooks p { + font-size: 13px; + margin: 0; + padding: 0; +} + +.ft-sbooks a { + font-size: 13px; + color: #8f8fd6; +} + +@media only screen and (max-width: 998px) { + #featured { + margin: 15px auto 25px; + } + + #featured hr { + margin: 20px auto 0; + } + + #featured .col-xs-6 { + width: 100% !important; + } + + .ft-sbooks .desc { + text-align: center; + padding-left: 15px !important; + padding-bottom: 20px; + } + + .ft-sbooks p { + text-align: center; + } + + .ft-sbooks a { + text-align: center; + } +} diff --git a/src/components/Homepage/Footer/images/mail-icon.png b/src/components/Homepage/Footer/images/mail-icon.png new file mode 100644 index 000000000000..bb3ca42e76e5 Binary files /dev/null and b/src/components/Homepage/Footer/images/mail-icon.png differ diff --git a/src/components/Homepage/Footer/images/medium-icon.png b/src/components/Homepage/Footer/images/medium-icon.png new file mode 100644 index 000000000000..e579a729906c Binary files /dev/null and b/src/components/Homepage/Footer/images/medium-icon.png differ diff --git a/src/components/Homepage/Footer/images/slack-icon.png b/src/components/Homepage/Footer/images/slack-icon.png new file mode 100644 index 000000000000..9c1b0bace256 Binary files /dev/null and b/src/components/Homepage/Footer/images/slack-icon.png differ diff --git a/src/components/Homepage/Footer/images/twitter-icon.png b/src/components/Homepage/Footer/images/twitter-icon.png new file mode 100644 index 000000000000..8c910015f9ed Binary files /dev/null and b/src/components/Homepage/Footer/images/twitter-icon.png differ diff --git a/src/components/Homepage/Footer/index.js b/src/components/Homepage/Footer/index.js new file mode 100644 index 000000000000..b0918ab32bfb --- /dev/null +++ b/src/components/Homepage/Footer/index.js @@ -0,0 +1,37 @@ +import React from 'react'; +import slackIcon from './images/slack-icon.png'; +import nlIcon from './images/mail-icon.png'; +import twitterIcon from './images/twitter-icon.png'; +import mediumIcon from './images/medium-icon.png'; +import './style.css'; + +const Footer = () => ( + +); + +export default Footer; diff --git a/src/components/Homepage/Footer/style.css b/src/components/Homepage/Footer/style.css new file mode 100644 index 000000000000..4be1a1375947 --- /dev/null +++ b/src/components/Homepage/Footer/style.css @@ -0,0 +1,35 @@ +#footer { + margin: 25px 0 +} + +#footer .logos { + margin: auto; + clear: both; +} + +#footer .logos img { + margin: 0 10px; + height: 50px; +} + +#footer #copyright p { + text-align: center; + font-size: 14px; + margin: 15px 0 10px; +} + +@media only screen and (max-width: 998px) { + #footer { + margin: 25px 0 + } + + #footer .logos img { + margin: 0 8px; + height: 40px; + } + + #footer #copyright p { + font-size: 13px; + margin: 13px 0 9px; + } +} diff --git a/src/components/Homepage/Header/index.js b/src/components/Homepage/Header/index.js new file mode 100644 index 000000000000..81cf1a96a7a2 --- /dev/null +++ b/src/components/Homepage/Header/index.js @@ -0,0 +1,54 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import './style.css'; + +import storybookLogo from '../../../design/homepage/storybook-logo.png'; + +const sections = [ + { id: 'home', caption: 'Home', href: '/' }, + { id: 'docs', caption: 'Docs', href: '/docs' }, +]; + +class Header extends React.Component { + renderSections() { + return sections.map(section => { + const { currentSection } = this.props; + const className = currentSection === section.id ? 'selected' : ''; + + return ( + + {section.caption} + + ); + }); + } + + render() { + const { currentSection } = this.props; + let titleClassname = 'pull-left'; + if (currentSection === 'home') { + titleClassname += ' hide'; + } + + return ( + + ); + } +} + +Header.propTypes = { + currentSection: PropTypes.string, +}; + +export default Header; diff --git a/src/components/Homepage/Header/style.css b/src/components/Homepage/Header/style.css new file mode 100644 index 000000000000..2c0a06d4a039 --- /dev/null +++ b/src/components/Homepage/Header/style.css @@ -0,0 +1,51 @@ +#header { + margin: 50px 0; +} + +#header .col-xs-12 { + padding: 0; +} + +#header-title img { + width: 125px; +} + +#header-links a { + padding-left: 15px; + text-transform: uppercase; +} + +#header-links a.selected { + font-weight: 600; +} + + +@media only screen and (max-width: 540px) { + #header { + margin: 50px 0 20px; + } + + #header .pull-right { + float: none !important; + clear: both !important; + text-align: center; + } + + #header .pull-left { + float: none !important; + clear: both !important; + } + + #header-title { + text-align: center; + margin-bottom: 12px; + } + + #header-title img { + width: 40%; + } + + #header a { + font-size: 11px !important; + } +} diff --git a/src/components/Homepage/Heading/index.js b/src/components/Homepage/Heading/index.js new file mode 100644 index 000000000000..9681cdab2210 --- /dev/null +++ b/src/components/Homepage/Heading/index.js @@ -0,0 +1,23 @@ +import React from 'react'; +import './style.css'; + +import storybookLogo from '../../../design/homepage/storybook-logo.png'; + +const Heading = () => ( +
    +
    + Storybook Logo +

    + UI Development Environment +
    + You'll + {' '} + + {' '} + to use +

    +
    +
    +); + +export default Heading; diff --git a/src/components/Homepage/Heading/style.css b/src/components/Homepage/Heading/style.css new file mode 100644 index 000000000000..6e9214ed9393 --- /dev/null +++ b/src/components/Homepage/Heading/style.css @@ -0,0 +1,64 @@ +#heading { + text-align: center; + padding: 85px 0 50px; +} + +.sb-title { + font-weight: 800; + width: 500px; +} + +.sb-tagline { + font-weight: 100; + font-size: 29px; + line-height: 42px; +} + +.heart { + color: red; +} + +@media only screen and (max-width: 980px) { + #heading { + padding: 45px 0 25px; + } + + .sb-title { + width: 400px; + } + + .sb-tagline { + font-size: 24px; + line-height: 33px; + } +} + +@media only screen and (max-width: 870px) { + #heading { + padding: 35px 0 15px; + } + + .sb-title { + width: 400px; + } + + .sb-tagline { + font-size: 20px; + line-height: 29px; + } +} + +@media only screen and (max-width: 540px) { + #heading { + padding: 15px 0 15px; + } + + .sb-title { + width: 80%; + } + + .sb-tagline { + ffont-size: 16px; + line-height: 26px; + } +} diff --git a/src/components/Homepage/MainLinks/images/docs.png b/src/components/Homepage/MainLinks/images/docs.png new file mode 100644 index 000000000000..75d10aa103b4 Binary files /dev/null and b/src/components/Homepage/MainLinks/images/docs.png differ diff --git a/src/components/Homepage/MainLinks/index.js b/src/components/Homepage/MainLinks/index.js new file mode 100644 index 000000000000..06e2e43c847d --- /dev/null +++ b/src/components/Homepage/MainLinks/index.js @@ -0,0 +1,35 @@ +import React from 'react'; +import './style.css'; + +const MainLinks = () => ( + +); + +export default MainLinks; diff --git a/src/components/Homepage/MainLinks/style.css b/src/components/Homepage/MainLinks/style.css new file mode 100644 index 000000000000..87cbe9c5f9d9 --- /dev/null +++ b/src/components/Homepage/MainLinks/style.css @@ -0,0 +1,96 @@ +#main-links { + margin: 40px auto 30px; +} + +#main-links hr { + margin: 50px auto 0; +} + +.main-links-container { + max-width: 600px; + margin: auto; +} + +#main-links h2 { + text-transform: uppercase; + text-align: center; + font-size: 25px; + font-weight: 600; +} + +#main-links pre { + background-color: #444; + color: #DDD; + padding: 15px 30px; + line-height: 22px; + border: 0; + border-radius: 0; +} + +.docs-img { + background: url('./images/docs.png'); + background-repeat: no-repeat; + width: 40px; + height: 54px; + margin-top: 25px; +} + +@media only screen and (max-width: 700px) { + #main-links .col-xs-6 { + margin: auto !important; + float: none !important; + clear: both; + width: 55% !important; + } + + #main-links { + margin: 30px auto 20px; + } + + #main-links h2 { + font-size: 22px; + } + + #main-links pre { + padding: 13px 28px; + font-size: 12px; + } +} + +@media only screen and (max-width: 600px) { + #main-links .col-xs-6 { + width: 65% !important; + } + + #main-links { + margin: 24px auto 16px; + } + + #main-links hr { + margin: 35px auto 0; + } + + #main-links h2 { + font-size: 20px; + } + + #main-links pre { + padding: 13px 20px; + font-size: 11px; + } +} + +@media only screen and (max-width: 480px) { + #main-links .col-xs-6 { + width: 75% !important; + } + + #main-links { + margin: 20px auto 13px; + } + + #main-links pre { + padding: 13px 20px; + line-height: 18px; + } +} diff --git a/src/components/Homepage/Platforms/index.js b/src/components/Homepage/Platforms/index.js new file mode 100644 index 000000000000..4ee3e69dcbd3 --- /dev/null +++ b/src/components/Homepage/Platforms/index.js @@ -0,0 +1,23 @@ +import React from 'react'; +import './style.css'; + +const Platform = () => ( +
    +
    +

    Built for

    +

    + React + {' '} + & + {' '} + + React Native + +

    + +
    +
    +
    +); + +export default Platform; diff --git a/src/components/Homepage/Platforms/style.css b/src/components/Homepage/Platforms/style.css new file mode 100644 index 000000000000..607d6472bd95 --- /dev/null +++ b/src/components/Homepage/Platforms/style.css @@ -0,0 +1,36 @@ +#platform { + text-align: center; + margin: 10px 0 30px; +} + +#platform hr { + margin: 45px auto 0; +} + +.built-for { + font-size: 20px; + font-weight: 100; +} + +.platforms, .platforms a { + font-size: 20px; + font-weight: 600; +} + +@media only screen and (max-width: 540px) { + #platform { + margin: 10px 0 20px; + } + + #platform hr { + margin: 30px auto 0; + } + + .built-for { + font-size: 19px; + } + + .platforms, .platforms a { + font-size: 19px; + } +} \ No newline at end of file diff --git a/src/components/Homepage/index.js b/src/components/Homepage/index.js new file mode 100644 index 000000000000..59cfc786e2cb --- /dev/null +++ b/src/components/Homepage/index.js @@ -0,0 +1,59 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Helmet from 'react-helmet'; +import './style.css'; +import Header from './Header'; +import Heading from './Heading'; +import Demo from './Demo'; +import Platforms from './Platforms'; +import MainLinks from './MainLinks'; +import Featured from './Featured'; +import Footer from './Footer'; + +const featuredStorybooks = [ + { + owner: 'https://avatars0.githubusercontent.com/u/698437?v=3&s=200', + storybook: { + name: 'React Dates', + link: 'http://airbnb.io/react-dates/', + }, + source: 'https://github.com/airbnb/react-dates', + }, + + { + owner: 'https://avatars3.githubusercontent.com/u/239676?v=3&s=460', + storybook: { + name: 'React Native Web', + link: 'https://necolas.github.io/react-native-web/storybook', + }, + source: 'https://github.com/necolas/react-native-web', + }, + + { + owner: 'https://avatars1.githubusercontent.com/u/15616844?v=3&s=200', + storybook: { + name: 'React Button', + link: 'http://kadira-samples.github.io/react-button/', + }, + source: 'https://github.com/kadira-samples/react-button', + }, +]; + +const Homepage = () => ( +
    + +
    + + + + + +
    +
    +); + +Homepage.propTypes = { + featuredStorybooks: PropTypes.array, +}; + +export default Homepage; diff --git a/src/components/Homepage/style.css b/src/components/Homepage/style.css new file mode 100644 index 000000000000..b627684abc36 --- /dev/null +++ b/src/components/Homepage/style.css @@ -0,0 +1,3 @@ +hr { + max-width: 540px; +} \ No newline at end of file diff --git a/src/containers/Docs.js b/src/containers/Docs.js new file mode 100644 index 000000000000..abed4004ed3e --- /dev/null +++ b/src/containers/Docs.js @@ -0,0 +1,42 @@ +import React from 'react'; +import Docs from '../components/Docs'; +import { + getCategories, + getNavigationData, + getItem, + getFirstItem, + getFirstItemOfSection, +} from '../docs'; + +class DocsContainer extends React.Component { + render() { + const { catId, sectionId, itemId } = this.props.params; + + let selectedItem; + const selectedCatId = catId || getCategories()[0].id; + + if (!sectionId) { + selectedItem = getFirstItem(selectedCatId); + } else if (!itemId) { + selectedItem = getFirstItemOfSection(selectedCatId, sectionId); + } else { + selectedItem = getItem(selectedCatId, sectionId, itemId); + } + + const selectedSectionId = sectionId || 'basics'; + const selectedItemId = selectedItem.id; + + const props = { + categories: getCategories(), + selectedCatId, + sections: getNavigationData(selectedCatId), + selectedItem, + selectedSectionId, + selectedItemId, + }; + + return ; + } +} + +export default DocsContainer; diff --git a/src/design/docs/docs-container.png b/src/design/docs/docs-container.png new file mode 100644 index 000000000000..613f086adfb5 Binary files /dev/null and b/src/design/docs/docs-container.png differ diff --git a/src/design/docs/docs-content.png b/src/design/docs/docs-content.png new file mode 100644 index 000000000000..8efcb47502b6 Binary files /dev/null and b/src/design/docs/docs-content.png differ diff --git a/src/design/docs/docs-nav.png b/src/design/docs/docs-nav.png new file mode 100644 index 000000000000..1fc11bfdaa26 Binary files /dev/null and b/src/design/docs/docs-nav.png differ diff --git a/src/design/docs/docs.png b/src/design/docs/docs.png new file mode 100644 index 000000000000..cb1c21cf7fbd Binary files /dev/null and b/src/design/docs/docs.png differ diff --git a/src/design/homepage/built-for.png b/src/design/homepage/built-for.png new file mode 100644 index 000000000000..3c6e76ea4188 Binary files /dev/null and b/src/design/homepage/built-for.png differ diff --git a/src/design/homepage/demo.png b/src/design/homepage/demo.png new file mode 100644 index 000000000000..7628d1f36b84 Binary files /dev/null and b/src/design/homepage/demo.png differ diff --git a/src/design/homepage/featured-storybooks.png b/src/design/homepage/featured-storybooks.png new file mode 100644 index 000000000000..29471da98635 Binary files /dev/null and b/src/design/homepage/featured-storybooks.png differ diff --git a/src/design/homepage/footer.png b/src/design/homepage/footer.png new file mode 100644 index 000000000000..bc3898a5b1d2 Binary files /dev/null and b/src/design/homepage/footer.png differ diff --git a/src/design/homepage/header.png b/src/design/homepage/header.png new file mode 100644 index 000000000000..65641894a4dd Binary files /dev/null and b/src/design/homepage/header.png differ diff --git a/src/design/homepage/heading.png b/src/design/homepage/heading.png new file mode 100644 index 000000000000..a2d8445c9e46 Binary files /dev/null and b/src/design/homepage/heading.png differ diff --git a/src/design/homepage/homepage.png b/src/design/homepage/homepage.png new file mode 100644 index 000000000000..2445cd98535f Binary files /dev/null and b/src/design/homepage/homepage.png differ diff --git a/src/design/homepage/main-links.png b/src/design/homepage/main-links.png new file mode 100644 index 000000000000..cc24bd4aa56d Binary files /dev/null and b/src/design/homepage/main-links.png differ diff --git a/src/design/homepage/screenshot.png b/src/design/homepage/screenshot.png new file mode 100644 index 000000000000..c00683c0a595 Binary files /dev/null and b/src/design/homepage/screenshot.png differ diff --git a/src/design/homepage/storybook-icon-sqaure.png b/src/design/homepage/storybook-icon-sqaure.png new file mode 100644 index 000000000000..e0de86e85f36 Binary files /dev/null and b/src/design/homepage/storybook-icon-sqaure.png differ diff --git a/src/design/homepage/storybook-icon.png b/src/design/homepage/storybook-icon.png new file mode 100644 index 000000000000..e23993523f1d Binary files /dev/null and b/src/design/homepage/storybook-icon.png differ diff --git a/src/design/homepage/storybook-logo.png b/src/design/homepage/storybook-logo.png new file mode 100644 index 000000000000..1a914fe76424 Binary files /dev/null and b/src/design/homepage/storybook-logo.png differ diff --git a/src/docs/index.js b/src/docs/index.js new file mode 100644 index 000000000000..89c695c3ceb6 --- /dev/null +++ b/src/docs/index.js @@ -0,0 +1,62 @@ +const data = { + 'react-storybook': { + title: 'React Storybook', + sections: require('./react-storybook').default, + }, +}; + +export function getCategories() { + const catIds = Object.keys(data); + const categories = []; + + catIds.forEach(catId => { + categories.push({ + id: catId, + title: data[catId].title, + }); + }); + + return categories; +} + +export function getNavigationData(catId) { + if (!catId) { + catId = getCategories[0].id; + } + + return data[catId].sections; +} + +export function getItem(catId, sectionId, itemId) { + if (!catId) { + catId = getCategories[0].id; + } + + const section = data[catId].sections.find(section => section.id === sectionId); + if (!section) return null; + + const item = section.items.find(item => item.id === itemId); + return item; +} + +export function getFirstItemOfSection(catId, sectionId) { + if (!catId) { + catId = getCategories[0].id; + } + + const section = data[catId].sections.find(section => section.id === sectionId); + if (!section) return null; + + return section.items[0]; +} + +export function getFirstItem(catId) { + if (!catId) { + catId = getCategories[0].id; + } + + const sections = data[catId].sections || []; + if (sections.length === 0) return null; + + return sections[0].items[0]; +} diff --git a/src/docs/react-storybook/addons/addon-gallery.js b/src/docs/react-storybook/addons/addon-gallery.js new file mode 100644 index 000000000000..2683c3ceabec --- /dev/null +++ b/src/docs/react-storybook/addons/addon-gallery.js @@ -0,0 +1,79 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'addon-gallery', + title: 'Addon Gallery', + content: stripIndent` + This is a list of available addons for Storybook. + + ## Built-In Addons. + + These addons ship with Storybook by default. You can use them right away. + + ### [Actions](https://github.com/storybooks/storybook/tree/master/packages/addon-actions) + + With actions, you can inspect events related to your components. This is pretty neat when you are manually testing your components. + + Also, you can think of this as a way to document events in your components. + + ### [Links](https://github.com/storybooks/storybook/tree/master/packages/addon-links) + + With links you can link stories together. With that, you can build demos and prototypes directly from your UI components. (Like you can do with [InVision](https://www.invisionapp.com/) and [Framer.js](https://framerjs.com/)) + + ## Third Party Addons + + You need to install these addons directly from NPM in order to use them. + + ### [Host](https://github.com/philcockfield/storybook-host) + + A [decorator](/docs/react-storybook/addons/introduction) with + powerful display options for hosting, sizing and framing your components. + + ### [Specs](https://github.com/mthuret/storybook-addon-specifications) + + This is a very special addon where it'll allow you to write test specs directly inside your stories. + You can even run these tests inside a CI box. + + ### [Knobs](https://github.com/storybooks/storybook/tree/master/packages/addon-knobs) + + Knobs allow you to edit React props dynamically using the Storybook UI. + You can also use Knobs as dynamic variables inside your stories. + + ### [Notes](https://github.com/storybooks/storybook/tree/master/packages/addon-notes) + + With this addon, you can write notes for each story in your component. This is pretty useful when you are working with a team. + + ### [Info](https://github.com/storybooks/storybook/tree/master/packages/addon-info) + + If you are using Storybook as a style guide, then this addon will help you to build a nice-looking style guide with docs, automatic sample source code with a PropType explorer. + + ### [Options](https://github.com/storybooks/storybook/tree/master/packages/addon-options) + + The Storybook webapp UI can be customised with this addon. It can be used to change the header, show/hide various UI elements and to enable full-screen mode by default. + + ### [Chapters](https://github.com/yangshun/react-storybook-addon-chapters) + + With this addon, you can showcase multiple components (or varying component states) within a story by breaking it down into smaller categories (chapters) and subcategories (sections) for more organizational goodness. + + ### [Backgrounds](https://github.com/NewSpring/react-storybook-addon-backgrounds) + + With this addon, you can switch between background colors and background images for your preview components. It is really helpful for styleguides. + + ### [Material-UI](https://github.com/sm-react/storybook-addon-material-ui) + + Wraps your story into MuiThemeProvider. It allows you to add your custom themes, switch between them, make changes in the visual editor and download as JSON file + + ### [README](https://github.com/tuchk4/storybook-readme) + + With this addon, you can add docs in markdown format for each story. It very useful because most projects and components already have README.md files. Now it is easy to add them into your Storybook. + + ### [i18n tools](https://github.com/joscha/storybook-addon-i18n-tools) + + With this addon, you can test your storybooks with a different text-direction. It is very useful if you are working on components that have to work both in LTR as well as in RTL languages. + + ### [Props Combinations](https://github.com/evgenykochetkov/react-storybook-addon-props-combinations) + + Given possible values for each prop, renders your component with all combinations of prop values. Useful for finding edge cases or just seeing all component states at once. + + `, +}; diff --git a/src/docs/react-storybook/addons/api.js b/src/docs/react-storybook/addons/api.js new file mode 100644 index 000000000000..42efb2a492f9 --- /dev/null +++ b/src/docs/react-storybook/addons/api.js @@ -0,0 +1,137 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'api', + title: 'API', + content: stripIndent` + ## Core Addon API + + This is the core addon API. This is how to get the addon API: + + ~~~js + import addonAPI from '@kadira/storybook-addons'; + ~~~ + + Have a look at the API methods for more details: + + ### addonAPI.getChannel() + + Get an instance to the channel where you can communicate with the manager and the preview. You can find this in both the addon register code and in your addon’s wrapper component (where used inside a story). + + It has a NodeJS [EventEmitter](https://nodejs.org/api/events.html) compatible API. So, you can use it to emit events and listen for events. + + ### addonAPI.register() + + This method allows you to register an addon and get the storybook API. You can do this only in the Manager App. + See how we can use this: + + ~~~js + // Register the addon with a unique name. + addonAPI.register('kadira/notes', (storybookAPI) => { + + }); + ~~~ + + Now you'll get an instance to our StorybookAPI. See the [api docs](/docs/react-storybook/addons/api#storybook-api) for Storybook API regarding using that. + + ### addonAPI.addPanel() + + This method allows you to add a panel to Storybook. (Storybook's Action Logger is a panel). You can do this only in the Manager App. + See how you can use this method: + + ~~~js + const MyPanel = () => ( +
    + This is a panel. +
    + ); + + // give a unique name for the panel + addonAPI.addPanel('kadira/notes/panel', { + title: 'Notes', + render: () => ( + + ), + }); + ~~~ + + As you can see, you can set any React Component as the panel. Currently, it's just a text. But you can do anything you want. + + You also pass the channel and the Storybook API into that. See: + + ~~~js + addonAPI.register('kadira/notes', (storybookAPI) => { + // Also need to set a unique name to the panel. + addonAPI.addPanel('kadira/notes/panel', { + title: 'Notes', + render: () => ( + + ), + }) + }) + ~~~ + + ## Storybook API + + Storybook API allows you to access different functionalities of Storybook UI. You can move an instance to the Storybook API when you register an addon. + + Let's have a look at API methods. + + ### storybookAPI.selectStory() + + With this method, you can select a story via an API. This method accepts two parameters. + + 1. story kind name + 2. story name (optional) + + Let's say you've got a story like this: + + ~~~js + storiesOf('Button', module) + .add('with text', () => ( + + )); + ~~~ + + This is how you can select the above story: + + ~~~js + storybookAPI.selectStory('Button', 'with text'); + ~~~ + + ### storybookAPI.setQueryParams() + + This method allows you to set query string parameters. You can use that as temporary storage for addons. Here's how you set query params. + + ~~~js + storybookAPI.setQueryParams({ + abc: 'this is abc', + bbc: 'this is bbc', + }); + ~~~ + + > If you need to remove a query param, use \`null\` for that. For an example, let's say we need to remove bbc query param. This is how we do it: + + ~~~js + storybookAPI.setQueryParams({ + bbc: null, + }); + ~~~ + + ### storybookAPI.getQueryParam() + + This method allows you to get a query param set by above API \`setQueryParams\`. For example, let's say we need to get the bbc query param. Then this how we do it: + + ~~~js + storybookAPI.getQueryParam('bbc'); + ~~~ + + ### storybookAPI.onStory(fn) + + This method allows you to register a handler function which will be called whenever the user navigates between stories. + + ~~~js + storybookAPI.onStory((kind, story) => console.log(kind, story)); + ~~~ + `, +}; diff --git a/src/docs/react-storybook/addons/introduction.js b/src/docs/react-storybook/addons/introduction.js new file mode 100644 index 000000000000..2337017f4955 --- /dev/null +++ b/src/docs/react-storybook/addons/introduction.js @@ -0,0 +1,111 @@ +import { stripIndent } from 'common-tags'; + +const images = { + addonActionsDemo: require('./static/addon-actions-demo.gif'), +}; + +export default { + id: 'introduction', + title: 'Intro to Addons', + content: stripIndent` + By default, Storybook comes with a way to list stories and visualize them. Addons implement extra features for Storybooks to make them more useful. + + Basically, there are two types of addons. (Decorators and Native Addons) + + ## 1. Decorators + + These are wrapper components or Storybook decorators that wrap a story. + + ### Wrapper Components + + For example, let's say we want to center a story rendered on the screen. For that, we can use a wrapper component like this: + + ~~~js + const Center = ({ children }) => ( +
    + { children } +
    + ); + ~~~ + + Then we can use it when writing stories. + + ~~~js + storiesOf('Button', module) + .add('with text', () => ( +
    + +
    + )); + ~~~ + + ### Storybook Decorators + + You can also expose this functionality as a Storybook decorator and use it like this. + + ~~~js + const CenterDecorator = (story) => ( +
    + {story()} +
    + ); + + storiesOf('Button', module) + .addDecorator(CenterDecorator) + .add('with text', () => ( + + )) + .add('with some emojies', () => ( + + )); + ~~~ + + You can also add a decorator globally for all stories like this: + + ~~~js + import { + storiesOf, action, addDecorator + } from '@kadira/storybook'; + + const CenterDecorator = (story) => ( +
    + {story()} +
    + ); + addDecorator(CenterDecorator); + + storiesOf('Welcome', module) + .add('to Storybook', () => ( + + )); + + storiesOf('Button', module) + .add('with text', () => ( + + )) + .add('with some emojies', () => ( + + )); + ~~~ + + > You can call \`addDecorator()\` inside the story definition file as shown above. But adding it to the Storybook config file is a much better option. + + ## 2. Native Addons + + Native addons use Storybook as a platform and interact with it. Native addons can add extra features beyond wrapping stories. + + For example, [storybook-actions](https://github.com/storybooks/storybook/tree/master/packages/addon-actions) is such an addon. + + ![Demo of Storybook Addon Actions](${images.addonActionsDemo}) + + > This addon ships with Storybook by default. [Check here](https://github.com/storybooks/storybook/tree/master/packages/addon-actions) for more info. + + It will allow you to inspect the parameters of any event of your components. + + See the following links to learn more about native addons: + + * [Using addons](/docs/react-storybook/addons/using-addons) + * [Addon gallery](/docs/react-storybook/addons/addon-gallery) + * [Write your own addon](/docs/react-storybook/addons/writing-addons) + `, +}; diff --git a/src/docs/react-storybook/addons/static/addon-actions-demo.gif b/src/docs/react-storybook/addons/static/addon-actions-demo.gif new file mode 100644 index 000000000000..27138de47c53 Binary files /dev/null and b/src/docs/react-storybook/addons/static/addon-actions-demo.gif differ diff --git a/src/docs/react-storybook/addons/static/default-addons.png b/src/docs/react-storybook/addons/static/default-addons.png new file mode 100644 index 000000000000..5dd38387bd17 Binary files /dev/null and b/src/docs/react-storybook/addons/static/default-addons.png differ diff --git a/src/docs/react-storybook/addons/static/stories-with-notes.png b/src/docs/react-storybook/addons/static/stories-with-notes.png new file mode 100644 index 000000000000..1a1d1226df93 Binary files /dev/null and b/src/docs/react-storybook/addons/static/stories-with-notes.png differ diff --git a/src/docs/react-storybook/addons/static/stories-without-notes.png b/src/docs/react-storybook/addons/static/stories-without-notes.png new file mode 100644 index 000000000000..d57234d5458b Binary files /dev/null and b/src/docs/react-storybook/addons/static/stories-without-notes.png differ diff --git a/src/docs/react-storybook/addons/static/storybook-components.png b/src/docs/react-storybook/addons/static/storybook-components.png new file mode 100644 index 000000000000..0cba354cc30c Binary files /dev/null and b/src/docs/react-storybook/addons/static/storybook-components.png differ diff --git a/src/docs/react-storybook/addons/using-addons.js b/src/docs/react-storybook/addons/using-addons.js new file mode 100644 index 000000000000..2842c6d0c40f --- /dev/null +++ b/src/docs/react-storybook/addons/using-addons.js @@ -0,0 +1,71 @@ +import { stripIndent } from 'common-tags'; + +const images = { + storiesWithoutNotes: require('./static/stories-without-notes.png'), + storiesWithNotes: require('./static/stories-with-notes.png'), +}; + +export default { + id: 'using-addons', + title: 'Using Addons', + content: stripIndent` + By default, Storybook comes with two addons, which are [actions](https://github.com/storybooks/storybook/tree/master/packages/addon-actions) and [links](https://github.com/storybooks/storybook/tree/master/packages/addon-links). But you can use any third party addons distributed via NPM. + + Here's how to do it. + + Now we are going to use an addon called [Notes](https://github.com/storybooks/storybook/tree/master/packages/addon-notes). Basically, it allows you to write notes for your stories. + + First, you need to create a file called \`addons.js\` inside the storybook config directory and add the following content: + + ~~~js + import '@kadira/storybook/addons'; + ~~~ + + This will load our default addons. + + Then install the notes addon with: + + ~~~sh + npm i --save '@kadira/storybook-addon-notes'; + ~~~ + + + After that, add it to the addons.js like this: + + ~~~js + import '@kadira/storybook/addons'; + import '@kadira/storybook-addon-notes/register'; + ~~~ + + + Then you'll be able to see those notes when you are viewing the story. + + ![Stories without notes](${images.storiesWithoutNotes}) + + Now when you are writing a story it like this and add some notes: + + ~~~js + import React from 'react'; + import { storiesOf, action, linkTo } from '@kadira/storybook'; + import Button from './Button'; + import { WithNotes } from '@kadira/storybook-addon-notes'; + + storiesOf('Button', module) + .add('with some emoji', () => ( + + + + )); + ~~~ + + Then you'll be able to see those notes when you are viewing the story. + + ![Stories with notes](${images.storiesWithNotes}) + + Just like this, you can install any other addon and use it. Have a look at our [addon gallery](/docs/react-storybook/addons/addon-gallery) to discover more addons. + + > This particular addon has created a panel in Storybook. Some addons may not create a panel and may use some other Storybook platform features. + > + > So, look at the addon’s own documentation on how to use it. + `, +}; diff --git a/src/docs/react-storybook/addons/writing-addons.js b/src/docs/react-storybook/addons/writing-addons.js new file mode 100644 index 000000000000..a16af2533f0e --- /dev/null +++ b/src/docs/react-storybook/addons/writing-addons.js @@ -0,0 +1,189 @@ +import { stripIndent } from 'common-tags'; + +const images = { + storybookComponents: require('./static/storybook-components.png'), + storiesWithoutNotes: require('./static/stories-without-notes.png'), + storiesWithNotes: require('./static/stories-with-notes.png'), +}; + +export default { + id: 'writing-addons', + title: 'Writing Addons', + content: stripIndent` + This is a complete guide on how to create addons for Storybook. + + ## Storybook Basics + + Before we begin, we need to learn a bit about how Storybook works. Basically, Storybook has a **Manager App** and a **Preview Area**. + + Manager App is the client side UI for Storybook. Preview Area is the place where the story is rendered. Usually the Preview Area is an iframe. + + When you select a story from the Manager App, the relevant story is rendered inside the Preview Area. + + ![Storybook Components](${images.storybookComponents}) + + As shown in the above image, there's a communication channel that the Manager App and Preview Area use to communicate with each other. + + ## Capabilities + + With an addon, you can add more functionality to Storybook. Here are a few things you could do: + + * Add a panel to Storybook (like Action Logger). + * Interact with the story and the panel. + * Set and get URL query params. + * Select a story. + * Register keyboard shortcuts (coming soon). + + With this, you can write some pretty cool addons. Look at our [Addon gallery](/docs/react-storybook/addons/addon-gallery) to have a look at some sample addons. + + ## Getting Started + + Let's write a simple addon for Storybook. It's a Notes addon on which you can display some notes for a story. + + + > Just for the simplicity, we'll write the addon right inside our app. But we can easily move it into a separate NPM module. + + ## How it looks + + We write a story for our addon like this: + + ~~~js + import React from 'react'; + import { storiesOf, action } from '@kadira/storybook'; + import Button from './Button'; + import { WithNotes } from '../notes-addon'; + + storiesOf('Button', module) + .add('with text', () => ( + + + + )) + .add('with some emoji', () => ( + + + + )); + ~~~ + + Then it will appear in the Notes panel like this: + + ![Without notes](${images.storiesWithNotes}) + + ## Setup + + First, create an \`addons.js\` inside the Storybook config directory and add the following content to it. + + ~~~js + // Storybook's default addons + import '@kadira/storybook/addons'; + ~~~ + + We'll use this file shortly to register the Notes addon we are building. + + Now we need to create two files, \`register.js\` and \`index.js,\` inside a directory called \`src/notes-addon\`. + + ## The Addon + + Let's add the following content to the \`index.js\`. It will expose a class called \`WithNotes\`, which wraps our story. + + ~~~js + import React from 'react'; + import addons from '@kadira/storybook-addons'; + + export class WithNotes extends React.Component { + render() { + const { children, notes } = this.props; + const channel = addons.getChannel(); + + // send the notes to the channel. + channel.emit('kadira/notes/add_notes', notes); + // return children elements. + return children; + } + } + ~~~ + + In this case, our component can access something called the channel. It lets us communicate with the panel (where we display notes). It has a NodeJS [EventEmitter](https://nodejs.org/api/events.html) compatible API. + + In the above case, it will emit the notes text to the channel, so our panel can listen to it. + + Then add the following code to the register.js. + + See: https://gist.github.com/arunoda/fb3859840ff616cc5ea0fa3ef8e3f358 + + It will register our addon and add a panel. In this case, the panel represents a React component called \`Notes\`. That component has access to the channel and storybook api. + + Then it will listen to the channel and render the notes text on the panel. Have a look at the above annotated code. + + > In this example, we are only sending messages from the Preview Area to the Manager App (our panel). But we can do it the other way around as well. + + It also listens to another event, called onStory, in the storybook API, which fires when the user selects a story. We use that event to clear the previous notes when selecting a story. + + ### Register the addon + + Now, finally, we need to register the addon by importing it to the \`.storybook/addons.js\` file. + + ~~~js + // Storybook's default addons + import '@kadira/storybook/addons'; + + // Our addon + import '../src/notes-addon/register'; + ~~~ + + > Above code runs in the Manager App but not in the preview area. + + + That's it. Now you can create notes for any story as shown below: + + ~~~js + import React from 'react'; + import { storiesOf, action } from '@kadira/storybook'; + import Button from './Button'; + import { WithNotes } from '../notes-addon'; + + storiesOf('Button', module) + .add('with text', () => ( + + + + )) + .add('with some emojies', () => ( + + + + )); + ~~~ + + ## Addon API + + Here we've only used a few functionalities of our [Addon API](/docs/react-storybook/addons/api). + You can learn more about the complete API [here](/docs/react-storybook/addons/api). + + ## Packaging + + You can package this addon into a NPM module very easily. Have a look at this [repo](https://github.com/storybooks/storybook/tree/master/packages/addon-notes/tree/version1). + + In addition to moving the above code to an NPM module, we've set \`react\` and \`@kadira/storybook-addons\` as peer dependencies. + + ### Local Development + + When you are developing your addon as a package, you can't use \`npm link\` to add it your project. Instead add your package as a local dependency into your \`package.json\` as shown below: + + ~~~json + { + ... + "dependencies": { + "@kadira/storybook-addon-notes": "file:///home/username/myrepo" + } + ... + } + ~~~ + + ### Package Maintenance + + Your packaged Storybook addon needed to be written in ES5. If you are using ES6, then you need to transpile it. + In that case, we recommend to use [React CDK](https://github.com/kadirahq/react-cdk) for that. + `, +}; diff --git a/src/docs/react-storybook/basics/exporting-storybook.js b/src/docs/react-storybook/basics/exporting-storybook.js new file mode 100644 index 000000000000..9d33fa255ab4 --- /dev/null +++ b/src/docs/react-storybook/basics/exporting-storybook.js @@ -0,0 +1,44 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'exporting-storybook', + title: 'Exporting Storybook as a Static App', + content: stripIndent` + Storybook gives a great developer experience with its dev time features, like instance change updates via Webpack's HMR. + + But Storybook is also a tool you can use to showcase your components to others. + Demos of [React Native Web](http://necolas.github.io/react-native-web/storybook/) and [React Dates](http://airbnb.io/react-dates/) are a good example for that. + + For that, Storybook comes with a tool to export your storybook into a static web app. Then you can deploy it to GitHub pages or any static hosting service. + + Simply add the following NPM script: + + ~~~sh + { + ... + "scripts": { + "storybook": "build-storybook -c .storybook -o .out" + } + ... + } + ~~~ + + Then run \`npm run storybook\`. + + This will build the storybook configured in the Storybook directory into a static webpack and place it inside the \`.out\` directory. + Now you can deploy the content in the \`.out\` directory wherever you want. + + To test it locally, simply run the following commands: + + ~~~sh + cd .out + python -m SimpleHTTPServer + ~~~ + + ## Deploying to GitHub Pages + + Additionally, you can deploy Storybook directly into GitHub pages with our [storybook-deployer](https://github.com/storybooks/storybook-deployer) tool. + + Or, you can simply export your storybook into the docs directory and use it as the root for GitHub pages. Have a look at [this guide](https://github.com/blog/2233-publish-your-project-documentation-with-github-pages) for more information. + `, +}; diff --git a/src/docs/react-storybook/basics/faq.js b/src/docs/react-storybook/basics/faq.js new file mode 100644 index 000000000000..3bcaafeef474 --- /dev/null +++ b/src/docs/react-storybook/basics/faq.js @@ -0,0 +1,17 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'faq', + title: 'Frequently Asked Questions', + content: stripIndent` + Here are some answers to frequently asked questions. If you have a question, you can ask it by opening an issue on the [Storybook Repository](https://github.com/storybooks/storybook/). + + ### How can I run coverage tests with Create React App and leave out stories? + + Create React App does not allow providing options to Jest in your \`package.json\`, however you can run \`jest\` with commandline arguments: + + ~~~ + npm test -- --coverage --collectCoverageFrom='["src/**/*.{js,jsx}","!src/**/stories/*"]' + ~~~ + `, +}; diff --git a/src/docs/react-storybook/basics/introduction.js b/src/docs/react-storybook/basics/introduction.js new file mode 100644 index 000000000000..2e9f5efc7335 --- /dev/null +++ b/src/docs/react-storybook/basics/introduction.js @@ -0,0 +1,25 @@ +import { stripIndent } from 'common-tags'; + +const images = { + screenshot: require('./static/screenshot.png'), +}; + +export default { + id: 'introduction', + title: 'Introduction', + content: stripIndent` + React Storybook is a UI development environment for your React components. With it, you can visualize different states of your UI components and develop them interactively. + + It runs outside of your app. So you can develop UI components in isolation without worrying about app specific dependencies and requirements. + + ![React Storybook](${images.screenshot}) + + React Storybook also comes with a lot of [addons](/docs/react-storybook/addons/introduction) and a great API to customize as you wish. You can also build a [static version](/docs/react-storybook/basics/exporting-storybook) of your storybook and deploy it anywhere you want. + + Here are some featured storybooks that you can reference to see how Storybook works: + + * [React Button](http://kadira-samples.github.io/react-button) - [source](https://github.com/kadira-samples/react-button) + * [Demo of React Dates](http://airbnb.io/react-dates/) - [source](https://github.com/airbnb/react-dates) + * [Demo of React Native Web](http://necolas.github.io/react-native-web/storybook/) - [source](https://github.com/necolas/react-native-web) + `, +}; diff --git a/src/docs/react-storybook/basics/quick-start-guide.js b/src/docs/react-storybook/basics/quick-start-guide.js new file mode 100644 index 000000000000..61e9d6c39a32 --- /dev/null +++ b/src/docs/react-storybook/basics/quick-start-guide.js @@ -0,0 +1,28 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'quick-start-guide', + title: 'Quick Start Guide', + content: stripIndent` + React Storybook is very easy to use. You can use it with any kind of React project. + Follow these steps to get started with Storybook. + + ~~~sh + npm i -g getstorybook + cd my-react-app + getstorybook + ~~~ + + This will configure your app for Storybook. After that, you can run your Storybook with: + + ~~~ + npm run storybook + ~~~ + + Then you can access your storybook from the browser. + + --- + + To learn more about what \`getstorybook\` command does, have a look at our [Slow Start Guide](/docs/react-storybook/basics/slow-start-guide). + `, +}; diff --git a/src/docs/react-storybook/basics/slow-start-guide.js b/src/docs/react-storybook/basics/slow-start-guide.js new file mode 100644 index 000000000000..6ab1a9a642b7 --- /dev/null +++ b/src/docs/react-storybook/basics/slow-start-guide.js @@ -0,0 +1,107 @@ +import { stripIndent } from 'common-tags'; + +const images = { + basicsStories: require('./static/basic-stories.png'), +}; + +export default { + id: 'slow-start-guide', + title: 'Slow Start Guide', + content: stripIndent` + You may have tried to use our quick start guide to setup your project for Storybook. If you want to set up Storybook manually, this is the guide for you. + + > This will also help you to understand how Storybook works. + + ## Basics + + Storybook has its own Webpack setup and a dev server. Webpack setup is very similar to [Create React App](https://github.com/facebookincubator/create-react-app), but allows you to configure as you want. + + In this guide, we are trying to set up Storybook for your React project. + + ## Add @kadira/storybook + + First of all, you need to add \`@kadira/storybook\` to your project. To do that, simply run: + + ~~~sh + npm i --save-dev @kadira/storybook + ~~~ + + ## Add react and react-dom + Make sure that you have \`react\` and \`react-dom\` in your dependencies as well: + + ~~~sh + npm i --save react react-dom + ~~~ + + Then add the following NPM script to your package json in order to start the storybook later in this guide: + + ~~~js + { + ... + "scripts": { + "storybook": "start-storybook -p 9001 -c .storybook" + } + ... + } + ~~~ + + ## Create the config file + + Storybook can be configured in several different ways. That’s why we need a config directory. We've added a \`-c\` option to the above NPM script mentioning \`.storybook\` as the config directory. + + For the basic Storybook configuration file, you don't need to do much, but simply tell Storybook where to find stories. + + To do that, simply create a file at \`.storybook/config.js\` with the following content: + + ~~~js + import { configure } from '@kadira/storybook'; + + function loadStories() { + require('../stories/index.js'); + // You can require as many stories as you need. + } + + configure(loadStories, module); + ~~~ + + That'll load stories in \`../stories/index.js\`. + + Just like that, you can load stories from wherever you want to. + + ## Write your stories + + Now you can write some stories inside the \`../stories/index.js\` file, like this: + + ~~~js + import React from 'react'; + import { storiesOf, action } from '@kadira/storybook'; + + storiesOf('Button', module) + .add('with text', () => ( + + )) + .add('with some emoji', () => ( + + )); + ~~~ + + Story is a single state of your component. In the above case, there are two stories for the native button component: + + 1. with text + 2. with some emoji + + ## Run your Storybook + + Now everything is ready. Simply run your storybook with: + + ~~~js + npm run storybook + ~~~ + + Then you can see all your stories, like this: + + ![](${images.basicsStories}) + + Now you can change components and write stories whenever you need to. You'll get those changes into Storybook in a snap with the help of Webpack's HMR API. + `, +}; diff --git a/src/docs/react-storybook/basics/static/basic-stories.png b/src/docs/react-storybook/basics/static/basic-stories.png new file mode 100644 index 000000000000..5dd38387bd17 Binary files /dev/null and b/src/docs/react-storybook/basics/static/basic-stories.png differ diff --git a/src/docs/react-storybook/basics/static/screenshot.png b/src/docs/react-storybook/basics/static/screenshot.png new file mode 100644 index 000000000000..dd851a9ffaec Binary files /dev/null and b/src/docs/react-storybook/basics/static/screenshot.png differ diff --git a/src/docs/react-storybook/basics/writing-stories.js b/src/docs/react-storybook/basics/writing-stories.js new file mode 100644 index 000000000000..724779f6e172 --- /dev/null +++ b/src/docs/react-storybook/basics/writing-stories.js @@ -0,0 +1,150 @@ +import { stripIndent } from 'common-tags'; + +const images = { + basicsStories: require('./static/basic-stories.png'), +}; + +export default { + id: 'writing-stories', + title: 'Writing Stories', + content: stripIndent` + Storybook is all about writing stories. Usually a story contains a single state of one of your components. That's like a visual test case. + + > Technically, a story is a function that returns a React element. + + You can write a set of stories for your components and you'll get a storybook. + + ## Keeping your stories + + There's no hard and fast rule for this. But, keeping stories close to your components is a good idea. + + For example, let's say your UI components live in a directory called: \`src/components.\` Then you can write stories inside the \`src/stories\` directory. + + This is just one way to do that. You can always edit your storybook config file and ask it to load stories from anywhere you want. + + ## Writing stories + + This is how you write stories: + (Let's assume there's a component called "Button" in \`src/components/Button.js\`.) + + ~~~js + // file: src/stories/index.js + + import React from 'react'; + import { storiesOf, action } from '@kadira/storybook'; + import Button from '../components/Button'; + + storiesOf('Button', module) + .add('with text', () => ( + + )) + .add('with some emoji', () => ( + + )); + ~~~ + + This will show stories in your storybook like this: + + ![](${images.basicsStories}) + + This is just our core API for writing stories. In addition to this, you can use the official and third party Storybook [addons](/docs/react-storybook/addons/introduction) to get more functionality. + + + ## Loading stories dynamically + + Sometimes, you will want to load your stories dynamically rather than explicitly requiring them in the Storybook config file. + + For example, you may write stories for your app inside the \`src/components\` directory with the \`.stories.js\` extension. Then you will want to load them at once. Simply edit your config directory at \`.storybook/config.js\` as follows: + + ~~~js + import { configure } from '@kadira/storybook'; + + const req = require.context('../src/components', true, /\.stories\.js$/) + + function loadStories() { + req.keys().forEach((filename) => req(filename)) + } + + configure(loadStories, module); + ~~~ + + Here we use Webpack's [require.context](https://webpack.github.io/docs/context.html#require-context) to load modules dynamically. Have a look at the relevant Webpack [docs](https://webpack.github.io/docs/context.html#require-context) to learn more about how to use require.context. + + ## Using Decorators + + A decorator is a way to wrap a story with a common set of component(s). Let's say you want to center all your stories. Here is how we can do this with a decorator: + + ~~~js + import React from 'react'; + import { storiesOf } from '@kadira/storybook'; + import MyComponent from '../my_component'; + + storiesOf('MyComponent', module) + .addDecorator((story) => ( +
    + {story()} +
    + )) + .add('without props', () => ()) + .add('with some props', () => ()); + ~~~ + + + Here we only add the decorator for the current set of stories. (In this example, we add it just for the **MyComponent** story group.) + + But, you can also add a decorator **globally** and it'll be applied to all the stories you create. This is how to add a decorator like that: + + ~~~js + import { configure, addDecorator } from '@kadira/storybook'; + addDecorator((story) => ( +
    + {story()} +
    + )); + + configure(function () { + ... + }, module); + ~~~ + + ## Managing stories + + Storybook has a very simple API to write stories. With that, you can’t display nested stories. + _**This is something we've done purposely.**_ + + But you might ask, how do I manage stories If I have many of them? Here's how different developers address this issue. Therefore, there's no need to build a built-in feature for this (at least in the short term). + + ### Prefix with dots + + For example, you can prefix story names with a dot (\`.\`): + + ~~~js + storiesOf('core.Button', module) + ~~~ + + Then you can filter stories to display only the stories you want to see. + + ### Run multiple storybooks + + You can run multiple storybooks for different kinds of stories (or components). To do that, you can create different NPM scripts to start different stories. See: + + ~~~js + { + ... + "scripts": { + "start-storybook-for-theme": "start-storybook -p 9001 -c .storybook-theme" + "start-storybook-for-app": "start-storybook -p 8001 -c .storybook-app" + } + ... + } + ~~~ + + ### Use multiple repos + + This is a popular option. You can create different repos for different kinds of UI components and have a storybook for each of them. Here are some ways to separate them: + + * Have one repo for the theme, and one for the app. + * Have one repo for each UI component and use those in different apps. + * Have a few repos for different kinds of UI components and use them in different apps. + `, +}; diff --git a/src/docs/react-storybook/configurations/add-custom-head-tags.js b/src/docs/react-storybook/configurations/add-custom-head-tags.js new file mode 100644 index 000000000000..5b67298bf390 --- /dev/null +++ b/src/docs/react-storybook/configurations/add-custom-head-tags.js @@ -0,0 +1,22 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'add-custom-head-tags', + title: 'Add Custom Head Tags', + content: stripIndent` + Sometimes, you may need to add different tags to the HTML head. This is useful for adding web fonts or some external scripts. + + You can do this very easily. Simply create a file called \`head.html\` inside the Storybook config directory and add tags like this: + + ~~~html + + + ~~~ + + That's it. Storybook will inject these tags. + + > **Important** + + > Storybook will inject these tags to the iframe where your components are rendered. So, these won’t be loaded into the main Storybook UI. + `, +}; diff --git a/src/docs/react-storybook/configurations/cli-options.js b/src/docs/react-storybook/configurations/cli-options.js new file mode 100644 index 000000000000..b478a8366630 --- /dev/null +++ b/src/docs/react-storybook/configurations/cli-options.js @@ -0,0 +1,43 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'cli-options', + title: 'CLI Options', + content: stripIndent` + React Storybook comes with two CLI utilities. They are \`start-storybook\` and \`build-storybook\`. + + They have some options you can pass to alter the storybook behaviors. We have seen some of them in previous docs. + + Here are all those options: + + ## For start-storybook + + ~~~ + Usage: start-storybook [options] + + Options: + + -h, --help output usage information + -V, --version output the version number + -p, --port [number] Port to run Storybook (Required) + -h, --host [string] Host to run Storybook + -s, --static-dir Directory where to load static files from + -c, --config-dir [dir-name] Directory where to load Storybook configurations from + ~~~ + + ## For build-storybook + + ~~~ + Usage: build-storybook [options] + + Options: + + -h, --help output usage information + -V, --version output the version number + -s, --static-dir Directory where to load static files from + -o, --output-dir [dir-name] Directory where to store built files + -c, --config-dir [dir-name] Directory where to load Storybook configurations from + ~~~ + + `, +}; diff --git a/src/docs/react-storybook/configurations/custom-babel-config.js b/src/docs/react-storybook/configurations/custom-babel-config.js new file mode 100644 index 000000000000..52635081ef80 --- /dev/null +++ b/src/docs/react-storybook/configurations/custom-babel-config.js @@ -0,0 +1,15 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'custom-babel-config', + title: 'Custom Babel Config', + content: stripIndent` + By default, Storybook loads your root \`.babelrc\` file and load those configurations. But sometimes some of those options may cause Storybook to throw errors. + + In that case, you can provide a custom \`.babelrc\` just for Storybook. For that, simply create a file called \`.babelrc\` file inside the Storybook config directory (by default, it's \`.storybook\`). + + Then Storybook will load the Babel configuration only from that file. + + > Currently we do not support loading the Babel config from the package.json. + `, +}; diff --git a/src/docs/react-storybook/configurations/custom-webpack-config.js b/src/docs/react-storybook/configurations/custom-webpack-config.js new file mode 100644 index 000000000000..20a74d51ce9b --- /dev/null +++ b/src/docs/react-storybook/configurations/custom-webpack-config.js @@ -0,0 +1,108 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'custom-webpack-config', + title: 'Custom Webpack Config', + content: stripIndent` + The default Webpack config of React Storybook is usually well balanced for a medium-size React project (specially created with [Create React App](https://github.com/facebookincubator/create-react-app)) or a library. But if you already have your own Webpack setup, that's not useable. + + That's why we allow you to customize our Webpack setup. There are a few ways to do it. Let's discuss: + + ## Simple Mode + + Let's say you want to add [SASS](http://sass-lang.com/) support to Storybook. This is how to do it. + Simply add the following content to a file called \`webpack.config.js\` in your Storybook config directory (\`.storybook\` by default ). + + ~~~js + const path = require('path'); + + module.exports = { + module: { + loaders: [ + { + test: /\.scss$/, + loaders: ["style", "css", "sass"], + include: path.resolve(__dirname, '../') + } + ] + } + } + ~~~ + + Since this config file stays in the Storybook directory, you need to set the include path as above. If the config directory stays in a different directory, you need to set the include path relative to that. + + You also need to install the loaders (style, css, and sass) used in above config manually. + + + > Once you create this \`webpack.config.js\` file, Storybook won't load the [default Webpack config](/docs/react-storybook/configurations/default-config) other than loading JS files with the Babel loader. + + ### Supported Webpack Options + + You can add any kind of Webpack configuration options with the above config, whether they are plugins, loaders, or aliases. + But you won't be able to change the following config options: + + * entry + * output + * js loader with babel + + ## Full Control Mode + + Sometimes, you will want to have full control over the webpack configuration. Maybe you want to add different configurations for dev and production environments. That's where you can use our full control mode. + + To enable that, you need to export a **function** from the above \`webpack.config.js\` file, just like this: + + ~~~js + // Export a function. Accept the base config as the only param. + module.exports = function(storybookBaseConfig, configType) { + // configType has a value of 'DEVELOPMENT' or 'PRODUCTION' + // You can change the configuration based on that. + // 'PRODUCTION' is used when building the static version of storybook. + + // Make whatever fine-grained changes you need + storybookBaseConfig.module.loaders.push({ + test: /\.scss$/, + loaders: ["style", "css", "sass"], + include: path.resolve(__dirname, '../') + }); + + // Return the altered config + return storybookBaseConfig; + }; + ~~~ + + Storybook uses the config returned from the above function. So, try to edit the \`storybookBaseConfig\` with care. Make sure to preserve the following config options: + + * entry + * output + * first loader in the module.loaders (Babel loader for JS) + + Other than that, you should try to keep the default set of plugins. + + ## Extending The Default Config + + You may want to keep Storybook's [default config](/docs/react-storybook/configurations/default-config), but just need to extend it. If so, this is how you do it using the Full Control Mode. + Add following content to the \`webpack.config.js\` in your Storybook config directory. + + ~~~js + // load the default config generator. + var genDefaultConfig = require('@kadira/storybook/dist/server/config/defaults/webpack.config.js'); + + module.exports = function(config, env) { + var config = genDefaultConfig(config, env); + + // Extend it as you need. + + return config; + }; + ~~~ + + ## Using Your Existing Config + + You may have an existing Webpack config for your project. So, you may need to copy and paste some config items into Storybook's custom Webpack config file. + + But you don't need to. There are a few options: + + * Simply import your main Webpack config into Storybook's \`webpack.config.js\` and use the loaders and plugins used in that. + * Create a new file with common Webpack options and use it in both inside the main Webpack config and inside Storybook's \`webpack.config.js\`. + `, +}; diff --git a/src/docs/react-storybook/configurations/default-config.js b/src/docs/react-storybook/configurations/default-config.js new file mode 100644 index 000000000000..ba42dc9c04c3 --- /dev/null +++ b/src/docs/react-storybook/configurations/default-config.js @@ -0,0 +1,72 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'default-config', + title: 'Default Config', + content: stripIndent` + Storybook has a default Webpack setup which is similar to [Create React App](https://github.com/facebookincubator/create-react-app). + Let's learn about the default config comes with Storybook. + + ## Babel + + We use Babel for JavaScript(ES6) transpiling. Here are some key features of Storybook's Babel configurations. + + ### ES2016+ Support + + We have added ES2016 support with Babel for transpiling your JS code. In addition to that, we've added a few experimental features, like object spreading and async await. Check out our [source](https://github.com/storybooks/storybook/blob/master/packages/react-storybook/src/server/config/babel.js#L19) to learn more about these plugins. + + ### .babelrc support + + If your project has a \`.babelrc\` file, we'll use that instead of the default config file. So, you could use any babel plugins or presets that you have used in your project with Storybook. + + ## Webpack + + We use Webpack to serve and load JavaScript modules for the web. We've added some Webpack loaders to bring some good defaults. (This setup is very close to what you get with the [Create React App](https://github.com/facebookincubator/create-react-app).) + + ### CSS Support + + You can simply import CSS files wherever you want, whether it's in the storybook config file, a UI component, or inside a story definition file. + + Basically, you can import CSS like this: + + ~~~js + // locally + import './styles.css' + // or from NPM modules + import 'bootstrap/dist/css/bootstrap.css'; + ~~~ + + ### Image and Static File Support + + You can also import images and media files directly via JavaScript. This helps you to write stories with media files easily. This is how to do it: + + ~~~js + import React from 'react'; + import { storiesOf, action } from '@kadira/storybook'; + + import imageFile from './static/image.png'; + + storiesOf('', module) + .add('with a image', () => ( + + )); + ~~~ + + When you are building a storybook, we'll also export the imported image. So, this is a good approach to loading all of your static content. + + + > Storybook also has a way to mention static directories via the -s option of the \`react-storybook\` and \`build-storybook\` commands. + + ### JSON Loader + + You can import \`.json\` files, as you do with Node.js. This will also allow you to use NPM projects, which imports \`.json\` files inside them. + + ## NPM Modules + + You can use any of the NPM modules installed on your project. You can simply import and use them. + + + > Unfortunately, we don't support Meteor packages. If your UI component includes one or more Meteor packages, try to avoid using them in UI components. + > If they are containers, you can use [React Stubber](https://github.com/kadirahq/react-stubber) to use them in Storybook. + `, +}; diff --git a/src/docs/react-storybook/configurations/env-vars.js b/src/docs/react-storybook/configurations/env-vars.js new file mode 100644 index 000000000000..741982e299d7 --- /dev/null +++ b/src/docs/react-storybook/configurations/env-vars.js @@ -0,0 +1,39 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'env-vars', + title: 'Using Environment Variables', + content: stripIndent` + Sometimes, we may use configuration items inside Storybook. It might be a theme color, some client secret, or a JSON string. So, we usually tend to hard code them. + + But you can expose those configurations via "environment variables." For that, you need to prefix your environment variables with \`STORYBOOK_\` prefix. + + For an example, let's expose two environment variables like this: + + ~~~sh + STORYBOOK_THEME=red STORYBOOK_DATA_KEY=12345 npm run storybook + ~~~ + + Then we can access these environment variables anywhere inside our JS code like below: + + ~~~js + console.log(process.env.STORYBOOK_THEME) + console.log(process.env.STORYBOOK_DATA_KEY) + ~~~ + + > Even though we can access these env variables anywhere in the client side JS code, it's better to use them only inside stories and inside the main Storybook config file. + + ## Build time environment variables + + You can also pass these environment variables when you are [building your storybook](/docs/react-storybook/basics/exporting-storybook) with \`build-storybook\`. + + Then they'll be hard coded to the static version of your Storybook. + + ## NODE_ENV env variable + + In addition to the above prefixed environment variables, you can also pass the NODE_ENV variable to Storybook. But, you normally don't need to do that since we set a reasonable default value for it. + + * When running \`npm run storybook\`, we set NODE_ENV to 'development' + * When building storybook, we set NODE_ENV to 'production' + `, +}; diff --git a/src/docs/react-storybook/configurations/serving-static-files.js b/src/docs/react-storybook/configurations/serving-static-files.js new file mode 100644 index 000000000000..65fb756384b7 --- /dev/null +++ b/src/docs/react-storybook/configurations/serving-static-files.js @@ -0,0 +1,79 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'serving-static-files', + title: 'Serving Static Files', + content: stripIndent` + It's often useful to load static files like images and videos when creating components and stories. + + Storybook provides two ways to do that. + + ## 1. Via Imports + + You can import any media assets by simply importing (or requiring) them as shown below. + + + ~~~js + import React from 'react'; + import { storiesOf, action } from '@kadira/storybook'; + + import imageFile from './static/image.png'; + + storiesOf('', module) + .add('with a image', () => ( + + )); + ~~~ + + + This is enabled with our [default config](/docs/react-storybook/configurations/default-config). But, if you are using a [custom Webpack config](/docs/react-storybook/configurations/custom-webpack-config), you need to add the [file-loader](https://github.com/webpack/file-loader) into your custom Webpack config. + + ## 2. Via a Directory + + You can also configure a directory (or a list of directories) for searching static content when you are starting Storybook. You can do that with the -s flag. + + See the following npm script on how to use it: + + ~~~js + { + "scripts": { + "start-storybook": "start-storybook -s ./public -p 9001" + } + } + ~~~ + + + Here \`./public\` is our static directory. Now you can use static files in the public directory in your components or stories like this. + + ~~~js + import React from 'react'; + import { storiesOf, action } from '@kadira/storybook'; + + // assume image.png is located in the "public" directory. + storiesOf('', module) + .add('with a image', () => ( + + )); + ~~~ + + + > You can also pass a list of directories, instead of a single directory, as shown below. + > ~~~js + > { + > "scripts": { + > "start-storybook": "start-storybook -s ./public,./static -p 9001" + > } + > } + > ~~~ + + ## Absolute versus relative paths + + Sometimes, you may want to deploy your storybook into a subpath, like https://kadira-samples.github.io/react-button. + + In this case, you need to have all your images and media files with relative paths. Otherwise, Storybook cannot locate those files. + + If you load static content via importing, this is automatic and you do not have to do anything. + + If you are using a static directory, then you need to use _relative paths_ to load images. + `, +}; diff --git a/src/docs/react-storybook/index.js b/src/docs/react-storybook/index.js new file mode 100644 index 000000000000..dba7c086415f --- /dev/null +++ b/src/docs/react-storybook/index.js @@ -0,0 +1,49 @@ +export default [ + { + id: 'basics', + heading: 'Basics', + items: [ + require('./basics/introduction').default, + require('./basics/quick-start-guide').default, + require('./basics/slow-start-guide').default, + require('./basics/writing-stories').default, + require('./basics/exporting-storybook').default, + require('./basics/faq').default, + ], + }, + { + id: 'configurations', + heading: 'Configurations', + items: [ + require('./configurations/default-config').default, + require('./configurations/custom-webpack-config').default, + require('./configurations/custom-babel-config').default, + require('./configurations/add-custom-head-tags').default, + require('./configurations/serving-static-files').default, + require('./configurations/env-vars').default, + require('./configurations/cli-options').default, + ], + }, + { + id: 'testing', + heading: 'Testing', + items: [ + require('./testing/react-ui-testing').default, + require('./testing/structural-testing').default, + require('./testing/interaction-testing').default, + require('./testing/css-style-testing').default, + require('./testing/manual-testing').default, + ], + }, + { + id: 'addons', + heading: 'Addons', + items: [ + require('./addons/introduction').default, + require('./addons/using-addons').default, + require('./addons/addon-gallery').default, + require('./addons/writing-addons').default, + require('./addons/api').default, + ], + }, +]; diff --git a/src/docs/react-storybook/testing/css-style-testing.js b/src/docs/react-storybook/testing/css-style-testing.js new file mode 100644 index 000000000000..e230d9dce2a8 --- /dev/null +++ b/src/docs/react-storybook/testing/css-style-testing.js @@ -0,0 +1,36 @@ +import { stripIndent } from 'common-tags'; + +import storybookScreenshot from './static/storybook-screenshot.png'; +import storybookIframeScreenshot from './static/storybook-iframe-screenshot.png'; + +export default { + id: 'css-style-testing', + title: 'CSS/Style Testing', + content: stripIndent` + We can also use Storybook as the base for CSS/Style testing with stories as the base. First, have a look at the following Storybook. + + ![Storybook Screenshot](${storybookScreenshot}) + + In that, you can see the Storybook's manager UI. It has UI elements that are not related to your app. However, there's a way to access just a single story. + + For an example, let's assume the above storybook runs on port 9009 and we can access it via [http://localhost:9009](http://localhost:9009/). + Then Let's pick a single story: the "with text" story of the Button. So, in this case: + + * selectedKind = Button + * selectedStory = with text + + Then, we can see the above story using the following URL: + + http://localhost:9009/iframe.html?selectedKind=Button&selectedStory=with+text&dataId=0 + + ![Storybook Iframe Screenshot](${storybookIframeScreenshot}) + + Just like that, you can access all of the stories in your Storybook. + + ## Supported CSS/Style Testing Frameworks + + It will be hard to use all the frameworks we've [mentioned](/docs/react-storybook/testing/react-ui-testing#3-css-style-testing), but we'll be able to use frameworks which are based on URL as the input source. (Such as [BackstopJS](https://github.com/garris/BackstopJS) and [Gemini](https://github.com/gemini-testing/gemini)) + + > In the future we are also planning to smooth this process with the help of [StoryShots](https://github.com/storybooks/storyshots). + `, +}; diff --git a/src/docs/react-storybook/testing/interaction-testing.js b/src/docs/react-storybook/testing/interaction-testing.js new file mode 100644 index 000000000000..23eb7d5241e0 --- /dev/null +++ b/src/docs/react-storybook/testing/interaction-testing.js @@ -0,0 +1,22 @@ +import { stripIndent } from 'common-tags'; + +import specsAddon from './static/specs-addon.png'; + +export default { + id: 'interaction-testing', + title: 'Interaction Testing', + content: stripIndent` + For the interaction testing, [Enzyme](https://github.com/airbnb/enzyme) is the best tool we can use. With that, we can [simulate](http://airbnb.io/enzyme/docs/api/ReactWrapper/simulate.html) user inputs and see what they are doing. + + You can directly write these kind of tests with a full-featured testing framework, such as **Mocha** or **Jest**. Have a look at the [Enzyme guidelines](https://github.com/airbnb/enzyme/) for more information on how to integrate them. + + ## Specs Addon + + If you like to write your tests directly inside your stories, we also have an addon called [specs](https://github.com/mthuret/storybook-addon-specifications). + + ![Storybook Specs Addon](${specsAddon}) + + With that, you can write test specs directly inside stories. + Additionally, you also can use your CI server to run those tests. + `, +}; diff --git a/src/docs/react-storybook/testing/manual-testing.js b/src/docs/react-storybook/testing/manual-testing.js new file mode 100644 index 000000000000..afd4e023f01e --- /dev/null +++ b/src/docs/react-storybook/testing/manual-testing.js @@ -0,0 +1,24 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'manual-testing', + title: 'Manual Testing', + content: stripIndent` + Now we arrive at the most interesting (but also hardest) part of UI testing. We usually do this as a team. + + First, we need to make a pretty solid Storybook or a set of Storybooks covering most of the scenarios of our UI components. For that we can follow the following structure: + + * Write stories for your individual UI components. + * Write another set of stories for integrating the above UI components (you could consider your pages). + + For the individual UI components, you may be using a different repository. Then, keep a storybook inside it for those components. Then, in the main app, write stories for integrations. + + ## Testing Plan + Open a new PR (or multiple of them). Then run your Storybook and start reviewing one story at a time. Then you can comment on the PR. + + > To get a better result, you can use **Storybook Hub**, where you can comment inside individual stories and share your storybook with non-developers.
    + > We will be releasing it in the first week of October. + > Join our [Newsletter](http://tinyletter.com/storybooks) or [Slack Team](https://storybooks-slackin.herokuapp.com/) to get updates. + + `, +}; diff --git a/src/docs/react-storybook/testing/react-ui-testing.js b/src/docs/react-storybook/testing/react-ui-testing.js new file mode 100644 index 000000000000..36bf6ece4619 --- /dev/null +++ b/src/docs/react-storybook/testing/react-ui-testing.js @@ -0,0 +1,81 @@ +import { stripIndent } from 'common-tags'; + +import loginForm from './static/login_form.png'; + +export default { + id: 'react-ui-testing', + title: 'Introduction: React UI Testing', + content: stripIndent` + There are different aspects we need to look at when testing UI. There are also a lot of tools and techniques we can use.  + + ## Reasons for Testing + + Before we talk about testing, we need to think about why we need to test. There are many reasons; here are some of our reasons: + + * To find bugs. + * To make sure things won't break between new code commits. + * To keep tests as living documentations. + + Specifically, testing is important when working with teams since it allows different people the ability to contribute with confidence. + + ## Different Aspects of UI Testing + + We refer UI for many things. To put this in focus, let's narrow it down to React based user interfaces. + + ### 1. Structural Testing + + Here we'll focus on the structure of the UI and how it's laid out. For an example, let's say we have a "login component" as shown below: + + ![Login Form](${loginForm}) + + For structural testing, we are testing whether or not it has following content: + + * A title with "Login in to Facebook" + * Two inputs for the username and password. + * A submit button. + * An error screen to show errors. + + For React, we have been using [Enzyme](https://github.com/airbnb/enzyme) as a way to do structural testing, but now we can also use [Jest's snapshot testing](https://facebook.github.io/jest/blog/2016/07/27/jest-14.html) to make things even more simple. + + ### 2. Interaction Testing + + UI is all about interacting with the user. We do this with a bunch of UI elements, such as buttons, links, and input elements. With interaction testing, we need to test if they are working properly. + + Let's again use the above login component as an example. It should do these things: + + * When we click the submit button, it should give us the username and password. + * When we click the "Forgotten Account" link, it should redirect to a new page. + + We have few ways to do this type of testing with React. The simple way is to use [Enzyme](https://github.com/airbnb/enzyme). + + ### 3. CSS/Style Testing + + UI is all about styles (whether they're simple, beautiful, or even ugly). With style testing, we are evaluating the look and feel of our UI components between code changes. This is a pretty complex subject and usually we do it by comparing images. + + If we are using inline styles all the way, we can use JEST snapshot testing. But to get even better results, we should consider using tools such as: + + * [BackstopJS](https://github.com/garris/BackstopJS) + * [PhantomCSS](https://github.com/Huddle/PhantomCSS) + * [Gemini](https://github.com/gemini-testing/gemini) + * [Happo](https://github.com/Galooshi/happo) + + ### 4. Manual Testing + + All the above sections are about testing with automated tools. But since we are building UI for humans, we must also manually test them to see how they feel. + + Another reason for manual testing is for the better user experience. + + We should always try to test our UI with the naked eye. For this, we can simply use our existing Storybook.This is something that we can't automate(yet) and takes time. But it would be great if we could do this once in a while (especially with a major code changes). + + ## How Storybook Can Help Us + + A **story** is a smallest unit in Storybook. It's a fully functioning UI element where the input can be used for any of the testing methods we've mentioned above. + + Let's look at how Storybook can help you do the above mentioned different aspects of testing. + + * [Structural Testing with StoryShots](/docs/react-storybook/testing/structural-testing) + * [Interaction Testing with Specs Addon](/docs/react-storybook/testing/interaction-testing) + * [Storybook as the Base for CSS/Style Testing](/docs/react-storybook/testing/css-style-testing) + * [Storybook for Manual UI Testing](/docs/react-storybook/testing/manual-testing) + `, +}; diff --git a/src/docs/react-storybook/testing/static/login_form.png b/src/docs/react-storybook/testing/static/login_form.png new file mode 100644 index 000000000000..07936f1c2c48 Binary files /dev/null and b/src/docs/react-storybook/testing/static/login_form.png differ diff --git a/src/docs/react-storybook/testing/static/specs-addon.png b/src/docs/react-storybook/testing/static/specs-addon.png new file mode 100644 index 000000000000..9d022232324e Binary files /dev/null and b/src/docs/react-storybook/testing/static/specs-addon.png differ diff --git a/src/docs/react-storybook/testing/static/storybook-iframe-screenshot.png b/src/docs/react-storybook/testing/static/storybook-iframe-screenshot.png new file mode 100644 index 000000000000..b9c66ab1d604 Binary files /dev/null and b/src/docs/react-storybook/testing/static/storybook-iframe-screenshot.png differ diff --git a/src/docs/react-storybook/testing/static/storybook-screenshot.png b/src/docs/react-storybook/testing/static/storybook-screenshot.png new file mode 100644 index 000000000000..10e9da337968 Binary files /dev/null and b/src/docs/react-storybook/testing/static/storybook-screenshot.png differ diff --git a/src/docs/react-storybook/testing/static/storyshots-diff-view.png b/src/docs/react-storybook/testing/static/storyshots-diff-view.png new file mode 100644 index 000000000000..ae963afb0e8d Binary files /dev/null and b/src/docs/react-storybook/testing/static/storyshots-diff-view.png differ diff --git a/src/docs/react-storybook/testing/static/storyshots-first-run.png b/src/docs/react-storybook/testing/static/storyshots-first-run.png new file mode 100644 index 000000000000..6ead231c8422 Binary files /dev/null and b/src/docs/react-storybook/testing/static/storyshots-first-run.png differ diff --git a/src/docs/react-storybook/testing/structural-testing.js b/src/docs/react-storybook/testing/structural-testing.js new file mode 100644 index 000000000000..f1553ce2889e --- /dev/null +++ b/src/docs/react-storybook/testing/structural-testing.js @@ -0,0 +1,65 @@ +import { stripIndent } from 'common-tags'; + +import storyshotsFirstRun from './static/storyshots-first-run.png'; +import storyshotsDiffView from './static/storyshots-diff-view.png'; + +export default { + id: 'structural-testing', + title: 'Structural Testing', + content: stripIndent` + + For React, [Jest's snapshot testing](https://facebook.github.io/jest/blog/2016/07/27/jest-14.html) is the best way to do Structural Testing. It's painless to use and maintain. We've integrated Jest's snapshot testing directly into Storybook using a new tool called [StoryShots](https://github.com/storybooks/storyshots). Now we can simply use existing stories as the input for snapshot testing. + + ## What's Snapshot Testing? + + With Snapshot testing, we keep a file copy of the structure of UI components. Think of it like a set of HTML sources. + + Then, after we've completed any UI changes, we compare new snapshots with the snapshots that we kept in the file. + + If things are not the same, we can do two things: + + 1. We can consider new snapshots that show the current state, and then update them as new snapshots. + 2. We can find the root cause for the change and fix our code. + + > We can also commit these snapshots directly into the source code. + + ## Using StoryShots + + [StoryShots](https://github.com/storybooks/storybook/tree/master/packages/storyshots) is our integration between Storybook and Jest Snapshot Testing. It's pretty simple to use. + + First, make sure you are inside a Storybook-enabled repo (make sure it has few stories). + Then, install StoryShots into your app with: + + ~~~sh + npm i -D @kadira/storyshots + ~~~ + + Then, add the following NPM script into your package.json: + + ~~~js + { + "scripts": { + "test-storybook": "storyshots" + } + } + ~~~ + + Now you can run the above command with: + ~~~sh + npm run test-storybook + ~~~ + + This will save the initial set of snapshots inside your Storybook config directory. + + ![StoryShots First ](${storyshotsFirstRun}) + + After you complete any changes, you can run the above NPM script again and find our structural changes. + + ![StoryShots Diff View](${storyshotsDiffView}) + + --- + + StoryShots also comes with a few important [productive features](https://github.com/storybooks/storyshots#key-features) that can be customized. Have a look at the StoryShots [repo](https://github.com/storybooks/storyshots) for more information. + + `, +}; diff --git a/src/docs/storybook-hub/basics/comments.js b/src/docs/storybook-hub/basics/comments.js new file mode 100644 index 000000000000..c2eedb52df18 --- /dev/null +++ b/src/docs/storybook-hub/basics/comments.js @@ -0,0 +1,43 @@ +import { stripIndent } from 'common-tags'; + +import commentsInsideStorybookImage from './static/comments-inside-storybook.png'; + +export default { + id: 'comments', + title: 'Comments', + content: stripIndent` + You can comment inside Storybooks and discuss with your team. Comments are namespaced based on the git branch. + So, any storybook associated with a particular branch has the same set of comments. + + ![Comment Inside Storybook](${commentsInsideStorybookImage}) + + Adding comments support is easy. Add following NPM packages into your app: + + ~~~sh + npm i -D @kadira/storybook-database-cloud + npm i -D @kadira/storybook-addon-comments + ~~~ + + Then add following code into your \`addons.js\` file in \`.storybook\` directory. + + ~~~js + // To get built in addons. + import '@kadira/storybook/addons'; + + import '@kadira/storybook-database-cloud/register'; + import '@kadira/storybook-addon-comments/register'; + ~~~ + + Then push this into GitHub. Now you could comment inside your storybooks. + + ## Access Comments Locally + + You can also access comments even locally. For that, your project needs to have few requirements: + + + * Make sure it has a correct GIT remote called origin points to your repo on GitHub + * Make sure you are on the correct branch. + + Then you'll be able to access these comments. You'll be asked to login to storybook Hub, if needed, right from your comments panel. + `, +}; diff --git a/src/docs/storybook-hub/basics/getting-started.js b/src/docs/storybook-hub/basics/getting-started.js new file mode 100644 index 000000000000..1a7a6221df24 --- /dev/null +++ b/src/docs/storybook-hub/basics/getting-started.js @@ -0,0 +1,57 @@ +import { stripIndent } from 'common-tags'; + +import authorizeGithubImage from './static/authorize-github.png'; +import storybooksViaHubImage from './static/storybooks-via-hub.png'; +import storybooksViaPRImage from './static/storybooks-via-pr.png'; + +export default { + id: 'getting-started', + title: 'Getting Started', + content: stripIndent` + This guide will help you to connect your React app (or project) to [Storybook Hub](https://hub.storybooks.js.org/). + + ## Add Storybook for Your Project + + Most probably, you'll have a storybook configured for your project. If not, follow our [docs](/docs/react-storybook/basics/introduction). + Then make sure it has a \`build-storybook\` npm script as shown below: + + ~~~js + { + "scripts": { + ... + "build-storybook": "build-storybook -o ./.out" + ... + } + } + ~~~ + + > Output directory provided with \`-o\` option could be any directory. We'll override it while we are building it. + + ## Create an App on Storybook Hub + + Then create a Storybook Hub account. Make sure to sign up via GitHub. Otherwise, you won't be able to link your GitHub repo. + Then follow the screens and create an app. + + Here, you'll be able to authorize Github again to grant more permission. + If you are looking to only use Storybook Hub for public app, authorize for public apps. Otherwise, authorize for private apps. + + ![Authorize GitHub for Storybook Hub](${authorizeGithubImage}) + + > Here, you authorize for your whole account. Not just for this app. So, make sure to do this with caution. + You could always go from public to private. + But going from private to public may be an issue if you already have private apps. + + + Then select your repo and create an app. + + ## Push Some Code Commits + + Now, everything is ready. Push some code commits and we'll start building storybooks for your apps. We group storybooks by branches. You can access them by visiting your app's page on Storybook Hub. + + ![Access Storybooks via Storybook Hub](${storybooksViaHubImage}) + + You can also access these storybooks, right next to your PR. + + ![Access Storybooks via GitHub PR](${storybooksViaPRImage}) + `, +}; diff --git a/src/docs/storybook-hub/basics/github-pr-integration.js b/src/docs/storybook-hub/basics/github-pr-integration.js new file mode 100644 index 000000000000..a5f318f0b40f --- /dev/null +++ b/src/docs/storybook-hub/basics/github-pr-integration.js @@ -0,0 +1,25 @@ +import { stripIndent } from 'common-tags'; + +import githubPRCommentImage from './static/github-pr-comment.png'; +import githubPRDeployLinkImage from './static/github-pr-deploy-link.png'; + +export default { + id: 'github-pr-integration', + title: 'GitHub PR Integration', + content: stripIndent` + We'll create Storybooks for every code commit in your app. You can also access them alongside your Github Pull Requests. + + For every full request, we'll add a comment like this: + + ![Storybooks via GitHub PR Comment](${githubPRCommentImage}) + + Here you'll get an URL to the latest version of the storybook for this branch. It's a fixed URL for your PR. + So, you can share it with anyone. + + You can even access storybooks for individual commits. They are listed just below the code commit like this: + + ![Storybooks via GitHub Deploy Link](${githubPRDeployLinkImage}) + + You can access all these storybooks by visiting your app's page on Storybook Hub as well. We arrange them according to your branches. + `, +}; diff --git a/src/docs/storybook-hub/basics/private-or-public-apps.js b/src/docs/storybook-hub/basics/private-or-public-apps.js new file mode 100644 index 000000000000..155bd764a7f4 --- /dev/null +++ b/src/docs/storybook-hub/basics/private-or-public-apps.js @@ -0,0 +1,24 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'private-or-public-apps', + title: 'Private or Public Apps', + content: stripIndent` + In Storybook Hub, you could create either private or public apps. This guide will help you choose an app type for your project. + + ## For Open Source Repos + + Most of the time, you may want to create a public app for your repo. Then, anyone could access these storybooks and comment inside them. + + If you need to keep conversation only between your team, you could make a private app for even your open source projects. Then, only your collaborators could access storybooks. + + ## For Private Repos + + You may want to create a private app on Storybook Hub where only you and your collaborators could access storybooks. + + However, you can also create a public app for your private repo. Then you can share storybooks with the public. With this way, anyone in the public could access these URLS and comment on them. + + > Anyway, since your GitHub repo is private, no one in the public will be able to see your storybooks, unless you share them. + Additionally, our storybook URLs are hard to guess. (They carried an UUID) + `, +}; diff --git a/src/docs/storybook-hub/basics/security.js b/src/docs/storybook-hub/basics/security.js new file mode 100644 index 000000000000..8af8748bca5e --- /dev/null +++ b/src/docs/storybook-hub/basics/security.js @@ -0,0 +1,63 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'security', + title: 'Security', + content: stripIndent` + We have access to your GitHub repos and some other personal information about you. We understand that with great power comes great responsibility. We try to minimize the possibility of security breaches by always following essential security measures and adhering to stringent company-wide policies. + + As a startup, we know that a security breach would pose a critical threat to our credibility and ability to attract and retain customers. So, we'll do whatever we can to prevent it. Here are some of the security measures we follow. + + ## Everything Goes Over SSL + + All our network communication is carried over SSL. This includes communications between: + + * Web browser and the Storybook Hub web server. + * All of our Internal Services. + * All of the Database communications. + + So, it's highly unlikely that someone in between you, us, and GitHub will be able to read your code or other information. + + ## We Don't Keep Your Code + + Even though we have access to your code, we don't keep it. We only keep your storybooks. Storybooks are stored on encrypted disks, and you can only access these storybooks by visiting a given storybook URLs. Only the collaborators in your workspace can access these storybooks. + + > In the case of public apps, anyone could access these storybooks. + + ## We Use Isolated Build Environments + + Each and every storybook is built inside an isolated build environment with the help of Docker containers. So, there's no way that others' repositories can access your code unless there's an issue with Docker itself. We always use a stable version of Docker and apply operating system security patches to minimize such issues. + + These build servers cannot be accessed from the Internet and run inside a private network. So, it's impossible for someone to launch an internet-based attack. + + ## Kadira Employees Don't Read Your Code + + Our developers do not have access to your GitHub access keys and they never will be able to read your code. Our system engineers and founders do have access to your access tokens, since they have full access to our servers and databases. But they don't read or use your access tokens manually for any reason. + + Each of our employees is required to sign a contract regarding these matters before they start working for us. + + ## We Don't Use Your Repositories When Testing + + For development and staging purposes, we won't use your access keys and repos—for those purposes, we use our own set of keys and repos. In this way, we make sure we won't damage your repositories or code while testing. + + ## Partner Access + + We use Amazon AWS, Google Cloud, Galaxy, Heroku, and Compose to build our infrastructure. So, if there were a security breach in those services it might be possible to access your code. However, this is a risk shared by all cloud deployments and is very unlikely. + + ## Deleting Your Apps and The Account + + You can delete apps via our user interface. Once you have done that, we'll stop building storybooks for the related repository and access code for that repository. However, we don't delete your existing storybooks. + + If you need to delete existing storybooks or delete your entire account, just contact us via [storybooks@kadira.io](mailto:storybooks@kadira.io). + + ## Enterpise Deployments + + Even though we've implemented these security practices, your company policies may prevent you from using Kadira Storybooks. We understand that. That's why we support Enterprise deployments that work nicely with your GitHub Enterprise instance (or [github.com](http://github.com/)). + + Contact us via [storybooks@kadira.io](mailto:storybooks@kadira.io) to get started. + + ## TALK TO US + + If you need more information or have found a vulnerability, email us at [storybooks@kadira.io](mailto:storybooks@kadira.io). We're happy to talk with you. + `, +}; diff --git a/src/docs/storybook-hub/basics/static/authorize-github.png b/src/docs/storybook-hub/basics/static/authorize-github.png new file mode 100644 index 000000000000..bdce2780962b Binary files /dev/null and b/src/docs/storybook-hub/basics/static/authorize-github.png differ diff --git a/src/docs/storybook-hub/basics/static/comments-inside-storybook.png b/src/docs/storybook-hub/basics/static/comments-inside-storybook.png new file mode 100644 index 000000000000..a8917bf8cee0 Binary files /dev/null and b/src/docs/storybook-hub/basics/static/comments-inside-storybook.png differ diff --git a/src/docs/storybook-hub/basics/static/github-pr-comment.png b/src/docs/storybook-hub/basics/static/github-pr-comment.png new file mode 100644 index 000000000000..7c43574f3702 Binary files /dev/null and b/src/docs/storybook-hub/basics/static/github-pr-comment.png differ diff --git a/src/docs/storybook-hub/basics/static/github-pr-deploy-link.png b/src/docs/storybook-hub/basics/static/github-pr-deploy-link.png new file mode 100644 index 000000000000..53c048d59b22 Binary files /dev/null and b/src/docs/storybook-hub/basics/static/github-pr-deploy-link.png differ diff --git a/src/docs/storybook-hub/basics/static/storybooks-via-hub.png b/src/docs/storybook-hub/basics/static/storybooks-via-hub.png new file mode 100644 index 000000000000..09be2a8d7f67 Binary files /dev/null and b/src/docs/storybook-hub/basics/static/storybooks-via-hub.png differ diff --git a/src/docs/storybook-hub/basics/static/storybooks-via-pr.png b/src/docs/storybook-hub/basics/static/storybooks-via-pr.png new file mode 100644 index 000000000000..5839419b2b5b Binary files /dev/null and b/src/docs/storybook-hub/basics/static/storybooks-via-pr.png differ diff --git a/src/docs/storybook-hub/index.js b/src/docs/storybook-hub/index.js new file mode 100644 index 000000000000..68e659709d9e --- /dev/null +++ b/src/docs/storybook-hub/index.js @@ -0,0 +1,24 @@ +export default [ + { + id: 'basics', + heading: 'Basics', + items: [ + require('./basics/getting-started').default, + require('./basics/github-pr-integration').default, + require('./basics/comments').default, + require('./basics/private-or-public-apps').default, + require('./basics/security').default, + ], + }, + + { + id: 'management-features', + heading: 'Management Features', + items: [ + require('./management-features/workspaces').default, + require('./management-features/sharing-storybooks').default, + require('./management-features/env-variables').default, + require('./management-features/private-npm-packages').default, + ], + }, +]; diff --git a/src/docs/storybook-hub/management-features/env-variables.js b/src/docs/storybook-hub/management-features/env-variables.js new file mode 100644 index 000000000000..ce0e6649f371 --- /dev/null +++ b/src/docs/storybook-hub/management-features/env-variables.js @@ -0,0 +1,24 @@ +import { stripIndent } from 'common-tags'; + +import editAppButtonImage from './static/edit-app-button.png'; +import envVarsImage from './static/env-vars.png'; + +export default { + id: 'env-variables', + title: 'Environment Variables', + content: stripIndent` + You can pass dynamic information to Storybook via environmental variables. This can be used to pass configurations and secrets. Here's [how to](/docs/react-storybook/configurations/env-vars) use them with storybook. + + As you expected, you can set env variables directly from Storybook Hub. Therefore, you don't need to commit those configuration into GitHub or any other store. + + These configurations are carried with your storybook. Since storybooks for private apps are only available to people you trust, this is a good way to pass secrets to Storybook. + + To set environment variables, click the "Edit" button your app page on Storybook Hub. + + ![Edit App Button](${editAppButtonImage}) + + Then you could set environment variables like this: + + ![Set Environment Varibles](${envVarsImage}) + `, +}; diff --git a/src/docs/storybook-hub/management-features/private-npm-packages.js b/src/docs/storybook-hub/management-features/private-npm-packages.js new file mode 100644 index 000000000000..fbb1cdf35892 --- /dev/null +++ b/src/docs/storybook-hub/management-features/private-npm-packages.js @@ -0,0 +1,35 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'private-npm-packages', + title: 'Private NPM Packages', + content: stripIndent` + If you are using private NPM repos or private Github urls we won't be able to access those repos by default. But there are some ways you could grant permission to us. + + ## NPM Token + + You can provide a NPM token while we are building storybooks. With that, we could could use your private NPM packages inside Storybook Hub. For that, simply create the following environmet variable. + + ~~~sh + SB_NPM_TOKEN + ~~~ + + ## Custom .npmrc + + You can set a [custom \`.npmrc\` file](http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules) when we are building storybook. With that, you can provide NPM tokens to access your private packages. For that, simply expose the following environmental variable with your custom .npmrc content: + + ~~~sh + SB_NPMRC + ~~~ + + We will append the above content to your existing .npmrc file if there's a one. + + ## SSH Private Keys + + You may use private Github repos as NPM packages. Then you can set a private key which is authorized to access those repos. For that, simply expose the following environmental variable: + + ~~~sh + SB_SSHKEY + ~~~ + `, +}; diff --git a/src/docs/storybook-hub/management-features/sharing-storybooks.js b/src/docs/storybook-hub/management-features/sharing-storybooks.js new file mode 100644 index 000000000000..5ba2733d158f --- /dev/null +++ b/src/docs/storybook-hub/management-features/sharing-storybooks.js @@ -0,0 +1,23 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'sharing-storybooks', + title: 'Sharing Storybooks', + content: stripIndent` + One of the coolest thing about Storybook Hub is you can share storybooks with anyone even they don't have a GitHub account. + + All of our storybook URLs are permalinks. So, you can share them with anyone you like. + You could get these URLS either from Github PRs or from Storybook Hub. + + ## Access Control + + ### public apps + + Any storybook URL associated with public apps is available for anyone. Anyone with an account at Storybook Hub could comment on storybooks. + + ### private apps + + For private apps, any collaborator in your workspace could access storybooks for that app. + You can easily add or remove collaborators by visiting your [workspace](https://hub.getstorybook.io/workspaces). + `, +}; diff --git a/src/docs/storybook-hub/management-features/static/edit-app-button.png b/src/docs/storybook-hub/management-features/static/edit-app-button.png new file mode 100644 index 000000000000..56bfb68008c2 Binary files /dev/null and b/src/docs/storybook-hub/management-features/static/edit-app-button.png differ diff --git a/src/docs/storybook-hub/management-features/static/env-vars.png b/src/docs/storybook-hub/management-features/static/env-vars.png new file mode 100644 index 000000000000..c4f39a2e485d Binary files /dev/null and b/src/docs/storybook-hub/management-features/static/env-vars.png differ diff --git a/src/docs/storybook-hub/management-features/workspaces.js b/src/docs/storybook-hub/management-features/workspaces.js new file mode 100644 index 000000000000..1fca227a0f3a --- /dev/null +++ b/src/docs/storybook-hub/management-features/workspaces.js @@ -0,0 +1,18 @@ +import { stripIndent } from 'common-tags'; + +export default { + id: 'workspaces', + title: 'Workspaces', + content: stripIndent` + We group apps on storybook based on workspaces. Every account on Storybook Hub has a workspace. + Each workspaces has: + + * It's own set of app + * A set of collaborators + * Isolated payment setup + + You'll have access to more workspaces as you are added as a collaborator. Then you can see apps from those workspaces as well. + + > Currently, we don't allow the creation of additional workspaces. But we'll be adding that feature soon. + `, +}; diff --git a/src/index.css b/src/index.css new file mode 100644 index 000000000000..10cbb1b06abd --- /dev/null +++ b/src/index.css @@ -0,0 +1,19 @@ +@import 'https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,800'; + +body, div, p, ul, li, a { + font-family: 'Open Sans', sans-serif; + font-size: 14px; +} + +a, +a:visited, +a:hover, +a:active, +a:focus { + text-decoration: none; + color: #000; +} + +a:hover { + opacity: 0.7; +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 000000000000..833e6f2ef02f --- /dev/null +++ b/src/index.js @@ -0,0 +1,19 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Homepage from './components/Homepage'; +import Docs from './containers/Docs'; +import { Router, Route, browserHistory, applyRouterMiddleware } from 'react-router'; +import { useScroll } from 'react-router-scroll'; +import 'bootstrap/dist/css/bootstrap.css'; +import './index.css'; +import './lib/autolinker'; +import 'airbnb-js-shims'; + +ReactDOM.render( + + + + + , + document.getElementById('root'), +); diff --git a/src/lib/autolinker.js b/src/lib/autolinker.js new file mode 100644 index 000000000000..9d98edd104d5 --- /dev/null +++ b/src/lib/autolinker.js @@ -0,0 +1,92 @@ +import { browserHistory } from 'react-router'; + +if (typeof window !== 'undefined') { + watchClickEvents(); +} + +function watchClickEvents() { + // This logic is taken from page.js + // See: https://github.com/visionmedia/page.js + const clickEvent = typeof document !== 'undefined' && document.ontouchstart + ? 'touchstart' + : 'click'; + document.addEventListener(clickEvent, onclick, false); + + function onclick(e) { + if (which(e) !== 1) { + return; + } + + if (e.metaKey || e.ctrlKey || e.shiftKey) { + return; + } + + if (e.defaultPrevented) { + return; + } + + // ensure link + // use shadow dom when available + let el = e.path ? e.path[0] : e.target; + while (el && el.nodeName !== 'A') { + el = el.parentNode; + } + + if (!el || el.nodeName !== 'A') { + return; + } + + // Ignore if tag has + // 1. "download" attribute + // 2. rel="external" attribute + if (el.hasAttribute('download') || el.getAttribute('rel') === 'external') { + return; + } + + // ensure non-hash for the same path + const link = el.getAttribute('href'); + if (el.pathname === location.pathname && (el.hash || link === '#')) { + return; + } + + // Check for mailto: in the href + if (link && link.indexOf('mailto:') > -1) { + return; + } + + // check target + if (el.target) { + return; + } + + // x-origin + if (!sameOrigin(el.href)) { + return; + } + + // rebuild path + let path = el.pathname + el.search + (el.hash || ''); + + // strip leading "/[drive letter]:" on NW.js on Windows + if (typeof process !== 'undefined' && path.match(/^\/[a-zA-Z]:\//)) { + path = path.replace(/^\/[a-zA-Z]:\//, '/'); + } + + e.preventDefault(); + browserHistory.push(path); + } + + function which(e) { + e = e || window.event; + return e.which === null ? e.button : e.which; + } + + function sameOrigin(href) { + let origin = `${location.protocol}//${location.hostname}`; + if (location.port) { + origin += `:${location.port}`; + } + + return href && href.indexOf(origin) === 0; + } +} diff --git a/src/lib/highlight.js b/src/lib/highlight.js new file mode 100644 index 000000000000..70ce9b5b0fb1 --- /dev/null +++ b/src/lib/highlight.js @@ -0,0 +1,35 @@ +import hljs from 'highlight.js'; +import PropTypes from 'prop-types'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +class Highlight extends React.Component { + componentDidMount() { + this.highlightCode(); + } + + componentDidUpdate() { + this.highlightCode(); + } + + highlightCode() { + const domNode = ReactDOM.findDOMNode(this); + const nodes = domNode.querySelectorAll('pre code'); + if (nodes.length > 0) { + for (let i = 0; i < nodes.length; i++) { + hljs.highlightBlock(nodes[i]); + } + } + } + + render() { + const { children } = this.props; + return
    ; + } +} + +Highlight.propTypes = { + children: PropTypes.string, +}; + +export default Highlight; diff --git a/src/stories/data.js b/src/stories/data.js new file mode 100644 index 000000000000..12b910fccd92 --- /dev/null +++ b/src/stories/data.js @@ -0,0 +1,88 @@ +export const docsData = { + categories: [ + { + id: 'cat-1', + title: 'CAT 1', + }, + { + id: 'cat-2', + title: 'CAT 2', + }, + ], + sections: [ + { + id: 'basics', + heading: 'Basics', + items: [ + { id: 'getting-started', title: 'Getting Started' }, + { id: 'writing-stories', title: 'Writing Stories' }, + { id: 'build-as-a-static-app', title: 'Build as a Static App' }, + ], + }, + { + id: 'configurations', + heading: 'Configuations', + items: [ + { id: 'default-config', title: 'Default Config' }, + { id: 'webpack', title: 'Webpack' }, + { id: 'babel', title: 'Babel' }, + ], + }, + ], + selectedItem: { + id: 'writing-stories', + section: 'basics', + title: 'Writing Stories', + content: ` +You need to write stories to show your components inside React Storybook.
    +We've a set of APIs allows you to write stories and do more with them. + +When you are writing stories, you can follow these guidelines
    +to write great stories. + +* Write UI components by passing data via props. +* In this way, you can isolate UI components easilly. +* Do not write app-specific code inside your UI components. + +~~~js +import { linkTo } from @kadira/Storybook + +storiesOf('Toggle', module) + .add('on', () => { + return + }) + .add('off', () => { + return + }); +~~~ + `, + }, + featuredStorybooks: [ + { + owner: 'https://avatars0.githubusercontent.com/u/698437?v=3&s=200', + storybook: { + name: 'React Dates', + link: 'http://airbnb.io/react-dates/', + }, + source: 'https://github.com/airbnb/react-dates', + }, + + { + owner: 'https://avatars3.githubusercontent.com/u/239676?v=3&s=460', + storybook: { + name: 'React Native Web', + link: 'https://necolas.github.io/react-native-web/storybook', + }, + source: 'https://github.com/necolas/react-native-web', + }, + + { + owner: 'https://avatars1.githubusercontent.com/u/15616844?v=3&s=200', + storybook: { + name: 'React Button', + link: 'http://kadira-samples.github.io/react-button/', + }, + source: 'https://github.com/kadira-samples/react-button', + }, + ], +}; diff --git a/src/stories/designs.js b/src/stories/designs.js new file mode 100644 index 000000000000..f7273cc286c4 --- /dev/null +++ b/src/stories/designs.js @@ -0,0 +1,161 @@ +export default { + 'Homepage.page': { + design: require('../design/homepage/homepage.png'), + note: ` + For this we'll use Bootsrap for sake of simplicity. (Specially for the layouts). + Then we use Open Sans as the base font. + + Overall this will be a simple design. + All these content should render inside a BS containers and it support mobile. + `, + }, + + 'Homepage.header': { + design: require('../design/homepage/header.png'), + note: ` + Just a simple header. In the mobile view, this will show one after other. + `, + }, + + 'Homepage.heading': { + design: require('../design/homepage/heading.png'), + note: ` + Use the "Storybook" font to make it super bold. (font-weight=800) + In the mobile view, try to make the font-size smaller. + `, + }, + + 'Homepage.demo': { + design: require('../design/homepage/demo.png'), + note: ` + Use the image located at src/design/homepage/screenshot.png for this. + But in production we use an animated GIF here. + `, + }, + + 'Homepage.built-for': { + design: require('../design/homepage/built-for.png'), + note: ` + In this, React and React Native are links for following repos: + + * React - https://github.com/storybooks/react-storybook + * React Native - https://github.com/storybooks/react-native-storybook + + --- + + This one and few components below share some commong features. + Those includes bottom border and margins. So create a common component inside + the Homepage/styles.css stylesheet and use that class in this other components below. + `, + }, + + 'Homepage.main-links': { + design: require('../design/homepage/main-links.png'), + note: ` + This one has two headings. Use a common style in Homepage/styles.css and use it in here. + You can also use that in the component below. + + In the mobile view, two sections in here show one after other. + `, + }, + + 'Homepage.featured-storybooks': { + design: require('../design/homepage/featured-storybooks.png'), + note: ` + This components accepts a input as follows and render links to storybooks as shown above. + In the mobile view, these links shows one after other. + + When we clicked on the Name in the above, we should load the storybook in a new tab. + + Here are the data for this components (to show links): + + [ + { + owner: "https://avatars0.githubusercontent.com/u/698437?v=3&s=200", + storybook: { + "name": "React Dates", + "link": "http://airbnb.io/react-dates/", + } + source: "https://github.com/airbnb/react-dates" + }, + + { + owner: "https://avatars3.githubusercontent.com/u/239676?v=3&s=460", + storybook: { + "name": "React Native Web", + "link": "https://necolas.github.io/react-native-web/storybook", + } + source: "https://github.com/necolas/react-native-web" + }, + + { + owner: "https://avatars1.githubusercontent.com/u/15616844?v=3&s=200", + storybook: { + "name": "React Button", + "link": "http://kadira-samples.github.io/react-button/", + } + source: "https://github.com/kadira-samples/react-button" + }, + ] + `, + }, + + 'Homepage.footer': { + design: require('../design/homepage/footer.png'), + note: ` + Here are the links: + + * Slack: https://storybooks-slackin.herokuapp.com/ + * NewsLetter: https://tinyletter.com/storybooks + * Twiiter: https://twitter.com/kadirahq + * Medium: https://voice.kadira.io + `, + }, + + 'Docs.page': { + design: require('../design/docs/docs.png'), + note: ` + Here we use the docs layout which is similar to BulletProof Meteor. + We've used Arial for some texts. I'll mention them. Otherwise still the + fonts are Open Sans. + + Here we reuse the header and footer from the Homepage. + Bootstrap Layout is also pretty similar to the Homepage. + `, + }, + + 'Docs.docs-container': { + design: require('../design/docs/docs-container.png'), + note: ` + This is a container and this as no content. + But this one has top and bottom borders and some margins. + `, + }, + + 'Docs.docs-nav': { + design: require('../design/docs/docs-nav.png'), + note: ` + This is docs navigation and accept some dataset to render this. + Here's some sample data: https://gist.github.com/arunoda/04fd23e93766eb883afcac93f06fbff7 + + Here topic Titles are on Open Sans with 20px and bold texts with color #444. + Others are with '"Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif"' and 17px. + Selected item is marked with bold and in this color: #E25E5E. + + `, + }, + + 'Docs.docs-content': { + design: require('../design/docs/docs-content.png'), + note: ` + This title is with Open Sans 30px font. color #444. + Content is with following style: + * font: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif. + * size: 17px, + * line-height: 25px + * color: #333 + + (You can look at the BulletProof Meteor for the actual styles.) + `, + }, +}; diff --git a/src/stories/implementations.js b/src/stories/implementations.js new file mode 100644 index 000000000000..d516eb495808 --- /dev/null +++ b/src/stories/implementations.js @@ -0,0 +1,66 @@ +import React from 'react'; +import Homepage from '../components/Homepage'; +import Header from '../components/Homepage/Header'; +import Heading from '../components/Homepage/Heading'; +import Demo from '../components/Homepage/Demo'; +import Platforms from '../components/Homepage/Platforms'; +import MainLinks from '../components/Homepage/MainLinks'; +import Featured from '../components/Homepage/Featured'; +import Footer from '../components/Homepage/Footer'; +import Docs from '../components/Docs'; +import DocsContainer from '../components/Docs/Container'; +import DocsContent from '../components/Docs/Content'; +import DocsNav from '../components/Docs/Nav'; + +import { docsData } from './data'; + +const content = ` + React Storybook is a UI development environment for your React components. + + With it, you can visualize different states of your UI components and develop them interactively. React Storybook runs outside of your app. So you can develop UI components in isolation without worrying about app specific dependencies and requirements. + + React Storybook also comes with a lot of [addons](/docs/addons/introduction) and a great API to customize as you wish. You can also build a [static version](/docs/basics/exporting-storybook) of your storybook and deploy it anywhere you want. + + Here are some featured storybooks that you can reference to see how Storybook works: + + * [React Button](http://kadira-samples.github.io/react-button) - [source](https://github.com/kadira-samples/react-button) + * [Demo of React Dates](http://airbnb.io/react-dates/) - [source](https://github.com/airbnb/react-dates) + * [Demo of React Native Web](http://necolas.github.io/react-native-web/storybook/) - [source](https://github.com/necolas/react-native-web) +`; + +export default { + 'Homepage.page': , + 'Homepage.header':
    , + 'Homepage.heading': , + 'Homepage.demo': , + 'Homepage.built-for': , + 'Homepage.main-links': , + 'Homepage.featured-storybooks': , + 'Homepage.footer':