Skip to content

Addon-docs: Add eject button to canvas toolbar#29825

Merged
shilman merged 15 commits into
storybookjs:nextfrom
mihkeleidast:add-eject-button-to-canvas-toolbar
Sep 28, 2025
Merged

Addon-docs: Add eject button to canvas toolbar#29825
shilman merged 15 commits into
storybookjs:nextfrom
mihkeleidast:add-eject-button-to-canvas-toolbar

Conversation

@mihkeleidast
Copy link
Copy Markdown
Contributor

@mihkeleidast mihkeleidast commented Dec 5, 2024

Closes #25123

What I did

Added the eject button back to Canvas toolbar in docs mode.

I think one future improvement could be to add controls integration to the primary story, as right now it opens with no params applied. But even so, this would be a nice upgrade for our usecases.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Not sure how these blocks are autotested at all. If you can point me in the right direction, I can look into adding tests.

Manual testing

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

  1. Run the default sandbox with yarn start
  2. Change sandbox/react-vite-default-ts/.storybook/preview.ts to enable toolbar on all stories (docs: { canvas: { withToolbar: true } })
  3. Open Storybook in your browser
  4. Open Button story
  5. See that the toolbar is shown on all stories with the eject button present, and all eject buttons have the correct href pointing at the correct story.

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook canary-release-pr.yml --field pr=<PR_NUMBER>

name before after diff z %
createSize 0 B 0 B 0 B - -
generateSize 80.5 MB 80.5 MB 0 B -1.04 0%
initSize 80.5 MB 80.5 MB 0 B -1.04 0%
diffSize 97 B 97 B 0 B - 0%
buildSize 7.31 MB 7.31 MB 1.6 kB 15.21 0%
buildSbAddonsSize 1.9 MB 1.9 MB 888 B 5 0%
buildSbCommonSize 195 kB 195 kB 0 B - 0%
buildSbManagerSize 1.88 MB 1.88 MB 0 B 2 0%
buildSbPreviewSize 0 B 0 B 0 B - -
buildStaticSize 0 B 0 B 0 B - -
buildPrebuildSize 3.97 MB 3.97 MB 888 B 9 0%
buildPreviewSize 3.34 MB 3.35 MB 708 B 25.78 0%
testBuildSize 0 B 0 B 0 B - -
testBuildSbAddonsSize 0 B 0 B 0 B - -
testBuildSbCommonSize 0 B 0 B 0 B - -
testBuildSbManagerSize 0 B 0 B 0 B - -
testBuildSbPreviewSize 0 B 0 B 0 B - -
testBuildStaticSize 0 B 0 B 0 B - -
testBuildPrebuildSize 0 B 0 B 0 B - -
testBuildPreviewSize 0 B 0 B 0 B - -
name before after diff z %
createTime 7.6s 29.2s 21.6s 2.68 🔺73.9%
generateTime 19.6s 19.5s -118ms -0.41 -0.6%
initTime 4.4s 4.5s 53ms -0.5 1.2%
buildTime 9s 9.2s 201ms -0.28 2.2%
testBuildTime 0ms 0ms 0ms - -
devPreviewResponsive 5.8s 5.2s -568ms -0.45 -10.7%
devManagerResponsive 5.6s 5s -537ms 1.28 🔰-10.6%
devManagerHeaderVisible 823ms 768ms -55ms -0.39 -7.2%
devManagerIndexVisible 829ms 773ms -56ms -0.53 -7.2%
devStoryVisibleUncached 2s 1.8s -196ms -1.9 🔰-10.3%
devStoryVisible 852ms 835ms -17ms -0.25 -2%
devAutodocsVisible 819ms 734ms -85ms -0.67 -11.6%
devMDXVisible 782ms 825ms 43ms 0.31 5.2%
buildManagerHeaderVisible 879ms 873ms -6ms 0.75 -0.7%
buildManagerIndexVisible 894ms 881ms -13ms 0.41 -1.5%
buildStoryVisible 813ms 842ms 29ms 0.77 3.4%
buildAutodocsVisible 674ms 612ms -62ms -0.38 -10.1%
buildMDXVisible 731ms 780ms 49ms 1.9 🔺6.3%

Greptile Summary

Added an "Open canvas in new tab" button to the Canvas toolbar in docs mode, restoring previously missing functionality from issue #25123.

  • Added ShareAltIcon eject button in code/lib/blocks/src/components/Toolbar.tsx with proper security attributes
  • Added EjectProps interface in Toolbar.tsx to handle storyId and baseUrl props
  • Modified Preview.tsx to pass story context and ID to Toolbar component
  • Added proper href handling with target="_blank" and rel="noopener noreferrer" security attributes
  • Need to add error handling for cases where href cannot be generated

