Skip to content

[spike] Modular SelectPanel with tabs (layered)#7966

Draft
joshfarrant wants to merge 4 commits into
mainfrom
joshfarrant/selectpanel-modular-opus
Draft

[spike] Modular SelectPanel with tabs (layered)#7966
joshfarrant wants to merge 4 commits into
mainfrom
joshfarrant/selectpanel-modular-opus

Conversation

@joshfarrant

@joshfarrant joshfarrant commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Draft — not for merge. Part of a spike comparing how a tabbed SelectPanel (Branches / Tags, one shared search) gets built across Primer's APIs. Same task, same model (Opus 4.8); the API is the only variable:


This branch decomposes SelectPanel into the 4-layer modular architecture so a consumer can compose a tabbed pickerand it is the hand-refined canonical example of the intended implementation (it may differ from a one-shot agent output; that's deliberate).

Design: compose, don't depend. SelectPanel does not own tabs and does not import Tabs. It provides the listbox-in-a-dialog machinery; the consumer brings the generic Tabs primitive and composes it. A tabbed picker is a consumer composition, not a component.

Layers (refined model):

  • Utilities (outside the model, @primer/react/hooks/experimental) — useFilter, useSelectionState, useAsyncList: generic, consumer-owned state, shareable across tabs.
  • L0 — compound hook useSelectPanel (/foundations/experimental): prop-getters, all behaviour + ARIA, no markup. Outer popup is role="dialog", with role="listbox" scoped inside the active tab panel.
  • L1 — unstyled components that wrap L0 (/foundations/experimental).
  • L2 — Primer-styled parts SelectPanel.* (no tab parts): Root/Anchor/Overlay/Header/Title/Input/List/Option/Empty/Footer.
  • L3 — thin no-tabs ready-made.

Accessibility model for the tabbed composition (see SelectPanelParts.stories.tsx): the tabpanel is non-focusable, the list is driven by the input's aria-activedescendant, the tabs are a roving-tabindex zone, and switching a tab returns focus to the input — so there is no keyboard dead-end. A shared composite-focus utility (Ariakit combobox-tabs style) is noted as a future enhancement, not required for correctness.

Start with SelectPanel.spec.md and the "Branches / Tags tabbed picker (styled)" story. This is the zero-glue end of the comparison.

Note surfaced by this work: the experimental Tabs primitive exports only hooks, not Tab/TabList/TabPanel components — the recipe wraps them locally; ideally Tabs would export them.

Decomposes SelectPanel into the modular architecture (hooks -> foundations ->
parts -> ready-made) so consumers can compose a tabbed picker (e.g. Branches /
Tags) with one shared search input — not possible with today's prop-based API.

- L0 hooks: useFilter, useSelectionState, useAsyncList (consumer-owned, reusable)
- L1 foundation: useSelectPanel + unstyled components (dialog/combobox/listbox/
  tablist ARIA; outer role=dialog, listbox scoped to the active tab panel)
- L2 parts: Primer-styled SelectPanel.* incl. TabList/Tab(count)/Panel
- L3 ready-made: thin no-tabs wrapper (tabbed use is L2-only by design)
- Per-layer stories, spec.md, and tests

Branched off main; contains only the SelectPanel work.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@changeset-bot

changeset-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 50bc9ea

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions github-actions Bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label Jun 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Action required

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label to this PR.

@primer

primer Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🤖 Lint issues have been automatically fixed and committed to this PR.

joshfarrant and others added 3 commits June 10, 2026 17:15
SelectPanel no longer depends on the Tabs primitive. Remove the TabList/Tab/Panel
parts (L2), the Panel foundation component (L1), and getPanelProps from
useSelectPanel, along with their now-unused type exports. A tabbed picker is now
a consumer composition of SelectPanel parts + the generic Tabs primitive.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrite the headline Parts story to build the Branches/Tags tabbed picker by
composing SelectPanel parts with the generic Tabs primitive via local
RefTabList/RefTab/RefTabPanel wrappers (Tabs exports only hooks). The tabpanel is
non-focusable, the list is input-driven via aria-activedescendant, tabs are a
roving-tabindex zone, and switching a tab returns focus to the input — no keyboard
dead-end. Update the SelectPanel tests to compose with Tabs and drop the removed
Panel assertions, and rewrite the spec around the compose-don't-depend model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ed layer model

- SelectPanel.Input forwards its ref, so the tabbed recipe returns focus via a real
  inputRef instead of a DOM querySelector hack
- spec.md: relabel to the refined model (useSelectPanel = L0, unstyled = L1, the
  generic state hooks are Utilities outside the model)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@primer

primer Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🤖 Lint issues have been automatically fixed and committed to this PR.

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

Labels

integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant