Skip to content

feat(ui): add ButtonGroup — a data-driven action bar#4339

Merged
warcos-fact merged 6 commits into
mainfrom
feat/ui-button-group
Jun 8, 2026
Merged

feat(ui): add ButtonGroup — a data-driven action bar#4339
warcos-fact merged 6 commits into
mainfrom
feat/ui-button-group

Conversation

@warcos-fact

@warcos-fact warcos-fact commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

What

Adds ButtonGroup, a data-driven action bar for arranging a row of action buttons. Actions are passed as props, not children — which keeps the surface constrained and structurally guarantees invariants (e.g. at most one primary). This is the outcome of review feedback on the original children-based draft.

  • primaryAction — a single solid button, pinned at the trailing edge (or a split button via type: "split").
  • secondaryActions — outline buttons, split buttons, or inline { type: "separator" } hairlines (or a single link). Plain secondaries that don't fit shed into a "⋯" menu by width measurement; split buttons and the primary are pinned and never collapse.
  • otherActionsDropdownItem[], always reachable through the "⋯" menu (supports separators and critical).
  • Responsivestack="sm" | "md" | "container-md" collapses into a full-width column (fullWidthOnStack, reverseOnStack to promote the primary to the top), with a responsive size={{ base, md }} and an automatic DropdownMobileDropdown swap. The active branch is chosen in JS so only one mounts at a time (and the overflow machinery initializes with its DOM present).
  • Destructive actionscritical renders red. Guard it in otherActions (behind the ⋯) by default; mark an inline secondary critical for a one-click delete when the resource is cheap to recreate.

Notes

  • Evolved from the original "layout primitive" (children-based) after review (@sauldom102, @rubenmoya). The prop shape mirrors F0CardRow and is consistent with the primaryAction / secondaryActions / otherActions triplet already used by BaseHeader and TimeLine.
  • Folds the earlier ButtonGroupOverflow into ButtonGroup (deleted) — width-driven overflow is now native via useOverflowCalculation. No change to the public export barrel; there were no external consumers.
  • Docs: autodocs + an MDX page documenting the row/stacked behavior, the ⋯ auto-swap, split-pinning, and the guard-by-default rule for destructive actions; behaviors stories cover the Trailing / Split / Reflowing patterns.

Verification

Driven through Storybook across every pattern (Trailing, Split, Reflowing, width-overflow, critical) — which surfaced and fixed two bugs invisible to type-check/lint: an infinite render loop (unmemoized array into useOverflowCalculation) and secondaries failing to measure when the group started stacked (observer attached before the row DOM existed). Type-check, lint, Chromatic, and the type build are green.

🤖 Generated with Claude Code

@warcos-fact warcos-fact requested a review from a team as a code owner June 5, 2026 09:32
@github-actions github-actions Bot added feat react Changes affect packages/react labels Jun 5, 2026
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

🔍 Visual review for your branch is published 🔍

Here are the links to:

ButtonGroup is a low-level layout primitive for arranging action buttons:
alignment, gap, orientation, responsive stacking (viewport or container query),
full-width-on-stack and reverseOnStack. It owns only arrangement — not
footer/header chrome or button size.

Also adds ButtonGroupSeparator (logical divider) and ButtonGroupOverflow
(width-driven collapse into a "more" ellipsis menu, built on OverflowList), plus
autodocs, an MDX page, and behaviors stories.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@warcos-fact warcos-fact force-pushed the feat/ui-button-group branch from c4f4d80 to 6e507f3 Compare June 5, 2026 09:41
@warcos-fact warcos-fact requested review from a team as code owners June 5, 2026 09:41
@warcos-fact warcos-fact changed the base branch from cocreation-patterns-definition to main June 5, 2026 09:41
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

✅ No New Circular Dependencies

No new circular dependencies detected. Current count: 0

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

📦 Alpha Package Version Published

Use pnpm i github:factorialco/f0#npm/alpha-pr-4339 to install the package

Use pnpm i github:factorialco/f0#201740f756d511d76ebd1197ec0d757d5c88fd82 to install this specific commit

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

✅ No breaking public API changes

No public exports were removed, renamed, or had existing props/types changed in a breaking way compared to main.

Comparing f0, experimental and ai against main. Adding components, types, or optional props is safe. This check is non-blocking.

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for packages/react

Status Category Percentage Covered / Total
🔵 Lines 52.72% 15281 / 28985
🔵 Statements 51.85% 15859 / 30581
🔵 Functions 44.88% 3437 / 7658
🔵 Branches 45.62% 10807 / 23686
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/react/src/ui/ButtonGroup/ButtonGroup.tsx 0% 0% 0% 0% 109-475
packages/react/src/ui/ButtonGroup/index.ts 100% 100% 100% 100%
packages/react/src/ui/ButtonGroup/variants.ts 0% 100% 100% 0% 12-87
Generated in workflow #14333 for commit 6ba83a7 by the Vitest Coverage Report Action

