Skip to content

Commit b523c45

Browse files
glebezbluetidepro
authored andcommitted
Feat: Add custom component display names (#933)
- User can define a custom name via a custom @sgDisplayName tag in the doc block
1 parent bfc0382 commit b523c45

File tree

14 files changed

+123
-11
lines changed

14 files changed

+123
-11
lines changed

docs/Cookbook.md

+20
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
- [How to add fonts from Google Fonts?](#how-to-add-fonts-from-google-fonts)
2828
- [How to reuse project’s webpack config?](#how-to-reuse-projects-webpack-config)
2929
- [How to use React Styleguidist with Redux, Relay or Styled Components?](#how-to-use-react-styleguidist-with-redux-relay-or-styled-components)
30+
- [How to change the names of components displayed in Styleguidist UI?](#how-to-change-the-names-of-components-displayed-in-styleguidist-ui)
3031
- [What’s the difference between Styleguidist and Storybook?](#whats-the-difference-between-styleguidist-and-storybook)
3132
- [Are there any other projects like this?](#are-there-any-other-projects-like-this)
3233

@@ -527,6 +528,25 @@ See in [configuring webpack](Webpack.md#reusing-your-projects-webpack-config).
527528

528529
See [working with third-party libraries](Thirdparties.md).
529530

531+
## How to change the names of components displayed in Styleguidist UI?
532+
533+
You might want to change your components' names to be displayed differently, for example, for stylistic purposes or to give them a more descriptive names in your styleguide.
534+
535+
This can be done by adding [@visibleName](Documenting.md#defining-custom-component-names) tag to your component documentation.
536+
537+
In case you want to change components' names in bulk, for example, based on their current name, you can use [updateDocs](Configuration.md#updatedocs) config option:
538+
539+
```javascript
540+
module.exports = {
541+
updateDocs(docs) {
542+
if (docs && docs.displayName) {
543+
docs.visibleName = docs.displayName.toLowerCase()
544+
}
545+
return docs
546+
}
547+
}
548+
```
549+
530550
## What’s the difference between Styleguidist and Storybook?
531551

532552
Both tools are good and mature, they have many similarities but also some distinctions that may make you choose one or the other. For me the biggest distinction is how you describe component variations.

docs/Documenting.md

+16
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Styleguidist generates documentation for your components based on the comments i
1313
- [External examples using doclet tags](#external-examples-using-doclet-tags)
1414
- [Public methods](#public-methods)
1515
- [Ignoring props](#ignoring-props)
16+
- [Defining custom component names](#defining-custom-component-names)
1617
- [Using JSDoc tags](#using-jsdoc-tags)
1718
- [Writing code examples](#writing-code-examples)
1819
- [Limitations](#limitations)
@@ -143,6 +144,21 @@ MyComponent.propTypes = {
143144
}
144145
```
145146

147+
## Defining custom component names
148+
149+
Use @visibleName JSDoc tag to define component names that are used in the Styleguidist UI:
150+
151+
```javascript
152+
/**
153+
* The only true button.
154+
*
155+
* @visibleName The Best Button Ever 🐙
156+
*/
157+
class Button extends React.Component {
158+
```
159+
160+
Now the component will be displayed with a custom 'The Bust Button Ever🐙' name and this will not change the name of the component that is used in the JSX.
161+
146162
## Using JSDoc tags
147163
148164
You can use the following [JSDoc](http://usejsdoc.org/) tags when documenting components, props and methods:

examples/basic/src/components/PushButton/PushButton.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import PropTypes from 'prop-types';
44
import './PushButton.css';
55

66
/**
7-
* An example-less button.
7+
* An example-less button with custom display name.
8+
* @visibleName Push Button 🎉
89
*/
910
export default function PushButton({ color, size, children }) {
1011
const styles = {

loaders/utils/__tests__/getProps.spec.js

+17
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,20 @@ it('should guess a displayName for components that react-docgen was not able to
161161
);
162162
expect(result).toHaveProperty('displayName', 'YourComponent');
163163
});
164+
165+
describe('with @visibleName tag present in the description', () => {
166+
const result = getProps({
167+
description: 'bar\n@visibleName foo',
168+
});
169+
it('should set visibleName property on the docs object', () => {
170+
expect(result).toHaveProperty('visibleName', 'foo');
171+
});
172+
173+
it('should delete visibleName from doclets on the docs object', () => {
174+
expect(result.doclets).not.toHaveProperty('visibleName');
175+
});
176+
177+
it('should delete visibleName from tags on the docs object', () => {
178+
expect(result.tags).not.toHaveProperty('visibleName');
179+
});
180+
});

loaders/utils/getProps.js

+11
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,16 @@ module.exports = function getProps(doc, filepath) {
106106
doc.displayName = getNameFromFilePath(filepath);
107107
}
108108

109+
if (doc.doclets && doc.doclets.visibleName) {
110+
doc.visibleName = doc.doclets.visibleName;
111+
112+
// custom tag is added both to doclets and tags
113+
// removing from both locations
114+
delete doc.doclets.visibleName;
115+
if (doc.tags) {
116+
delete doc.tags.visibleName;
117+
}
118+
}
119+
109120
return doc;
110121
};

src/rsg-components/ComponentsList/ComponentsList.spec.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import { ComponentsListRenderer } from './ComponentsListRenderer';
55
it('should set the correct href for items', () => {
66
const components = [
77
{
8+
visibleName: 'Button',
89
name: 'Button',
910
slug: 'button',
1011
},
1112
{
13+
visibleName: 'Input',
1214
name: 'Input',
1315
slug: 'input',
1416
},
@@ -21,10 +23,12 @@ it('should set the correct href for items', () => {
2123
it('should set a parameter on link when useHashId is activated', () => {
2224
const components = [
2325
{
26+
visibleName: 'Button',
2427
name: 'Button',
2528
slug: 'button',
2629
},
2730
{
31+
visibleName: 'Input',
2832
name: 'Input',
2933
slug: 'input',
3034
},
@@ -69,12 +73,12 @@ it('should set a sub route on link when useHashId is deactivated', () => {
6973
it('should render sections with nested components', () => {
7074
const components = [
7175
{
72-
name: 'Button',
76+
visibleName: 'Button',
7377
slug: 'button',
7478
href: '#button',
7579
},
7680
{
77-
name: 'Input',
81+
visibleName: 'Input',
7882
slug: 'input',
7983
href: '#input',
8084
},
@@ -90,10 +94,10 @@ it('should return null when the list is empty', () => {
9094
expect(actual.getElement()).toBe(null);
9195
});
9296

93-
it('should ignore items without name', () => {
97+
it('should ignore items without visibleName', () => {
9498
const components = [
9599
{
96-
name: 'Button',
100+
visibleName: 'Button',
97101
slug: 'button',
98102
href: '#button',
99103
},

src/rsg-components/ComponentsList/ComponentsListRenderer.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,21 @@ const styles = ({ color, fontFamily, fontSize, space, mq }) => ({
3434
});
3535

3636
export function ComponentsListRenderer({ classes, items }) {
37-
items = items.filter(item => item.name);
37+
items = items.filter(item => item.visibleName);
3838

3939
if (!items.length) {
4040
return null;
4141
}
4242

4343
return (
4444
<ul className={classes.list}>
45-
{items.map(({ heading, name, href, content }) => (
45+
{items.map(({ heading, visibleName, href, content }) => (
4646
<li
4747
className={cx(classes.item, (!content || !content.props.items.length) && classes.isChild)}
4848
key={href}
4949
>
5050
<Link className={cx(heading && classes.heading)} href={href}>
51-
{name}
51+
{visibleName}
5252
</Link>
5353
{content}
5454
</li>

src/rsg-components/ComponentsList/__snapshots__/ComponentsList.spec.js.snap

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`should ignore items without name 1`] = `
3+
exports[`should ignore items without visibleName 1`] = `
44
<ul>
55
<li
66
className=""
@@ -52,11 +52,13 @@ exports[`should set a parameter on link when useHashId is activated 1`] = `
5252
"href": "blank#/Components?id=button",
5353
"name": "Button",
5454
"slug": "button",
55+
"visibleName": "Button",
5556
},
5657
Object {
5758
"href": "blank#/Components?id=input",
5859
"name": "Input",
5960
"slug": "input",
61+
"visibleName": "Input",
6062
},
6163
]
6264
}
@@ -92,11 +94,13 @@ exports[`should set the correct href for items 1`] = `
9294
"href": "blank#button",
9395
"name": "Button",
9496
"slug": "button",
97+
"visibleName": "Button",
9598
},
9699
Object {
97100
"href": "blank#input",
98101
"name": "Input",
99102
"slug": "input",
103+
"visibleName": "Input",
100104
},
101105
]
102106
}

src/rsg-components/ReactComponent/ReactComponent.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export default class ReactComponent extends Component {
5050
config: { pagePerSection },
5151
} = this.context;
5252
const { component, depth, usageMode, exampleMode } = this.props;
53-
const { name, slug, filepath, pathLine } = component;
53+
const { name, visibleName, slug, filepath, pathLine } = component;
5454
const { description, examples = [], tags = {} } = component.props;
5555
if (!name) {
5656
return null;
@@ -77,7 +77,7 @@ export default class ReactComponent extends Component {
7777
}}
7878
depth={depth}
7979
>
80-
{name}
80+
{visibleName}
8181
</SectionHeading>
8282
}
8383
examples={

src/rsg-components/ReactComponent/ReactComponent.spec.js

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const options = {
2121

2222
const component = {
2323
name: 'Foo',
24+
visibleName: 'Foo',
2425
slug: 'foo',
2526
pathLine: 'foo/bar.js',
2627
props: {
@@ -32,6 +33,7 @@ const component = {
3233
};
3334
const componentWithEverything = {
3435
name: 'Foo',
36+
visibleName: 'Foo',
3537
slug: 'foo',
3638
pathLine: 'foo/bar.js',
3739
props: {

src/rsg-components/ReactComponent/__snapshots__/ReactComponent.spec.js.snap

+3
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ exports[`ReactComponent should pass rendered description, usage, examples, etc.
8484
],
8585
},
8686
"slug": "foo",
87+
"visibleName": "Foo",
8788
}
8889
}
8990
>
@@ -142,6 +143,7 @@ exports[`ReactComponent should pass rendered description, usage, examples, etc.
142143
],
143144
},
144145
"slug": "foo",
146+
"visibleName": "Foo",
145147
}
146148
}
147149
/>
@@ -195,6 +197,7 @@ exports[`ReactComponent should pass rendered description, usage, examples, etc.
195197
],
196198
},
197199
"slug": "foo",
200+
"visibleName": "Foo",
198201
}
199202
}
200203
/>

src/utils/__tests__/__snapshots__/renderStyleguide.spec.js.snap

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ exports[`renderStyleguide should render StyleGuide component 1`] = `
1717
"displayName": "Button",
1818
"examples": Array [],
1919
},
20+
"visibleName": "Button",
2021
},
2122
Object {
2223
"module": "ImageModule",
@@ -25,6 +26,7 @@ exports[`renderStyleguide should render StyleGuide component 1`] = `
2526
"displayName": "Image",
2627
"examples": Array [],
2728
},
29+
"visibleName": "Image",
2830
},
2931
],
3032
"sections": Array [],
@@ -48,6 +50,7 @@ exports[`renderStyleguide should render StyleGuide component 1`] = `
4850
"displayName": "Button",
4951
"examples": Array [],
5052
},
53+
"visibleName": "Button",
5154
},
5255
Object {
5356
"module": "ImageModule",
@@ -56,6 +59,7 @@ exports[`renderStyleguide should render StyleGuide component 1`] = `
5659
"displayName": "Image",
5760
"examples": Array [],
5861
},
62+
"visibleName": "Image",
5963
},
6064
],
6165
"sections": Array [],

src/utils/__tests__/processComponents.spec.js

+29
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,35 @@ describe('processComponents', () => {
1515
expect(result[0].name).toBe('Foo');
1616
});
1717

18+
describe('should set visibleName property on the component', () => {
19+
it('from an visibleName component prop if available', () => {
20+
const components = deepfreeze([
21+
{
22+
props: {
23+
displayName: 'Foo',
24+
visibleName: 'Foo Bar',
25+
},
26+
module: 13,
27+
},
28+
]);
29+
const result = processComponents(components);
30+
expect(result[0].visibleName).toBe('Foo Bar');
31+
});
32+
33+
it('from an displayName component prop if visibleName prop is not available', () => {
34+
const components = deepfreeze([
35+
{
36+
props: {
37+
displayName: 'Foo',
38+
},
39+
module: 13,
40+
},
41+
]);
42+
const result = processComponents(components);
43+
expect(result[0].visibleName).toBe('Foo');
44+
});
45+
});
46+
1847
it('should append @example doclet to all examples', () => {
1948
const components = deepfreeze([
2049
{

src/utils/processComponents.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default function processComponents(components) {
1212

1313
// Add .name shortcuts for names instead of .props.displayName.
1414
name: component.props.displayName,
15+
visibleName: component.props.visibleName || component.props.displayName,
1516

1617
props: {
1718
...component.props,

0 commit comments

Comments
 (0)