Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions code/addons/docs/src/blocks/blocks/Source.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export const useSourceProps = (
format,
language,
dark,
simplifiedCode: source?.simplifiedCode,
};
};

Expand Down
10 changes: 9 additions & 1 deletion code/addons/docs/src/blocks/blocks/SourceContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function argsHash(args: Args): ArgsHash {
export interface SourceItem {
code: string;
format?: SyntaxHighlighterFormatTypes;
simplifiedCode?: string;
}

export type StorySources = Record<StoryId, Record<ArgsHash, SourceItem>>;
Expand All @@ -31,6 +32,7 @@ type SnippetRenderedEvent = {
source: string;
args?: Args;
format?: SyntaxHighlighterFormatTypes;
simplifiedSource?: string;
};

export const UNKNOWN_ARGS_HASH = '--unknown--';
Expand All @@ -52,11 +54,13 @@ export const SourceContainer: FC<PropsWithChildren<{ channel: DocsContextProps['
args = undefined,
source,
format,
simplifiedSource,
} = typeof idOrEvent === 'string'
? {
id: idOrEvent,
source: inputSource,
format: inputFormat,
simplifiedSource: undefined,
}
: idOrEvent;

Expand All @@ -75,7 +79,11 @@ export const SourceContainer: FC<PropsWithChildren<{ channel: DocsContextProps['
...current,
[id]: {
...current[id],
[hash]: { code: source || '', format },
[hash]: {
code: source || '',
format,
simplifiedCode: simplifiedSource,
},
},
};

Expand Down
75 changes: 64 additions & 11 deletions code/addons/docs/src/blocks/components/Source.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ComponentProps, FunctionComponent } from 'react';
import React from 'react';
import React, { useState } from 'react';

import type { SupportedLanguage, SyntaxHighlighterProps } from 'storybook/internal/components';
import { SyntaxHighlighter } from 'storybook/internal/components';
Expand Down Expand Up @@ -50,8 +50,38 @@ export interface SourceCodeProps {
export interface SourceProps extends SourceCodeProps {
isLoading?: boolean;
error?: SourceError;
simplifiedCode?: string;
}

const SourceToggleWrapper = styled.div({
position: 'relative',
});

const SourceToggleButton = styled.button(({ theme }) => ({
position: 'absolute',
top: 10,
right: 10,
zIndex: 1,
padding: '6px 12px',
fontSize: 12,
fontWeight: 600,
lineHeight: '16px',
color: theme.color.lightest,
background: 'rgba(255, 255, 255, 0.1)',
border: `1px solid rgba(255, 255, 255, 0.2)`,
borderRadius: theme.appBorderRadius,
cursor: 'pointer',
transition: 'all 150ms ease-out',
'&:hover': {
background: 'rgba(255, 255, 255, 0.2)',
borderColor: 'rgba(255, 255, 255, 0.3)',
},
'&:focus': {
outline: 'none',
boxShadow: `0 0 0 1px ${theme.color.secondary}`,
},
}));

const SourceSkeletonWrapper = styled.div(({ theme }) => ({
background: theme.background.content,
borderRadius: theme.appBorderRadius,
Expand Down Expand Up @@ -91,27 +121,50 @@ const Source: FunctionComponent<SourceProps> = ({
code,
dark,
format = true,
simplifiedCode,
...rest
}) => {
const { typography } = useTheme();
const [showSimplified, setShowSimplified] = useState(true);

if (isLoading) {
return <SourceSkeleton />;
}
if (error) {
return <EmptyBlock>{error}</EmptyBlock>;
}

// Determine which code to display based on toggle state
const displayCode = showSimplified && simplifiedCode ? simplifiedCode : code;

// Only show toggle if we have both versions and they're different
const showToggle = simplifiedCode && simplifiedCode !== code;

const syntaxHighlighter = (
<StyledSyntaxHighlighter
bordered
copyable
format={format}
language={language ?? 'jsx'}
className="docblock-source sb-unstyled"
{...rest}
>
{code}
</StyledSyntaxHighlighter>
<SourceToggleWrapper>
{showToggle && (
<SourceToggleButton
onClick={() => setShowSimplified(!showSimplified)}
title={
showSimplified
? 'Expand to show full component names (e.g., Card.Header)'
: 'Collapse to show simplified names (e.g., CardHeader)'
}
>
{showSimplified ? 'Expand code' : 'Collapse code'}
</SourceToggleButton>
)}
<StyledSyntaxHighlighter
bordered
copyable
format={format}
language={language ?? 'jsx'}
className="docblock-source sb-unstyled"
{...rest}
>
{displayCode}
</StyledSyntaxHighlighter>
</SourceToggleWrapper>
);
if (typeof dark === 'undefined') {
return syntaxHighlighter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,24 @@ type Transformer =
| ((code: string, storyContext: ReducedStoryContext) => string | Promise<string>)
| undefined;

export async function emitTransformCode(source: string | undefined, context: ReducedStoryContext) {
export async function emitTransformCode(
source: string | undefined,
context: ReducedStoryContext,
simplifiedSource?: string
) {
const transform = context.parameters?.docs?.source?.transform as Transformer;
const { id, unmappedArgs } = context;

const transformed = transform && source ? transform?.(source, context) : source;
const result = transformed ? await transformed : undefined;
const transformedSimplified =
transform && simplifiedSource ? transform?.(simplifiedSource, context) : simplifiedSource;
const simplifiedResult = transformedSimplified ? await transformedSimplified : undefined;

addons.getChannel().emit(SNIPPET_RENDERED, {
id,
source: result,
args: unmappedArgs,
simplifiedSource: simplifiedResult,
});
}
46 changes: 40 additions & 6 deletions code/renderers/react/src/docs/jsxDecorator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,11 @@ type JSXOptions = Options & {
displayName?: string | Options['displayName'];
};

/** Apply the users parameters and render the jsx for a story */
export const renderJsx = (code: React.ReactElement, options?: JSXOptions) => {
/** Apply the users parameters and render the jsx for a story, with option to use simplified names */
export const renderJsx = (
code: React.ReactElement,
options?: JSXOptions & { useSimplifiedNames?: boolean }
) => {
if (typeof code === 'undefined') {
logger.warn('Too many skip or undefined component');
return null;
Expand Down Expand Up @@ -134,6 +137,27 @@ export const renderJsx = (code: React.ReactElement, options?: JSXOptions) => {
displayNameDefaults = {
// To get exotic component names resolving properly
displayName: (el: any): string => {
// This is used to generate the "simplified" version for the toggle UI
if (options?.useSimplifiedNames) {
if (el.type.name && el.type.name !== '_default') {
return el.type.name;
} else if (typeof el.type === 'function') {
return 'No Display Name';
} else if (isForwardRef(el.type)) {
return el.type.render.name;
} else if (isMemo(el.type)) {
return el.type.type.name;
} else if (
typeof el.type === 'symbol' ||
(el.type.$$typeof && typeof el.type.$$typeof === 'symbol')
) {
return getReactSymbolName(el.type);
} else {
return el.type;
}
}

// Default behavior: use displayName if available (preserves dot notation)
if (el.type.displayName) {
return el.type.displayName;
} else if (getDocgenSection(el.type, 'displayName')) {
Expand Down Expand Up @@ -236,6 +260,7 @@ export const jsxDecorator = (
context: StoryContext<ReactRenderer>
) => {
const jsx = useRef<undefined | string>(undefined);
const simplifiedJsx = useRef<undefined | string>(undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n 'simplifiedJsx' code/renderers/react/src/docs/

Repository: storybookjs/storybook

Length of output: 292


🏁 Script executed:

sed -n '260,300p' code/renderers/react/src/docs/jsxDecorator.tsx

Repository: storybookjs/storybook

Length of output: 1141


🏁 Script executed:

sed -n '280,305p' code/renderers/react/src/docs/jsxDecorator.tsx

Repository: storybookjs/storybook

Length of output: 676


Remove unused simplifiedJsx ref or add explanatory comment.

The simplifiedJsx ref is assigned on line 294 but never read anywhere in the code. The value is computed in the local variable renderedSimplified which is used directly in the emitTransformCode call, making the ref assignment dead code. Either remove it or add a comment explaining its purpose if intended for future use.

🤖 Prompt for AI Agents
In `@code/renderers/react/src/docs/jsxDecorator.tsx` at line 263, The ref
simplifiedJsx is never read (its value is computed into renderedSimplified and
passed directly to emitTransformCode), so either remove the unused const
simplifiedJsx declaration or keep it but add a clear explanatory comment/TODO
above the ref explaining its intended future use; if you remove it, also delete
the later assignment that sets simplifiedJsx.current (the assignment near where
renderedSimplified is computed) so there is no dead code left, and if you keep
it, document why it’s stored (e.g., for debugging or future access) and mark it
with a TODO.

const story = storyFn();

const skip = skipJsxRender(context);
Expand All @@ -254,10 +279,19 @@ export const jsxDecorator = (

const sourceJsx = mdxToJsx(storyJsx);

const rendered = renderJsx(sourceJsx, options);
if (rendered && jsx.current !== rendered) {
emitTransformCode(rendered, context);
jsx.current = rendered;
const renderedFull = renderJsx(sourceJsx, options);
const renderedSimplified = renderJsx(sourceJsx, { ...options, useSimplifiedNames: true });

if (renderedFull && jsx.current !== renderedFull) {
const hasCompoundComponents = renderedFull !== renderedSimplified;

emitTransformCode(
renderedFull,
context,
hasCompoundComponents && renderedSimplified ? renderedSimplified : undefined
);
jsx.current = renderedFull;
simplifiedJsx.current = renderedSimplified ?? undefined;
}
});

Expand Down