💡 (1/5) You can manually trigger the bot by mentioning @greptileai in a comment!

Summary by CodeRabbit

  • New Features

    • Added a right-aligned toolbar button to open the current story’s canvas in a new tab (visible when the story is ready), with a share icon.
    • Shows a subtle placeholder in the toolbar while loading.
  • Bug Fixes

    • Improved preview behavior to avoid computing or using the story ID during loading, preventing incorrect navigation or flicker.
    • More robust handling of child props ensures correct story identification once content is ready.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

LGTM

2 file(s) reviewed, no comment(s)
Edit PR Review Bot Settings | Greptile

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

2 file(s) reviewed, 1 comment(s)
Edit PR Review Bot Settings | Greptile

Comment on lines 259 to 261
storyId={getStoryId(getChildProps(children), context)}
baseUrl="./iframe.html"
/>
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.

style: baseUrl is hardcoded to './iframe.html' which may not be correct in all environments. Consider making this configurable via context or props.

Copy link
Copy Markdown
Contributor Author

@mihkeleidast mihkeleidast Dec 5, 2024

Choose a reason for hiding this comment

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

Does the baseUrl here need similar improvements as #12233?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah it probably does. If you fix it I can test it out for you!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@shilman would appreciate if you helped test if this is necessary, I'm not sure it is.

Here's my thinking:

  • I see there are some composed storybooks in the test sandbox
  • The configuration of my sandbox storybook does not affect those, which makes sense - this feature would have to be enabled in both parent and child storybooks.
  • When viewing a child storybook docs page, it renders the child storybook docs page in an iframe, meaning that the links in there should already be correct (they should link to the child storybook story url).

#12233 was needed because the link on the top toolbar is always in the parent storybook, but this is not the case for canvas toolbar.

Granted, I did not manage to get composition working between two sandboxes in localhost, so could not completely test this myself (got some CORS errors, tried vite server proxy but that also failed). If there are issues with composition, I'd appreciate some tips on how to get this working.

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Dec 5, 2024

View your CI Pipeline Execution ↗ for commit c3f22cf

Command Status Duration Result
nx run-many -t build --parallel=3 ✅ Succeeded 47s View ↗

☁️ Nx Cloud last updated this comment at 2025-09-28 10:08:23 UTC

@mihkeleidast
Copy link
Copy Markdown
Contributor Author

@shilman any feedback on this one?

@shilman
Copy link
Copy Markdown
Member

shilman commented Feb 19, 2025

Sorry this got assigned to me but I missed it. I'll take a closer look today. Thanks so much @mihkeleidast for the contribution and the ping!!! 🙏

@shilman shilman changed the title feat: add eject button to canvas toolbar Addon-docs: Add eject button to canvas toolbar Feb 19, 2025
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

LGTM

2 file(s) reviewed, no comment(s)
Edit PR Review Bot Settings | Greptile

Copy link
Copy Markdown
Member

@shilman shilman left a comment

Choose a reason for hiding this comment

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

@mihkeleidast I just tested and this looks great. I did not test the composition use case mentioned in your comment, but I'm pretty sure it will need something similar. Do you know how to test that? If not, I can help you set something up.

FYI, we are in feature freeze right now for 8.6, which should have been released on Tuesday but is slipping. So I'll wait until that goes out before merging.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

2 file(s) reviewed, 2 comment(s)
Edit PR Review Bot Settings | Greptile

Comment on lines +101 to +103
<a
href={getStoryHref(baseUrl, storyId)}
target="_blank"
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.

logic: Add null check before calling getStoryHref since both baseUrl and storyId are optional props

Comment on lines +99 to +110
<Wrapper key="right">
<IconButton key="opener" asChild>
<a
href={getStoryHref(baseUrl, storyId)}
target="_blank"
rel="noopener noreferrer"
title="Open canvas in new tab"
>
<ShareAltIcon />
</a>
</IconButton>
</Wrapper>
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.

style: Consider conditionally rendering the eject button only when storyId and baseUrl are available

Suggested change
<Wrapper key="right">
<IconButton key="opener" asChild>
<a
href={getStoryHref(baseUrl, storyId)}
target="_blank"
rel="noopener noreferrer"
title="Open canvas in new tab"
>
<ShareAltIcon />
</a>
</IconButton>
</Wrapper>
<Wrapper key="right">
{baseUrl && storyId && (
<IconButton key="opener" asChild>
<a
href={getStoryHref(baseUrl, storyId)}
target="_blank"
rel="noopener noreferrer"
title="Open canvas in new tab"
>
<ShareAltIcon />
</a>
</IconButton>
)}
</Wrapper>

