Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
a7208d4
Spike for breadcrumbs overflow
pksjce Aug 11, 2025
54c9148
Add changeset for breadcrumbs overflow
pksjce Aug 11, 2025
dc5dac1
Add review comments and change behavior
pksjce Aug 12, 2025
7df321a
Fix up some issues.
pksjce Aug 13, 2025
611d703
check for zero children
pksjce Aug 13, 2025
1a84b8a
Fix bug with hideRoot
pksjce Aug 13, 2025
c567b15
Add test cases
pksjce Aug 13, 2025
35c0971
Add docs
pksjce Aug 13, 2025
901ba0c
Breadcrumbs can have more stories
pksjce Aug 13, 2025
f8fd857
Add features stories for breadcrumbs
pksjce Aug 13, 2025
f415276
Add wrapped breadcrumbs in story
pksjce Aug 14, 2025
68eba14
Fix for ssr and child key
pksjce Aug 15, 2025
f4bba96
Add IconButton
pksjce Aug 15, 2025
b55755c
Fix for SSR
pksjce Aug 18, 2025
9477af7
Fix for SSR
pksjce Aug 18, 2025
df9aa0f
Final changes for SSR
pksjce Aug 18, 2025
fcd8a8e
Rework calculations
pksjce Aug 19, 2025
0a09e45
Tests are passing
pksjce Aug 19, 2025
3b9891a
Small fixes to menu button
pksjce Aug 21, 2025
273a055
Fix styling issues with button
pksjce Aug 21, 2025
08637ae
Fix focus states
pksjce Aug 22, 2025
17a3107
Fix focus states
pksjce Aug 22, 2025
ed838f3
Make sure old behavior works
pksjce Aug 25, 2025
1870eff
Fix tests and lint
pksjce Aug 25, 2025
0eaf106
Create eighty-queens-tap.md
pksjce Aug 25, 2025
a24d75a
Merge branch 'main' into pk/breadcrumbs-with-overflow-menu
pksjce Aug 25, 2025
40cb6a9
chore(deps-dev): bump the eslint group with 3 updates (#6657)
dependabot[bot] Aug 25, 2025
027f4f6
chore(deps): bump the rollup group with 2 updates (#6659)
dependabot[bot] Aug 25, 2025
7b4921c
chore(deps-dev): bump postcss-mixins from 11.0.1 to 12.1.2 (#6660)
dependabot[bot] Aug 25, 2025
bc2749c
chore(deps-dev): bump @github/markdownlint-github from 0.7.0 to 0.8.0…
dependabot[bot] Aug 25, 2025
f9c9a2d
feat(mcp): add better primitives output, add coding guidelines tool (…
joshblack Aug 25, 2025
870a8ca
Convert menu to disclosure pattern
pksjce Aug 28, 2025
bc99a17
Fix bugs
pksjce Aug 28, 2025
020f0b7
Fix up infinite loop at 1 remaining visible item
pksjce Aug 29, 2025
8cf263e
Remove unnecessary comments
pksjce Aug 29, 2025
23ddd0e
Use ref callback for menu width calculation
pksjce Aug 29, 2025
639a782
Fix up review comments
pksjce Sep 1, 2025
2e367bf
Add feature flags
pksjce Sep 1, 2025
a5b9c20
Merge branch 'pk/breadcrumbs-with-overflow-menu' of https://github.co…
pksjce Sep 1, 2025
bb5aa77
Delete .changeset/good-cougars-hug.md
pksjce Sep 1, 2025
b8063b8
Add different prop to hideRoot
pksjce Sep 1, 2025
c7669d6
Add tests
pksjce Sep 1, 2025
5c3fc21
Merge branch 'pk/breadcrumbs-with-overflow-menu' of https://github.co…
pksjce Sep 1, 2025
e2f34c5
Merge branch 'main' into pk/breadcrumbs-with-overflow-menu
pksjce Sep 1, 2025
a7418b7
Fix lint and test issue
pksjce Sep 1, 2025
7c2ced8
Merge branch 'pk/breadcrumbs-with-overflow-menu' of https://github.co…
pksjce Sep 1, 2025
0846a5a
Dont use story in aat
pksjce Sep 1, 2025
7df8356
Fix for aat
pksjce Sep 1, 2025
92b813e
Story color needs changed
pksjce Sep 1, 2025
2854259
Some css improvements
pksjce Sep 3, 2025
b2e8931
Fix the button role
pksjce Sep 4, 2025
e37d05e
Merge branch 'main' into pk/breadcrumbs-with-overflow-menu
pksjce Sep 4, 2025
c247260
Breadcrumb overflow styling (#6728)
langermank Sep 8, 2025
7ff66f2
Couple of css tweaks
pksjce Sep 8, 2025
ea60437
Merge branch 'main' into pk/breadcrumbs-with-overflow-menu
pksjce Sep 8, 2025
9e83957
Remove unnecessary dependencies
pksjce Sep 8, 2025
1249ae8
Add some doc comments
pksjce Sep 8, 2025
f9ec096
fix
pksjce Sep 8, 2025
f43f6e6
Fix esc button focus
pksjce Sep 8, 2025
5fa7c62
Update snapshots
pksjce Sep 8, 2025
9b5c42f
Bug has to be fixed in iconbutton
pksjce Sep 8, 2025
78119fb
Merge branch 'main' into pk/breadcrumbs-with-overflow-menu
pksjce Sep 9, 2025
71753ad
Merge branch 'main' into pk/breadcrumbs-with-overflow-menu
pksjce Sep 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eighty-queens-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

Breadcrumbs : Add overflow menu for responsive behavior
5 changes: 5 additions & 0 deletions .changeset/hot-bears-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

Breadcrumb overflow styling
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
162 changes: 162 additions & 0 deletions packages/react/src/Breadcrumbs/Breadcrumbs.dev.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import {useState} from 'react'
import Breadcrumbs from '.'
import TextInput from '../TextInput'

export default {
title: 'Components/Breadcrumbs/Dev',
}

export const DynamicChildren = () => {
const [items, setItems] = useState([
{id: 1, href: '#', name: 'Home'},
{id: 2, href: '#', name: 'Docs'},
{id: 3, href: '#', name: 'Components'},
])

const addItem = () => {
const newId = Math.max(...items.map(item => item.id)) + 1
const names = ['Advanced', 'Examples', 'Guides', 'API', 'Tutorials', 'Reference']
const randomName = names[Math.floor(Math.random() * names.length)]
setItems([...items, {id: newId, href: '#', name: `${randomName}-${newId}`}])
}

const removeItem = () => {
if (items.length > 1) {
setItems(items.slice(0, -1))
}
}

const addMultipleItems = () => {
const newItems = [
{id: Date.now() + 1, href: '#', name: 'Category'},
{id: Date.now() + 2, href: '#', name: 'Subcategory'},
{id: Date.now() + 3, href: '#', name: 'Item'},
{id: Date.now() + 4, href: '#', name: 'Details'},
{id: Date.now() + 5, href: '#', name: 'Specifications'},
]
setItems([...items, ...newItems])
}

const reset = () => {
setItems([
{id: 1, href: '#', name: 'Home'},
{id: 2, href: '#', name: 'Docs'},
{id: 3, href: '#', name: 'Components'},
])
}

return (
<div style={{display: 'flex', flexDirection: 'column', gap: '16px'}}>
<div style={{display: 'flex', gap: '8px', marginBottom: '16px'}}>
<button type="button" onClick={addItem} style={{padding: '4px 8px'}}>
Add Item
</button>
<button type="button" onClick={removeItem} style={{padding: '4px 8px'}}>
Remove Item
</button>
<button type="button" onClick={addMultipleItems} style={{padding: '4px 8px'}}>
Add Many Items
</button>
<button type="button" onClick={reset} style={{padding: '4px 8px'}}>
Reset
</button>
</div>

<div>
<h4 id="dynamic-breadcrumbs-heading" style={{margin: '0 0 8px 0'}}>
Dynamic breadcrumbs
</h4>
<Breadcrumbs overflow="menu-with-root">
{items.map((item, index) => (
<Breadcrumbs.Item key={item.id} href={item.href} selected={index === items.length - 1}>
{item.name}
</Breadcrumbs.Item>
))}
</Breadcrumbs>
</div>

<div style={{marginTop: '16px', fontSize: '12px'}}>
Current items: {items.length} | Try adding/removing items to see how overflow behavior changes
</div>
</div>
)
}

export const OverflowMenuNarrowContainer = () => (
<div style={{width: '350px', border: '1px solid #ccc', padding: '8px'}}>
<Breadcrumbs overflow="menu">
<Breadcrumbs.Item href="#">Home</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Products</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Category</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Subcategory</Breadcrumbs.Item>
<Breadcrumbs.Item href="#" selected>
Current Page
</Breadcrumbs.Item>
</Breadcrumbs>
</div>
)

// Wrapper components to test that BreadcrumbsItem works when wrapped
const StyledWrapper = ({children}: {children: React.ReactNode}) => (
<span style={{padding: '2px', border: '1px dotted #999'}}>{children}</span>
)

const ConditionalWrapper = ({children, condition}: {children: React.ReactNode; condition: boolean}) => {
return condition ? <strong>{children}</strong> : <>{children}</>
}

const DataAttributeWrapper = ({children}: {children: React.ReactNode}) => (
<span data-testid="wrapper" className="custom-wrapper">
{children}
</span>
)

export const WrappedBreadcrumbItemsWithOverflow = () => (
<Breadcrumbs overflow="menu">
<StyledWrapper>
<Breadcrumbs.Item href="#">Wrapped Home</Breadcrumbs.Item>
</StyledWrapper>
<ConditionalWrapper condition={false}>
<Breadcrumbs.Item href="#">Products</Breadcrumbs.Item>
</ConditionalWrapper>
<DataAttributeWrapper>
<Breadcrumbs.Item href="#">Category</Breadcrumbs.Item>
</DataAttributeWrapper>
<StyledWrapper>
<Breadcrumbs.Item href="#">Subcategory</Breadcrumbs.Item>
</StyledWrapper>
<ConditionalWrapper condition={true}>
<Breadcrumbs.Item href="#">Item</Breadcrumbs.Item>
</ConditionalWrapper>
<DataAttributeWrapper>
<Breadcrumbs.Item href="#">Details</Breadcrumbs.Item>
</DataAttributeWrapper>
<Breadcrumbs.Item href="#" selected>
Current Page
</Breadcrumbs.Item>
</Breadcrumbs>
)

export const WithEditableNameInput = () => (
<Breadcrumbs>
<Breadcrumbs.Item href="#">Home</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Documents</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Project Alpha</Breadcrumbs.Item>
<Breadcrumbs.Item>
<TextInput
defaultValue="Untitled Document"
size="small"
sx={{
minWidth: '120px',
maxWidth: '180px',
fontSize: 'inherit',
border: '1px dashed var(--borderColor-muted)',
'&:focus': {
border: '1px solid var(--borderColor-accent-emphasis)',
},
}}
aria-label="Edit document name"
/>
</Breadcrumbs.Item>
</Breadcrumbs>
)
28 changes: 24 additions & 4 deletions packages/react/src/Breadcrumbs/Breadcrumbs.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,27 @@
"name": "className",
"type": "string",
"required": false,
"description": "",
"description": "Additional CSS class names to apply to the breadcrumbs container",
"defaultValue": ""
},
{
"name": "children",
"type": "Breadcrumbs.Item[]",
"defaultValue": "",
"description": ""
"description": "Breadcrumb items to render. Each item should be a Breadcrumbs.Item component."
},
{
"name": "overflow",
"type": "'wrap' | 'menu' | 'menu-with-root'",
"required": false,
"description": "How to handle overflow when breadcrumbs don't fit in the container. 'wrap' allows items to wrap to new lines. 'menu' collapses items into an overflow menu. 'menu-with-root' also collapses items into an overflow menu but includes the root (first) breadcrumb in the menu so only the last items remain visible.",
"defaultValue": "'wrap'"
},
{
"name": "sx",
"type": "SystemStyleObject",
"deprecated": true
"deprecated": true,
"description": "System styles (deprecated, use CSS classes instead)"
}
],
"subcomponents": [
Expand All @@ -37,7 +45,7 @@
"name": "selected",
"type": "boolean",
"defaultValue": "false",
"description": "Whether this item represents the current page"
"description": "Whether this item represents the current page. Sets aria-current='page' for accessibility."
},
{
"name": "to",
Expand All @@ -46,6 +54,18 @@
"description": "Used when the item is rendered using a component like React Router's `Link`. The path to navigate to.",
"defaultValue": ""
},
{
"name": "href",
"type": "string",
"required": false,
"description": "The URL that the breadcrumb item links to. Used with regular anchor elements."
},
{
"name": "children",
"type": "React.ReactNode",
"required": true,
"description": "The content to display inside the breadcrumb item, typically text."
},
{
"name": "ref",
"type": "React.RefObject<HTMLAnchorElement>"
Expand Down
112 changes: 112 additions & 0 deletions packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type {Meta} from '@storybook/react-vite'
import type React from 'react'
import type {ComponentProps} from '../utils/types'
import Breadcrumbs from './Breadcrumbs'
import {FeatureFlags} from '../FeatureFlags'

export default {
title: 'Components/Breadcrumbs/Features',
component: Breadcrumbs,
} as Meta<ComponentProps<typeof Breadcrumbs>>

export const OverflowWrap = () => (
<Breadcrumbs overflow="wrap">
<Breadcrumbs.Item href="#">Home</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Products</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Category</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Subcategory</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Item</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Details</Breadcrumbs.Item>
<Breadcrumbs.Item href="#" selected>
Current Page
</Breadcrumbs.Item>
</Breadcrumbs>
)

export const OverflowMenuFeatureFlagEnabled = () => (
<FeatureFlags flags={{primer_react_breadcrumbs_overflow_menu: true}}>
<Breadcrumbs overflow="menu">
<Breadcrumbs.Item href="#">Home</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Products</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Category</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Subcategory</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Item</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Details</Breadcrumbs.Item>
<Breadcrumbs.Item href="#" selected>
Current Page
</Breadcrumbs.Item>
</Breadcrumbs>
</FeatureFlags>
)

export const OverflowMenuFeatureFlagDisabled = () => (
<Breadcrumbs overflow="menu">
<Breadcrumbs.Item href="#">Home</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Products</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Category</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Subcategory</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Item</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Details</Breadcrumbs.Item>
<Breadcrumbs.Item href="#" selected>
Current Page
</Breadcrumbs.Item>
</Breadcrumbs>
)

export const OverflowMenuShowRootFeatureFlagDisabled = () => (
<Breadcrumbs overflow="menu-with-root">
<Breadcrumbs.Item href="#">github</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Teams</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Engineering</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">core-productivity</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">collaboration-workflows-flex</Breadcrumbs.Item>
<Breadcrumbs.Item href="#" selected>
global-navigation-reviewers
</Breadcrumbs.Item>
</Breadcrumbs>
)

export const OverflowMenuShowRootFeatureFlagEnabled = () => (
<FeatureFlags flags={{primer_react_breadcrumbs_overflow_menu: true}}>
<Breadcrumbs overflow="menu-with-root">
<Breadcrumbs.Item href="#">github</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Teams</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Engineering</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">core-productivity</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">collaboration-workflows-flex</Breadcrumbs.Item>
<Breadcrumbs.Item href="#" selected>
global-navigation-reviewers
</Breadcrumbs.Item>
</Breadcrumbs>
</FeatureFlags>
)

export const SpaciousVariantWithOverflowMenu = () => (
<FeatureFlags flags={{primer_react_breadcrumbs_overflow_menu: true}}>
<Breadcrumbs overflow="menu" variant="spacious">
<Breadcrumbs.Item href="#">Home</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Products</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Category</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Subcategory</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Item</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Details</Breadcrumbs.Item>
<Breadcrumbs.Item href="#" selected>
Current Page
</Breadcrumbs.Item>
</Breadcrumbs>
</FeatureFlags>
)

export const SpaciousVariantWithOverflowWrap = () => (
<Breadcrumbs variant="spacious">
<Breadcrumbs.Item href="#">Home</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Products</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Category</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Subcategory</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Item</Breadcrumbs.Item>
<Breadcrumbs.Item href="#">Details</Breadcrumbs.Item>
<Breadcrumbs.Item href="#" selected>
Current Page
</Breadcrumbs.Item>
</Breadcrumbs>
)
Loading
Loading