Skip to content

Commit 5e0906b

Browse files
add vue
1 parent b1f1d88 commit 5e0906b

15 files changed

+421
-98
lines changed

README.md

+83-11
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class YourClass {
7676

7777
## @component plugin [BETA]
7878

79-
Better-docs also allows you to document your [React](https://reactjs.org/) ([Vue](https://vuejs.org/) coming soon) components automatically. The only thing you have to do is to add a `@component` tag. It will take all props from your components and along with an `@example` tag - will generate a __live preview__.
79+
Better-docs also allows you to document your [React](https://reactjs.org/) and [Vue](https://vuejs.org/) components automatically. The only thing you have to do is to add a `@component` tag. It will take all props from your components and along with an `@example` tag - will generate a __live preview__.
8080

8181
### Installation instructions
8282

@@ -132,9 +132,36 @@ export default Documented
132132

133133
The plugin will take the information from your [PropTypes](https://reactjs.org/docs/typechecking-with-proptypes.html) and put them into an array.
134134

135+
For Vue it looks similar:
136+
137+
```vue
138+
<script>
139+
/**
140+
* @component
141+
*/
142+
export default {
143+
name: 'ExampleComponent',
144+
props: {
145+
spent: {
146+
type: Number,
147+
default: 30,
148+
},
149+
remaining: {
150+
type: Number,
151+
default: 40,
152+
}
153+
},
154+
}
155+
</script>
156+
```
157+
158+
Here props will be taken from `props` property.
159+
135160
### Preview
136161

137-
`@component` plugin also modifies the behaviour of `@example` tag in a way that it can generate an actual __component preview__. What you have to do is to ad an `@example` tag and return component from it:
162+
`@component` plugin also modifies the behaviour of `@example` tag in a way that it can generate an actual __component preview__. What you have to do is to add an `@example` tag and return component from it:
163+
164+
**React example:**
138165

139166
```javacript
140167
/**
@@ -152,7 +179,55 @@ const Documented = (props) => {
152179
}
153180
```
154181

155-
You can put as many `@example` tags as you like in one component.
182+
**Vue example 1:**
183+
184+
```
185+
<script>
186+
/**
187+
* @component
188+
* @example
189+
* <ExampleComponent :spent="100" :remaining="50"></ExampleComponent>
190+
*/
191+
export default {
192+
name: 'ExampleComponent',
193+
//...
194+
}
195+
</script>
196+
```
197+
198+
**Vue example 2:**
199+
200+
```
201+
<script>
202+
/**
203+
* @component
204+
* @example
205+
* {
206+
* template: `<Box>
207+
* <ProgressBar :spent="spent" :remaining="50"></ProgressBar>
208+
* <ProgressBar :spent="50" :remaining="50" style="margin-top: 20px"></ProgressBar>
209+
* </Box>`,
210+
* data: function() {
211+
* return {spent: 223};
212+
* }
213+
* }
214+
*/
215+
export default {
216+
name: 'ExampleComponent',
217+
//...
218+
}
219+
</script>
220+
```
221+
222+
You can put as many `@example` tags as you like in one component and name each of them like this:
223+
224+
```javascript
225+
/**
226+
* @component
227+
* @example <caption>Example usage of method1.</caption>
228+
* // your example here
229+
*/
230+
```
156231

157232
### Mixing components in preview
158233

@@ -182,7 +257,7 @@ const Component1 = (props) => {...}
182257
const Component2 = (props) => {...}
183258
```
184259

185-
### Wrapper components
260+
### Wrapper component [only React]
186261

187262
Most probably your components will have to be run within a particular context, like within redux store provider or with custom CSS libraries.
188263
You can simulate this by passing a `component.wrapper` in your `jsdoc.json`:
@@ -233,7 +308,7 @@ const Component = (props) => {
233308
export default Component
234309
```
235310

236-
### Styling examples
311+
### Styling React examples
237312

238313
Better-docs inserts all examples within an `iframe`. This results in following styling options:
239314

@@ -277,7 +352,7 @@ export default Component
277352

278353
`@component` plugin creates an entry file: `.entry.js` in your _docs_ output folder. Sometimes you might want to add something to it. You can do this by passing: `component.entry` option, which is an array of strings.
279354

280-
So let's say you want to add `babel-polyfill` to your bundle. You can do it like this:
355+
So let's say you want to add `babel-polyfill` and 'bulma.css' framework to your bundle. You can do it like this:
281356

282357
```json
283358
// jsdoc.json
@@ -288,7 +363,8 @@ So let's say you want to add `babel-polyfill` to your bundle. You can do it like
288363
"name": "AdminBro Documentation",
289364
"component": {
290365
"entry": [
291-
"import \"babel-polyfill\""
366+
"import 'babel-polyfill';",
367+
"import 'bulma/css/bulma.css';"
292368
]
293369
},
294370
"...": "...",
@@ -297,10 +373,6 @@ So let's say you want to add `babel-polyfill` to your bundle. You can do it like
297373
}
298374
```
299375

300-
### Document Vue components
301-
302-
_Vue is coming soon_
303-
304376
## Customization
305377

306378
First of all, let me state that better-docs extends the `default` template. That is why default template parameters are also handled.

bundler.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ module.exports = function bundle (Components, out, config) {
3636
window.Wrapper = Wrapper;\n
3737
`
3838
}
39+
40+
// Import css
41+
init = init + `
42+
import './styles/reset.css';\n
43+
import './styles/iframe.css';\n
44+
`
45+
3946
if (config.betterDocs.component) {
4047
if(config.betterDocs.component.wrapper) {
4148
const absolute = path.resolve(config.betterDocs.component.wrapper)
@@ -49,12 +56,13 @@ module.exports = function bundle (Components, out, config) {
4956
init = `${config.betterDocs.component.entry.join('\n')}\n${init}`
5057
}
5158
}
59+
5260
const entryFile = init + Components.map(c => {
5361
const { displayName, filePath} = c.component
5462
const relativePath = path.relative(absoluteOut, filePath)
5563
return [
5664
`import ${displayName} from '${relativePath}';`,
57-
`Components.${displayName} = ${displayName};`,
65+
`Components['${displayName}'] = ${displayName};`,
5866
].join('\n')
5967
}).join('\n\n')
6068

component.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,15 @@ var parseReact = function (filePath, doclet) {
5555
}
5656

5757
return {
58-
props: docGen.props,
59-
methods: docGen.methods,
58+
props: Object.entries(docGen.props || {}).map(([key, prop]) => ({
59+
name: key,
60+
description: prop.description,
61+
type: prop.type.name,
62+
required: typeof prop.required === 'boolean' && prop.required,
63+
defaultValue: prop.defaultValue
64+
? (prop.defaultValue.computed ? 'function()' : prop.defaultValue.value)
65+
: undefined
66+
})),
6067
displayName: docGen.displayName,
6168
filePath: filePath,
6269
}
@@ -85,3 +92,4 @@ var parseVue = function (filePath, doclet) {
8592
}
8693

8794
exports.parseVue = parseVue
95+
exports.parseReact = parseReact

component.spec.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
const path = require('path')
22
const { expect } = require('chai')
33

4-
const { parseVue } = require('./component')
4+
const { parseVue, parseReact } = require('./component')
55

66
const VUE_PATH = path.join(__dirname, 'fixtures/component.vue')
7+
const REACT_PATH = path.join(__dirname, 'fixtures/component.jsx')
78

89
describe('@component', function () {
910
describe('.parseVue', function () {
@@ -36,4 +37,26 @@ describe('@component', function () {
3637
})
3738
})
3839
})
40+
41+
describe('.parseReact', function () {
42+
beforeEach(function () {
43+
this.doclet = {}
44+
this.output = parseReact(REACT_PATH, this.doclet)
45+
})
46+
47+
it('returns displayName', function () {
48+
expect(this.output.displayName).to.equal('Documented')
49+
})
50+
51+
it('returns prop types', function () {
52+
expect(this.output.props).to.have.lengthOf(2)
53+
expect(this.output.props[0]).to.deep.equal({
54+
description: 'Text is a text',
55+
name: 'text',
56+
required: false,
57+
type: 'string',
58+
defaultValue: '\'Hello World\''
59+
})
60+
})
61+
})
3962
})

fixtures/component.jsx

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import styled from 'styled-components'
4+
5+
const Bold = styled.div`
6+
background: red;
7+
color: #fff;
8+
padding: 5px;
9+
`
10+
11+
/**
12+
* Some documented component
13+
*
14+
* @component
15+
* @example <caption>Default example</caption>
16+
* const text = 'Meva'
17+
* return (
18+
* <Documented2>
19+
* <Documented text={text} />
20+
* </Documented2>
21+
* )
22+
*
23+
* @example <caption>Ala ma kota</caption>
24+
* const text = 'some example text 2'
25+
* return (
26+
* <Documented2>
27+
* <Documented text={text} header={'sime'} />
28+
* </Documented2>
29+
* )
30+
*/
31+
const Documented = (props) => {
32+
const { text, header } = props
33+
return (
34+
<p>
35+
{header}
36+
<Bold>
37+
{text}
38+
</Bold>
39+
</p>
40+
)
41+
}
42+
43+
Documented.propTypes = {
44+
/**
45+
* Text is a text
46+
*/
47+
text: PropTypes.string,
48+
header: PropTypes.string.isRequired,
49+
}
50+
51+
Documented.defaultProps = {
52+
text: 'Hello World',
53+
}
54+
55+
export default Documented

lib/react-wrapper.js

+39-21
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,13 @@ function (_React$Component) {
6161
window.component = window.component || {};
6262
_this.iframeRef = _react["default"].createRef();
6363
_this.handleChange = _this.handleChange.bind(_assertThisInitialized(_this));
64+
_this.toggleEditor = _this.toggleEditor.bind(_assertThisInitialized(_this));
6465
var example = props.example;
6566
example = example || 'return (<div>Example</div>)';
6667
_this.state = {
6768
example: example,
68-
height: 200
69+
height: 200,
70+
showEditor: false
6971
};
7072

7173
_this.executeScript(example);
@@ -118,7 +120,7 @@ function (_React$Component) {
118120
key: "computeHeight",
119121
value: function computeHeight() {
120122
var height = this.state.height;
121-
var padding = 30; // buffer for any unstyled margins
123+
var padding = 5; // buffer for any unstyled margins
122124

123125
if (this.iframeRef.current && this.iframeRef.current.node.contentDocument && this.iframeRef.current.node.contentDocument.body.offsetHeight !== 0 && this.iframeRef.current.node.contentDocument.body.offsetHeight !== height - padding) {
124126
this.setState({
@@ -131,15 +133,47 @@ function (_React$Component) {
131133
value: function componentDidUpdate() {
132134
this.computeHeight();
133135
}
136+
}, {
137+
key: "toggleEditor",
138+
value: function toggleEditor(event) {
139+
event.preventDefault();
140+
this.setState(function (state) {
141+
return _objectSpread2({}, state, {
142+
showEditor: !state.showEditor
143+
});
144+
});
145+
}
134146
}, {
135147
key: "render",
136148
value: function render() {
137149
var _this2 = this;
138150

139151
var _this$state = this.state,
140152
component = _this$state.component,
141-
height = _this$state.height;
142-
return _react["default"].createElement("div", null, _react["default"].createElement("div", null, _react["default"].createElement("div", {
153+
height = _this$state.height,
154+
showEditor = _this$state.showEditor;
155+
return _react["default"].createElement("div", null, _react["default"].createElement(_reactFrameComponent["default"], {
156+
className: "component-wrapper",
157+
ref: this.iframeRef,
158+
style: {
159+
width: '100%',
160+
height: height
161+
},
162+
onLoad: this.computeHeight()
163+
}, _react["default"].createElement("link", {
164+
type: "text/css",
165+
rel: "stylesheet",
166+
href: "./build/entry.css"
167+
}), _react["default"].createElement(_reactFrameComponent.FrameContextConsumer, null, function (frameContext) {
168+
return _react["default"].createElement(_componentRenderer["default"], {
169+
frameContext: frameContext
170+
}, component);
171+
})), _react["default"].createElement("div", {
172+
"class": "bd__button"
173+
}, _react["default"].createElement("a", {
174+
href: "#",
175+
onClick: this.toggleEditor
176+
}, "Modify Example Code")), showEditor ? _react["default"].createElement("div", {
143177
className: "field"
144178
}, _react["default"].createElement(_reactAce["default"], {
145179
style: {
@@ -157,23 +191,7 @@ function (_React$Component) {
157191
editorProps: {
158192
$useSoftTabs: true
159193
}
160-
}))), _react["default"].createElement("div", null, _react["default"].createElement("h5", null, "Preview"), _react["default"].createElement(_reactFrameComponent["default"], {
161-
className: "component-wrapper",
162-
ref: this.iframeRef,
163-
style: {
164-
width: '100%',
165-
height: height
166-
},
167-
onLoad: this.computeHeight()
168-
}, _react["default"].createElement("link", {
169-
type: "text/css",
170-
rel: "stylesheet",
171-
href: "./build/entry.css"
172-
}), _react["default"].createElement(_reactFrameComponent.FrameContextConsumer, null, function (frameContext) {
173-
return _react["default"].createElement(_componentRenderer["default"], {
174-
frameContext: frameContext
175-
}, component);
176-
}))));
194+
})) : "");
177195
}
178196
}]);
179197

0 commit comments

Comments
 (0)