├── .codeclimate.yml ├── .editorconfig ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .nvmrc ├── .storybook ├── config.js └── webpack.config.js ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets └── logo.png ├── example ├── .babelrc ├── .gitignore ├── .storybook │ ├── addons.js │ ├── config.js │ └── webpack.config.js ├── components │ ├── BaseBlank.vue │ ├── BaseButton.vue │ ├── NumberList.vue │ └── customDocs │ │ └── wrapper │ │ ├── Wrapper.css │ │ └── Wrapper.vue ├── package.json └── stories │ ├── compatibilities │ └── official │ │ ├── a11y.stories.js │ │ ├── backgrounds.stories.js │ │ ├── components │ │ └── MyButton.vue │ │ └── knobs.stories.js │ ├── examples │ └── index.stories.js │ └── issues │ ├── 21 │ ├── BulmaButton.vue │ └── index.stories.js │ ├── 50 │ └── index.stories.js │ ├── 53 │ └── index.stories.js │ ├── 59 │ ├── child.vue │ ├── index.stories.js │ └── parent.vue │ ├── 63 │ ├── component.vue │ └── index.stories.js │ ├── 71 │ ├── component.vue │ └── index.stories.js │ ├── 72 │ ├── component.vue │ └── index.stories.js │ ├── 85 │ ├── component.vue │ ├── index.stories.js │ └── instance.js │ ├── 86 │ ├── component.vue │ ├── index.stories.js │ └── mixin.vue │ ├── 95 │ ├── component.vue │ └── index.stories.js │ ├── 119 │ ├── component.js │ └── index.stories.js │ ├── 122 │ ├── camel.vue │ ├── index.stories.js │ ├── kebab.vue │ └── mixed.vue │ └── 126 │ ├── component.vue │ └── index.stories.js ├── loader └── index.js ├── netlify.toml ├── package.json ├── rollup.config.js ├── src ├── addon.ts ├── components │ ├── Component │ │ ├── index.vue │ │ └── style.css │ ├── Docs │ │ ├── index.vue │ │ └── style.css │ ├── Header │ │ ├── index.vue │ │ └── style.css │ ├── Preview │ │ ├── index.vue │ │ └── style.css │ ├── SectionContainer │ │ ├── index.stories.js │ │ ├── index.vue │ │ └── style.css │ ├── Separator │ │ ├── index.vue │ │ └── style.css │ ├── StorySource │ │ ├── index.vue │ │ └── style.css │ ├── Summary │ │ ├── index.vue │ │ └── style.css │ ├── Table │ │ ├── index.stories.js │ │ ├── index.vue │ │ └── style.css │ ├── Wrapper │ │ ├── index.stories.js │ │ └── index.vue │ └── index.ts ├── extract │ ├── decideTargets.ts │ ├── extractDocgenInfo.ts │ ├── getProps.ts │ ├── index.ts │ └── lookup.ts ├── index.ts ├── lib.d.ts ├── options.ts ├── register.tsx ├── types │ ├── dedent-tabs.ts │ ├── info.ts │ └── vue.ts ├── typings.d.ts ├── utils │ ├── constructorToString.spec.ts │ ├── constructorToString.ts │ ├── getJSXFromRenderFn.spec.ts │ ├── getJSXFromRenderFn.ts │ ├── getTagNames.spec.ts │ ├── getTagNames.ts │ ├── removeDuplicates.spec.ts │ └── removeDuplicates.ts └── view │ └── index.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | plugins: 4 | duplication: 5 | enabled: true 6 | config: 7 | count_threshold: 3 8 | 9 | exclude_patterns: 10 | - 'example/' 11 | - '.storybook/' 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | indent_size = 2 7 | indent_style = space 8 | 9 | [*.md] 10 | trim_trailing_whitespace = false 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | --- 5 | 6 | **Describe the bug** 7 | A clear and concise description of what the bug is. 8 | 9 | **To Reproduce** 10 | Steps to reproduce the behavior: 11 | 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Versions** 24 | 25 | - `storybook-addon-vue-info`: 26 | - `@storybook/vue`: 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Do not add your OS/IDE/Editor files to here. 2 | # Please use global ignore instead. 3 | # https://help.github.com/articles/ignoring-files/#create-a-global-gitignore 4 | 5 | node_modules 6 | *.log 7 | 8 | # Rollup cache 9 | .rpt2_cache 10 | 11 | # Package packed by `npm pack` 12 | *.tgz 13 | 14 | lib 15 | 16 | example/build 17 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v11.6.0 2 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/vue' 2 | 3 | const req = require.context('../src/components', true, /.stories.js$/) 4 | 5 | configure(function loadStories() { 6 | req.keys().forEach(f => req(f)) 7 | }, module) 8 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (baseConfig, env, defaultConfig) => { 2 | defaultConfig.module.rules = defaultConfig.module.rules.filter( 3 | r => r.test.toString() !== /\.css$/.toString() 4 | ) 5 | 6 | defaultConfig.module.rules.push({ 7 | test: /\.css$/, 8 | oneOf: [ 9 | { 10 | resourceQuery: /module/, 11 | use: [ 12 | 'vue-style-loader', 13 | { 14 | loader: 'css-loader', 15 | options: { 16 | modules: true 17 | } 18 | } 19 | ] 20 | }, 21 | { 22 | use: ['vue-style-loader', 'css-loader'] 23 | } 24 | ] 25 | }) 26 | 27 | return defaultConfig 28 | } 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | cache: yarn 4 | 5 | script: 6 | - yarn lint 7 | - yarn test 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [1.4.2] - 2020-02-24 11 | 12 | ### Fixed 13 | 14 | - Stringify function props' default value. (Issue: [#126](https://github.com/pocka/storybook-addon-vue-info/issues/126), PR: [#127](https://github.com/pocka/storybook-addon-vue-info/pull/127)) 15 | 16 | ## [1.4.1] - 2019-12-19 17 | 18 | ### Fixed 19 | 20 | - Fix incorrect component detection. 21 | 22 | ## [1.4.0] - 2019-12-11 23 | 24 | ### Added 25 | 26 | - Add `casing` option. (Issue: [#122](https://github.com/pocka/storybook-addon-vue-info/issues/122), PR: [#123](https://github.com/pocka/storybook-addon-vue-info/pull/123)) 27 | 28 | ## [1.3.4] - 2019-11-15 29 | 30 | ### Fixed 31 | 32 | - Fix compatibility issue with vue-docgen-api@4. 33 | 34 | ## [1.3.3] - 2019-11-14 35 | 36 | ### Deprecated 37 | 38 | - Deprecate the custom loader in favor of vue-docgen-loader. (PR: [#121](https://github.com/pocka/storybook-addon-vue-info/pull/121)) 39 | 40 | ## [1.3.2] - 2019-09-24 41 | 42 | ### Fixed 43 | 44 | - Do not reset preview style with `all: initial`. (Issue: [#21](https://github.com/pocka/storybook-addon-vue-info/issues/21), PR: [#115](https://github.com/pocka/storybook-addon-vue-info/pull/115)) 45 | 46 | ## [1.3.1] - 2019-09-07 47 | 48 | ### Fixed 49 | 50 | - Ignore story decorators. (Issue: [#50](https://github.com/pocka/storybook-addon-vue-info/issues/50), PR: [#113](https://github.com/pocka/storybook-addon-vue-info/pull/113)) 51 | 52 | ## [1.3.0] - 2019-08-26 53 | 54 | ### Added 55 | 56 | - Add `description` story option, which supports descriptions for props, events and slots. 57 | 58 | ### Deprecated 59 | 60 | - Deprecate `propsDescription` in favor of `description`. 61 | 62 | ## [1.2.3] - 2019-08-15 63 | 64 | ### Security 65 | 66 | - Bump marked from 0.6.2 to 0.7.0. (Issue: [#110](https://github.com/pocka/storybook-addon-vue-info/issues/110), Vulnerability detail: [npm](https://github.com/pocka/storybook-addon-vue-info/issues/110)) 67 | 68 | ## [1.2.2] - 2019-07-26 69 | 70 | ### Fixed 71 | 72 | - Get default value only if prop is specified as an object. (Issue: [#107](https://github.com/pocka/storybook-addon-vue-info/issues/107), PR: [#108](https://github.com/pocka/storybook-addon-vue-info/pull/108)) 73 | 74 | ## [1.2.1] - 2019-06-06 75 | 76 | ### Fixed 77 | 78 | - Styling problems in props table on narrow screen/addon-panel (PR: [#103](https://github.com/pocka/storybook-addon-vue-info/pull/103)) 79 | 80 | ## [1.2.0] - 2019-05-11 81 | 82 | ### Added 83 | 84 | - `previewClassName` and `previewStyle` option (Issue: [#93](https://github.com/pocka/storybook-addon-vue-info/issues/93), PR: [#98](https://github.com/pocka/storybook-addon-vue-info/pull/98)) 85 | 86 | ## [1.1.2] - 2019-05-11 87 | 88 | ### Fixed 89 | 90 | - Show `any` for prop type when the prop has no `type` field (Issue: [#95](https://github.com/pocka/storybook-addon-vue-info/issues/95), PR: [#97](https://github.com/pocka/storybook-addon-vue-info/pull/97)) 91 | 92 | ## [1.1.1] - 2019-04-20 93 | 94 | ### Fixed 95 | 96 | - Use dedent-tabs, which is a fork of dedent, to support tab dedenting. (Issue: [#79](https://github.com/pocka/storybook-addon-vue-info/issues/79), PR: [#90](https://github.com/pocka/storybook-addon-vue-info/pull/90)) 97 | 98 | ## [1.1.0] - 2019-04-20 99 | 100 | ### Added 101 | 102 | - Allow passing options for vue-docgen-api (PR: [#86](https://github.com/pocka/storybook-addon-vue-info/pull/86)). 103 | - Show error message if vue-docgen-api fails to parse component (PR: [#85](https://github.com/pocka/storybook-addon-vue-info/pull/85)). 104 | 105 | ### Fixed 106 | 107 | - Remove global affecting highlight.js' CSS (PR: [#87](https://github.com/pocka/storybook-addon-vue-info/pull/87)). 108 | 109 | ## [1.0.1] - 2019-03-21 110 | 111 | ### Fixed 112 | 113 | - Fix re-render issue (only when using `docsInPanel = true`). 114 | 115 | ## [1.0.0] - 2019-03-06 116 | 117 | ### Changed 118 | 119 | - **BREAKING**: Adapt to the latest decorator API. You need to specify the info parameters for each story (can omit by setting default). 120 | - **BREAKING**: `propTables` option changed to `components`. 121 | - **BREAKING**: `propsDescription` now takes the components' name as a key. 122 | - Massive changes to UI. 123 | 124 | ### Removed 125 | 126 | - **BREAKING**: Support for SB3. 127 | - `propTablesExclude` option. 128 | 129 | ### Added 130 | 131 | - Support for SB4&5. 132 | - `docsInPanel` option to show the info in an addon panel (enabled by default) 133 | - Added a webpack loader parsing Vue components with vue-docgen-api. Thanks to this feature, you can see info for events and slots, and show info exists in mixins or inherited components. 134 | 135 | ## [0.7.1] - 2019-01-23 136 | 137 | ### Fixed 138 | 139 | - Display class name when prop type is class (PR: [#64](https://github.com/pocka/storybook-addon-vue-info/pull/64)) 140 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Shota Fuji 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Deprecated 2 | 3 | This addon is deprecated due to [the retirement of official addon-info](https://github.com/storybookjs/deprecated-addons). 4 | 5 | Storybook now has an alternative addon, called [Docs addon(`addon-docs`)](https://github.com/storybookjs/storybook/tree/next/addons/docs), 6 | which comes with native Vue.js support and automatically props/events/slots documentation powered by vue-docgen-api. 7 | 8 | There will be no feature addition nor bug fixes to this repo. Please use Docs addon instead. 9 | 10 | ### Migration to Docs 11 | 12 | As described above, Docs addon uses `vue-docgen-api` via `vue-docgen-loader`. 13 | They are also the tools `storybook-addon-vue-info` uses internally. 14 | So the migration steps is quite simple. 15 | 16 | #### docgen tools are no longer `peerDependencies` 17 | 18 | Since the Docs addon specifies `vue-docgen-api` and `vue-docgen-loader` as direct dependency, 19 | you don't have to list them in your `package.json`. 20 | 21 | ```diff 22 | "dependencies": { 23 | - "vue-docgen-api": "x.x.x", 24 | - "vue-docgen-loader": "x.x.x" 25 | } 26 | ``` 27 | 28 | Of course, you can keep them to controll which exact versions to use. 29 | 30 | #### Explicitly specify which component to documentate 31 | 32 | You need to set `component` field in your story metadata. 33 | 34 | ```js 35 | // foo.stories.js 36 | import MyComponent from './my-component.vue' 37 | 38 | export default { 39 | title: 'Components/MyComponent', 40 | component: MyComponent 41 | } 42 | 43 | export const story = () => ({ 44 | components: { MyComponent }, 45 | template: '' 46 | }) 47 | ``` 48 | 49 | #### Move `summary` inside a JSDoc comment or MDX 50 | 51 | `summary` option equivalent in Docs addon is component comments or MDX. 52 | Docs addon reads a component comment and displays it as a description for the component. 53 | 54 | ```js 55 | // legacy.stories.js 56 | export const myStory = () => ({ 57 | /* ... */ 58 | }) 59 | 60 | myStory.story = { 61 | info: { 62 | summary: 'foo bar' 63 | } 64 | } 65 | ``` 66 | 67 | ```html 68 | 69 | 77 | ``` 78 | 79 | Or you can use [MDX](https://github.com/storybookjs/storybook/tree/next/addons/docs#mdx) for more complicated usage. 80 | 81 | --- 82 | 83 |
84 | 85 | logo 86 |
87 | 88 | [![Build Status](https://travis-ci.com/pocka/storybook-addon-vue-info.svg?branch=master)](https://travis-ci.com/pocka/storybook-addon-vue-info) 89 | [![npm version](https://badge.fury.io/js/storybook-addon-vue-info.svg)](https://badge.fury.io/js/storybook-addon-vue-info) 90 | [![Monthly download](https://img.shields.io/npm/dm/storybook-addon-vue-info.svg)](https://www.npmjs.com/package/storybook-addon-vue-info) 91 | [![GitHub license](https://img.shields.io/github/license/pocka/storybook-addon-vue-info.svg)](https://github.com/pocka/storybook-addon-vue-info/blob/master/LICENSE) 92 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 93 | 94 |
95 | 96 |
97 | 98 | ## storybook-addon-vue-info 99 | 100 | A Storybook addon that shows Vue component's information. 101 | 102 | - [Demo][live examples] 103 | 104 | ## Requirements 105 | 106 | - `@storybook/vue>=4.0.0` 107 | 108 | ## Getting started 109 | 110 | First, install the addon. 111 | 112 | ```sh 113 | npm install --save-dev storybook-addon-vue-info 114 | 115 | # yarn add -D storybook-addon-vue-info 116 | ``` 117 | 118 | Then register in `addons.js`. 119 | 120 | ```js 121 | // .storybook/addons.js 122 | 123 | // Don't forget "/lib/" !! 124 | import 'storybook-addon-vue-info/lib/register' 125 | ``` 126 | 127 | And setup a webpack loader in order to extract component information with [vue-docgen-api](https://github.com/vue-styleguidist/vue-docgen-api). 128 | 129 | ```sh 130 | npm install --save-dev vue-docgen-loader vue-docgen-api 131 | 132 | # yarn add -D vue-docgen-loader vue-docgen-api 133 | ``` 134 | 135 | ```js 136 | // .storybook/webpack.config.js 137 | 138 | // This example uses "Full control mode + default". 139 | // If you are using other mode, add payload of `config.module.rules.push` to rules list. 140 | module.exports = ({ config }) => { 141 | config.module.rules.push({ 142 | test: /\.vue$/, 143 | loader: 'vue-docgen-loader', 144 | enforce: 'post' 145 | }) 146 | 147 | return config 148 | } 149 | ``` 150 | 151 | ## Usage 152 | 153 | Add `withInfo` decorator then set `info` options to the story. 154 | 155 | NOTE: `info` option is required for the addon. If you omit it, the addon does nothing. 156 | 157 | ```js 158 | import { storiesOf } from '@storybook/vue' 159 | 160 | import { withInfo } from 'storybook-addon-vue-info' 161 | 162 | storiesOf('MyComponent', module) 163 | .addDecorator(withInfo) 164 | .add( 165 | 'foo', 166 | () => ({ 167 | components: { MyAwesomeComponent }, 168 | template: '' 169 | }), 170 | { 171 | info: { 172 | summary: 'Summary for MyComponent' 173 | } 174 | } 175 | ) 176 | ``` 177 | 178 | You can set the addon as global decorator. 179 | 180 | ```js 181 | // config.js 182 | import { addDecorator } from '@storybook/vue' 183 | 184 | import { withInfo } from 'storybook-addon-vue-info' 185 | 186 | addDecorator(withInfo) 187 | ``` 188 | 189 | To set default options, use `setDefaults`. 190 | 191 | ```js 192 | // .storybook/config.js 193 | import { setDefaults } from 'storybook-addon-vue-info' 194 | 195 | setDefaults({ 196 | header: false 197 | }) 198 | ``` 199 | 200 | For more details, see [live examples]. 201 | 202 | ## Options 203 | 204 | | Name | Data type | Default value | Description | 205 | | ------------------ | --------------------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------- | 206 | | `header` | `boolean` | `true` | Whether to show header or not. | 207 | | `source` | `boolean` | `true` | Whether to show source(usage) or not. | 208 | | `wrapperComponent` | `Component` | [default wrapper](src/components/Wrapper/index.vue) | Override inline docs component. | 209 | | `previewClassName` | `string` | `undefined` | Class name passed down to preview container. | 210 | | `previewStyle` | Style object | `undefined` | Style passed down to preview container. | 211 | | `summary` | `string` | `''` | Summary for the story. Accepts Markdown. | 212 | | `components` | `{ [name: string]: Component }\|null` | `null` | Display info for these components. Same type as component's `components` property. | 213 | | `docsInPanel` | `boolean` | `true` | Whether to show docs in addon panel. | 214 | | `useDocgen` | `boolean` | `true` | Whether to use result of vue-docgen-api. | 215 | | `casing` | `"kebab" \| "camel" \| "pascal" \| undefined` | `undefined` | Which case to use. For detailed usage, see below. | 216 | 217 | ### Valid `casing` options 218 | 219 | ```js 220 | { 221 | // Don't convert names 222 | casing: undefined 223 | } 224 | 225 | { 226 | // Show names in kebab-case 227 | casing 'kebab' 228 | } 229 | 230 | { 231 | // Show prop names in camelCase and 232 | // Show component names in PascalCase 233 | casing: 'camel' // or 'pascal' 234 | } 235 | 236 | { 237 | // Show prop names in camelCase and 238 | // Show component names in kebab-case 239 | casing: { 240 | props: 'camel', 241 | component: 'kebab' 242 | } 243 | } 244 | ``` 245 | 246 | In addition to addon options, we have a component option. 247 | 248 | ### Set descriptions manually 249 | 250 | With vue-docgen-api, the addon automatically shows descriptions and types extracted by docgen ([see example in vue-docgen-api README](https://github.com/vue-styleguidist/vue-styleguidist/tree/dev/packages/vue-docgen-api#example)). 251 | However, if you want to explicitly specify desciprion for component props, events or slots, add `description` option for your story component. 252 | 253 | Assume `` have props `label` and `visible`. 254 | 255 | ```js 256 | storiesOf('MyComponent', module) 257 | .addDecorator(withInfo) 258 | .add( 259 | 'foo', 260 | () => ({ 261 | components: { MyAwesomeComponent }, 262 | template: '', 263 | description: { 264 | MyAwesomeComponent: { 265 | props: { 266 | // These description will appear in `description` column in props table 267 | label: 'A label for my awesome component', 268 | visible: 'Whether component is visible or not' 269 | }, 270 | events: { 271 | click: 'Event for user clicking the component' 272 | }, 273 | slots: { 274 | default: 'Place text or icon here' 275 | } 276 | } 277 | } 278 | }), 279 | { 280 | info: true 281 | } 282 | ) 283 | ``` 284 | 285 | For more detail, please take a look at [live example](https://storybook-addon-vue-info.netlify.com/?path=/story/examples-advance-usage--set-descriptions-manually). 286 | 287 | ## Example 288 | 289 | For real example, see `example` directory. 290 | 291 | [live examples]: https://storybook-addon-vue-info.netlify.com/?path=/story/examples-basic-usage--simple-example 292 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pocka/storybook-addon-vue-info/59b662a48575c685895acc06bfcc86cb4db19b33/assets/logo.png -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-vue-jsx" 4 | ], 5 | "presets": ["vue"] 6 | } 7 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | -------------------------------------------------------------------------------- /example/.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import 'storybook-addon-vue-info/lib/register' 2 | 3 | import '@storybook/addon-a11y/register' 4 | import '@storybook/addon-knobs/register' 5 | import '@storybook/addon-backgrounds/register' 6 | import '@storybook/addon-viewport/register' 7 | import '@storybook/addon-storysource/register' 8 | -------------------------------------------------------------------------------- /example/.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure, addDecorator } from '@storybook/vue' 2 | 3 | import Vue from 'vue' 4 | import VueI18n from 'vue-i18n' 5 | 6 | import { withInfo } from 'storybook-addon-vue-info' 7 | 8 | import BaseButton from '../components/BaseButton.vue' 9 | 10 | Vue.component('base-button', BaseButton) 11 | 12 | Vue.use(VueI18n) 13 | 14 | addDecorator(withInfo) 15 | 16 | configure(require.context('../stories', true, /\.stories\.js$/), module) 17 | -------------------------------------------------------------------------------- /example/.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = ({ config }) => { 4 | config.resolve.alias = { 5 | ...config.resolve.alias, 6 | '~': path.resolve(__dirname, '../stories') 7 | } 8 | 9 | config.module.rules.push({ 10 | test: /\.vue$/, 11 | loader: 'vue-docgen-loader', 12 | options: { 13 | docgenOptions: { 14 | alias: config.resolve.alias 15 | } 16 | }, 17 | enforce: 'post' 18 | }) 19 | 20 | config.module.rules.push({ 21 | test: /\.js$/, 22 | resourceQuery: /component/, 23 | loader: 'vue-docgen-loader', 24 | enforce: 'post' 25 | }) 26 | 27 | config.module.rules.push({ 28 | test: /\.stories\.js$/, 29 | loaders: [require.resolve('@storybook/addon-storysource/loader')], 30 | enforce: 'pre' 31 | }) 32 | 33 | return config 34 | } 35 | -------------------------------------------------------------------------------- /example/components/BaseBlank.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /example/components/BaseButton.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 42 | 43 | 67 | -------------------------------------------------------------------------------- /example/components/NumberList.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 26 | -------------------------------------------------------------------------------- /example/components/customDocs/wrapper/Wrapper.css: -------------------------------------------------------------------------------- 1 | .savi-wrapper { 2 | position: absolute; 3 | top: 0; 4 | right: 0; 5 | bottom: 0; 6 | left: 0; 7 | 8 | overflow: auto; 9 | color: #fff; 10 | background-color: #d5287b; 11 | font-family: Roboto, sans-serif; 12 | } 13 | 14 | .header { 15 | padding: 32px; 16 | padding-bottom: 60px; 17 | } 18 | 19 | .title { 20 | margin: 0; 21 | font-size: 32px; 22 | line-height: 1.1875; 23 | 24 | font-weight: 500; 25 | } 26 | 27 | .subtitle { 28 | margin: 0; 29 | margin-top: 8px; 30 | font-size: 24px; 31 | line-height: 1.208; 32 | 33 | font-weight: 400; 34 | 35 | opacity: 0.85; 36 | } 37 | 38 | .preview-container { 39 | padding: 32px; 40 | 41 | background-color: #fff; 42 | } 43 | 44 | .preview { 45 | all: initial; 46 | display: block; 47 | position: relative; 48 | } 49 | 50 | .info-body { 51 | padding: 32px; 52 | } 53 | 54 | .info-body > :not(:first-child) { 55 | padding-top: 32px; 56 | } 57 | 58 | .heading { 59 | margin: 0; 60 | font-size: 24px; 61 | line-height: 1.208; 62 | 63 | font-weight: 500; 64 | } 65 | 66 | .subheading { 67 | margin: 0; 68 | font-size: 16px; 69 | line-height: 1.1875; 70 | 71 | font-weight: 500; 72 | } 73 | 74 | .codeblock { 75 | margin: 0; 76 | margin-top: 16px; 77 | padding: 8px 16px; 78 | font-size: 12px; 79 | 80 | background-color: #444; 81 | border-radius: 4px; 82 | } 83 | 84 | .component > :not(:first-child) { 85 | margin-top: 16px; 86 | } 87 | 88 | .list { 89 | margin: 0; 90 | margin-top: 8px; 91 | padding: 0; 92 | 93 | list-style: none; 94 | } 95 | 96 | .item:not(:first-child) { 97 | margin-top: 8px; 98 | } 99 | .item::before { 100 | content: '-'; 101 | } 102 | -------------------------------------------------------------------------------- /example/components/customDocs/wrapper/Wrapper.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 146 | 147 | 26 | -------------------------------------------------------------------------------- /example/stories/compatibilities/official/knobs.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import { withKnobs, text } from '@storybook/addon-knobs' 4 | 5 | import MyButton from './components/MyButton.vue' 6 | 7 | /** 8 | * Vue.js + addon-knobs has no-updating issue now. 9 | * Stories here written with a workaround described in the issue. 10 | * https://github.com/storybooks/storybook/issues/5129 11 | */ 12 | 13 | storiesOf('Compatibilities/@storybook/addon-knobs', module) 14 | .addDecorator(withKnobs) 15 | .add( 16 | 'panel', 17 | () => ({ 18 | props: { 19 | label: { 20 | type: String, 21 | default: text('Label', 'foo') 22 | } 23 | }, 24 | components: { MyButton }, 25 | template: '{{label}}' 26 | }), 27 | { 28 | info: {} 29 | } 30 | ) 31 | .add( 32 | 'inline', 33 | () => ({ 34 | props: { 35 | label: { 36 | type: String, 37 | default: text('Label', 'foo') 38 | } 39 | }, 40 | components: { MyButton }, 41 | template: '{{label}}' 42 | }), 43 | { 44 | info: { docsInPanel: false } 45 | } 46 | ) 47 | -------------------------------------------------------------------------------- /example/stories/examples/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import BaseBlank from '../../components/BaseBlank.vue' 4 | import BaseButton from '../../components/BaseButton.vue' 5 | import NumberList from '../../components/NumberList.vue' 6 | 7 | import CustomWrapper from '../../components/customDocs/wrapper/Wrapper.vue' 8 | 9 | storiesOf('Examples/Basic usage', module) 10 | .add( 11 | 'Simple example', 12 | () => ({ 13 | components: { LocalButton: BaseButton }, 14 | template: '' 15 | }), 16 | { 17 | info: {} 18 | } 19 | ) 20 | .add( 21 | 'Show summary', 22 | () => ({ 23 | components: { BaseButton }, 24 | template: '' 25 | }), 26 | { 27 | info: ` 28 | # This is _summary_ 29 | 30 | You can write summary in [Markdown](https://en.wikipedia.org/wiki/Markdown). 31 | 32 | There is also syntax highlighting powered by [Highlight.js](https://highlightjs.org/). 33 | 34 | \`\`\`js 35 | export function foo() { 36 | console.log('Hello, World!') 37 | } 38 | \`\`\` 39 | ` 40 | } 41 | ) 42 | .add( 43 | 'Multiple components', 44 | () => ({ 45 | components: { BaseButton, NumberList }, 46 | template: ` 47 |
48 | 49 | 50 |
51 | ` 52 | }), 53 | { 54 | info: 'You can specify more than one components to show info tables.' 55 | } 56 | ) 57 | .add( 58 | 'Specify components', 59 | () => ({ 60 | components: { BaseButton, NumberList, BaseBlank }, 61 | template: ` 62 |
63 | 64 | 65 | 66 |
67 | ` 68 | }), 69 | { 70 | info: { 71 | summary: ` 72 | You can specify which components to be shown in info tables by passing \`components\` option to the addon. 73 | 74 | \`\`\`js 75 | storiesOf('...') 76 | .add('...', () => ({ 77 | // Addon ignore this 78 | components: { BaseButton, NumberList, BaseBlank }, 79 | template: '...' 80 | }), { 81 | info: { 82 | // Show tables for these 83 | components: { BaseButton, NumberList } 84 | } 85 | }) 86 | \`\`\` 87 | `, 88 | components: { BaseButton, NumberList } 89 | } 90 | } 91 | ) 92 | 93 | storiesOf('Examples/Advance usage', module) 94 | .add( 95 | 'Hide header', 96 | () => ({ 97 | components: { BaseButton }, 98 | template: '' 99 | }), 100 | { 101 | info: { 102 | summary: ` 103 | To hide header section, set \`header\` option to \`false\`. 104 | 105 | \`\`\`js 106 | { 107 | info: { 108 | header: false 109 | } 110 | } 111 | \`\`\` 112 | `, 113 | header: false 114 | } 115 | } 116 | ) 117 | .add( 118 | 'Hide story source', 119 | () => ({ 120 | components: { BaseButton }, 121 | template: '' 122 | }), 123 | { 124 | info: { 125 | summary: ` 126 | To hide story source, set \`source\` option to \`false\`. 127 | 128 | \`\`\`js 129 | { 130 | info: { 131 | source: false 132 | } 133 | } 134 | \`\`\` 135 | `, 136 | source: false 137 | } 138 | } 139 | ) 140 | .add( 141 | 'Ignore docgen info', 142 | () => ({ 143 | components: { BaseButton }, 144 | template: '' 145 | }), 146 | { 147 | info: { 148 | summary: ` 149 | To ignore component's information generated by vue-docgen-api, 150 | set \`useDocgen\` option to \`false\`. 151 | This only affects when you set our custom loader in Storybook's webpack config. 152 | 153 | \`\`\`js 154 | { 155 | info: { 156 | useDocgen: false 157 | } 158 | } 159 | \`\`\` 160 | `, 161 | useDocgen: false 162 | } 163 | } 164 | ) 165 | .add( 166 | 'Set descriptions manually', 167 | () => ({ 168 | components: { BaseButton }, 169 | template: '', 170 | description: { 171 | BaseButton: { 172 | props: { 173 | disabled: 'DISABLED!', 174 | type: 'TYPE!', 175 | label: 'LABEL!' 176 | }, 177 | events: { 178 | click: 'AWESOME CLICK EVENT!' 179 | }, 180 | slots: { 181 | default: "DEFAULT SLOT, THAT'S ALL!" 182 | } 183 | } 184 | } 185 | }), 186 | { 187 | info: { 188 | summary: ` 189 | You can set description for each props explicitly. 190 | 191 | \`\`\`js 192 | () => ({ 193 | components: { BaseButton }, 194 | template: '', 195 | description: { 196 | BaseButton: { 197 | props: { 198 | disabled: 'DISABLED!', 199 | type: 'TYPE!', 200 | label: 'LABEL!' 201 | }, 202 | events: { 203 | click: 'AWESOME CLICK EVENT!' 204 | }, 205 | slots: { 206 | default: 'DEFAULT SLOT, THAT\'S ALL!' 207 | } 208 | } 209 | } 210 | }) 211 | \`\`\` 212 | 213 | This will override descriptions generated by docgen. 214 | 215 | --- 216 | 217 | **CAUTION!** 218 | 219 | If you write descriptions for events and slots without using vue-docgen-api, 220 | the addon shows the description no matter whether the slot or event exists. 221 | The reason is we cannot know what events and slots a component have: 222 | so the addon shows attributes user wrote as "information acquired in runtime". 223 | ` 224 | } 225 | } 226 | ) 227 | .add( 228 | 'Show docs in preview area', 229 | () => ({ 230 | components: { BaseButton }, 231 | template: '' 232 | }), 233 | { 234 | info: { 235 | summary: ` 236 | To show docs in preview area, turn off \`docsInPanel\` option. 237 | 238 | \`\`\`js 239 | { 240 | info: { 241 | docsInPanel: false 242 | } 243 | } 244 | \`\`\` 245 | 246 | This will override descriptions generated by docgen. 247 | 248 | If you never use docsInPanel feature, 249 | you can omit \`import 'storybook-addon-vue-info/register\` in \`.storybook/addons.js\`. 250 | 251 | ### ATTENTION! 252 | 253 | Docs in preview wraps story component, which may cause unexpected behaviors/bugs. 254 | I personally recommend to use \`docsInPanel: true\`. 255 | `, 256 | docsInPanel: false 257 | } 258 | } 259 | ) 260 | .add( 261 | 'Apply custom styles to preview', 262 | () => ({ 263 | components: { BaseButton }, 264 | template: '' 265 | }), 266 | { 267 | info: { 268 | docsInPanel: false, 269 | previewClassName: 'my-custom-class-name', 270 | previewStyle: { 271 | backgroundColor: 'tomato', 272 | padding: '1em' 273 | }, 274 | summary: ` 275 | You can customize preview wrapper style by 276 | passing \`previewClassName\` option or \`previewStyle\` option. 277 | ` 278 | } 279 | } 280 | ) 281 | .add( 282 | 'Customize docs', 283 | () => ({ 284 | components: { BaseButton }, 285 | template: '' 286 | }), 287 | { 288 | info: { 289 | summary: ` 290 | ## Customize docs in preview 291 | 292 | To customize docs in preview area, set your docs component to \`wrapperComponent\` option. 293 | 294 | This addon passes two props: 295 | 296 | - \`info\` ... Information about story and contained components. Interface defined in \`src/types/info\` (\`StoryInfo\`). 297 | - \`options\` ... Addon options. 298 | 299 | Story component is passed as default slot. 300 | 301 | For more detail, please look at source code of this example 302 | (\`example/components/customDocs/wrapper\`). 303 | 304 |
305 | 306 | ## Customize docs in addon panel 307 | 308 | Not supported. 309 | 310 | It's challenging to change components in addon panel. 311 | To avoid plugin being complex and messy, customizing addon panel is not supported. 312 | `, 313 | wrapperComponent: CustomWrapper, 314 | docsInPanel: false 315 | } 316 | } 317 | ) 318 | .add( 319 | 'Writes story in JSX', 320 | () => ({ 321 | render(h) { 322 | return 323 | } 324 | }), 325 | { 326 | info: { 327 | summary: ` 328 | You can also write story in render function with JSX. 329 | Of course you should configure Babel. 330 | 331 | \`\`\`jsx 332 | () => ({ 333 | render(h) { 334 | return 335 | } 336 | }) 337 | \`\`\` 338 | ` 339 | } 340 | } 341 | ) 342 | -------------------------------------------------------------------------------- /example/stories/issues/119/component.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | const JsButton = Vue.extend({ 4 | props: { 5 | /** 6 | * My awesome props! 7 | */ 8 | awesome: { 9 | type: String, 10 | default: 'foo' 11 | } 12 | }, 13 | template: '' 14 | }) 15 | 16 | export { JsButton } 17 | -------------------------------------------------------------------------------- /example/stories/issues/119/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import { JsButton as Issue119 } from './component.js?component' 4 | 5 | storiesOf('Issues/#119', module).add( 6 | 'Parse non-SFC component with docgen', 7 | () => ({ 8 | components: { Issue119 }, 9 | template: '' 10 | }), 11 | { 12 | info: { 13 | summary: '' 14 | } 15 | } 16 | ) 17 | -------------------------------------------------------------------------------- /example/stories/issues/122/camel.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /example/stories/issues/122/index.stories.js: -------------------------------------------------------------------------------- 1 | import CamelComponent from './camel.vue' 2 | import KebabComponent from './kebab.vue' 3 | import MixedComponent from './mixed.vue' 4 | 5 | export default { 6 | title: 'Issues/#122' 7 | } 8 | 9 | export const camelToKebab = () => { 10 | return { 11 | components: { CamelComponent }, 12 | template: '' 13 | } 14 | } 15 | camelToKebab.story = { 16 | title: 'Convert camelCase to kebab-case', 17 | parameters: { 18 | info: { 19 | casing: 'kebab' 20 | } 21 | } 22 | } 23 | 24 | export const kebabToCamel = () => { 25 | return { 26 | components: { KebabComponent }, 27 | template: '' 28 | } 29 | } 30 | kebabToCamel.story = { 31 | title: 'Convert kebab-case to camelCase', 32 | parameters: { 33 | info: { 34 | casing: 'camel' 35 | } 36 | } 37 | } 38 | 39 | export const mixedToKebab = () => { 40 | return { 41 | components: { MixedComponent }, 42 | template: '' 43 | } 44 | } 45 | mixedToKebab.story = { 46 | title: 'Convert to kebab-case (mixed)', 47 | parameters: { 48 | info: { 49 | casing: 'kebab' 50 | } 51 | } 52 | } 53 | 54 | export const mixedToCamel = () => { 55 | return { 56 | components: { MixedComponent }, 57 | template: '' 58 | } 59 | } 60 | mixedToCamel.story = { 61 | title: 'Convert to camelCase (mixed)', 62 | parameters: { 63 | info: { 64 | casing: 'camel' 65 | } 66 | } 67 | } 68 | 69 | export const preserve = () => { 70 | return { 71 | components: { MixedComponent }, 72 | template: '' 73 | } 74 | } 75 | preserve.story = { 76 | title: 'Preserve casing when undefined', 77 | parameters: { 78 | info: {} 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example/stories/issues/122/kebab.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /example/stories/issues/122/mixed.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 19 | -------------------------------------------------------------------------------- /example/stories/issues/126/component.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /example/stories/issues/126/index.stories.js: -------------------------------------------------------------------------------- 1 | import Issue126 from './component.vue' 2 | 3 | export default { 4 | title: 'Issues/#126' 5 | } 6 | 7 | export const showDefaultValue = () => { 8 | return { 9 | components: { Issue126 }, 10 | template: '' 11 | } 12 | } 13 | 14 | showDefaultValue.story = { 15 | title: 'Show default value without docgen', 16 | parameters: { 17 | info: { 18 | useDocgen: false 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/stories/issues/21/BulmaButton.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /example/stories/issues/21/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import BulmaButton from './BulmaButton' 4 | 5 | storiesOf('Issues/#21', module).add( 6 | 'Do not reset preview style', 7 | () => ({ 8 | components: { BulmaButton }, 9 | template: '' 10 | }), 11 | { 12 | info: { 13 | docsInPanel: false, 14 | summary: '' 15 | } 16 | } 17 | ) 18 | -------------------------------------------------------------------------------- /example/stories/issues/50/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import BaseButton from '../../../components/BaseButton.vue' 4 | 5 | storiesOf('Issues/#50', module) 6 | .addDecorator(() => ({ 7 | template: '
' 8 | })) 9 | .add( 10 | 'Do not show decorators', 11 | () => ({ 12 | components: { BaseButton }, 13 | template: '' 14 | }), 15 | { 16 | info: { 17 | summary: '' 18 | } 19 | } 20 | ) 21 | -------------------------------------------------------------------------------- /example/stories/issues/53/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import NumberList from '../../../components/NumberList.vue' 4 | 5 | storiesOf('Issues/#53', module).add( 6 | 'Should load tables even for PascalCase components', 7 | () => ({ 8 | components: { NumberList }, 9 | template: '' 10 | }), 11 | { 12 | info: { 13 | summary: '', 14 | useDocgen: false 15 | } 16 | } 17 | ) 18 | -------------------------------------------------------------------------------- /example/stories/issues/59/child.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /example/stories/issues/59/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import Issue59 from './child.vue' 4 | 5 | storiesOf('Issues/#59', module).add( 6 | 'Should load extended component properly', 7 | () => ({ 8 | components: { Issue59 }, 9 | template: '' 10 | }), 11 | { 12 | info: '' 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /example/stories/issues/59/parent.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /example/stories/issues/63/component.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /example/stories/issues/63/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import VueI18n from 'vue-i18n' 4 | 5 | import Issue63 from './component.vue' 6 | 7 | storiesOf('Issues/#63', module).add( 8 | 'Should display error message when failed to get default', 9 | () => ({ 10 | components: { Issue63 }, 11 | i18n: new VueI18n({ 12 | locale: 'en', 13 | messages: { 14 | en: { 15 | hello: 'Hello, World' 16 | } 17 | } 18 | }), 19 | template: '' 20 | }), 21 | { 22 | info: { 23 | summary: '', 24 | useDocgen: false 25 | } 26 | } 27 | ) 28 | -------------------------------------------------------------------------------- /example/stories/issues/71/component.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /example/stories/issues/71/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import Issue71 from './component.vue' 4 | 5 | storiesOf('Issues/#71', module).add( 6 | 'Should not emit error when component has no props', 7 | () => ({ 8 | components: { Issue71 }, 9 | template: '' 10 | }), 11 | { 12 | info: '' 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /example/stories/issues/72/component.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /example/stories/issues/72/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import Issue72 from './component.vue' 4 | 5 | storiesOf('Issues/#72', module).add( 6 | 'Should not emit error when events has no payloads', 7 | () => ({ 8 | components: { Issue72 }, 9 | template: '' 10 | }), 11 | { 12 | info: '' 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /example/stories/issues/85/component.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /example/stories/issues/85/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import Issue85 from './component.vue' 4 | 5 | storiesOf('Issues/#85', module).add( 6 | 'Should show an error when docgen-api failed to parse', 7 | () => ({ 8 | components: { Issue85 }, 9 | template: '' 10 | }), 11 | { 12 | info: '' 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /example/stories/issues/85/instance.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | label() { 4 | return 'Hello, World!' 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /example/stories/issues/86/component.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /example/stories/issues/86/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import Issue86 from './component.vue' 4 | 5 | storiesOf('Issues/#86', module).add( 6 | 'Configure docgen-api via loader options', 7 | () => ({ 8 | components: { Issue86 }, 9 | template: '' 10 | }), 11 | { 12 | info: '' 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /example/stories/issues/86/mixin.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /example/stories/issues/95/component.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /example/stories/issues/95/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import Issue95 from './component.vue' 4 | 5 | storiesOf('Issues/#95', module) 6 | .add( 7 | 'Should not emit error for no-type props (with docgen)', 8 | () => ({ 9 | components: { Issue95 }, 10 | template: '' 11 | }), 12 | { 13 | info: '' 14 | } 15 | ) 16 | .add( 17 | 'Should not emit error for no-type props (runtime)', 18 | () => ({ 19 | components: { Issue95 }, 20 | template: '' 21 | }), 22 | { 23 | info: { 24 | summary: 25 | '', 26 | useDocgen: false 27 | } 28 | } 29 | ) 30 | -------------------------------------------------------------------------------- /loader/index.js: -------------------------------------------------------------------------------- 1 | const clone = require('clone') 2 | const docgen = require('vue-docgen-api') 3 | const loaderUtils = require('loader-utils') 4 | const qs = require('querystring') 5 | 6 | console.warn( 7 | 'storybook-addon-vue-info/loader is deprecated. Please consider using vue-docgen-loader instead.' 8 | ) 9 | 10 | module.exports = function(content, map) { 11 | const queries = qs.parse(this.resourceQuery.slice(1)) 12 | 13 | // Ensure not to apply to each blocks ( 48 | 49 | 105 | 106 | 24 | -------------------------------------------------------------------------------- /src/components/Header/style.css: -------------------------------------------------------------------------------- 1 | .container { 2 | color: #333; 3 | font-family: Roboto, sans-serif; 4 | } 5 | 6 | .title, 7 | .subtitle { 8 | margin: 0; 9 | } 10 | 11 | .title { 12 | font-size: 24px; 13 | 14 | font-weight: 500; 15 | } 16 | 17 | .subtitle { 18 | font-size: 18px; 19 | margin-top: 8px; 20 | 21 | font-weight: 400; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Preview/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 28 | 29 | 82 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Component } from './Component/index.vue' 2 | export { default as Docs } from './Docs/index.vue' 3 | export { default as Header } from './Header/index.vue' 4 | export { default as Preview } from './Preview/index.vue' 5 | export { default as SectionContainer } from './SectionContainer/index.vue' 6 | export { default as Separator } from './Separator/index.vue' 7 | export { default as StorySource } from './StorySource/index.vue' 8 | export { default as Summary } from './Summary/index.vue' 9 | export { default as Table } from './Table/index.vue' 10 | export { default as Wrapper } from './Wrapper/index.vue' 11 | -------------------------------------------------------------------------------- /src/extract/decideTargets.ts: -------------------------------------------------------------------------------- 1 | import { paramCase } from 'change-case' 2 | import Vue, { ComponentOptions } from 'vue' 3 | 4 | import { InfoAddonOptions } from '../options' 5 | import { ComponentRegistory } from '../types/vue' 6 | import * as getTagNames from '../utils/getTagNames' 7 | 8 | import { lookupGlobalComponent, LookupResult } from './lookup' 9 | 10 | export function decideTargets( 11 | story: ComponentOptions, 12 | options: InfoAddonOptions 13 | ): ComponentRegistory { 14 | // Components user set explicitly 15 | if (options.components) { 16 | return normalizeComponents(options.components) 17 | } 18 | 19 | // Components used in story 20 | if (story.components) { 21 | return normalizeComponents(story.components) 22 | } 23 | 24 | // If components property does not exist in options nor story components, 25 | // parse template/render and return components used in template/render. 26 | 27 | if (!story.template && !story.render) { 28 | throw new Error('`template` or `render` must be on component options.') 29 | } 30 | 31 | const tagNames = story.template 32 | ? getTagNames.fromTemplate(story.template).map(s => paramCase(s)) 33 | : getTagNames.fromJSX(story.render!) 34 | 35 | const components = tagNames 36 | .map(tagName => lookupGlobalComponent(tagName)) 37 | .filter((res): res is LookupResult => !!res) 38 | 39 | const ret: ComponentRegistory = {} 40 | 41 | for (const { name, component } of components) { 42 | ret[name] = component 43 | } 44 | 45 | return normalizeComponents(ret) 46 | } 47 | 48 | const normalizeComponents = (c: ComponentRegistory): ComponentRegistory => { 49 | const ret: ComponentRegistory = {} 50 | 51 | // There are cases component options only exists under "options" property 52 | for (const key of Object.keys(c)) { 53 | ret[key] = (c[key] as any).__docgenInfo 54 | ? c[key] 55 | : (c[key] as any).options || c[key] 56 | } 57 | 58 | return ret 59 | } 60 | -------------------------------------------------------------------------------- /src/extract/extractDocgenInfo.ts: -------------------------------------------------------------------------------- 1 | import { ComponentInfo } from '../types/info' 2 | import { AnyComponent } from '../types/vue' 3 | 4 | type Extracted = Pick> 5 | 6 | const normalizeAttrs = (attrs: { [name: string]: any } | any[]): any[] => 7 | attrs instanceof Array ? attrs : Object.keys(attrs).map(key => attrs[key]) 8 | 9 | export function extractDocgenInfo(component: AnyComponent): Extracted { 10 | const docs = (component as any).__docgenInfo 11 | 12 | const props = docs.props 13 | ? normalizeAttrs(docs.props).map(prop => { 14 | return { 15 | name: prop.name, 16 | type: prop.type ? prop.type.name : 'any', 17 | required: !!prop.required, 18 | default: prop.defaultValue ? prop.defaultValue.value : undefined, 19 | description: prop.description 20 | } 21 | }) 22 | : [] 23 | 24 | const events = docs.events 25 | ? normalizeAttrs(docs.events).map(ev => { 26 | const type = ev.type ? ev.type.names.join(', ') : '' 27 | 28 | return { 29 | name: ev.name, 30 | type, 31 | description: ev.description 32 | } 33 | }) 34 | : [] 35 | 36 | const slots = docs.slots 37 | ? normalizeAttrs(docs.slots).map(slot => { 38 | return { 39 | name: slot.name, 40 | description: slot.description 41 | } 42 | }) 43 | : [] 44 | 45 | return { props, events, slots } 46 | } 47 | -------------------------------------------------------------------------------- /src/extract/getProps.ts: -------------------------------------------------------------------------------- 1 | import { PropInfo } from '../types/info' 2 | import { AnyComponent } from '../types/vue' 3 | import { constructorToString } from '../utils/constructorToString' 4 | 5 | export function getProps(component: AnyComponent): PropInfo[] { 6 | // Async component (Promise) 7 | if ( 8 | component instanceof Promise || 9 | ('component' in component && component.component instanceof Promise) 10 | ) { 11 | console.warn( 12 | '[storybook-addon-vue-info] Cannot enumerate props for async component' 13 | ) 14 | return [] 15 | } 16 | 17 | // Case given Vue instance 18 | if (!('props' in component)) { 19 | return [] 20 | } 21 | 22 | const { props } = component 23 | 24 | if (!props) { 25 | return [] 26 | } 27 | 28 | return Object.keys(props).map(name => { 29 | const propDef = props[name] 30 | 31 | // If there are no props defined in "prop: Object" sytle, 32 | // Vue does not convert "prop: Constructor" into "prop: Object" style (See #3). 33 | if (typeof propDef === 'function') { 34 | return { 35 | name, 36 | type: constructorToString(propDef), 37 | required: false, 38 | default: undefined 39 | } 40 | } 41 | 42 | let default$ 43 | 44 | if (propDef.type === Function && propDef.default) { 45 | default$ = propDef.default.toString() 46 | } else if (typeof propDef.default === 'function') { 47 | try { 48 | default$ = JSON.stringify(propDef.default.apply(component)) 49 | } catch (e) { 50 | console.error(e) 51 | console.warn( 52 | `[storybook-addon-vue-info] Failed to get default value for ${name}` 53 | ) 54 | default$ = 'Failed to get default value' 55 | } 56 | } else { 57 | default$ = 58 | typeof propDef === 'object' && 'default' in propDef 59 | ? JSON.stringify(propDef.default) 60 | : '' 61 | } 62 | 63 | return { 64 | name, 65 | type: propDef.type ? constructorToString(propDef.type) : 'any', 66 | required: propDef.required, 67 | default: default$ 68 | } 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /src/extract/index.ts: -------------------------------------------------------------------------------- 1 | import { paramCase } from 'change-case' 2 | import dedent from 'dedent-tabs' 3 | import hljs from 'highlight.js' 4 | import marked from 'marked' 5 | import Vue, { ComponentOptions } from 'vue' 6 | 7 | import { InfoAddonOptions } from '../options' 8 | import { ComponentInfo, StoryInfo } from '../types/info' 9 | import { getJSXFromRenderFn } from '../utils/getJSXFromRenderFn' 10 | 11 | import { decideTargets } from './decideTargets' 12 | import { extractDocgenInfo } from './extractDocgenInfo' 13 | import { getProps } from './getProps' 14 | 15 | export function extract( 16 | story: ComponentOptions, 17 | kindName: string, 18 | storyName: string, 19 | options: InfoAddonOptions 20 | ): StoryInfo { 21 | const targets = decideTargets(story, options) 22 | 23 | const descriptions = 24 | getDescriptionsFromStory(story) || formatPropsDescription(story) 25 | 26 | const components = Object.keys(targets).map(name => { 27 | const component = targets[name] 28 | const kebabName = paramCase(name) 29 | 30 | const propDescriptions = 31 | (descriptions[kebabName] && descriptions[kebabName].props) || {} 32 | const eventDescriptions = 33 | (descriptions[kebabName] && descriptions[kebabName].events) || {} 34 | const slotDescriptions = 35 | (descriptions[kebabName] && descriptions[kebabName].slots) || {} 36 | 37 | if (options.useDocgen && '__docgenInfo' in component) { 38 | const partial = extractDocgenInfo(component) 39 | 40 | const hydrateStoryDescription = < 41 | T extends { name: string; description?: string } 42 | >( 43 | defs: T[], 44 | description: Description 45 | ): T[] => { 46 | return defs.map(def => { 47 | if (def.name in description) { 48 | return { 49 | ...def, 50 | description: description[def.name] 51 | } 52 | } 53 | 54 | return def 55 | }) 56 | } 57 | 58 | return { 59 | name, 60 | ...partial, 61 | props: 62 | partial.props && 63 | hydrateStoryDescription(partial.props, propDescriptions), 64 | events: 65 | partial.events && 66 | hydrateStoryDescription(partial.events, eventDescriptions), 67 | slots: 68 | partial.slots && 69 | hydrateStoryDescription(partial.slots, slotDescriptions) 70 | } 71 | } 72 | 73 | const props = getProps(component).map(prop => { 74 | if (prop.name in propDescriptions) { 75 | return { 76 | ...prop, 77 | description: propDescriptions[prop.name] 78 | } 79 | } 80 | 81 | return prop 82 | }) 83 | 84 | const events = Object.keys(eventDescriptions).map(eventName => { 85 | return { 86 | name: eventName, 87 | description: eventDescriptions[eventName] 88 | } 89 | }) 90 | 91 | const slots = Object.keys(slotDescriptions).map(slotName => { 92 | return { 93 | name: slotName, 94 | description: slotDescriptions[slotName] 95 | } 96 | }) 97 | 98 | return { name, props, events, slots } 99 | }) 100 | 101 | // Set up markdown renderer for summary 102 | const renderer = new marked.Renderer() 103 | 104 | renderer.code = (code, lang) => 105 | `
${
106 |       hljs.highlightAuto(code, lang ? [lang] : undefined).value
107 |     }
` 108 | 109 | marked.setOptions({ renderer }) 110 | 111 | return { 112 | title: kindName, 113 | subtitle: storyName, 114 | summary: marked(dedent(options.summary)), 115 | storySource: dedent( 116 | story.template || getJSXFromRenderFn(story.render! as any) 117 | ), 118 | jsxStory: !!story.render, 119 | components 120 | } 121 | } 122 | 123 | // Description for props, events and methods. 124 | interface Description { 125 | [name: string]: string 126 | } 127 | 128 | // Description object user 129 | interface Descriptions { 130 | [componentName: string]: { 131 | props?: Description 132 | events?: Description 133 | slots?: Description 134 | } 135 | } 136 | 137 | const getDescriptionsFromStory = (story: any): Descriptions | null => { 138 | if (!story.description) { 139 | return null 140 | } 141 | 142 | const ret: Descriptions = {} 143 | 144 | for (const component of Object.keys(story.description)) { 145 | ret[paramCase(component)] = story.description[component] 146 | } 147 | 148 | return ret 149 | } 150 | 151 | const formatPropsDescription = (story: any): Descriptions => { 152 | if (!story.propsDescription) { 153 | return {} 154 | } 155 | 156 | console.warn( 157 | '[storybook-addon-vue-info] `propsDescription` is deprecated. Please consider switching to `description.props`' 158 | ) 159 | 160 | const components: Descriptions = {} 161 | 162 | for (const component of Object.keys(story.propsDescription)) { 163 | components[paramCase(component)] = { 164 | props: story.propsDescription[component] 165 | } 166 | } 167 | 168 | return components 169 | } 170 | -------------------------------------------------------------------------------- /src/extract/lookup.ts: -------------------------------------------------------------------------------- 1 | import { paramCase } from 'change-case' 2 | import Vue from 'vue' 3 | 4 | import { AnyComponent } from '../types/vue' 5 | 6 | export interface LookupResult { 7 | name: string 8 | component: AnyComponent 9 | } 10 | 11 | /** 12 | * Lookup component matched to specified name which registered at global level. 13 | * @param name A name to lookup (must be hyphenate) 14 | */ 15 | export function lookupGlobalComponent(name: string): LookupResult | null { 16 | for (const componentName in (Vue as any).options.components) { 17 | if (paramCase(componentName) === name) { 18 | const target = (Vue as any).options.components[componentName] 19 | 20 | return { 21 | name, 22 | component: target 23 | } 24 | } 25 | } 26 | 27 | return null 28 | } 29 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import { makeDecorator } from '@storybook/addons' 4 | 5 | import { extract } from './extract' 6 | import { defaultOptions, InfoAddonOptions } from './options' 7 | import { renderToPanel } from './view' 8 | 9 | export * from './components' 10 | 11 | function findBottomStorybookWraps(w: any) { 12 | while ( 13 | w && 14 | w.options && 15 | w.options.components && 16 | w.options.components.story && 17 | w.options.components.story.options && 18 | w.options.components.story.options.STORYBOOK_WRAPS 19 | ) { 20 | w = w.options.components.story.options.STORYBOOK_WRAPS 21 | } 22 | return w 23 | } 24 | 25 | function getComponentOptions(story: any) { 26 | if (story.fnOptions && story.fnOptions.STORYBOOK_WRAPS) { 27 | return findBottomStorybookWraps(story.fnOptions.STORYBOOK_WRAPS).options 28 | } 29 | return ((story.componentOptions && story.componentOptions.Ctor) || {}).options 30 | } 31 | 32 | export const withInfo = makeDecorator({ 33 | name: 'withInfo', 34 | parameterName: 'info', 35 | skipIfNoParametersOrOptions: true, 36 | wrapper(getStory, context, { parameters }) { 37 | // Normalize options and set defaults 38 | const options = { 39 | ...defaultOptions, 40 | ...(parameters === true 41 | ? {} 42 | : typeof parameters === 'string' 43 | ? { summary: parameters } 44 | : parameters) 45 | } 46 | 47 | return Vue.extend({ 48 | render(h) { 49 | const story = h(getStory(context)) as any 50 | const componentOptions = getComponentOptions(story) 51 | const info = extract( 52 | componentOptions, 53 | context.kind, 54 | context.story, 55 | options 56 | ) 57 | 58 | if (options.docsInPanel) { 59 | renderToPanel(info, options) 60 | return story 61 | } 62 | 63 | return h(options.wrapperComponent, { props: { info, options } }, [ 64 | story 65 | ]) 66 | } 67 | }) 68 | } 69 | }) 70 | 71 | /** 72 | * Set default options. 73 | * 74 | * @param options options to override with 75 | */ 76 | export function setDefaults(options: Partial | string): void { 77 | Object.assign( 78 | defaultOptions, 79 | typeof options === 'string' ? { summary: options } : options 80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /src/lib.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:ban-types */ 2 | 3 | declare module 'vue-template-compiler' { 4 | export interface ASTElement { 5 | attrs?: any[] 6 | attrsList: any[] 7 | attrsMap: { [key: string]: any } 8 | children: ASTElement[] 9 | parent?: ASTElement 10 | plain: boolean 11 | static: boolean 12 | tag?: string 13 | } 14 | export interface CompileResult { 15 | ast?: ASTElement 16 | render: string 17 | staticRenderFns: string[] 18 | errors: string[] 19 | } 20 | export function compile(template: string): CompileResult 21 | } 22 | 23 | declare module 'vuera' { 24 | import * as React from 'react' 25 | import { AsyncComponent, Component } from 'vue' 26 | 27 | interface Props { 28 | component: 29 | | Component 30 | | AsyncComponent 31 | [prop: string]: any 32 | } 33 | 34 | export const VueWrapper: React.SFC 35 | } 36 | 37 | declare module '@storybook/addons' { 38 | import { SFC } from 'react' 39 | 40 | export interface Api { 41 | onStory(callback: Function): () => void 42 | } 43 | 44 | export interface Channel { 45 | on(name: string, handler: Function): void 46 | emit(name: string, payload: any): void 47 | 48 | removeListener(name: string, handler: Function): void 49 | } 50 | 51 | interface Mod { 52 | register(name: string, callback: (api: Api) => any): void 53 | 54 | addPanel( 55 | name: string, 56 | options: { 57 | title: string 58 | render: SFC<{ active: boolean; key: string }> 59 | } 60 | ): void 61 | 62 | getChannel(): Channel 63 | } 64 | 65 | const addons: Mod 66 | 67 | export default addons 68 | 69 | export interface DecoratorOptions { 70 | name: string 71 | parameterName: string 72 | skipIfNoParametersOrOptions?: boolean 73 | wrapper( 74 | getStory: (ctx: any) => any, 75 | ctx: any, 76 | options: { parameters: any } 77 | ): any 78 | } 79 | 80 | export function makeDecorator(opts: DecoratorOptions): any 81 | } 82 | -------------------------------------------------------------------------------- /src/options.ts: -------------------------------------------------------------------------------- 1 | import { AnyComponent, ComponentRegistory } from './types/vue' 2 | 3 | import DefaultWrapper from './components/Wrapper/index.vue' 4 | 5 | export const defaultOptions: InfoAddonOptions = { 6 | header: true, 7 | source: true, 8 | summary: '', 9 | components: null, 10 | wrapperComponent: DefaultWrapper, 11 | docsInPanel: true, 12 | useDocgen: true, 13 | casing: undefined 14 | } 15 | 16 | type Casing = 17 | | undefined 18 | | 'camel' 19 | | 'camelCase' 20 | | 'kebab' 21 | | 'kebab-case' 22 | | 'pascal' 23 | | 'PascalCase' 24 | 25 | /** 26 | * Addon options 27 | */ 28 | export interface InfoAddonOptions { 29 | /** 30 | * Toggles display of header with component name and description 31 | */ 32 | header: boolean 33 | 34 | /** 35 | * Displays the source of story Component 36 | */ 37 | source: boolean 38 | 39 | /** 40 | * Summary for the Story 41 | */ 42 | summary: string 43 | 44 | previewClassName?: string 45 | previewStyle?: ElementCSSInlineStyle 46 | 47 | /** 48 | * Explicitly specify components to display info 49 | */ 50 | components: ComponentRegistory | false | null 51 | 52 | /** 53 | * Customize wrapper docs by overriding components 54 | */ 55 | wrapperComponent: AnyComponent 56 | 57 | /** 58 | * Whether to show docs in panel instead of wrapping story 59 | */ 60 | docsInPanel: boolean 61 | 62 | /** 63 | * Whether to use component infomation generated by vue-docgen-api 64 | */ 65 | useDocgen: boolean 66 | 67 | casing: 68 | | Casing 69 | | { 70 | props: Casing 71 | component: Casing 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/register.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import Vue, { ComponentOptions } from 'vue' 3 | import { VueWrapper } from 'vuera' 4 | 5 | import addons, { Api, Channel } from '@storybook/addons' 6 | 7 | import { AddonName, PanelName, Events } from './addon' 8 | import { InfoAddonOptions } from './options' 9 | import { StoryInfo } from './types/info' 10 | 11 | import Wrapper from './components/Wrapper/index.vue' 12 | 13 | interface Props { 14 | channel: Channel 15 | api: Api 16 | active: boolean 17 | } 18 | 19 | interface State { 20 | /** Information of current story */ 21 | info?: StoryInfo 22 | /** Addon options */ 23 | options?: InfoAddonOptions 24 | } 25 | 26 | class Info extends React.Component { 27 | public state: State = {} 28 | 29 | /** Callback to remove event handler */ 30 | private stopListen?: () => void 31 | 32 | /** 33 | * Callback for ShowDocs event 34 | */ 35 | private onShowDocs = ({ info, options }: State) => { 36 | this.setState({ info, options }) 37 | } 38 | 39 | public componentDidMount() { 40 | const { channel, api } = this.props 41 | 42 | channel.on(Events.ShowDocs, this.onShowDocs) 43 | 44 | this.stopListen = api.onStory(() => { 45 | // Clear panel when story changes 46 | this.setState({ 47 | info: undefined, 48 | options: undefined 49 | }) 50 | }) 51 | } 52 | 53 | public render() { 54 | const { info, options } = this.state 55 | const { active } = this.props 56 | 57 | if (!active || !info || !options) { 58 | return null 59 | } 60 | 61 | return ( 62 |
63 | 64 |
65 | ) 66 | } 67 | 68 | public componentWillUnmount() { 69 | if (this.stopListen) { 70 | this.stopListen() 71 | } 72 | 73 | const { channel } = this.props 74 | 75 | channel.removeListener(Events.ShowDocs, this.onShowDocs) 76 | } 77 | } 78 | 79 | addons.register(AddonName, api => { 80 | addons.addPanel(PanelName, { 81 | title: 'Info(Vue)', 82 | render: ({ active, key }) => ( 83 | 84 | ) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /src/types/dedent-tabs.ts: -------------------------------------------------------------------------------- 1 | declare module 'dedent-tabs' 2 | declare function dedent(literals: string): string 3 | declare function dedent( 4 | literals: TemplateStringsArray, 5 | ...placeholders: any[] 6 | ): string 7 | -------------------------------------------------------------------------------- /src/types/info.ts: -------------------------------------------------------------------------------- 1 | export interface StoryInfo { 2 | title: string 3 | subtitle: string 4 | 5 | summary?: string 6 | 7 | storySource: string 8 | jsxStory: boolean 9 | 10 | components: ComponentInfo[] 11 | } 12 | 13 | export interface ComponentInfo { 14 | name: string 15 | 16 | props: PropInfo[] 17 | events: EventInfo[] 18 | slots: SlotInfo[] 19 | } 20 | 21 | export interface PropInfo { 22 | name: string 23 | type: string 24 | required: boolean 25 | default?: string 26 | description?: string 27 | } 28 | 29 | export interface EventInfo { 30 | name: string 31 | type?: string 32 | description?: string 33 | } 34 | 35 | export interface SlotInfo { 36 | name: string 37 | description?: string 38 | } 39 | -------------------------------------------------------------------------------- /src/types/vue.ts: -------------------------------------------------------------------------------- 1 | import { AsyncComponent, Component } from 'vue' 2 | 3 | /** 4 | * Any component, for convenience. 5 | */ 6 | export type AnyComponent = 7 | | Component 8 | | AsyncComponent 9 | 10 | /** 11 | * Type of components property. 12 | */ 13 | export interface ComponentRegistory { 14 | [name: string]: 15 | | Component 16 | | AsyncComponent 17 | } 18 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | 6 | declare module '*.css' { 7 | const modules: { 8 | [className: string]: string 9 | } 10 | 11 | export default modules 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/constructorToString.spec.ts: -------------------------------------------------------------------------------- 1 | import constructorToString from './constructorToString' 2 | 3 | it('Label of String to equal "string"', () => { 4 | expect(constructorToString(String)).toBe('string') 5 | }) 6 | 7 | it('Label of Number to equal "number"', () => { 8 | expect(constructorToString(Number)).toBe('number') 9 | }) 10 | 11 | it('Label of Object to equal "object"', () => { 12 | expect(constructorToString(Object)).toBe('object') 13 | }) 14 | 15 | it('Label of Boolean to equal "boolean"', () => { 16 | expect(constructorToString(Boolean)).toBe('boolean') 17 | }) 18 | 19 | it('Label of Function to equal "function"', () => { 20 | expect(constructorToString(Function)).toBe('function') 21 | }) 22 | 23 | it('Label of Symbol to equal "Symbol"', () => { 24 | expect(constructorToString(Symbol)).toBe('Symbol') 25 | }) 26 | 27 | it('Label of Function to equal "function"', () => { 28 | expect(constructorToString(Array)).toBe('array') 29 | }) 30 | 31 | it('Label of Class to equal its name', () => { 32 | function MyClass() {} 33 | 34 | expect(constructorToString(MyClass)).toBe('MyClass') 35 | }) 36 | 37 | it('Other value will be "unknown"', () => { 38 | expect(constructorToString(() => null)).toBe('unknown') 39 | }) 40 | -------------------------------------------------------------------------------- /src/utils/constructorToString.ts: -------------------------------------------------------------------------------- 1 | type Constructor = () => void 2 | 3 | /** 4 | * Returns type string of constructor. 5 | * @param constructor Constructor function 6 | */ 7 | export const constructorToString = ( 8 | constructor: Constructor | Constructor[] 9 | ): string => { 10 | if (constructor instanceof Array) { 11 | return constructor.map(constructorToString).join(' | ') 12 | } else if (constructor === Number) { 13 | return 'number' 14 | } else if (constructor === String) { 15 | return 'string' 16 | } else if (constructor === Object) { 17 | return 'object' 18 | } else if (constructor === Boolean) { 19 | return 'boolean' 20 | } else if (constructor === Function) { 21 | return `function` 22 | } else if (constructor === Symbol) { 23 | return 'Symbol' 24 | } else if (constructor === Array) { 25 | return 'array' 26 | } else { 27 | return constructor.name || 'unknown' 28 | } 29 | } 30 | 31 | export default constructorToString 32 | -------------------------------------------------------------------------------- /src/utils/getJSXFromRenderFn.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import getJSXFromRenderFn, { CreateJSX } from './getJSXFromRenderFn' 4 | 5 | it('First argument for createElement will be tag name', () => { 6 | expect(getJSXFromRenderFn(h => h('div'))).toBe('
') 7 | 8 | const MyButton = Vue.component('my-button', { 9 | template: '' 10 | }) 11 | 12 | expect(getJSXFromRenderFn(h => h(MyButton))).toBe('') 13 | }) 14 | 15 | it('Anonymous component will be shown as ', () => { 16 | const MyLabel = Vue.extend({ 17 | template: '