Storybook Docs
Custom Titles for Storybook Docs?
Remember last time when I talked about StoryIndexers
being one of the hidden features of Storybook. I actually added proper documentation for that feature on the official Storybook docs, you can check it out now here 👇.
One of the issues I was still facing was the story titles in the sidebar, where Storybook was insisting on using the file name instead of offering me a way to customize the name. Given that in .mdx
we can use the following to set the title
<Meta title="Legendary Component" />
I wanted to introduce more ways for customizing the title in the form of
- Custom titles array passed to the addon thru
main.js
- Title in an HTML tag in
.md
or.html
file - Title in a Meta tag in
.md
or.html
file - Title in a Markdown comment
{/*title:"Something Something"*/}
Comes into the picture, another hidden feature by Storybook 😄. This one in the form of makeTitle
method which is passed to the compile
method of @storybook/mdx2-csf
library.
How `makeTitle` works?
Simple enough makeTitle
either calls a custom method you devleop, or use the autoTitle
. What is autoTitle
you say? That’s the mechanism by which Storybook
determines the title of a story, long story short (see what I did there 😄), it reads the title of the current sidebar item based on:
title
property of the default exportmeta
component
property of the default exportmeta
- name of the story export, e.g.
export const Primary: Story = {};
- the actual
name
property of the story export (formerlystoryName
) title
attribute of theMeta
tag in MDXname
attribute of theStory
tag in MDX- When using
of={ComponentStories}
attribute on theMeta
tag,ComponentStories
has to be compiled first, then the title will be retrieved from there.
Yep, quite a tall order for processing a title of a sidebar item. Nevertheless, you can override the makeTitle
method to make it return the title you want. So here is how mine looks like:
import { resolve } from 'path';
import { readFileSync } from 'fs';
export const getMakeTitle = (fileName, addonOptions) => {
const addonFiles = addonOptions.titles.map(key => resolve(addonOptions.configDir, key));
return userTitle => {
const shortName = fileName.split('/').pop().split('.').shift();
const titleKey = addonFiles.find(key => key === fileName);
const content = readFileSync(fileName, 'utf8').toString();
const markdownTitle = (content.match(/\{\/\*.*title:[`'"""''„"«»](.*)[`'"""''„"«»]\*\/\}/i) || [])[1];
const metaTitle = (content.match(/<meta title=[`'"""''„"«»](.*)[`'"""''„"«»] \/>/i) || [])[1];
const htmlTitle = (content.match(/<title>(.*)<\/title>/i) || [])[1];
if (titleKey) {
return titles[titleKey];
} else if (markdownTitle) {
return markdownTitle;
} else if (metaTitle) {
return metaTitle;
} else if (htmlTitle) {
return htmlTitle;
}
return userTitle || shortName;
};
};
And simply pass it along your StoryIndexer
like so:
import { readFileSync } from 'fs';
import { makeTitle } from './makeTitle';
export const storyIndexer = (indexers = [], addonOptions = {}) => {
return [
{
test: /\.(md|html)$/,
indexer: async (fileName, compileOptions) => {
const code = readFileSync(fileName, 'utf-8');
return loadCsf(code, {
...compileOptions,
fileName,
makeTitle: getMakeTitle(fileName, addonOptions),
});
},
},
...indexers,
];
};
Of course my final code won’t be looking like that, but I wanted to show the concepts for simplicity.
On other news, we moved from the name @sheriffmoose/storybook-md
to @storybook-extras/markdown
which is now available in v0.0.21
with the updates above. Check it out.