mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-02 05:03:44 +08:00
Merge pull request #17726 from passbyval/passbyval-patch-1
Fix nesting issue for refs in sidebar component
This commit is contained in:
commit
7053de3384
@ -25,6 +25,7 @@ import { createContext } from './context';
|
||||
import Store, { Options } from './store';
|
||||
import getInitialState from './initial-state';
|
||||
import type { StoriesHash, Story, Root, Group } from './lib/stories';
|
||||
import type { ComposedRef, Refs } from './modules/refs';
|
||||
import { isGroup, isRoot, isStory } from './lib/stories';
|
||||
|
||||
import * as provider from './modules/provider';
|
||||
@ -328,7 +329,7 @@ export function useStorybookApi(): API {
|
||||
return api;
|
||||
}
|
||||
|
||||
export type { StoriesHash, Story, Root, Group };
|
||||
export type { StoriesHash, Story, Root, Group, ComposedRef, Refs };
|
||||
export { ManagerConsumer as Consumer, ManagerProvider as Provider, isGroup, isRoot, isStory };
|
||||
|
||||
export interface EventMap {
|
||||
|
@ -3,7 +3,7 @@ import React, { FunctionComponent, useMemo } from 'react';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
import { ScrollArea, Spaced } from '@storybook/components';
|
||||
import type { StoriesHash, State } from '@storybook/api';
|
||||
import type { StoriesHash, State, ComposedRef } from '@storybook/api';
|
||||
|
||||
import { Heading } from './Heading';
|
||||
|
||||
@ -100,28 +100,24 @@ export const Sidebar: FunctionComponent<SidebarProps> = React.memo(
|
||||
enableShortcuts = true,
|
||||
refs = {},
|
||||
}) => {
|
||||
const collapseFn = DOCS_MODE ? collapseAllStories : collapseDocsOnlyStories;
|
||||
const selected: Selection = useMemo(() => storyId && { storyId, refId }, [storyId, refId]);
|
||||
const stories = useMemo(
|
||||
() => (DOCS_MODE ? collapseAllStories : collapseDocsOnlyStories)(storiesHash),
|
||||
[DOCS_MODE, storiesHash]
|
||||
);
|
||||
const stories = useMemo(() => collapseFn(storiesHash), [DOCS_MODE, storiesHash]);
|
||||
|
||||
const adaptedRefs = useMemo(() => {
|
||||
if (DOCS_MODE) {
|
||||
return Object.keys(refs).reduce((acc: Refs, cur) => {
|
||||
const ref = refs[cur];
|
||||
if (ref.stories) {
|
||||
acc[cur] = {
|
||||
...ref,
|
||||
stories: collapseDocsOnlyStories(ref.stories),
|
||||
};
|
||||
} else {
|
||||
acc[cur] = ref;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
return refs;
|
||||
return Object.entries(refs).reduce((acc: Refs, [id, ref]: [string, ComposedRef]) => {
|
||||
if (ref.stories) {
|
||||
acc[id] = {
|
||||
...ref,
|
||||
stories: collapseFn(ref.stories),
|
||||
};
|
||||
} else {
|
||||
acc[id] = ref;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}, [DOCS_MODE, refs]);
|
||||
|
||||
const dataset = useCombination(stories, storiesConfigured, storiesFailed, adaptedRefs);
|
||||
const isLoading = !dataset.hash[DEFAULT_REF_ID].ready;
|
||||
const lastViewedProps = useLastViewed(selected);
|
||||
|
112
lib/ui/src/components/sidebar/__tests__/Sidebar.test.tsx
Normal file
112
lib/ui/src/components/sidebar/__tests__/Sidebar.test.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { ThemeProvider, ensure, themes } from '@storybook/theming';
|
||||
|
||||
import type { Story, StoriesHash, Refs } from '@storybook/api';
|
||||
import type { Theme } from '@storybook/theming';
|
||||
import type { RenderResult } from '@testing-library/react';
|
||||
import { Sidebar } from '../Sidebar';
|
||||
import type { SidebarProps } from '../Sidebar';
|
||||
|
||||
global.DOCS_MODE = false;
|
||||
|
||||
const PAGE_NAME = 'Page';
|
||||
|
||||
const factory = (props: Partial<SidebarProps>): RenderResult => {
|
||||
const theme: Theme = ensure(themes.light);
|
||||
|
||||
return render(
|
||||
<ThemeProvider theme={theme}>
|
||||
<Sidebar storiesConfigured menu={[]} stories={{}} refs={{}} {...props} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const generateStories = ({ kind, refId }: { kind: string; refId?: string }): StoriesHash => {
|
||||
const [root, storyName]: [string, string] = kind.split('/') as any;
|
||||
const rootId: string = root.toLowerCase().replace(/\s+/g, '-');
|
||||
const hypenatedstoryName: string = storyName.toLowerCase().replace(/\s+/g, '-');
|
||||
const storyId = `${rootId}-${hypenatedstoryName}`;
|
||||
const pageId = `${rootId}-${hypenatedstoryName}--page`;
|
||||
|
||||
const storyBase: Partial<Story>[] = [
|
||||
{
|
||||
id: rootId,
|
||||
name: root,
|
||||
children: [storyId],
|
||||
startCollapsed: false,
|
||||
},
|
||||
{
|
||||
id: storyId,
|
||||
name: storyName,
|
||||
children: [pageId],
|
||||
isComponent: true,
|
||||
parent: rootId,
|
||||
},
|
||||
{
|
||||
id: pageId,
|
||||
name: PAGE_NAME,
|
||||
story: PAGE_NAME,
|
||||
kind,
|
||||
componentId: storyId,
|
||||
parent: storyId,
|
||||
title: kind,
|
||||
},
|
||||
];
|
||||
|
||||
return storyBase.reduce(
|
||||
(accumulator: StoriesHash, current: Partial<Story>, index: number): StoriesHash => {
|
||||
const { id, name } = current;
|
||||
const isRoot: boolean = index === 0;
|
||||
|
||||
const story: Story = {
|
||||
...current,
|
||||
depth: index,
|
||||
isRoot,
|
||||
isLeaf: name === PAGE_NAME,
|
||||
refId,
|
||||
};
|
||||
|
||||
if (!isRoot) {
|
||||
story.parameters = {};
|
||||
story.parameters.docsOnly = true;
|
||||
}
|
||||
|
||||
accumulator[id] = story;
|
||||
|
||||
return accumulator;
|
||||
},
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
describe('Sidebar', () => {
|
||||
test("should not render an extra nested 'Page'", async () => {
|
||||
const refId = 'next';
|
||||
const kind = 'Getting Started/Install';
|
||||
const refStories: StoriesHash = generateStories({ refId, kind });
|
||||
const internalStories: StoriesHash = generateStories({ kind: 'Welcome/Example' });
|
||||
|
||||
const refs: Refs = {
|
||||
[refId]: {
|
||||
stories: refStories,
|
||||
id: refId,
|
||||
ready: true,
|
||||
title: refId,
|
||||
},
|
||||
};
|
||||
|
||||
factory({
|
||||
refs,
|
||||
refId,
|
||||
stories: internalStories,
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByText('Install'));
|
||||
fireEvent.click(screen.getByText('Example'));
|
||||
|
||||
const pageItems: HTMLElement[] = await screen.queryAllByText('Page');
|
||||
|
||||
expect(pageItems).toHaveLength(0);
|
||||
});
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
import type { StoriesHash } from '@storybook/api';
|
||||
import { collapseDocsOnlyStories, collapseAllStories } from './data';
|
||||
import { collapseDocsOnlyStories, collapseAllStories } from '../data';
|
||||
|
||||
type Item = StoriesHash[keyof StoriesHash];
|
||||
|
@ -9,11 +9,11 @@ export const collapseAllStories = (stories: StoriesHash) => {
|
||||
|
||||
// 1) remove all leaves
|
||||
const leavesRemoved = Object.values(stories).filter(
|
||||
(item) => !(item.isLeaf && stories[item.parent].isComponent)
|
||||
(item: Story) => !(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 componentsFlattened = leavesRemoved.map((item: Story) => {
|
||||
const { id, isComponent, children, ...rest } = item;
|
||||
|
||||
// this is a folder, so just leave it alone
|
||||
@ -23,7 +23,7 @@ export const collapseAllStories = (stories: StoriesHash) => {
|
||||
|
||||
const nonLeafChildren: string[] = [];
|
||||
const leafChildren: string[] = [];
|
||||
children.forEach((child) =>
|
||||
children.forEach((child: string) =>
|
||||
(stories[child].isLeaf ? leafChildren : nonLeafChildren).push(child)
|
||||
);
|
||||
|
||||
@ -61,7 +61,7 @@ export const collapseAllStories = (stories: StoriesHash) => {
|
||||
}
|
||||
|
||||
const { children, ...rest } = item;
|
||||
const rewritten = children.map((child) => componentIdToLeafId[child] || child);
|
||||
const rewritten = children.map((child: string) => componentIdToLeafId[child] || child);
|
||||
|
||||
return { children: rewritten, ...rest };
|
||||
});
|
||||
@ -76,7 +76,7 @@ export const collapseAllStories = (stories: StoriesHash) => {
|
||||
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) => {
|
||||
const docsOnlyStoriesRemoved = Object.values(storiesHash).filter((item: Story) => {
|
||||
if (item.isLeaf && item.parameters && item.parameters.docsOnly) {
|
||||
componentIdToLeafId[item.parent] = item.id;
|
||||
return false; // filter it out
|
||||
@ -84,7 +84,7 @@ export const collapseDocsOnlyStories = (storiesHash: StoriesHash) => {
|
||||
return true;
|
||||
});
|
||||
|
||||
const docsOnlyComponentsCollapsed = docsOnlyStoriesRemoved.map((item) => {
|
||||
const docsOnlyComponentsCollapsed = docsOnlyStoriesRemoved.map((item: Story) => {
|
||||
// collapse docs-only components
|
||||
const { isComponent, children, id } = item;
|
||||
if (isComponent && children.length === 1) {
|
||||
@ -103,7 +103,7 @@ export const collapseDocsOnlyStories = (storiesHash: StoriesHash) => {
|
||||
|
||||
// update groups
|
||||
if (children) {
|
||||
const rewritten = children.map((child) => componentIdToLeafId[child] || child);
|
||||
const rewritten = children.map((child: string) => componentIdToLeafId[child] || child);
|
||||
return { ...item, children: rewritten };
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user