Skip to content

Merge component pages across frameworks with per-framework tabs #631

Description

@nathanacurtis

Subissue of #574. Wave 1.

Problem

Emitted stories are titled by emitter — React/Button, WebComponents/Button — so one component appears twice in the navigation and a reader has to know which tree they are in. The system has one Button. Storybook should show one Button, with the frameworks as views of it.

Resolved: the host side works

A spike on specs-testing/workspaces/de-library (Storybook 8.6, branch spike/storybook-framework-tabs, uncommitted) has the whole interaction working. Everything below is verified in a browser, and all of it is host configuration — two files, .storybook/manager.tsx and .storybook/preview.tsx, plus one staticDirs entry in main.ts.

Capability Mechanism
Per-framework canvas tabs — React · Web Components · Specs addons.add(id, { type: types.TAB, route, match, render }). route/match receive storyId, and useStorybookState() gives it live inside render, so a globally registered tab resolves the selected component
Web Components view Derives the sibling story id by prefix swap and renders iframe.html?id=<sibling>. Under a docs entry it shows the whole Web Components docs page — its own Controls table, its own stories
Specs view Fetches the workspace's specs/<dir>/<file>.yaml over a static dir, rendered with SyntaxHighlighter and TabsState from storybook/internal/components, with api / variants / examples as sub-tabs
Component is the sidebar link docs: { docsMode: true }, with defaultName: 'Overview' naming the parent's own page where a component has subcomponents
One Components section api.experimental_setFilter hides the Web Components tree from the sidebar while leaving it in the index for the tab
Section order storySort.order: ['Overview', 'Foundations', 'Components', 'Analysis']
Per-story controls on the docs page docs.canvas with withToolbar: false and an additionalActions entry that recovers the story id from the Canvas block's anchor and opens it full screen

Remaining work

One transform change, in react-from-specs: emit title: 'Components/<Name>' instead of title: 'React/<Name>', subcomponent segments included. Nothing else in either transform changes.

Everything else is host configuration, so it belongs to #607 (document it) and #610 (template it) rather than here. Close this issue when the title change ships and #607 carries the configuration above.

Findings that constrain the design

Four things the spike settled, each of which contradicts a reasonable first guess:

  1. Identical titles across the two trees do not work. Matching titles produce matching story ids, and Storybook rejects duplicates. The Web Components tree keeps its own titles; the tab maps between the two id spaces by prefix swap. The invariant that actually matters is slug and story-name parity between trees, which already holds — Default, AlertWithActions and the rest exist in both. That parity is incidental today and needs a test if the tabs ship, or a divergence in one emitter silently drops the reader onto a missing story.
  2. tags: ['!dev'] does not remove stories from the sidebar here. The tag lands in the index and the section renders anyway. experimental_setFilter is what works, which means hiding the Web Components tree costs the emitters nothing.
  3. The canvas tab label cannot be a story parameter. Set as parameters.previewTabs, the manager only learns the rename once the preview reports that story's parameters, so the tab reads "Canvas" on every navigation and corrects itself a moment later. addons.setConfig({ previewTabs: … }) in the manager is known before any story loads. Since the sidebar only ever shows the React tree, the label is a scaffold decision keyed on which trees a workspace has — not something an emitter can know.
  4. A component with subcomponents is a folder, not a link. Storybook has no node that is both leaf and parent, so DE Alert expands while DE Avatar opens. Accepted as-is: subcomponents stay in the navigation, and defaultName: 'Overview' names the parent's own page.

Compatibility risk

types.TAB is marked @unstable in Storybook 8's own types — "might be removed in the future". The host also imports from storybook/internal/manager-api, storybook/internal/components, and storybook/internal/theming. It is what the built-in Docs tab uses, so none of this is obscure, but a scaffold that writes this code couples customers to internal entrypoints of a tool we do not control. Recorded in #574's ADR A section as a compatibility question rather than left here.

Acceptance criteria

  • react-from-specs emits Components/<Name> titles.
  • One navigation entry per component, with the React view as the default.
  • A Web Components view reachable without leaving the component.
  • A Specs view showing api, variants, and examples formatted, not raw.
  • Document the complete reference Storybook host #607's documented host includes every row of the capability table above.
  • Story-name parity between the two emitted trees is covered by a test.

Case data

  • Workspace: de-library
  • Territory: cli
  • Size: m

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

clispecs-cli commandsdocsDocumentation that appears on specsplugin.com

Type

No type

Fields

Priority

None yet

Projects

  • Status
    Ready

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions