Skip to content

Add custom component display names #933

New issue

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

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

Already on GitHub? Sign in to your account

Merged
20 changes: 20 additions & 0 deletions docs/Cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* [How to add fonts from Google Fonts?](#how-to-add-fonts-from-google-fonts)
* [How to reuse project’s webpack config?](#how-to-reuse-projects-webpack-config)
* [How to use React Styleguidist with Redux, Relay or Styled Components?](#how-to-use-react-styleguidist-with-redux-relay-or-styled-components)
* [How to change the names of components displayed in Styleguidist UI?](#how-to-change-the-names-of-components-displayed-in-styleguidist-ui)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's not ordered alphabetically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, it seems that the rest of the items is not ordered alphabetically as well. Should I order them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be super cool! But preferably in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, I finally got to alphabetically ordering the cookbook sections and want to ask: is this really a good idea? In this case, the first entry in the cookbook will be 'Are there any other projects like this?' and it feels that there are more important/useful topics in the cookbook than this... It feels that there can be a benefit in structuring the cookbook, but in some other, more semantic way, like from beginner topics to more advanced stuff or from most frequent requests to more rare/specific.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's skip it then ;-) Are you going to change anything else or I can merge it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All right! Nope, the rest of the changes is in place already, so this one is ready to go 🚢

* [What’s the difference between Styleguidist and Storybook?](#whats-the-difference-between-styleguidist-and-storybook)
* [Are there any other projects like this?](#are-there-any-other-projects-like-this)

Expand Down Expand Up @@ -479,6 +480,25 @@ See in [configuring webpack](Webpack.md#reusing-your-projects-webpack-config).

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

## How to change the names of components displayed in Styleguidist UI?

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.

This can be done by adding [@visibleName](Documenting.md#defining-custom-component-names) tag to your component documentation.

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:

```javascript
module.exports = {
updateDocs(docs) {
if (docs && docs.displayName) {
docs.visibleName = docs.displayName.toLowerCase()
}
return docs
}
}
```

## What’s the difference between Styleguidist and Storybook?

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.
Expand Down
16 changes: 16 additions & 0 deletions docs/Documenting.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Styleguidist generates documentation for your components based on the comments i
* [External examples using doclet tags](#external-examples-using-doclet-tags)
* [Public methods](#public-methods)
* [Ignoring props](#ignoring-props)
* [Defining custom component names](#defining-custom-component-names)
* [Using JSDoc tags](#using-jsdoc-tags)
* [Writing code examples](#writing-code-examples)
* [Limitations](#limitations)
Expand Down Expand Up @@ -143,6 +144,21 @@ MyComponent.propTypes = {
}
```

## Defining custom component names

Use @visibleName JSDoc tag to define component names that are used in the Styleguidist UI:

```javascript
/**
* The only true button.
*
* @visibleName The Best Button Ever 🐙
*/
class Button extends React.Component {
```

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.

## Using JSDoc tags

You can use the following [JSDoc](http://usejsdoc.org/) tags when documenting components, props and methods:
Expand Down
3 changes: 2 additions & 1 deletion examples/basic/src/components/PushButton/PushButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import PropTypes from 'prop-types';
import './PushButton.css';

/**
* An example-less button.
* An example-less button with custom display name.
* @visibleName Push Button 🎉
*/
export default function PushButton({ color, size, children }) {
const styles = {
Expand Down
17 changes: 17 additions & 0 deletions loaders/utils/__tests__/getProps.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,20 @@ it('should guess a displayName for components that react-docgen was not able to
);
expect(result).toHaveProperty('displayName', 'YourComponent');
});

describe('with @visibleName tag present in the description', () => {
const result = getProps({
description: 'bar\n@visibleName foo',
});
it('should set visibleName property on the docs object', () => {
expect(result).toHaveProperty('visibleName', 'foo');
});

it('should delete visibleName from doclets on the docs object', () => {
expect(result.doclets).not.toHaveProperty('visibleName');
});

it('should delete visibleName from tags on the docs object', () => {
expect(result.tags).not.toHaveProperty('visibleName');
});
});
11 changes: 11 additions & 0 deletions loaders/utils/getProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,16 @@ module.exports = function getProps(doc, filepath) {
doc.displayName = getNameFromFilePath(filepath);
}

if (doc.doclets && doc.doclets.visibleName) {
doc.visibleName = doc.doclets.visibleName;

// custom tag is added both to doclets and tags
// removing from both locations
delete doc.doclets.visibleName;
if (doc.tags) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between doclets and tags? Worth adding a comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I don't know the difference :D I just noticed, that the custom tag gets added both to doclets and tags while parsing, so i decided it's a good idea to remove it from both. I'll investigate a little more on the matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After investigating a little, it seems that doclets and tags are a little redundant.
Here's an example of doclet:

{
  version: '1.0.1',
  author: '[Jeremy Gayed](https://github.com/tizmagik)',
  deprecated: 'Use the [only true button](#button) instead'
}

And here's the tags object:

{
  version: [ { title: 'version', description: '1.0.1' } ],
  author:
   [ { title: 'author',
       description: '[Jeremy Gayed](https://github.com/tizmagik)' } ],
  deprecated:
   [ { title: 'deprecated',
       description: 'Use the [only true button](#button) instead' } ]
}

Seems, it's the same data, structured a little differently. The interesting part, that we are using two different parsers to get those results. So, unless there are some subtle differences that I've missed, we can do some refactoring and let one of those go. But anyway, I think this work is outside of this pull request purpose. Should I file an issue for that?

delete doc.tags.visibleName;
}
}

return doc;
};
12 changes: 8 additions & 4 deletions src/rsg-components/ComponentsList/ComponentsList.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { ComponentsListRenderer } from './ComponentsListRenderer';
it('should set the correct href for items', () => {
const components = [
{
visibleName: 'Button',
name: 'Button',
slug: 'button',
},
{
visibleName: 'Input',
name: 'Input',
slug: 'input',
},
Expand All @@ -21,10 +23,12 @@ it('should set the correct href for items', () => {
it('should set the correct href for items when isolated links should be used', () => {
const components = [
{
visibleName: 'Button',
name: 'Button',
slug: 'button',
},
{
visibleName: 'Input',
name: 'Input',
slug: 'input',
},
Expand All @@ -37,12 +41,12 @@ it('should set the correct href for items when isolated links should be used', (
it('should render sections with nested components', () => {
const components = [
{
name: 'Button',
visibleName: 'Button',
slug: 'button',
href: '#button',
},
{
name: 'Input',
visibleName: 'Input',
slug: 'input',
href: '#input',
},
Expand All @@ -58,10 +62,10 @@ it('should return null when the list is empty', () => {
expect(actual.getElement()).toBe(null);
});

it('should ignore items without name', () => {
it('should ignore items without visibleName', () => {
const components = [
{
name: 'Button',
visibleName: 'Button',
slug: 'button',
href: '#button',
},
Expand Down
6 changes: 3 additions & 3 deletions src/rsg-components/ComponentsList/ComponentsListRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,21 @@ const styles = ({ color, fontFamily, fontSize, space, mq }) => ({
});

export function ComponentsListRenderer({ classes, items }) {
items = items.filter(item => item.name);
items = items.filter(item => item.visibleName);

if (!items.length) {
return null;
}

return (
<ul className={classes.list}>
{items.map(({ heading, name, href, content }) => (
{items.map(({ heading, visibleName, href, content }) => (
<li
className={cx(classes.item, (!content || !content.props.items.length) && classes.isChild)}
key={href}
>
<Link className={cx(heading && classes.heading)} href={href}>
{name}
{visibleName}
</Link>
{content}
</li>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should ignore items without name 1`] = `
exports[`should ignore items without visibleName 1`] = `
<ul>
<li
className=""
Expand Down Expand Up @@ -52,11 +52,13 @@ exports[`should set the correct href for items 1`] = `
"href": "blank#button",
"name": "Button",
"slug": "button",
"visibleName": "Button",
},
Object {
"href": "blank#input",
"name": "Input",
"slug": "input",
"visibleName": "Input",
},
]
}
Expand All @@ -72,11 +74,13 @@ exports[`should set the correct href for items when isolated links should be use
"href": "blank#!/Button",
"name": "Button",
"slug": "button",
"visibleName": "Button",
},
Object {
"href": "blank#!/Input",
"name": "Input",
"slug": "input",
"visibleName": "Input",
},
]
}
Expand Down
4 changes: 2 additions & 2 deletions src/rsg-components/ReactComponent/ReactComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default class ReactComponent extends Component {
const { activeTab } = this.state;
const { displayMode } = this.context;
const { component, depth } = this.props;
const { name, slug, filepath, pathLine } = component;
const { name, visibleName, slug, filepath, pathLine } = component;
const { description, examples = [], tags = {} } = component.props;
if (!name) {
return null;
Expand All @@ -70,7 +70,7 @@ export default class ReactComponent extends Component {
}}
depth={depth}
>
{name}
{visibleName}
</SectionHeading>
}
examples={
Expand Down
2 changes: 2 additions & 0 deletions src/rsg-components/ReactComponent/ReactComponent.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const options = {

const component = {
name: 'Foo',
visibleName: 'Foo',
slug: 'foo',
pathLine: 'foo/bar.js',
props: {
Expand All @@ -28,6 +29,7 @@ const component = {
};
const componentWithEverything = {
name: 'Foo',
visibleName: 'Foo',
slug: 'foo',
pathLine: 'foo/bar.js',
props: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ exports[`ReactComponent should pass rendered description, usage, examples, etc.
],
},
"slug": "foo",
"visibleName": "Foo",
}
}
>
Expand Down Expand Up @@ -141,6 +142,7 @@ exports[`ReactComponent should pass rendered description, usage, examples, etc.
],
},
"slug": "foo",
"visibleName": "Foo",
}
}
/>
Expand Down Expand Up @@ -195,6 +197,7 @@ exports[`ReactComponent should pass rendered description, usage, examples, etc.
],
},
"slug": "foo",
"visibleName": "Foo",
}
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ exports[`renderStyleguide should render StyleGuide component 1`] = `
"displayName": "Button",
"examples": Array [],
},
"visibleName": "Button",
},
Object {
"module": "ImageModule",
Expand All @@ -25,6 +26,7 @@ exports[`renderStyleguide should render StyleGuide component 1`] = `
"displayName": "Image",
"examples": Array [],
},
"visibleName": "Image",
},
],
"sections": Array [],
Expand All @@ -48,6 +50,7 @@ exports[`renderStyleguide should render StyleGuide component 1`] = `
"displayName": "Button",
"examples": Array [],
},
"visibleName": "Button",
},
Object {
"module": "ImageModule",
Expand All @@ -56,6 +59,7 @@ exports[`renderStyleguide should render StyleGuide component 1`] = `
"displayName": "Image",
"examples": Array [],
},
"visibleName": "Image",
},
],
"sections": Array [],
Expand Down
29 changes: 29 additions & 0 deletions src/utils/__tests__/processComponents.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,35 @@ describe('processComponents', () => {
expect(result[0].name).toBe('Foo');
});

describe('should set visibleName property on the component', () => {
it('from an visibleName component prop if available', () => {
const components = deepfreeze([
{
props: {
displayName: 'Foo',
visibleName: 'Foo Bar',
},
module: 13,
},
]);
const result = processComponents(components);
expect(result[0].visibleName).toBe('Foo Bar');
});

it('from an displayName component prop if visibleName prop is not available', () => {
const components = deepfreeze([
{
props: {
displayName: 'Foo',
},
module: 13,
},
]);
const result = processComponents(components);
expect(result[0].visibleName).toBe('Foo');
});
});

it('should append @example doclet to all examples', () => {
const components = deepfreeze([
{
Expand Down
1 change: 1 addition & 0 deletions src/utils/processComponents.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default function processComponents(components) {

// Add .name shortcuts for names instead of .props.displayName.
name: component.props.displayName,
visibleName: component.props.visibleName || component.props.displayName,

props: {
...component.props,
Expand Down