@mihkeleidast
Copy link
Copy Markdown
Contributor Author

@shilman as 8.6 is out now, pinging again to see if you can help me figure out if the composition usecase needs additional work or not - see my previous comment in the thread for details.

@shilman
Copy link
Copy Markdown
Member

shilman commented Mar 5, 2025

Will do @mihkeleidast . Thanks for your patience!!

@github-actions github-actions Bot added the Stale label Jul 5, 2025
@mihkeleidast
Copy link
Copy Markdown
Contributor Author

@shilman It's been a while again, any chance we can move this forward?

Comment thread code/addons/docs/src/blocks/components/Preview.tsx Outdated
@Sidnioulz Sidnioulz self-assigned this Sep 5, 2025
Copy link
Copy Markdown
Contributor

@Sidnioulz Sidnioulz left a comment

Choose a reason for hiding this comment

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

LGTM overall, good job @mihkeleidast!

We'd need small clarifications on the change in Preview, and wrapping of the re-added icon with isLoading logic, and we'll be good to go.

Comment on lines +101 to +111
<IconButton key="opener" asChild>
<a
href={getStoryHref(baseUrl, storyId)}
target="_blank"
rel="noopener noreferrer"
title="Open canvas in new tab"
>
<ShareAltIcon />
</a>
</IconButton>
</Wrapper>
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.

This needs to be guarded with isLoading and to return <IconPlaceholder /> when loading.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added!

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.

Sweet! Thank you!

@Sidnioulz
Copy link
Copy Markdown
Contributor

@mihkeleidast I've had a look at the CI beyond the known issue with Angular, and there are a few stories that fail to render on Chromatic, like this one: https://635781f3500dd2c49e189caf-nzzwesbkun.chromatic.com/?path=/story/addons-docs-blocks-components-docspage--loading

You should be able to reproduce this in your local storybook:ui. This is happening because you added a context consumer, but some stories/tests likely do not have a provider for it.

Could you please have a look at add a decorator to the relevant stories? Thanks!

zoom={(z: number) => setScale(scale * z)}
resetZoom={() => setScale(1)}
storyId={getStoryId(children)}
storyId={!isLoading && childProps ? getStoryId(childProps, context) : undefined}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added a bail-out here for when:

  • isLoading=true, as then DocsContext is not defined yet
  • if child props cannot be resolved - this is the case for multi-story previews, which makes sense since then we can't resolve a single story URL.

</>
)}
</Wrapper>
{isLoading ? (
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since storyId is resolved only after loading, changed the placeholder logic here to always show placeholder while loading, and then not rendering the actual button if storyId is not resolved.

@mihkeleidast
Copy link
Copy Markdown
Contributor Author

@Sidnioulz thanks for checking, really should've caught them myself 😅 I changed some logic for the loading state and for multi-story preview, and now CI is passing!

I went over the changes in chromatic, most of them seem expected as the eject button is added to the toolbar. Only the "Stories: Different Toolbars" screenshot on Chrome is not rendering correctly there, but that seems not related to my changes as the toolbar change should not affect the canvas height in any way (so maybe it's flaky?). If I can resolve that on my own, let me know, otherwise maybe all is now good here.

Comment thread code/addons/docs/src/blocks/components/Toolbar.tsx Outdated
Copy link
Copy Markdown
Contributor

@Sidnioulz Sidnioulz left a comment

Choose a reason for hiding this comment

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

LGTM.

Co-authored-by: Steve Dodier-Lazaro <Sidnioulz@users.noreply.github.com>
@Sidnioulz
Copy link
Copy Markdown
Contributor

@Sidnioulz thanks for checking, really should've caught them myself 😅 I changed some logic for the loading state and for multi-story preview, and now CI is passing!

I went over the changes in chromatic, most of them seem expected as the eject button is added to the toolbar. Only the "Stories: Different Toolbars" screenshot on Chrome is not rendering correctly there, but that seems not related to my changes as the toolbar change should not affect the canvas height in any way (so maybe it's flaky?). If I can resolve that on my own, let me know, otherwise maybe all is now good here.

I can't find a good reason why this height change is happening, but the Storybook canvas isn't opening. This shouldn't be flaky to the best of my knowledge. Let's see how CI ends up after updating next and let's see if the SB canvas is reachable then.

@Sidnioulz
Copy link
Copy Markdown
Contributor

Sidnioulz commented Sep 22, 2025

@mihkeleidast if you test on the "Different Toolbars" autodocs page, what do you see? I see an eject button only for the second story in the Chromatic render. Isn't it supposed to show none?

Apart from that, the height problem appears to be gone.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 22, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Updated Docs Preview to derive storyId from child props via DocsContext and getStoryId only when not loading. Enhanced Toolbar to conditionally show an “Open canvas in new tab” button using getStoryHref and ShareAltIcon when baseUrl and storyId are available; shows a placeholder while loading.

Changes

Cohort / File(s) Summary of changes
Docs Preview context-based story resolution
code/addons/docs/src/blocks/components/Preview.tsx
Import/use DocsContext and getStoryId; add getChildProps helper; compute childProps and set storyId only when not loading; minor import updates.
Docs Toolbar open-in-canvas control
code/addons/docs/src/blocks/components/Toolbar.tsx
Import getStoryHref and ShareAltIcon; render right-aligned control: placeholder when loading, link button to canvas when baseUrl and storyId exist; imports tidied.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant Preview as Docs Preview
  participant Ctx as DocsContext
  participant Toolbar as Docs Toolbar
  participant Nav as Browser

  U->>Preview: Render(children, isLoading)
  Preview->>Preview: getChildProps(children)
  Preview->>Ctx: useContext()
  alt not isLoading and childProps
    Preview->>Preview: storyId = getStoryId(childProps, context)
  else
    Preview->>Preview: storyId = undefined
  end
  Preview-->>Toolbar: props { isLoading, baseUrl, storyId }

  Toolbar->>Toolbar: render zoom controls (left)
  alt isLoading
    Toolbar->>Toolbar: render IconPlaceholder (right)
  else isLoading == false and baseUrl && storyId
    Toolbar->>Toolbar: href = getStoryHref(baseUrl, storyId)
    U->>Nav: Click "Open canvas in new tab"
    Nav->>Nav: Open new tab to canvas URL
  else
    Toolbar->>Toolbar: render nothing (right)
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested labels

ui

Suggested reviewers

  • jonniebigodes

Poem

A hop, a skip, a canvas tab—hooray! 🐇
I nudge the link and bound away.
While loading, I’ll patiently stare,
Then share my story with a hare’s flair.
Click—new burrow, fresh display.
Carrot-coded, bright as day.
boop-boop, open tabs to play!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Addon-docs: Add eject button to canvas toolbar" is concise and accurately describes the primary change in this PR—restoring an "Open canvas in new tab" eject control to the docs Canvas toolbar (implemented in Toolbar.tsx with supporting changes in Preview.tsx). It is specific, focused, and easy for a reviewer to scan in history.
Linked Issues Check ✅ Passed The PR implements the core requirement from linked issue #25123 by reintroducing the "Open canvas in new tab" eject button (adds a ShareAltIcon link in Toolbar.tsx) and by passing story context/ID from Preview.tsx to generate the canvas href, which directly addresses the bug report. The changes align with the linked issue's objective, though the PR currently lacks automated tests and notes outstanding Chromatic rendering discrepancies and missing href error handling that should be resolved before final merge.
Out of Scope Changes Check ✅ Passed All modifications in the provided diff are confined to docs addon components (Preview.tsx and Toolbar.tsx) and related imports to support the eject button; there are no unrelated file changes or public API signature changes introduced in the summary. The edits appear directly related to the stated objectives.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
code/addons/docs/src/blocks/components/Toolbar.tsx (2)

69-72: preventDefault() is likely unnecessary on button clicks.

For IconButton without href, default action is already inert; removing noise improves clarity.

-            onClick={(e: SyntheticEvent) => {
-              e.preventDefault();
-              zoom(0.8);
-            }}
+            onClick={() => {
+              zoom(0.8);
+            }}
...
-            onClick={(e: SyntheticEvent) => {
-              e.preventDefault();
-              zoom(1.25);
-            }}
+            onClick={() => {
+              zoom(1.25);
+            }}
...
-            onClick={(e: SyntheticEvent) => {
-              e.preventDefault();
-              resetZoom();
-            }}
+            onClick={() => {
+              resetZoom();
+            }}

Also applies to: 79-82, 89-92


62-63: Drop unused key props on static elements.

These wrappers aren’t part of an array diff; keys add no value here.

-    <Wrapper key="left">
+    <Wrapper>
...
-        <Wrapper key="right">
+        <Wrapper>

Also applies to: 107-119

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab5f8e9 and 315d015.

📒 Files selected for processing (2)
  • code/addons/docs/src/blocks/components/Preview.tsx (6 hunks)
  • code/addons/docs/src/blocks/components/Toolbar.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
code/addons/docs/src/blocks/components/Toolbar.tsx (1)
code/core/src/components/index.ts (1)
  • getStoryHref (84-84)
code/addons/docs/src/blocks/components/Preview.tsx (1)
code/core/src/preview-api/index.ts (1)
  • DocsContext (41-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (9)
code/addons/docs/src/blocks/components/Preview.tsx (6)

2-2: Import organization follows convention.

The new imports are appropriately placed and follow the existing import pattern. The context pattern is well-documented for mocking providers in Storybook.


12-13: Imports correctly reference Storybook's context system.

Both imports are properly sourced from the correct modules and will support the context-aware story ID resolution functionality.


157-165: Robust helper function for extracting child props.

The getChildProps function correctly handles the single-child case and gracefully returns null when props cannot be extracted. This aligns with the conditional logic that prevents storyId resolution for multi-story previews.


211-212: Context consumption follows React patterns.

The DocsContext is properly consumed using the useContext hook, enabling access to story metadata needed for ID resolution.


248-248: Efficient child props extraction.

Computing childProps once and reusing it for storyId determination is more efficient than multiple traversals and follows good React practices.


262-262: Well-designed conditional story ID resolution.

The logic correctly handles multiple scenarios:

  • Bails out during loading state when DocsContext is not available
  • Prevents storyId resolution when child props cannot be extracted (multi-story previews)
  • Only resolves storyId when both context and child props are available

This addresses the past review comments about resolving IDs correctly for non-primary stories.

code/addons/docs/src/blocks/components/Toolbar.tsx (3)

100-120: Eject button rendering and a11y look solid.

Guarding with isLoading, gating on baseUrl && storyId, using target="_blank" with rel="noopener noreferrer", and adding aria-label are all correct. Nice work.


53-121: Add lightweight RTL tests for loading vs. eject states.

Add three small RTL tests asserting: (1) placeholder icons render when isLoading=true; (2) an anchor with aria-label "Open canvas in new tab" renders when baseUrl+storyId are provided; (3) no anchor renders otherwise.
Add colocated tests for both Toolbar implementations: code/addons/docs/src/blocks/components/Toolbar.tsx and code/core/src/manager/components/preview/Toolbar.tsx (e.g., Toolbar.test.tsx) using @testing-library/react. I can draft them if desired.


108-117: Keep asChild — current usage is correct.
IconButton forwards props to Button and Button sets Comp = Slot when asChild is true, so the anchor child receives the attributes (avoids nested interactive elements). Do not switch to passing href directly unless Button is changed to render an (e.g., add as="a" or href→as conversion).

@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot Bot commented Sep 22, 2025

Package Benchmarks

Commit: c3f22cf, ran on 28 September 2025 at 09:53:58 UTC

No significant changes detected, all good. 👏

@mihkeleidast
Copy link
Copy Markdown
Contributor Author

mihkeleidast commented Sep 22, 2025

@mihkeleidast if you test on the "Different Toolbars" autodocs page, what do you see? I see an eject button only for the second story in the Chromatic render. Isn't it supposed to show none?

@Sidnioulz Not sure I understand. The second story is configured to show toolbar, so it shows the toolbar for it, and the eject button is on the toolbar. Can you specify what you think is wrong?

Do you mean when the sb_theme global is stacked, then it technically is rendering multiple stories in the same canvas, so it should not show the eject button? As that is a custom decorator in preview.tsx, not sure it can be modified to bail out, as the logic still resolves a single story from children.

@Sidnioulz
Copy link
Copy Markdown
Contributor

@mihkeleidast if you test on the "Different Toolbars" autodocs page, what do you see? I see an eject button only for the second story in the Chromatic render. Isn't it supposed to show none?

@Sidnioulz Not sure I understand. The second story is configured to show toolbar, so it shows the toolbar for it, and the eject button is on the toolbar. Can you specify what you think is wrong?

Do you mean when the sb_theme global is stacked, then it technically is rendering multiple stories in the same canvas, so it should not show the eject button? As that is a custom decorator in preview.tsx, not sure it can be modified to bail out, as the logic still resolves a single story from children.

uuugh I'm so sorry I'm a bit sleep-deprived 😆 The dots didn't connect in my head. Your code is great!

Copy link
Copy Markdown
Member

@shilman shilman left a comment

Choose a reason for hiding this comment

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

Thanks @mihkeleidast and @Sidnioulz for making this happen and for your patience on getting it merged. Great UI improvement for docs!

@shilman shilman merged commit df23220 into storybookjs:next Sep 28, 2025
51 of 52 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 30, 2025
16 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: "Open canvas in new tab" missing in <Canvas> toolbar

4 participants