Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible to use addons? #11

Open
jesconstantine opened this issue Jan 4, 2019 · 8 comments
Open

Possible to use addons? #11

jesconstantine opened this issue Jan 4, 2019 · 8 comments

Comments

@jesconstantine
Copy link

I was curious if it was possible to use storybook addons with expobook? If so, is there an example implementation that someone can point me to? Thanks!

@kirkness
Copy link
Contributor

kirkness commented Jan 4, 2019

Hello! It is not, but what are you wanting to do exactly?

@jesconstantine
Copy link
Author

Hi @kirkness! I'm looking for 3 pieces of functionality that I've seen with react + storybook:

  1. Writing individual component stories that live with a component, for example:
    components/
        Button/
            Button.knobs.options.js
            Button.md
            Button.js
            Button.stories.js
    
    And in expobook.js we'd just search in all of components' sub directories and load all *.stories.js files
  2. Allowing component interaction via knobs (whose options are written in Button.knobs.options.js and imported into the story) to showcase all of a component's variants
  3. Documenting a component via a markdown file which is pulled in to the story

Here's a look at the implementation I'm thinking of:
screen recording 2019-01-04 at 08 41 am

Let me know if you think there's a way I can achieve any of this functionality. Thanks again!

@ezhlobo
Copy link
Contributor

ezhlobo commented Jan 4, 2019