@rubenmoya rubenmoya left a comment

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.

LGTM, just a minor comment

Comment thread packages/react/src/ui/ButtonGroup/ButtonGroupOverflow.tsx Outdated
The hidden measurement copy is purely for layout sizing, so make it
fully non-interactive and remove its children from the tab order.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/react/src/ui/ButtonGroup/__stories__/ButtonGroup.stories.tsx Outdated
warcos-fact and others added 4 commits June 8, 2026 10:24
Replace the children-based layout primitive with a typed, data-driven API
addressing review feedback (@sauldom102): actions are passed as props, not
arbitrary JSX, structurally guaranteeing invariants (≤1 primary).

- primaryAction (solid, pinned) + secondaryActions (outline, or split buttons
  / inline separators / a single link) + otherActions (always in the "⋯" menu).
- Secondaries shed into the "⋯" menu by width (useOverflowCalculation); splits
  and the primary are pinned and never collapse.
- Responsive: stack into a full-width column below sm/md/container-md, with
  responsive size ({ base, md }) and a Dropdown↔MobileDropdown swap, all chosen
  in JS from the matching breakpoint so only one branch mounts.
- Fold ButtonGroupOverflow into ButtonGroup (deleted); carry its inert
  measurement copy over, set via setAttribute since inert isn't a typed JSX
  prop in @types/react 18.3.
- Rewrite stories + mdx to the prop API.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expose F0Button's critical (destructive/red) variant on action buttons, so
authors can choose how guarded a destructive action is instead of the component
enforcing one rule:

- Guarded (default for permanent resources): critical in otherActions → behind
  the "⋯" menu, the extra click as a safety gate.
- One-click (cheap/recoverable resources): a critical secondaryActions button
  renders inline in red with no extra click.

critical is carried into the "⋯" menu when an inline critical secondary
overflows. Documents the guard-by-default guideline in the mdx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Found by running Storybook and a comparative review against BaseHeader /
TimeLine/Actions / CardActions.

Bugs:
- Infinite render loop ("Maximum update depth exceeded"): the fresh
  secondaryActions.filter() array changed useOverflowCalculation's callback
  identity every render. Memoize it.
- Secondaries never measured when starting stacked: useOverflowCalculation's
  ResizeObserver attached before the row DOM existed (usehooks-ts doesn't
  re-observe when the ref element changes), so everything shed into the menu.
  Split the row/stacked branches into separate keyed children so the overflow
  hook mounts with its DOM present; keep the container-width observer on a
  stable outer wrapper.

Review polish:
- useMediaQuery: explicit { initializeWithValue: false } (SSR-safe, matches the
  repo-wide convention).
- Separator hairline: bg-f1-border-secondary (the house token) instead of the
  unused bg-f1-background-secondary.
- Remove the dead `wrap` variant from variants.ts.
- Document why `id` is required (overflow identity), unlike index-keyed lists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@warcos-fact warcos-fact changed the title feat(ui): add ButtonGroup layout primitive feat(ui): add ButtonGroup — a data-driven action bar Jun 8, 2026
@warcos-fact warcos-fact merged commit 170314e into main Jun 8, 2026
25 checks passed
@warcos-fact warcos-fact deleted the feat/ui-button-group branch June 8, 2026 10:58
@eliseo-juan eliseo-juan mentioned this pull request Jun 8, 2026
warcos-fact added a commit that referenced this pull request Jun 8, 2026
Replace the bespoke overflow/primary-secondary-other machinery in
CardRowActions with a thin adapter over the data-driven ButtonGroup
(#4339), which now owns the width-driven "⋯" overflow, the primary
pinning, and the row/stacked layout. The card keeps its own concerns:
stopping click propagation so actions don't trigger the row link, the
stacked own-line footer hairline at the container breakpoint, and the
confirm/reject icon variant (mapped to two pinned secondaries).

The actions wrapper takes the remaining row space (flex-1) so
ButtonGroup has a bound wider than its content to measure against;
a shrink-to-fit container made it shed its tail into the menu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
warcos-fact added a commit that referenced this pull request Jun 8, 2026
Replace the bespoke div + buttons layout with F0CardRow — the confirm action
becomes the primary, cancel becomes a secondary. Third step of the sequence
after ButtonGroup (#4339) and F0CardRow (#4340).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
warcos-fact added a commit that referenced this pull request Jun 8, 2026
Replace the bespoke div + buttons layout with F0CardRow — the confirm action
becomes the primary, cancel becomes a secondary. Third step of the sequence
after ButtonGroup (#4339) and F0CardRow (#4340).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat react Changes affect packages/react

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants