Skip to content

feat(OverflowList): add maxVisibleItems cap and bounded multi-row (#4176)#4197

Draft
cixzhang wants to merge 1 commit into
mainfrom
feat/overflowlist-cap-multirow
Draft

feat(OverflowList): add maxVisibleItems cap and bounded multi-row (#4176)#4197
cixzhang wants to merge 1 commit into
mainfrom
feat/overflowlist-cap-multirow

Conversation

@cixzhang

Copy link
Copy Markdown
Contributor

What

Adds two optional, default-off, non-breaking props to OverflowList (and the underlying useOverflow hook):

  • maxVisibleItems?: number — a hard ceiling on how many items render, even when they all fit. The symmetric partner to the existing minVisibleItems floor; extra items collapse into the overflow indicator. Defaults to undefined (no cap).
  • maxRows?: number — bounded multi-row wrapping. Items wrap onto up to N rows, then the remainder collapses into the overflow indicator. A number, not a boolean (unbounded wrapping is a plain flex-wrap layout, not overflow collapse). Defaults to undefined (single line); maxRows={1} is equivalent to today.

Closes #4176.

Why

Capped lists ("show at most N, then +count") and bounded multi-row tag clouds ("wrap onto two rows, then +count") are the two most common collapsing-row patterns the current single-line, floor-only API can't express. A numeric cap matches the industry-standard shape (max, maxCount, maxTagCount, AvatarGroup max); maxVisibleItems mirrors the existing minVisibleItems vocabulary so the pair is discoverable.

Both props are optional and undefined by default — no existing call site changes behavior.

Behavior

  • Single source of truth for the count: visibleCount = clamp(fitCount, minVisibleItems, maxVisibleItems ?? itemCount).
  • max < min precedence: the floor wins (it's the stronger guarantee), with a dev-only console.warn.
  • Measurement (Strategy A): multi-row packing is simulated from the already-measured per-item widths — walk items, accumulate row width, wrap when the next item would exceed the available width, stop admitting once a new row would exceed maxRows, reserving indicator space on the final row. This reuses the existing "measure widths once, compute in JS" model — one measure pass, no extra reflow. It assumes uniform row height (true for typical tag/pill/avatar rows); the visible container's max-height is derived from the measured max item height.
  • collapseFrom × multi-row: end → the indicator ends the last visible row; start → the indicator begins the first row (packing runs in the same reversed order the single-line path already uses).
  • The single-line path is untouched when maxRows is undefined; the visible container only switches to flexWrap: wrap with a bounded max-height when maxRows is set.

Independent unit tests for the overflow math

Given the history of overflow-calculation bugs, the core math is extracted into a pure, DOM-free helper — packages/core/src/hooks/computeOverflow.ts — that takes plain inputs (item widths, gap, available width, indicator width, min/max, maxRows, collapseFrom) and returns {visibleCount, rows}. useOverflow calls it after measuring the DOM.

computeOverflow.test.ts exercises it in isolation (34 cases): cap-only, floor-only, cap+floor, max<min (min wins + warn), exact-fit and off-by-one boundaries, indicator-space reservation, multi-row packing (1 row, N rows, overflow into indicator), cap-vs-rows precedence both directions, collapseFrom start vs end, resize shrink/grow re-clamping never exceeding the cap, and degenerate values (0, cap>itemCount, maxRows taller than content, empty list). All assert exact numbers.

Docs & stories

  • Docsite examples: two new live example blocks — Multi-row Tags (maxRows={2}) and Capped Toolbar (maxVisibleItems={3}) — under packages/cli/templates/blocks/components/OverflowList/, following the existing exampleFor block convention.
  • Storybook: CappedItems, MultiRow, and CappedMultiRow stories.
  • .doc.mjs: new prop entries (en/zh/dense) and the "vertical layouts" best-practice reworded to distinguish supported multi-row horizontal wrap from unsupported vertical stacks.

Testing

  • computeOverflow.test.ts — 34 pure-math cases, all pass.
  • useOverflow.test.ts — existing single-line parity preserved; new cap/multi-row hook cases (26 total).
  • OverflowList.test.tsx — existing behavior preserved; new cap and multi-row component cases (19 total).
  • Docsite data-extraction registry tests pass with the new example blocks.
  • eslint clean on changed files; @astryxdesign/core builds cleanly (StyleX CSS generated for the dynamic multi-row max-height).

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview, Comment Jul 22, 2026 12:09pm

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 22, 2026
@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Jul 22, 2026
@cixzhang

Copy link
Copy Markdown
Contributor Author

Self-review — Quick Self-Review checklist (updates to an existing component)

  1. Icons from registry — n/a; no icons added.
  2. themeProps present — no new interactive root elements; the visible container keeps its existing themeProps('overflow-list').
  3. No wrapper divs — no new wrapping DOM; multi-row reuses the existing visible container by switching its style branch (whiteSpace: nowrapflexWrap: wrap with a bounded max-height) only when maxRows is set.
  4. No CSS shorthands — the multi-row style uses explicit flexWrap/alignContent; the dynamic max-height is a calc() string via a StyleX dynamic function (a supported capability), not a raw inline style.
  5. ARIA still correct — no primitive/role changes; the measurement container stays aria-hidden/inert.
  6. Tests pass locallycomputeOverflow.test.ts (34), useOverflow.test.ts (26), OverflowList.test.tsx (19) all green; docsite data-extraction registry tests green.
  7. Sandbox/stories updated — three new Storybook stories (cap, multi-row, cap+multi-row) and two new docsite example blocks; no props removed.
  8. Popover background — n/a.

Extra reviewer requirements

  • Independent pure-function math tests. The fit/clamp logic and the multi-row row-packing simulation were extracted out of the hook into a pure, DOM-free helper — packages/core/src/hooks/computeOverflow.ts — that takes plain inputs and returns {visibleCount, rows}. useOverflow measures the DOM, then delegates to it. computeOverflow.test.ts covers the math in isolation with 34 cases asserting exact numbers: cap-only, floor-only, cap+floor, max<min (min wins + warn), exact-fit + off-by-one boundaries, indicator-space reservation, multi-row packing (1/N rows, overflow into indicator), cap-vs-rows precedence both ways, collapseFrom start vs end, resize shrink/grow re-clamp (never exceeds the cap), and degenerate values (0, cap>itemCount, maxRows taller than content, empty list). Single-line parity with the previous inline loop is preserved (existing hook tests unchanged and green).

  • Docsite multi-row example. Added live docsite example blocks following the existing exampleFor convention under packages/cli/templates/blocks/components/OverflowList/: OverflowList — Multi-row Tags (maxRows={2}) and OverflowList — Capped Toolbar (maxVisibleItems={3}). The generated example registry picks both up (verified via the docsite data-extraction tests).

Spec compliance / decisions

Built to the approved contract: prop names maxVisibleItems/maxRows (D2/D3), min-wins-with-dev-warn (D1), Strategy A simulated row-packing with documented uniform-row-height assumption (D4), collapseFrom end→last-row / start→first-row (D5), row height from measured max item height (D6), and the .doc.mjs best-practice reworded to distinguish multi-row horizontal wrap from vertical stacks (D7). No deviations.

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

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OverflowList: add maxShownItems (cap) + multi-row support

1 participant