@jesconstantine I was able to keep stories near the component. To do that I wrote a script which was going through all my components and collecting all stories into one file (it's done less than in a second) and then I just build the expobook by using that file.

The content of generated file was similar to that:

export default [
  {
    name: 'Button',
    source: require('./src/components/Button/story.js'),
  },
  ...
]

To render that I specified own entryPoint for the book, where I inherited from basic expobook components and built my own interface with list of all my stories.

@kirkness
Copy link
Contributor

kirkness commented Jan 4, 2019

I'd need to check to be sure, but I think in theory you could:

// expobook.js
import expobook from './my-expobook-instance';

export default expobook.build();
// my-expobook-instance.js
import createExpobook from 'expobook';

export default createExpobook();

And then in each of your component dirs:

import expobook from '../my-expobook-instance';

expobook.add('Something', <Button />);

// Note, no need to export it.

Essentially, createExpobook() returns an object that is mutated when you call .add() on it. So as long as you are calling add() on the same instance that is exported from expobook.js, you should be fine.

It's the intention, but I may be wrong.

@jesconstantine
Copy link
Author

Thanks @ezhlobo and @kirkness -- I'll give that a try and report back.

@jesconstantine
Copy link
Author

jesconstantine commented Jan 4, 2019

@kirkness I believe I've tried your method but when expo loads the components are not shown in the menu. Can you take a look at my code snippets and see if I've misunderstood something? Thanks!

// expobook.js

import expobook from './expobook-instance';

export default expobook.build();
// expobook-instance.js

import createExpobook from 'expobook';

export default createExpobook();
// components/Button/Button.stories.js

import React from 'react'
import expobook from '../../expobook-instance';

import Button from '../Button';
expobook.add('Button with Text', () => <Button>Text</Button>);
// components/Incrementer/Incrementer.stories.js

import React from 'react';
import expobook from '../../expobook-instance';

import Incrementer from '../Incrementer';
expobook.add('Button Incrementer', () => <Incrementer buttonText={'Touch Me to increment!'}/>);

Screenshot of expo in iOS simulator:
image

@mitramejia
Copy link

@jesconstantine I was able to keep stories near the component. To do that I wrote a script which was going through all my components and collecting all stories into one file (it's done less than in a second) and then I just build the expobook by using that file.

The content of generated file was similar to that:

export default [
  {
    name: 'Button',
    source: require('./src/components/Button/story.js'),
  },
  ...
]

To render that I specified own entryPoint for the book, where I inherited from basic expobook components and built my own interface with list of all my stories.

@ezhlobo is it possible for you to share this script 🙌 like you I prefer to keep my stories close to components

@ezhlobo
Copy link
Contributor

ezhlobo commented May 21, 2020

@mitramejia first of all, it was more than a year ago, so might not be workable nowadays. However there are some snippets that I think are meaningful:

Scripts in package.json

"book": "yarn book-prepare && exp start --lan --config ./book.json",
"book-prepare": "node -r dotenv/config ./config/book/prepare.js",

Not sure why not book and prebook 🤔, likely there is no reason 😄.

config/book/prepare.js

const path = require('path')
const fs = require('fs')

const BOOK_DIR = '__book'
const EXAMPLE_FILTER = '(/book.js|/book/index.js)'

const getAbsoluteName = name => `${process.cwd()}/${name}`

const extractName = filename => filename
  .replace(process.cwd(), '')
  .replace(new RegExp(`^/src/\\w+/(\\w+)${EXAMPLE_FILTER}$`), '$1')
  .replace(new RegExp('^/src/\\w+/Icons/(\\w+)\\.js$'), '$1')

const getPaths = dir => fs.readdirSync(dir)
  .reduce((files, file) => {
    if (fs.statSync(path.join(dir, file)).isDirectory()) {
      return files.concat(getPaths(path.join(dir, file)))
    }

    return files.concat(path.join(dir, file))
  }, [])

const components = getPaths(getAbsoluteName('src'))
  .filter(filename => new RegExp(`${EXAMPLE_FILTER}$`).test(filename))
const icons = getPaths(getAbsoluteName('src/components/Icons'))
  .filter(filename => !new RegExp(`${EXAMPLE_FILTER}$`).test(filename))
  .filter(filename => !new RegExp('/book/[\\w\\.]+$').test(filename))

const writeToFile = (filePath, list) => {
  const fileStream = fs.createWriteStream(filePath)

  fileStream.write('module.exports = [\n')

  list.forEach((filename) => {
    fileStream.write('  {\n')
    fileStream.write(`    name: '${extractName(filename)}',\n`)
    fileStream.write(`    source: require('${filename}'),\n`)
    fileStream.write('  },\n')
  })

  fileStream.write(']')

  fileStream.end()
}

if (!fs.existsSync(getAbsoluteName(BOOK_DIR))) {
  fs.mkdirSync(getAbsoluteName(BOOK_DIR))
}

writeToFile(getAbsoluteName(`${BOOK_DIR}/components.js`), components)
writeToFile(getAbsoluteName(`${BOOK_DIR}/icons.js`), icons)

It suppose to create __book/components.js and __book/icons.js (we had custom icon set, so we created a separate look for it).

Out entry.js for expobook

That is specific setup for our project, I provide it because it might be useful to share how we consume our generated __book/components.

import { KeepAwake, registerRootComponent } from 'expo'

// eslint-disable-next-line import/no-extraneous-dependencies
import buildBook from 'expobook/__expobook__/index'

import React from 'react'
import { Provider } from 'react-redux'
import store from 'src/store'
import Cache from 'src/Cache'
import InnerComponent from './InnerComponent'
import WrapperComponent from './WrapperComponent'

import collection from '../../__book/components'

const components = collection
  .sort((a, b) => (a.name < b.name ? -1 : 1))
  .reduce((all, file) => {
    const name = file.source.default || file.name
    const modules = Object.keys(file.source).filter(key => key !== 'default')

    /* eslint-disable no-param-reassign */

    all[name] = () => pug`
      WrapperComponent
        each name in modules
          InnerComponent(key=name)
            = React.createElement(file.source[name], {})
    `

    /* eslint-enable */

    return all
  }, {})

if (__DEV__) {
  KeepAwake.activate()
}

const Book = buildBook(components)

registerRootComponent(() => pug`
  Provider(store=store)
    Cache
      Book
`)

I hope pug won't be a big deal to read this ;).

Example of a book (src/components/Button/book.js)

import React from 'react'

import Button from '.'

export const Default = () => pug`
  Button Hello
`

export const Primary = () => pug`
  Button(primary) Hello
`

export const Disabled = () => pug`
  Button(disabled) Hello
`

Just in case, for me it would be a hard job to give a working example. However if you share your own codebase, I can assist with applying our approach.

If Pug is hard to read, there is a converter: https://react.pugjs.org/ ;).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants