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

Addon-docs: Fix non-React support & add Vue example #7222

Merged
merged 9 commits into from Jul 1, 2019
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions examples/official-storybook/stories/addon-docs.stories.mdx
Expand Up @@ -10,6 +10,8 @@ import {
} from '@storybook/addon-docs/blocks';
import { action } from '@storybook/addon-actions';
import { Button } from '@storybook/react/demo';
import FlowTypeButton from '../components/FlowTypeButton';
import DocgenButton from '../components/DocgenButton';

<Meta
title="Addons|Docs/mdx"
Expand Down Expand Up @@ -86,3 +88,15 @@ export const nonStory2 = () => <Button>Not a story</Button>; // another one
<Description markdown="this is _markdown_" />

<Description of={Button} />

## Props

### Docgen

<Props of={DocgenButton} />

### Flow

Flow types are not officially supported

<Props of={FlowTypeButton} />
4 changes: 4 additions & 0 deletions examples/vue-kitchen-sink/.storybook/config.js
Expand Up @@ -13,8 +13,12 @@ Vue.use(Vuex);
addParameters({
options: {
hierarchyRootSeparator: /\|/,
docs: {
iframeHeight: '60px',
},
},
docs: DocsPage,
});

load(require.context('../src/stories', true, /\.stories\.js$/), module);
load(require.context('../src/stories', true, /\.stories\.mdx$/), module);
8 changes: 8 additions & 0 deletions examples/vue-kitchen-sink/.storybook/presets.js
@@ -0,0 +1,8 @@
module.exports = [
{
name: '@storybook/addon-docs/vue/preset',
options: {
configureJSX: true,
},
},
];
5 changes: 5 additions & 0 deletions examples/vue-kitchen-sink/.storybook/webpack.config.js
@@ -1,6 +1,11 @@
const path = require('path');

module.exports = async ({ config }) => {
config.module.rules.push({
test: /\.vue$/,
loader: 'storybook-addon-vue-info/loader',
enforce: 'post',
});
config.module.rules.push({
test: [/\.stories\.js$/, /index\.js$/],
loaders: [require.resolve('@storybook/addon-storysource/loader')],
Expand Down
Expand Up @@ -280,7 +280,7 @@ exports[`Storyshots Button square 1`] = `

exports[`Storyshots Core|Parameters passed to story 1`] = `
<div>
Parameters are {"options":{"hierarchyRootSeparator":{},"hierarchySeparator":{}},"globalParameter":"globalParameter","framework":"vue","chapterParameter":"chapterParameter","storyParameter":"storyParameter"}
Parameters are {"options":{"hierarchyRootSeparator":{},"hierarchySeparator":{},"docs":{"iframeHeight":"60px"}},"globalParameter":"globalParameter","framework":"vue","chapterParameter":"chapterParameter","storyParameter":"storyParameter"}
</div>
`;

Expand Down
2 changes: 2 additions & 0 deletions examples/vue-kitchen-sink/package.json
Expand Up @@ -35,6 +35,8 @@
"babel-loader": "^8.0.5",
"cross-env": "^5.2.0",
"file-loader": "^3.0.1",
"prop-types": "^15.7.2",
"storybook-addon-vue-info": "^1.2.1",
"svg-url-loader": "^2.3.2",
"vue-loader": "^15.7.0",
"webpack": "^4.33.0",
Expand Down
82 changes: 82 additions & 0 deletions examples/vue-kitchen-sink/src/stories/addon-docs.stories.mdx
@@ -0,0 +1,82 @@
import { Story, Preview, Meta } from '@storybook/addon-docs/blocks';

# Storybook Docs for Vue

Storybook supports every major view layer:
React, Vue, Angular, Ember, React Native, etc.

Storybook Docs will too.

<Story id="welcome--welcome" height="370px" />

Let's check out a Vue prototype to see how this works.

## Component Declaration

Just like in React, we first declare our component.

<Meta title="Addon|Docs" />

This export doesn't show up in the MDX output.

## SB5 "Classic" Vue Stories

Next let's declare some stories.

But first let's review how it's done in SB5 for Vue.

```
storiesOf('Button', module)
.add('rounded', () => ({
template: '<my-button :rounded="true">A Button with rounded edges</my-button>',
}))
```

## MDX Stories

Unsurprisingly, here's how we do it in the Docs MDX format:

<Story name="rounded" height="60px">
{{
template: '<my-button :rounded="true">A Button with rounded edges</my-button>',
}}
</Story>

This isn't the final syntax, but it gets the job done.

## Another one

Let's add another one. The UI updates automatically as you'd expect.

<Story name="square" height="60px">
{{
template: '<my-button :rounded="false">A Button with square edges</my-button>',
}}
</Story>

## Longform docs

And just like in the React, case we're generating long-form docs as we go.

The primary difference is that for Vue Docs we generate an iframe per story.

## Optimization

This can be optimized using a library like [Vuera](https://github.com/akxcv/vuera)
for embedding Vue components in React.

We could also change the JSX rendering to render the entire page in Vue.

## Previews

Just like in React, we can easily reference other stories in our docs:

<Story id="addon-knobs--all-knobs" height="400px" />

## More info

For more info, check out the [Storybook Docs Technical Preview](https://docs.google.com/document/d/1un6YX7xDKEKl5-MVb-egnOYN8dynb5Hf7mq0hipk8JE/edit?usp=sharing).

We want your feedback to help make this more useful.

Follow us on Twitter for more short demos & project updates! ❤️📈🛠
@@ -1,5 +1,10 @@
import Button from '../Button.vue';

export default {
title: 'Button',
parameters: {
component: Button,
},
};

export const rounded = () => ({
Expand Down
Expand Up @@ -9,7 +9,9 @@ export default {
},
};

export const welcome = () => ({
render: h => h(Welcome, { props: { goToButton: linkTo('Button') } }),
});
welcome.story = { welcome };
export const welcome = () => {
return {
render: h => h(Welcome, { props: { goToButton: linkTo('Button') } }),
};
};
welcome.story = { name: 'to storybook' };
2 changes: 1 addition & 1 deletion jest.config.js
Expand Up @@ -3,7 +3,7 @@ module.exports = {
clearMocks: true,
moduleNameMapper: {
// non-js files
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|mdx)$':
'<rootDir>/__mocks__/fileMock.js',
'\\.(css|scss|stylesheet)$': '<rootDir>/__mocks__/styleMock.js',
'\\.(md)$': '<rootDir>/__mocks__/htmlMock.js',
Expand Down
26 changes: 25 additions & 1 deletion lib/core/src/client/preview/start.js
Expand Up @@ -190,11 +190,35 @@ export default function start(render, { decorateStory } = {}) {
addons.getChannel().emit(Events.STORY_CHANGED, id);
}

// Docs view renders into a different root ID to avoid conflicts
// with the user's view layer. Therefore we need to clean up whenever
// we transition between view modes
if (viewMode !== previousViewMode) {
switch (viewMode) {
case 'docs': {
document.getElementById('root').setAttribute('hidden', true);
document.getElementById('docs-root').removeAttribute('hidden');
break;
}
case 'story':
default: {
if (previousViewMode === 'docs') {
document.getElementById('docs-root').setAttribute('hidden', true);
ReactDOM.unmountComponentAtNode(document.getElementById('docs-root'));
document.getElementById('root').removeAttribute('hidden');
}
}
}
}
// Given a cleaned up state, render the appropriate view mode
switch (viewMode) {
case 'docs': {
const NoDocs = () => <div style={{ fontFamily: 'sans-serif' }}>No docs found</div>;
const StoryDocs = (parameters && parameters.docs) || NoDocs;
ReactDOM.render(<StoryDocs context={renderContext} />, document.getElementById('root'));
ReactDOM.render(
<StoryDocs context={renderContext} />,
document.getElementById('docs-root')
);
break;
}
case 'story':
Expand Down
78 changes: 33 additions & 45 deletions lib/core/src/server/templates/index.ejs
@@ -1,48 +1,36 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title><%= options.title || 'Storybook'%></title>

<% if (files.favicon) { %>
<link rel="shortcut icon" href="<%= files.favicon%>">
<% } %>

<meta name="viewport" content="width=device-width, initial-scale=1">

<% if (typeof headHtmlSnippet !== 'undefined') { %>
<%= headHtmlSnippet %>
<% } %>

<% files.css.forEach(file => { %>
<link href="<%= file %>" rel="stylesheet">
<% }); %>

</head>
<body>

<% if (typeof bodyHtmlSnippet !== 'undefined') { %>
<%= bodyHtmlSnippet %>
<% } %>

<div id="root"></div>

<% if (options.window) { %>
<script>
<% for (var varName in options.window) { %>
window['<%=varName%>'] = <%= JSON.stringify(options.window[varName]) %>;
<% } %>
</script>
<% } %>

<% dlls.forEach(file => { %>
<script src="<%= file %>"></script>
<% }); %>

<% files.js.forEach(file => { %>
<script src="<%= file %>"></script>
<% }); %>

</body>
<head>
<meta charset="utf-8" />
shilman marked this conversation as resolved.
Show resolved Hide resolved
<title><%= options.title || 'Storybook'%></title>

<% if (files.favicon) { %>
<link rel="shortcut icon" href="<%= files.favicon%>" />
<% } %>

<meta name="viewport" content="width=device-width, initial-scale=1" />

<% if (typeof headHtmlSnippet !== 'undefined') { %> <%= headHtmlSnippet %> <% } %> <%
files.css.forEach(file => { %>
<link href="<%= file %>" rel="stylesheet" />
<% }); %>
</head>
<body>
<% if (typeof bodyHtmlSnippet !== 'undefined') { %> <%= bodyHtmlSnippet %> <% } %>

<div id="root"></div>
<div id="docs-root"></div>

<% if (options.window) { %>
<script>
<% for (var varName in options.window) { %>
window['<%=varName%>'] = <%= JSON.stringify(options.window[varName]) %>;
<% } %>
</script>
<% } %> <% dlls.forEach(file => { %>
<script src="<%= file %>"></script>
<% }); %> <% files.js.forEach(file => { %>
<script src="<%= file %>"></script>
<% }); %>
</body>
</html>