-
Notifications
You must be signed in to change notification settings - Fork 625
Breadcrumbs : Add overflow menu for responsive behavior #6664
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: a24d75a The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
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 |
👋 Hi, this pull request contains changes to the source code that github/github depends on. If you are GitHub staff, we recommend testing these changes with github/github using the integration workflow. Thanks! |
size-limit report 📦
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great!
One edge case issue I uncovered (not necessarily blocking), is that when I have the root item shown and resize the window to a narrower viewport, at certain widths, the component seems to struggle with the root item getting hidden (which may be triggering another resize event), creating an infinite loop of hiding and showing the root crumb:
breadcrumbs.mov
/> | ||
</ActionMenu.Anchor> | ||
<ActionMenu.Overlay width="auto"> | ||
<ActionList role="menu"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this have the a11y enhancements in? if not, is the plan to follow up this PR with those enhancements? if so:
- I think it makes sense for this PR not to close https://github.com/github/primer/issues/5122
- would it be a breaking change to get this in and then switch the underlying html structure? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created subissues on that issue to tackle the ally issues!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I just mean feels weird to close the parent issue if there's still open subissues
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes yes, I won't be closing the parent issue. It's just an old description. I just want it linked to the issue
"@primer/react": patch | ||
--- | ||
|
||
Spike for breadcrumbs overflow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to be updated as it'll show up in the changelog
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 I thought i did. I think its fine now. I think something with my cherry-pick messed it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now there's two 😅 , can you delete this one?
// if (typeof window !== 'undefined') { | ||
// effectiveOverflow = overflow | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀
} | ||
const menuElement = ( | ||
<li className={classes.BreadcrumbsItem} key="breadcrumbs-menu"> | ||
<BreadcrumbsMenuItem |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from https://github.com/github/primer/issues/5122
Next Steps
Merge Breadcrumbs as currently implemented under a feature flag
But I don't see a FF here 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know! I debated it but I decided since its only addition of a prop and only that triggers the new feature, we don't need it. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the good thing about a FF is we can get away with changing more things within it to refine stuff, but if you don't foresee making any major changes (outside the pre-release window), then a prop is fine 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still undecided. There is definite change in the html structure and css to the original. I want to have a look at the review-lab here and decide
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great! Just left some comments/questions/ideas.
One odd thing I noticed when working with the component is that when I saved the breadcrumb file, the layout would break 🤔 Curious if you ran into that too
Screen.Recording.2025-08-25.at.4.26.56.PM.mov
setChildArrayWidths(widths) | ||
setRootItemWidth(listElementArray[0].offsetWidth) | ||
} | ||
}, [childArray.length]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this will synchronize correctly if it depends on the length of the items instead of the items themselves changing.
Put another way, if the items are updated to be different items but of the same length then this code won't re-run and set up the state values we have for item and root width.
// Show: [root breadcrumb, overflow menu, leaf breadcrumb] | ||
return [rootElement, menuElement, ...visibleElements] | ||
} | ||
}, [overflow, menuItems, effectiveHideRoot, visibleItems, rootItem, children]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm understanding right, I think on every resize this value will be memoized is that right? Even if the items aren't changing. Would it make sense in handleResize
to compare the two lists (visible items, menu items) to see if they have changed before updating?
I'm also curious if, with React compiler, we need to use memo for this 🤔 Curious what you've run into for that 👀
const BreadcrumbsMenuItem = React.forwardRef<HTMLButtonElement, BreadcrumbsMenuItemProps>( | ||
({items, 'aria-label': ariaLabel, ...rest}, ref) => { | ||
return ( | ||
<ActionMenu> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think my personal preference would be to have this use the disclosure pattern before getting this in since using a menu for navigation is not preferred.
I assume with this work that if we went down that route we would have to do more from scratch since that's not supported in ActionMenu
at the moment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, We need to use a pattern involving Details
. I tried it once on Friday and it was not straightforward. Will give it another go, now that I have a clean slate
overflow?: 'wrap' | 'menu' | ||
hideRoot?: boolean |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely not sure what the right names would be, but what would you think of something like:
overflow?: 'wrap' | 'menu' | |
hideRoot?: boolean | |
overflow?: 'wrap' | 'hide' | 'hide-root' |
So that we wouldn't have the case of hideRoot
only making sense with overflow="menu"
. It would also avoid the confusion here with it not being a menu
since this would be a popup with a list of links versus role="menu"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh this may be an improvement, but do you think it makes the api more convoluted? menu
is what it practically looks like. hide
and hide-root
as values dont semantically make sense in this context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I correct in saying that hideRoot would only be relevant with overflow: menu
? If yes, how do you feel about overflow: 'menu' | 'menu-with-hidden-root'
? A little long but keeping related options together?
setMenuButtonWidth(measuredWidth) | ||
} | ||
} | ||
}, [menuItems.length]) // Re-measure when menu button appears/disappears |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One idea for measuring the menu button could be to measure it in a ref callback, curious if that would make sense or not 🤔 Would avoid the need for us to have an effect for it. Hopefully this doesn't violate the rules of hooks too 😅 lol
const itemToHide = currentVisibleItems.slice(0)[0] | ||
const itemToHideWidth = currentVisibleItemWidths.slice(0)[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the call to .slice(0)[0]
for here? Is it to do a deep copy on the item in the array or or something else?
currentVisibleItems = [...currentVisibleItems.slice(1)] | ||
currentVisibleItemWidths = [...currentVisibleItemWidths.slice(1)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One idea, I think .slice(1)
would create a new array here so we could avoid spreading it into a new array
currentVisibleItems = [...currentVisibleItems.slice(1)] | |
currentVisibleItemWidths = [...currentVisibleItemWidths.slice(1)] | |
currentVisibleItems = currentVisibleItems.slice(1) | |
currentVisibleItemWidths = currentVisibleItemWidths.slice(1) |
visibleItems: [...currentVisibleItems], | ||
menuItems: [...currentMenuItems], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One idea with the spread:
visibleItems: [...currentVisibleItems], | |
menuItems: [...currentMenuItems], | |
visibleItems: currentVisibleItems, | |
menuItems: currentMenuItems, |
Let me know if these spread suggestions aren't applicable here btw!
<BreadcrumbsMenuItem | ||
ref={menuButtonRef} | ||
items={effectiveMenuItems} | ||
aria-label={`${effectiveMenuItems.length} more breadcrumb items`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be a good use for the …
character specifically on toggle element (got the idea from spectrum: https://react-spectrum.adobe.com/react-spectrum/Breadcrumbs.html) and that might mean we don't need a tooltip on this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember Katie specifically mentioned that its good to use the iconbutton for the toggle element. Is that what you mean here? Also the tooltip is the same as they used in context-region
Closes #https://github.com/github/primer/issues/5122
Changelog
Breadcrumbs now show an overflow menu with the following features
hideRoot
is false then we show the root/overflow menu/leafhttps://www.loom.com/share/8902798416b741c9959fc7bb501185a5
New
Changed
Removed
Rollout strategy
Testing & Reviewing
Merge checklist