Skip to content

Commit

Permalink
Merge pull request #11537 from storybookjs/fix/rendering-docs-only-re…
Browse files Browse the repository at this point in the history
…fd-stories

Composition: Fix docs-only story handling for composed storybooks
  • Loading branch information
shilman authored Jul 15, 2020
2 parents 84e4692 + d65af63 commit 764328b
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 132 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { StoriesHash } from '@storybook/api';
import { collapseDocsOnlyStories, collapseAllStories } from './sidebar';
import { collapseDocsOnlyStories, collapseAllStories } from './State';

type Item = StoriesHash[keyof StoriesHash];

Expand Down
134 changes: 130 additions & 4 deletions lib/ui/src/components/sidebar/Tree/State.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,127 @@
import { DOCS_MODE } from 'global';
import rfdc from 'rfdc';
import React, { useMemo, useState, useEffect } from 'react';
import { isRoot } from '@storybook/api';
import { isRoot as isRootFn, Story, StoriesHash } from '@storybook/api';
import { toFiltered, getMains, getParents } from './utils';
import { BooleanSet, FilteredType, Item, DataSet } from '../RefHelpers';

export type ItemType = StoriesHash[keyof StoriesHash];

export const collapseAllStories = (stories: StoriesHash) => {
// keep track of component IDs that have been rewritten to the ID of their first leaf child
const componentIdToLeafId: Record<string, string> = {};

// 1) remove all leaves
const leavesRemoved = Object.values(stories).filter(
(item) => !(item.isLeaf && stories[item.parent].isComponent)
);

// 2) make all components leaves and rewrite their ID's to the first leaf child
const componentsFlattened = leavesRemoved.map((item) => {
const { id, isComponent, isRoot, children, ...rest } = item;

// this is a folder, so just leave it alone
if (!isComponent) {
return item;
}

const nonLeafChildren: string[] = [];
const leafChildren: string[] = [];
children.forEach((child) =>
(stories[child].isLeaf ? leafChildren : nonLeafChildren).push(child)
);

if (leafChildren.length === 0) {
return item; // pass through, we'll handle you later
}

const leafId = leafChildren[0];
const component = {
...rest,
id: leafId,
kind: (stories[leafId] as Story).kind,
isRoot: false,
isLeaf: true,
isComponent: true,
children: [] as string[],
};
componentIdToLeafId[id] = leafId;

// this is a component, so it should not have any non-leaf children
if (nonLeafChildren.length !== 0) {
throw new Error(
`Unexpected '${item.id}': ${JSON.stringify({ isComponent, nonLeafChildren })}`
);
}

return component;
});

// 3) rewrite all the children as needed
const childrenRewritten = componentsFlattened.map((item) => {
if (item.isLeaf) {
return item;
}

const { children, ...rest } = item;
const rewritten = children.map((child) => componentIdToLeafId[child] || child);

return { children: rewritten, ...rest };
});

const result = {} as StoriesHash;
childrenRewritten.forEach((item) => {
result[item.id] = item as Item;
});
return result;
};

export const collapseDocsOnlyStories = (storiesHash: StoriesHash) => {
// keep track of component IDs that have been rewritten to the ID of their first leaf child
const componentIdToLeafId: Record<string, string> = {};
const docsOnlyStoriesRemoved = Object.values(storiesHash).filter((item) => {
if (item.isLeaf && item.parameters && item.parameters.docsOnly) {
componentIdToLeafId[item.parent] = item.id;
return false; // filter it out
}
return true;
});

const docsOnlyComponentsCollapsed = docsOnlyStoriesRemoved.map((item) => {
// collapse docs-only components
const { isComponent, children, id } = item;
if (isComponent && children.length === 1) {
const leafId = componentIdToLeafId[id];
if (leafId) {
const collapsed = {
...item,
id: leafId,
isLeaf: true,
children: [] as string[],
};
return collapsed;
}
}

// update groups
if (children) {
const rewritten = children.map((child) => componentIdToLeafId[child] || child);
return { ...item, children: rewritten };
}

// pass through stories unmodified
return item;
});

const result = {} as StoriesHash;
docsOnlyComponentsCollapsed.forEach((item) => {
result[item.id] = item as Item;
});
return result;
};

const clone = rfdc({ circles: true });

export const ExpanderContext = React.createContext<{
setExpanded: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
expandedSet: BooleanSet;
Expand Down Expand Up @@ -75,7 +194,14 @@ const useFiltered = (dataset: DataSet, filter: string, parents: Item[], storyId:
);
};

export const useDataset = (dataset: DataSet = {}, filter: string, storyId: string) => {
export const useDataset = (storiesHash: DataSet = {}, filter: string, storyId: string) => {
const dataset = useMemo(() => {
// protect against mutation
const copy = clone(storiesHash);

return DOCS_MODE ? collapseAllStories(copy) : collapseDocsOnlyStories(copy);
}, [DOCS_MODE, storiesHash]);

const emptyInitial = useMemo(
() => ({
filtered: {},
Expand Down Expand Up @@ -110,7 +236,7 @@ export const useDataset = (dataset: DataSet = {}, filter: string, storyId: strin
const topLevel = useMemo(
() =>
Object.values(filteredSet).filter(
(i) => (i.depth === 0 && !isRoot(i)) || (!isRoot(i) && isRoot(filteredSet[i.parent]))
(i) => (i.depth === 0 && !isRootFn(i)) || (!isRootFn(i) && isRootFn(filteredSet[i.parent]))
),
[filteredSet]
);
Expand All @@ -127,7 +253,7 @@ export const useDataset = (dataset: DataSet = {}, filter: string, storyId: strin
() =>
getMains(filteredSet).reduce(
(acc, item) => {
return isRoot(item)
return isRootFn(item)
? { ...acc, roots: [...acc.roots, item] }
: { ...acc, others: [...acc.others, item] };
},
Expand Down
130 changes: 3 additions & 127 deletions lib/ui/src/containers/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,129 +1,12 @@
import { DOCS_MODE } from 'global';
import React, { FunctionComponent, useMemo } from 'react';
import rfdc from 'rfdc';
import React, { FunctionComponent } from 'react';

import { Consumer, Combo, StoriesHash, Story } from '@storybook/api';
import { Consumer, Combo, StoriesHash } from '@storybook/api';

import SidebarComponent from '../components/sidebar/Sidebar';
import { useMenu } from './menu';

export type Item = StoriesHash[keyof StoriesHash];

export const collapseAllStories = (stories: StoriesHash) => {
// keep track of component IDs that have been rewritten to the ID of their first leaf child
const componentIdToLeafId: Record<string, string> = {};

// 1) remove all leaves
const leavesRemoved = Object.values(stories).filter(
(item) => !(item.isLeaf && stories[item.parent].isComponent)
);

// 2) make all components leaves and rewrite their ID's to the first leaf child
const componentsFlattened = leavesRemoved.map((item) => {
const { id, isComponent, isRoot, children, ...rest } = item;

// this is a folder, so just leave it alone
if (!isComponent) {
return item;
}

const nonLeafChildren: string[] = [];
const leafChildren: string[] = [];
children.forEach((child) =>
(stories[child].isLeaf ? leafChildren : nonLeafChildren).push(child)
);

if (leafChildren.length === 0) {
return item; // pass through, we'll handle you later
}

const leafId = leafChildren[0];
const component = {
...rest,
id: leafId,
kind: (stories[leafId] as Story).kind,
isRoot: false,
isLeaf: true,
isComponent: true,
children: [] as string[],
};
componentIdToLeafId[id] = leafId;

// this is a component, so it should not have any non-leaf children
if (nonLeafChildren.length !== 0) {
throw new Error(
`Unexpected '${item.id}': ${JSON.stringify({ isComponent, nonLeafChildren })}`
);
}

return component;
});

// 3) rewrite all the children as needed
const childrenRewritten = componentsFlattened.map((item) => {
if (item.isLeaf) {
return item;
}

const { children, ...rest } = item;
const rewritten = children.map((child) => componentIdToLeafId[child] || child);

return { children: rewritten, ...rest };
});

const result = {} as StoriesHash;
childrenRewritten.forEach((item) => {
result[item.id] = item as Item;
});
return result;
};

export const collapseDocsOnlyStories = (storiesHash: StoriesHash) => {
// keep track of component IDs that have been rewritten to the ID of their first leaf child
const componentIdToLeafId: Record<string, string> = {};
const docsOnlyStoriesRemoved = Object.values(storiesHash).filter((item) => {
if (item.isLeaf && item.parameters && item.parameters.docsOnly) {
componentIdToLeafId[item.parent] = item.id;
return false; // filter it out
}
return true;
});

const docsOnlyComponentsCollapsed = docsOnlyStoriesRemoved.map((item) => {
// collapse docs-only components
const { isComponent, children, id } = item;
if (isComponent && children.length === 1) {
const leafId = componentIdToLeafId[id];
if (leafId) {
const collapsed = {
...item,
id: leafId,
isLeaf: true,
children: [] as string[],
};
return collapsed;
}
}

// update groups
if (children) {
const rewritten = children.map((child) => componentIdToLeafId[child] || child);
return { ...item, children: rewritten };
}

// pass through stories unmodified
return item;
});

const result = {} as StoriesHash;
docsOnlyComponentsCollapsed.forEach((item) => {
result[item.id] = item as Item;
});
return result;
};

const clone = rfdc({ circles: true });

const Sidebar: FunctionComponent<{}> = React.memo(() => {
const mapper = ({ state, api }: Combo) => {
const {
Expand All @@ -137,19 +20,12 @@ const Sidebar: FunctionComponent<{}> = React.memo(() => {
refs,
} = state;

const stories = useMemo(() => {
// protect against mutation
const copy = clone(storiesHash);

return DOCS_MODE ? collapseAllStories(copy) : collapseDocsOnlyStories(copy);
}, [DOCS_MODE, storiesHash]);

const menu = useMenu(api, isFullscreen, showPanel, showNav, enableShortcuts);

return {
title: name,
url,
stories,
stories: storiesHash,
storiesFailed,
storiesConfigured,
refs,
Expand Down

0 comments on commit 764328b

Please sign in to comment.