diff --git a/docs/configure/features-and-behavior.md b/docs/configure/features-and-behavior.md
index 3dc9c0fc33b6..b6b0596f7c5a 100644
--- a/docs/configure/features-and-behavior.md
+++ b/docs/configure/features-and-behavior.md
@@ -35,3 +35,4 @@ The following options are configurable under the `sidebar` namespace:
| ----------------------|:-------------:|:-------------------------------------------------------------:|:----------------------------------------------:|
| **showRoots** | Boolean |Display the top-level nodes as a "root" in the sidebar |`false` |
| **collapsedRoots** | Array |Set of root node IDs to visually collapse by default |`['misc', 'other']` |
+| **renderLabel** | Function |Create a custom label for tree nodes; must return a ReactNode |`(item) => {item.name}`|
diff --git a/examples/official-storybook/manager.js b/examples/official-storybook/manager.js
index cd74ecf5fd6f..b46efbc338c5 100644
--- a/examples/official-storybook/manager.js
+++ b/examples/official-storybook/manager.js
@@ -1,10 +1,21 @@
+import React from 'react';
import { addons } from '@storybook/addons';
-import { themes } from '@storybook/theming';
+import { themes, styled } from '@storybook/theming';
+import { Icons } from '@storybook/components';
import addHeadWarning from './head-warning';
addHeadWarning('manager-head-not-loaded', 'Manager head not loaded');
+const PrefixIcon = styled(Icons)(({ theme }) => ({
+ marginRight: 8,
+ fontSize: 'inherit',
+ height: '1em',
+ width: '1em',
+ display: 'inline',
+ alignSelf: 'center',
+}));
+
addons.setConfig({
theme: themes.light, // { base: 'dark', brandTitle: 'Storybook!' },
previewTabs: {
@@ -17,5 +28,34 @@ addons.setConfig({
},
sidebar: {
collapsedRoots: ['other'],
+ renderLabel: ({ id, name }) => {
+ const map = {
+ addons: (
+ <>
+
+ {name}
+ >
+ ),
+ 'addons-a11y': (
+ <>
+
+ {name}
+ >
+ ),
+ 'addons-a11y-basebutton': (
+ <>
+
+ {name}
+ >
+ ),
+ 'addons-a11y-basebutton--default': (
+ <>
+
+ {name}
+ >
+ ),
+ };
+ return map[id];
+ },
},
});
diff --git a/lib/api/src/lib/stories.ts b/lib/api/src/lib/stories.ts
index c6e0991da219..3983d06525f8 100644
--- a/lib/api/src/lib/stories.ts
+++ b/lib/api/src/lib/stories.ts
@@ -1,3 +1,4 @@
+import React from 'react';
import deprecate from 'util-deprecate';
import dedent from 'ts-dedent';
import { sanitize } from '@storybook/csf';
@@ -19,6 +20,7 @@ export interface Root {
isComponent: false;
isRoot: true;
isLeaf: false;
+ label?: React.ReactNode;
startCollapsed?: boolean;
}
@@ -32,6 +34,7 @@ export interface Group {
isComponent: boolean;
isRoot: false;
isLeaf: false;
+ label?: React.ReactNode;
// MDX docs-only stories are "Group" type
parameters?: {
docsOnly?: boolean;
@@ -50,6 +53,7 @@ export interface Story {
isComponent: boolean;
isRoot: false;
isLeaf: true;
+ label?: React.ReactNode;
parameters?: {
fileName: string;
options: {
@@ -153,7 +157,7 @@ export const transformStoriesRawToStoriesHash = (
const storiesHashOutOfOrder = values.reduce((acc, item) => {
const { kind, parameters } = item;
const { sidebar = {}, showRoots: deprecatedShowRoots } = provider.getConfig();
- const { showRoots = deprecatedShowRoots, collapsedRoots = [] } = sidebar;
+ const { showRoots = deprecatedShowRoots, collapsedRoots = [], renderLabel } = sidebar;
if (typeof deprecatedShowRoots !== 'undefined') {
warnLegacyShowRoots();
@@ -182,7 +186,7 @@ export const transformStoriesRawToStoriesHash = (
}
if (root.length && index === 0) {
- list.push({
+ const rootElement: Root = {
id,
name,
depth: index,
@@ -191,9 +195,10 @@ export const transformStoriesRawToStoriesHash = (
isLeaf: false,
isRoot: true,
startCollapsed: collapsedRoots.includes(id),
- });
+ };
+ list.push({ ...rootElement, label: renderLabel?.(rootElement) });
} else {
- list.push({
+ const groupElement: Group = {
id,
name,
parent,
@@ -206,6 +211,10 @@ export const transformStoriesRawToStoriesHash = (
docsOnly: parameters?.docsOnly,
viewMode: parameters?.viewMode,
},
+ };
+ list.push({
+ ...groupElement,
+ label: renderLabel?.(groupElement),
});
}
@@ -232,7 +241,7 @@ export const transformStoriesRawToStoriesHash = (
isComponent: false,
isRoot: false,
};
- acc[item.id] = story;
+ acc[item.id] = { ...story, label: renderLabel?.(story) };
return acc;
}, {} as StoriesHash);
diff --git a/lib/api/src/modules/provider.ts b/lib/api/src/modules/provider.ts
index b0766d71e0e0..298546b1dcdd 100644
--- a/lib/api/src/modules/provider.ts
+++ b/lib/api/src/modules/provider.ts
@@ -2,13 +2,14 @@ import { ReactNode } from 'react';
import { Channel } from '@storybook/channels';
import { ThemeVars } from '@storybook/theming';
-import { API, State, ModuleFn } from '../index';
+import { API, State, ModuleFn, Root, Group, Story } from '../index';
import { StoryMapper, Refs } from './refs';
import { UIOptions } from './layout';
interface SidebarOptions {
showRoots?: boolean;
collapsedRoots?: string[];
+ renderLabel?: (item: Root | Group | Story) => ReactNode;
}
type IframeRenderer = (
diff --git a/lib/ui/src/components/sidebar/Tree.stories.tsx b/lib/ui/src/components/sidebar/Tree.stories.tsx
index 8cb2c0ef791d..98a38247522e 100644
--- a/lib/ui/src/components/sidebar/Tree.stories.tsx
+++ b/lib/ui/src/components/sidebar/Tree.stories.tsx
@@ -44,6 +44,7 @@ const singleStoryComponent = {
isComponent: true,
isLeaf: false,
isRoot: false,
+ label: 🔥 Single,
},
'single--single': {
id: 'single--single',
@@ -58,6 +59,7 @@ const singleStoryComponent = {
isLeaf: true,
isComponent: false,
isRoot: false,
+ label: 🔥 Single,
},
};
diff --git a/lib/ui/src/components/sidebar/Tree.tsx b/lib/ui/src/components/sidebar/Tree.tsx
index 5156fdef0a09..616724ccbec4 100644
--- a/lib/ui/src/components/sidebar/Tree.tsx
+++ b/lib/ui/src/components/sidebar/Tree.tsx
@@ -137,7 +137,7 @@ const Node = React.memo(
onSelectStoryId(item.id);
}}
>
- {item.name}
+ {item.label || item.name}
);
}
@@ -162,7 +162,7 @@ const Node = React.memo(
}}
>
- {item.name}
+ {item.label || item.name}
{isExpanded && (
(
if (item.isComponent && !isExpanded) onSelectStoryId(item.id);
}}
>
- {item.name}
+ {item.label || item.name}
);
}