mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-09 00:19:13 +08:00
Merge branch 'valentin/6-to-8-automigrations' into valentin/enhance-mdx-to-csf-codemod
This commit is contained in:
commit
78fa304252
23
code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts
vendored
Normal file
23
code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
import { global as globalThis } from '@storybook/global';
|
||||
|
||||
export default {
|
||||
title: 'Multiple CSF Files Same Title',
|
||||
component: globalThis.Components.Html,
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
content: '<p>paragraph</p>',
|
||||
},
|
||||
parameters: {
|
||||
chromatic: { disable: true },
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultA = {};
|
||||
|
||||
export const SpanContent = {
|
||||
args: { content: '<span>span</span>' },
|
||||
};
|
||||
|
||||
export const CodeContent = {
|
||||
args: { content: '<code>code</code>' },
|
||||
};
|
23
code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts
vendored
Normal file
23
code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
import { global as globalThis } from '@storybook/global';
|
||||
|
||||
export default {
|
||||
title: 'Multiple CSF Files Same Title',
|
||||
component: globalThis.Components.Html,
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
content: '<p>paragraph</p>',
|
||||
},
|
||||
parameters: {
|
||||
chromatic: { disable: true },
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultB = {};
|
||||
|
||||
export const H1Content = {
|
||||
args: { content: '<h1>heading 1</h1>' },
|
||||
};
|
||||
|
||||
export const H2Content = {
|
||||
args: { content: '<h2>heading 2</h2>' },
|
||||
};
|
@ -210,4 +210,19 @@ test.describe('addon-docs', () => {
|
||||
await expect(componentReactVersion).toHaveText(expectedReactVersion);
|
||||
await expect(componentReactDomVersion).toHaveText(expectedReactVersion);
|
||||
});
|
||||
|
||||
test('should have stories from multiple CSF files in autodocs', async ({ page }) => {
|
||||
const sbPage = new SbPage(page);
|
||||
await sbPage.navigateToStory('/addons/docs/multiple-csf-files-same-title', 'docs');
|
||||
const root = sbPage.previewRoot();
|
||||
|
||||
const storyHeadings = root.locator('.sb-anchor > h3');
|
||||
await expect(await storyHeadings.count()).toBe(6);
|
||||
await expect(storyHeadings.nth(0)).toHaveText('Default A');
|
||||
await expect(storyHeadings.nth(1)).toHaveText('Span Content');
|
||||
await expect(storyHeadings.nth(2)).toHaveText('Code Content');
|
||||
await expect(storyHeadings.nth(3)).toHaveText('Default B');
|
||||
await expect(storyHeadings.nth(4)).toHaveText('H 1 Content');
|
||||
await expect(storyHeadings.nth(5)).toHaveText('H 2 Content');
|
||||
});
|
||||
});
|
||||
|
@ -81,7 +81,7 @@ export const blocker = createBlocker({
|
||||
default:
|
||||
return dedent`
|
||||
Support for ${data.packageName} version < ${data.minimumVersion} has been removed.
|
||||
Since version 8, Storybook needs minimum version of ${data.minimumVersion}, but you had version ${data.installedVersion}.
|
||||
Since version 8, Storybook needs a minimum version of ${data.minimumVersion}, but you have version ${data.installedVersion}.
|
||||
|
||||
Please update this dependency.
|
||||
`;
|
||||
|
@ -84,7 +84,7 @@ export const mdxgfm: Fix<Options> = {
|
||||
Because of this you need to explicitly add the GFM plugin in the addon-docs options:
|
||||
https://storybook.js.org/docs/react/writing-docs/mdx#lack-of-github-flavored-markdown-gfm
|
||||
|
||||
We recommend you follow the guide on the link above, however we can add a temporary Storybook addon that helps make this migration easier.
|
||||
We recommend that you follow the guide in the link above; however, we can add a temporary Storybook addon to help make this migration easier.
|
||||
We'll install the addon and add it to your storybook config.
|
||||
`;
|
||||
},
|
||||
|
@ -76,7 +76,8 @@ export const removeArgtypesRegex: Fix<{ argTypesRegex: NodePath; previewConfigPa
|
||||
|
||||
Make sure to assign an explicit ${chalk.cyan('fn')} to your args for those usages.
|
||||
|
||||
For more information please visit our migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#implicit-actions-can-not-be-used-during-rendering-for-example-in-the-play-function
|
||||
For more information please visit our docs:
|
||||
https://storybook.js.org/docs/8.0/essentials/actions#via-storybooktest-fn-spy-function
|
||||
`;
|
||||
},
|
||||
};
|
||||
|
@ -23,7 +23,7 @@ export interface RunOptions<ResultType> {
|
||||
* promptType defines how the user will be prompted to apply an automigration fix
|
||||
* - auto: the fix will be applied automatically
|
||||
* - manual: the user will be prompted to apply the fix
|
||||
* - notification: the user will be notified about some changes. A fix isn't required, thought
|
||||
* - notification: the user will be notified about some changes. A fix isn't required, though
|
||||
*/
|
||||
export type Prompt = 'auto' | 'manual' | 'notification';
|
||||
|
||||
|
@ -104,7 +104,6 @@ export class Yarn2Proxy extends JsPackageManager {
|
||||
}
|
||||
|
||||
public async findInstallations(pattern: string[]) {
|
||||
console.log(['info', '--name-only', '--recursive', ...pattern].join(' '));
|
||||
const commandResult = await this.executeCommand({
|
||||
command: 'yarn',
|
||||
args: ['info', '--name-only', '--recursive', ...pattern],
|
||||
|
@ -17,7 +17,7 @@ import { join, relative, resolve } from 'path';
|
||||
import { deprecate } from '@storybook/node-logger';
|
||||
import { dedent } from 'ts-dedent';
|
||||
import { readFile } from 'fs-extra';
|
||||
import { MissingBuilderError } from '@storybook/core-events/server-errors';
|
||||
import { MissingBuilderError, NoStatsForViteDevError } from '@storybook/core-events/server-errors';
|
||||
import { storybookDevServer } from './dev-server';
|
||||
import { outputStats } from './utils/output-stats';
|
||||
import { outputStartupInformation } from './utils/output-startup-information';
|
||||
@ -192,7 +192,16 @@ export async function buildDevStandalone(
|
||||
if (options.smokeTest) {
|
||||
const warnings: Error[] = [];
|
||||
warnings.push(...(managerStats?.toJson()?.warnings || []));
|
||||
warnings.push(...(previewStats?.toJson()?.warnings || []));
|
||||
try {
|
||||
warnings.push(...(previewStats?.toJson()?.warnings || []));
|
||||
} catch (err) {
|
||||
if (err instanceof NoStatsForViteDevError) {
|
||||
// pass, the Vite builder has no warnings in the stats object anyway,
|
||||
// but no stats at all in dev mode
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const problems = warnings
|
||||
.filter((warning) => !warning.message.includes(`export 'useInsertionEffect'`))
|
||||
|
@ -517,11 +517,8 @@ export function useArgTypes(): ArgTypes {
|
||||
|
||||
export { addons } from './lib/addons';
|
||||
|
||||
/**
|
||||
* We need to rename this so it's not compiled to a straight re-export
|
||||
* Our globalization plugin can't handle an import and export of the same name in different lines
|
||||
* @deprecated
|
||||
*/
|
||||
// We need to rename this so it's not compiled to a straight re-export
|
||||
// Our globalization plugin can't handle an import and export of the same name in different lines
|
||||
const typesX = types;
|
||||
|
||||
export { typesX as types };
|
||||
|
@ -33,6 +33,52 @@ describe('referenceCSFFile', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('attachCSFFile', () => {
|
||||
const firstCsfParts = csfFileParts('first-meta--first-story', 'first-meta');
|
||||
const secondCsfParts = csfFileParts('second-meta--second-story', 'second-meta');
|
||||
const store = {
|
||||
componentStoriesFromCSFFile: ({ csfFile }: { csfFile: CSFFile }) =>
|
||||
csfFile === firstCsfParts.csfFile ? [firstCsfParts.story] : [secondCsfParts.story],
|
||||
} as unknown as StoryStore<Renderer>;
|
||||
|
||||
it('attaches multiple CSF files', () => {
|
||||
// Arrange - create a context with both CSF files
|
||||
const context = new DocsContext(channel, store, renderStoryToElement, [
|
||||
firstCsfParts.csfFile,
|
||||
secondCsfParts.csfFile,
|
||||
]);
|
||||
|
||||
// Act - attach the first CSF file
|
||||
context.attachCSFFile(firstCsfParts.csfFile);
|
||||
|
||||
// Assert - the first story is now the primary story and the only component story
|
||||
expect(context.storyById()).toEqual(firstCsfParts.story);
|
||||
expect(context.componentStories()).toEqual([firstCsfParts.story]);
|
||||
|
||||
// Assert - stories from both CSF files are available
|
||||
expect(context.componentStoriesFromCSFFile(firstCsfParts.csfFile)).toEqual([
|
||||
firstCsfParts.story,
|
||||
]);
|
||||
expect(context.componentStoriesFromCSFFile(secondCsfParts.csfFile)).toEqual([
|
||||
secondCsfParts.story,
|
||||
]);
|
||||
|
||||
// Act - attach the second CSF file
|
||||
context.attachCSFFile(secondCsfParts.csfFile);
|
||||
|
||||
// Assert - the first story is still the primary story but both stories are available
|
||||
expect(context.storyById()).toEqual(firstCsfParts.story);
|
||||
expect(context.componentStories()).toEqual([firstCsfParts.story, secondCsfParts.story]);
|
||||
|
||||
// Act - attach the second CSF file again
|
||||
context.attachCSFFile(secondCsfParts.csfFile);
|
||||
|
||||
// Assert - still only two stories are available
|
||||
expect(context.storyById()).toEqual(firstCsfParts.story);
|
||||
expect(context.componentStories()).toEqual([firstCsfParts.story, secondCsfParts.story]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveOf', () => {
|
||||
const { story, csfFile, storyExport, metaExport, moduleExports, component } = csfFileParts();
|
||||
|
||||
|
@ -27,7 +27,7 @@ export class DocsContext<TRenderer extends Renderer> implements DocsContextProps
|
||||
|
||||
private nameToStoryId: Map<StoryName, StoryId>;
|
||||
|
||||
private attachedCSFFile?: CSFFile<TRenderer>;
|
||||
private attachedCSFFiles: Set<CSFFile<TRenderer>>;
|
||||
|
||||
private primaryStory?: PreparedStory<TRenderer>;
|
||||
|
||||
@ -38,11 +38,12 @@ export class DocsContext<TRenderer extends Renderer> implements DocsContextProps
|
||||
/** The CSF files known (via the index) to be refererenced by this docs file */
|
||||
csfFiles: CSFFile<TRenderer>[]
|
||||
) {
|
||||
this.componentStoriesValue = [];
|
||||
this.storyIdToCSFFile = new Map();
|
||||
this.exportToStory = new Map();
|
||||
this.exportsToCSFFile = new Map();
|
||||
this.nameToStoryId = new Map();
|
||||
this.componentStoriesValue = [];
|
||||
this.attachedCSFFiles = new Set();
|
||||
|
||||
csfFiles.forEach((csfFile, index) => {
|
||||
this.referenceCSFFile(csfFile);
|
||||
@ -71,10 +72,15 @@ export class DocsContext<TRenderer extends Renderer> implements DocsContextProps
|
||||
if (!this.exportsToCSFFile.has(csfFile.moduleExports)) {
|
||||
throw new Error('Cannot attach a CSF file that has not been referenced');
|
||||
}
|
||||
if (this.attachedCSFFiles.has(csfFile)) {
|
||||
// this CSF file is already attached, don't do anything
|
||||
return;
|
||||
}
|
||||
|
||||
this.attachedCSFFile = csfFile;
|
||||
this.attachedCSFFiles.add(csfFile);
|
||||
|
||||
const stories = this.store.componentStoriesFromCSFFile({ csfFile });
|
||||
|
||||
stories.forEach((story) => {
|
||||
this.nameToStoryId.set(story.name, story.id);
|
||||
this.componentStoriesValue.push(story);
|
||||
@ -115,15 +121,18 @@ export class DocsContext<TRenderer extends Renderer> implements DocsContextProps
|
||||
return { type: 'story', story: this.primaryStory } as TResolvedExport;
|
||||
}
|
||||
|
||||
if (!this.attachedCSFFile)
|
||||
if (this.attachedCSFFiles.size === 0)
|
||||
throw new Error(
|
||||
`No CSF file attached to this docs file, did you forget to use <Meta of={} />?`
|
||||
);
|
||||
|
||||
if (moduleExportType === 'meta')
|
||||
return { type: 'meta', csfFile: this.attachedCSFFile } as TResolvedExport;
|
||||
const firstAttachedCSFFile = Array.from(this.attachedCSFFiles)[0];
|
||||
|
||||
const { component } = this.attachedCSFFile.meta;
|
||||
if (moduleExportType === 'meta') {
|
||||
return { type: 'meta', csfFile: firstAttachedCSFFile } as TResolvedExport;
|
||||
}
|
||||
|
||||
const { component } = firstAttachedCSFFile.meta;
|
||||
if (!component)
|
||||
throw new Error(
|
||||
`Attached CSF file does not defined a component, did you forget to export one?`
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { CSFFile, PreparedStory } from '@storybook/types';
|
||||
|
||||
export function csfFileParts() {
|
||||
export function csfFileParts(storyId = 'meta--story', metaId = 'meta') {
|
||||
// These compose the raw exports of the CSF file
|
||||
const component = {};
|
||||
const metaExport = { component };
|
||||
@ -9,13 +9,13 @@ export function csfFileParts() {
|
||||
|
||||
// This is the prepared story + CSF file after SB has processed them
|
||||
const storyAnnotations = {
|
||||
id: 'meta--story',
|
||||
id: storyId,
|
||||
moduleExport: storyExport,
|
||||
} as CSFFile['stories'][string];
|
||||
const story = { id: 'meta--story', moduleExport: storyExport } as PreparedStory;
|
||||
const meta = { id: 'meta', title: 'Meta', component, moduleExports } as CSFFile['meta'];
|
||||
const story = { id: storyId, moduleExport: storyExport } as PreparedStory;
|
||||
const meta = { id: metaId, title: 'Meta', component, moduleExports } as CSFFile['meta'];
|
||||
const csfFile = {
|
||||
stories: { 'meta--story': storyAnnotations },
|
||||
stories: { [storyId]: storyAnnotations },
|
||||
meta,
|
||||
moduleExports,
|
||||
} as CSFFile;
|
||||
|
@ -358,7 +358,12 @@ export interface Addon_BaseType {
|
||||
* This is called as a function, so if you want to use hooks,
|
||||
* your function needs to return a JSX.Element within which components are rendered
|
||||
*/
|
||||
render: (renderOptions: Partial<Addon_RenderOptions>) => ReactElement<any, any> | null;
|
||||
render: (props: Partial<Addon_RenderOptions>) => ReturnType<FC<Partial<Addon_RenderOptions>>>;
|
||||
// TODO: for Storybook 9 I'd like to change this to be:
|
||||
// render: FC<Partial<Addon_RenderOptions>>;
|
||||
// This would bring it in line with how every other addon is set up.
|
||||
// We'd need to change how the render function is called in the manager:
|
||||
// https://github.com/storybookjs/storybook/blob/4e6fc0dde0842841d99cb3cf5148ca293a950301/code/ui/manager/src/components/preview/Preview.tsx#L105
|
||||
/**
|
||||
* @unstable
|
||||
*/
|
||||
|
@ -130,7 +130,7 @@ export const renderJsx = (code: React.ReactElement, options: JSXOptions) => {
|
||||
return string;
|
||||
}).join('\n');
|
||||
|
||||
return result.replace(/function\s+noRefCheck\(\)\s+\{\}/g, '() => {}');
|
||||
return result.replace(/function\s+noRefCheck\(\)\s*\{\}/g, '() => {}');
|
||||
};
|
||||
|
||||
const defaultOpts = {
|
||||
|
@ -5,4 +5,4 @@
|
||||
export let content = '';
|
||||
</script>
|
||||
|
||||
<div>{@html content}></div>
|
||||
<div>{@html content}</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import React, { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { useMediaQuery } from '../hooks/useMedia';
|
||||
import { BREAKPOINT } from '../../constants';
|
||||
|
||||
@ -32,22 +32,29 @@ export const LayoutProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
const isDesktop = useMediaQuery(`(min-width: ${BREAKPOINT}px)`);
|
||||
const isMobile = !isDesktop;
|
||||
|
||||
return (
|
||||
<LayoutContext.Provider
|
||||
value={{
|
||||
isMobileMenuOpen,
|
||||
setMobileMenuOpen,
|
||||
isMobileAboutOpen,
|
||||
setMobileAboutOpen,
|
||||
isMobilePanelOpen,
|
||||
setMobilePanelOpen,
|
||||
isDesktop,
|
||||
isMobile,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LayoutContext.Provider>
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
isMobileMenuOpen,
|
||||
setMobileMenuOpen,
|
||||
isMobileAboutOpen,
|
||||
setMobileAboutOpen,
|
||||
isMobilePanelOpen,
|
||||
setMobilePanelOpen,
|
||||
isDesktop,
|
||||
isMobile,
|
||||
}),
|
||||
[
|
||||
isMobileMenuOpen,
|
||||
setMobileMenuOpen,
|
||||
isMobileAboutOpen,
|
||||
setMobileAboutOpen,
|
||||
isMobilePanelOpen,
|
||||
setMobilePanelOpen,
|
||||
isDesktop,
|
||||
isMobile,
|
||||
]
|
||||
);
|
||||
return <LayoutContext.Provider value={contextValue}>{children}</LayoutContext.Provider>;
|
||||
};
|
||||
|
||||
export const useLayout = () => useContext(LayoutContext);
|
||||
|
@ -5,7 +5,7 @@ import Downshift from 'downshift';
|
||||
import type { FuseOptions } from 'fuse.js';
|
||||
import Fuse from 'fuse.js';
|
||||
import { global } from '@storybook/global';
|
||||
import React, { useMemo, useRef, useState, useCallback } from 'react';
|
||||
import React, { useRef, useState, useCallback } from 'react';
|
||||
import { CloseIcon, SearchIcon } from '@storybook/icons';
|
||||
import { DEFAULT_REF_ID } from './Sidebar';
|
||||
import type {
|
||||
@ -176,8 +176,8 @@ export const Search = React.memo<{
|
||||
[api, inputRef, showAllComponents, DEFAULT_REF_ID]
|
||||
);
|
||||
|
||||
const list: SearchItem[] = useMemo(() => {
|
||||
return dataset.entries.reduce<SearchItem[]>((acc, [refId, { index, status }]) => {
|
||||
const makeFuse = useCallback(() => {
|
||||
const list = dataset.entries.reduce<SearchItem[]>((acc, [refId, { index, status }]) => {
|
||||
const groupStatus = getGroupStatus(index || {}, status);
|
||||
|
||||
if (index) {
|
||||
@ -196,12 +196,12 @@ export const Search = React.memo<{
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
return new Fuse(list, options);
|
||||
}, [dataset]);
|
||||
|
||||
const fuse = useMemo(() => new Fuse(list, options), [list]);
|
||||
|
||||
const getResults = useCallback(
|
||||
(input: string) => {
|
||||
const fuse = makeFuse();
|
||||
if (!input) return [];
|
||||
|
||||
let results: DownshiftItem[] = [];
|
||||
@ -229,7 +229,7 @@ export const Search = React.memo<{
|
||||
|
||||
return results;
|
||||
},
|
||||
[allComponents, fuse]
|
||||
[allComponents, makeFuse]
|
||||
);
|
||||
|
||||
const stateReducer = useCallback(
|
||||
|
@ -481,55 +481,73 @@ export const Tree = React.memo<{
|
||||
|
||||
const groupStatus = useMemo(() => getGroupStatus(collapsedData, status), [collapsedData, status]);
|
||||
|
||||
return (
|
||||
<Container ref={containerRef} hasOrphans={isMain && orphanIds.length > 0}>
|
||||
<IconSymbols />
|
||||
{collapsedItems.map((itemId) => {
|
||||
const item = collapsedData[itemId];
|
||||
const id = createId(itemId, refId);
|
||||
|
||||
if (item.type === 'root') {
|
||||
const descendants = expandableDescendants[item.id];
|
||||
const isFullyExpanded = descendants.every((d: string) => expanded[d]);
|
||||
return (
|
||||
// @ts-expect-error (TODO)
|
||||
<Root
|
||||
key={id}
|
||||
item={item}
|
||||
refId={refId}
|
||||
isOrphan={false}
|
||||
isDisplayed
|
||||
isSelected={selectedStoryId === itemId}
|
||||
isExpanded={!!expanded[itemId]}
|
||||
setExpanded={setExpanded}
|
||||
isFullyExpanded={isFullyExpanded}
|
||||
expandableDescendants={descendants}
|
||||
onSelectStoryId={onSelectStoryId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const isDisplayed = !item.parent || ancestry[itemId].every((a: string) => expanded[a]);
|
||||
const color = groupStatus[itemId] ? statusMapping[groupStatus[itemId]][1] : null;
|
||||
const treeItems = useMemo(() => {
|
||||
return collapsedItems.map((itemId) => {
|
||||
const item = collapsedData[itemId];
|
||||
const id = createId(itemId, refId);
|
||||
|
||||
if (item.type === 'root') {
|
||||
const descendants = expandableDescendants[item.id];
|
||||
const isFullyExpanded = descendants.every((d: string) => expanded[d]);
|
||||
return (
|
||||
<Node
|
||||
api={api}
|
||||
// @ts-expect-error (TODO)
|
||||
<Root
|
||||
key={id}
|
||||
item={item}
|
||||
status={status?.[itemId]}
|
||||
refId={refId}
|
||||
color={color}
|
||||
docsMode={docsMode}
|
||||
isOrphan={orphanIds.some((oid) => itemId === oid || itemId.startsWith(`${oid}-`))}
|
||||
isDisplayed={isDisplayed}
|
||||
isOrphan={false}
|
||||
isDisplayed
|
||||
isSelected={selectedStoryId === itemId}
|
||||
isExpanded={!!expanded[itemId]}
|
||||
setExpanded={setExpanded}
|
||||
isFullyExpanded={isFullyExpanded}
|
||||
expandableDescendants={descendants}
|
||||
onSelectStoryId={onSelectStoryId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
}
|
||||
|
||||
const isDisplayed = !item.parent || ancestry[itemId].every((a: string) => expanded[a]);
|
||||
const color = groupStatus[itemId] ? statusMapping[groupStatus[itemId]][1] : null;
|
||||
|
||||
return (
|
||||
<Node
|
||||
api={api}
|
||||
key={id}
|
||||
item={item}
|
||||
status={status?.[itemId]}
|
||||
refId={refId}
|
||||
color={color}
|
||||
docsMode={docsMode}
|
||||
isOrphan={orphanIds.some((oid) => itemId === oid || itemId.startsWith(`${oid}-`))}
|
||||
isDisplayed={isDisplayed}
|
||||
isSelected={selectedStoryId === itemId}
|
||||
isExpanded={!!expanded[itemId]}
|
||||
setExpanded={setExpanded}
|
||||
onSelectStoryId={onSelectStoryId}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}, [
|
||||
ancestry,
|
||||
api,
|
||||
collapsedData,
|
||||
collapsedItems,
|
||||
docsMode,
|
||||
expandableDescendants,
|
||||
expanded,
|
||||
groupStatus,
|
||||
onSelectStoryId,
|
||||
orphanIds,
|
||||
refId,
|
||||
selectedStoryId,
|
||||
setExpanded,
|
||||
status,
|
||||
]);
|
||||
return (
|
||||
<Container ref={containerRef} hasOrphans={isMain && orphanIds.length > 0}>
|
||||
<IconSymbols />
|
||||
{treeItems}
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user