Skip to content

Replace dynamic overflow with horizontal scroll in UnderlineNav#7648

Open
iansan5653 wants to merge 74 commits intomainfrom
underline-nav-scroll
Open

Replace dynamic overflow with horizontal scroll in UnderlineNav#7648
iansan5653 wants to merge 74 commits intomainfrom
underline-nav-scroll

Conversation

@iansan5653
Copy link
Contributor

Alternative approach to #7506; just get rid of the 'More' menu and scroll instead.

Changelog

New

Changed

Removed

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

Merge checklist

TylerJDev and others added 30 commits February 6, 2026 15:05
…e.module.css

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…e.tsx

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

changeset-bot bot commented Mar 10, 2026

🦋 Changeset detected

Latest commit: 2a4484d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another 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 Mar 10, 2026
@github-actions
Copy link
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. Or, apply the integration-tests: skipped manually label to skip these checks.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR simplifies UnderlineNav overflow behavior by removing the dynamic “More” overflow menu and switching to a horizontally scrollable container (with icon-hiding when space is tight), aligning with the goal of reducing layout shifts and complexity.

Changes:

  • Replaced JS-driven overflow + “More” menu logic in UnderlineNav with a horizontal scroll container and overflow-driven icon hiding.
  • Updated shared underline tabbed interface styling/markup to support the new layout and icon-hiding strategy.
  • Updated stories/tests/e2e snapshots in-progress to reflect the new overflow model.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/react/src/internal/components/UnderlineTabbedInterface.tsx Removes iconsVisible prop usage and allows UnderlineItemList className merging.
packages/react/src/internal/components/UnderlineTabbedInterface.module.css Updates layout/spacing and moves icon-hiding to a data-hide-icons attribute on the wrapper.
packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx Switches from data-icons-visible to data-hide-icons for icon visibility control.
packages/react/src/experimental/UnderlinePanels/UnderlinePanels.module.css Removes now-redundant icon-hiding rule (handled by shared styles).
packages/react/src/UnderlineNav/styles.ts Deletes overflow-menu-related inline style exports (no longer needed).
packages/react/src/UnderlineNav/UnderlineNavItem.tsx Adds “scroll selected item into view” behavior; removes old measurement logic.
packages/react/src/UnderlineNav/UnderlineNavContext.tsx Removes width/icon visibility state from context; keeps loadingCounters.
packages/react/src/UnderlineNav/UnderlineNav.tsx Replaces overflow menu implementation with scroll container + ResizeObserver-based icon hiding.
packages/react/src/UnderlineNav/UnderlineNav.test.tsx Adjusts tests for new structure and viewport-sensitive behavior.
packages/react/src/UnderlineNav/UnderlineNav.module.css Implements scroll container styling + progressive icon-hiding logic.
packages/react/src/UnderlineNav/UnderlineNav.interactions.stories.tsx Partially updated selectors; still structured around the removed “More” menu.
packages/react/src/UnderlineNav/UnderlineNav.features.stories.tsx Updates overflow story setup to reflect scroll-based overflow behavior.
e2e/components/UnderlineNav.test.ts Re-enables screenshots but still asserts “More” menu interactions (now removed).
.changeset/underline-nav-css-overflow.md Adds a minor changeset documenting the behavior change.

Comment on lines +63 to +66
// Putting the resizeobserver on the list ensures it runs if the contents change;
// the container is fullwidth but the list is elastic
const list = scrollContainer.current.querySelector('ul')
if (!list) return
Comment on lines +113 to 114
const moreBtn = canvas.getByRole('button', {name: 'More items'})
userEvent.hover(moreBtn)
Comment on lines 204 to +214
await page.setViewportSize({width: viewports['primer.breakpoint.sm'], height: 768})
await page.locator('button', {hasText: 'More Repository Items'}).waitFor()
await page.locator('button', {hasText: 'More items'}).waitFor()

// Resize
// expect(await page.screenshot()).toMatchSnapshot()
expect(await page.screenshot()).toMatchSnapshot()

await page.getByRole('button', {name: 'More Repository Items'}).click()
// expect(await page.screenshot()).toMatchSnapshot()
await page.getByRole('button', {name: 'More items'}).click()
expect(await page.screenshot()).toMatchSnapshot()

await page.getByRole('link', {name: 'Settings (10)'}).click()
// expect(await page.screenshot()).toMatchSnapshot()
await page.getByRole('menuitem', {name: 'Settings (10)'}).click()
expect(await page.screenshot()).toMatchSnapshot()
Comment on lines +62 to +64
/** Registry of currently-overflowing underline items. If an item is not overflowing, its value will be `null`. */
export const UnderlineNavItemsRegistry = createDescendantRegistry<UnderlineNavItemProps | null>()

Comment on lines +66 to +82
// Walk up the offset parent chain to get the true left offset relative to `container`
let offsetLeft = 0
let el: HTMLElement | null = descendant
while (el && el !== container) {
offsetLeft += el.offsetLeft
el = el.offsetParent as HTMLElement | null
}

// scrollIntoView would be more convenient but would scroll the entire page unless we pass `options.container`,
// for which browser support is very limited
const descendantLeft = offsetLeft - container.scrollLeft
const descendantRight = descendantLeft + descendant.offsetWidth

const containerWidth = container.clientWidth

if (descendantLeft < 0) container.scrollLeft += descendantLeft
else if (descendantRight > containerWidth) container.scrollLeft += descendantRight - containerWidth
Comment on lines +9 to +13
/* Progressive enhancement: Detect overflow using scroll-based animations.
This lets us calculate the icon visibility during SSR. */
animation: detect-overflow linear;
animation-timeline: scroll(self inline);

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.

5 participants