@@ -36,15 +36,18 @@ const BreadcrumbsMenuItem = React.forwardRef<HTMLButtonElement, BreadcrumbsMenuI
36
36
< ActionMenu >
37
37
< ActionMenu . Button
38
38
ref = { ref }
39
- aria-label = { ariaLabel || `${ items . length } more items` }
39
+ aria-label = { ariaLabel || `${ items . length } more breadcrumb items` }
40
+ aria-haspopup = "menu"
41
+ aria-expanded = "false"
40
42
variant = "invisible"
43
+ trailingAction = { null }
41
44
style = { { display : 'inline-flex' } }
42
45
{ ...rest }
43
46
>
44
47
…
45
48
</ ActionMenu . Button >
46
49
< ActionMenu . Overlay width = "auto" >
47
- < ActionList >
50
+ < ActionList role = "menu" >
48
51
{ items . map ( ( item , index ) => {
49
52
const href = item . props . href
50
53
const children = item . props . children
@@ -53,6 +56,7 @@ const BreadcrumbsMenuItem = React.forwardRef<HTMLButtonElement, BreadcrumbsMenuI
53
56
< ActionList . LinkItem
54
57
key = { index }
55
58
href = { href }
59
+ role = "menuitem"
56
60
aria-current = { selected ? 'page' : undefined }
57
61
className = { selected ? classes . ItemSelected : undefined }
58
62
>
@@ -75,6 +79,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo
75
79
const [ visibleItems , setVisibleItems ] = useState < React . ReactElement [ ] > ( [ ] )
76
80
const [ menuItems , setMenuItems ] = useState < React . ReactElement [ ] > ( [ ] )
77
81
const [ itemWidths , setItemWidths ] = useState < number [ ] > ( [ ] )
82
+ const [ effectiveHideRoot , setEffectiveHideRoot ] = useState < boolean > ( hideRoot )
78
83
const previousWidthsRef = useRef < string > ( '' )
79
84
80
85
const childArray = React . Children . toArray ( children ) . filter ( child =>
@@ -120,56 +125,120 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo
120
125
if ( overflow === 'wrap' ) {
121
126
setVisibleItems ( childArray )
122
127
setMenuItems ( [ ] )
128
+ setEffectiveHideRoot ( hideRoot )
123
129
return
124
130
}
125
131
126
132
// For 'menu' overflow mode
127
- const lastItem = childArray [ childArray . length - 1 ] // Leaf breadcrumb
128
- const firstItem = childArray [ 0 ] // Root breadcrumb
129
-
130
- // First check: if more than 5 items, always use overflow
131
- if ( childArray . length > 5 ) {
132
- if ( hideRoot ) {
133
- // Show only overflow menu and leaf breadcrumb
134
- const itemsToHide = childArray . slice ( 0 , - 1 ) // All except last
135
- setMenuItems ( itemsToHide )
136
- setVisibleItems ( [ lastItem ] )
137
- } else {
138
- // Show root breadcrumb, overflow menu, and leaf breadcrumb
139
- const itemsToHide = childArray . slice ( 1 , - 1 ) // All except first and last
140
- setMenuItems ( itemsToHide )
141
- setVisibleItems ( [ firstItem , lastItem ] )
133
+ // Helper function to calculate visible items and menu items with progressive hiding
134
+ const calculateOverflow = ( availableWidth : number ) => {
135
+ const MENU_BUTTON_WIDTH = 50 // Approximate width of "..." button
136
+
137
+ let currentVisibleItems = [ ...childArray ]
138
+ let currentMenuItems : React . ReactElement [ ] = [ ]
139
+
140
+ // If more than 5 items, start by reducing to 5 visible items (including menu)
141
+ if ( childArray . length > 5 ) {
142
+ // Target: 4 visible items + 1 menu = 5 total
143
+ const itemsToHide = childArray . slice ( 0 , childArray . length - 4 )
144
+ currentMenuItems = itemsToHide
145
+ currentVisibleItems = childArray . slice ( childArray . length - 4 )
142
146
}
143
- return
144
- }
145
147
146
- // Second check: if we have measured widths and container width, check if items fit
147
- if ( containerWidth > 0 && itemWidths . length === childArray . length && itemWidths . length > 0 ) {
148
- const totalItemsWidth = itemWidths . reduce ( ( sum , width ) => sum + width , 0 )
149
- // Add some buffer for the ellipsis menu button (approximately 50px)
150
- const bufferWidth = 50
151
-
152
- if ( totalItemsWidth + bufferWidth > containerWidth ) {
153
- // Items don't fit, need to overflow
154
- if ( hideRoot ) {
155
- // Show only overflow menu and leaf breadcrumb
156
- const itemsToHide = childArray . slice ( 0 , - 1 ) // All except last
157
- setMenuItems ( itemsToHide )
158
- setVisibleItems ( [ lastItem ] )
159
- } else {
160
- // Show root breadcrumb, overflow menu, and leaf breadcrumb
161
- const itemsToHide = childArray . slice ( 1 , - 1 ) // All except first and last
162
- setMenuItems ( itemsToHide )
163
- setVisibleItems ( [ firstItem , lastItem ] )
148
+ // Now check if current visible items fit in available width
149
+ if ( availableWidth > 0 && itemWidths . length === childArray . length ) {
150
+ let visibleItemsWidthTotal = currentVisibleItems
151
+ . map ( item => {
152
+ const index = childArray . findIndex ( child => child . key === item . key )
153
+ return index !== - 1 ? itemWidths [ index ] : 0
154
+ } )
155
+ . reduce ( ( sum , width ) => sum + width , 0 )
156
+
157
+ // Add menu button width if we have hidden items
158
+ if ( currentMenuItems . length > 0 ) {
159
+ visibleItemsWidthTotal += MENU_BUTTON_WIDTH
160
+ }
161
+
162
+ // Progressive hiding: keep moving items to menu until they fit
163
+ let effectiveHideRoot = hideRoot
164
+
165
+ while ( visibleItemsWidthTotal > availableWidth && currentVisibleItems . length > 1 ) {
166
+ // Determine which item to hide based on hideRoot setting
167
+ let itemToHide : React . ReactElement
168
+
169
+ if ( effectiveHideRoot ) {
170
+ // Hide from start when hideRoot is true
171
+ itemToHide = currentVisibleItems [ 0 ]
172
+ currentVisibleItems = currentVisibleItems . slice ( 1 )
173
+ } else {
174
+ // Try to hide second item (keep root and leaf) when hideRoot is false
175
+ itemToHide = currentVisibleItems [ 1 ]
176
+ currentVisibleItems = [ currentVisibleItems [ 0 ] , ...currentVisibleItems . slice ( 2 ) ]
177
+ }
178
+
179
+ currentMenuItems = [ itemToHide , ...currentMenuItems ]
180
+
181
+ // Recalculate width
182
+ visibleItemsWidthTotal = currentVisibleItems
183
+ . map ( item => {
184
+ const index = childArray . findIndex ( child => child . key === item . key )
185
+ return index !== - 1 ? itemWidths [ index ] : 0
186
+ } )
187
+ . reduce ( ( sum , width ) => sum + width , 0 )
188
+
189
+ // Add menu button width
190
+ if ( currentMenuItems . length > 0 ) {
191
+ visibleItemsWidthTotal += MENU_BUTTON_WIDTH
192
+ }
193
+
194
+ // If hideRoot is false but we still don't fit with root + menu + leaf,
195
+ // fallback to hideRoot=true behavior (menu + leaf only)
196
+ if (
197
+ ! hideRoot &&
198
+ ! effectiveHideRoot &&
199
+ currentVisibleItems . length === 2 &&
200
+ visibleItemsWidthTotal > availableWidth
201
+ ) {
202
+ effectiveHideRoot = true
203
+ // Move the root item to menu as well
204
+ const rootItem = currentVisibleItems [ 0 ]
205
+ currentVisibleItems = currentVisibleItems . slice ( 1 )
206
+ currentMenuItems = [ rootItem , ...currentMenuItems ]
207
+
208
+ // Recalculate width one more time
209
+ visibleItemsWidthTotal = currentVisibleItems
210
+ . map ( item => {
211
+ const index = childArray . findIndex ( child => child . key === item . key )
212
+ return index !== - 1 ? itemWidths [ index ] : 0
213
+ } )
214
+ . reduce ( ( sum , width ) => sum + width , 0 )
215
+
216
+ if ( currentMenuItems . length > 0 ) {
217
+ visibleItemsWidthTotal += MENU_BUTTON_WIDTH
218
+ }
219
+ }
220
+ }
221
+
222
+ // Final check: if even the leaf breadcrumb + menu doesn't fit, just show them anyway
223
+ // The CSS will handle truncation of the leaf breadcrumb
224
+ if ( visibleItemsWidthTotal > availableWidth && currentVisibleItems . length === 1 ) {
225
+ // Keep the current configuration - CSS will handle truncation
164
226
}
165
- return
227
+ }
228
+
229
+ return {
230
+ visibleItems : currentVisibleItems ,
231
+ menuItems : currentMenuItems ,
232
+ effectiveHideRoot,
166
233
}
167
234
}
168
235
169
- // No overflow needed - show all items
170
- setVisibleItems ( childArray )
171
- setMenuItems ( [ ] )
172
- } , [ childArray , overflow , containerWidth , hideRoot , itemWidths ] )
236
+ // Apply the overflow calculation
237
+ const result = calculateOverflow ( containerWidth )
238
+ setVisibleItems ( result . visibleItems )
239
+ setMenuItems ( result . menuItems )
240
+ setEffectiveHideRoot ( result . effectiveHideRoot )
241
+ } , [ childArray , overflow , containerWidth , hideRoot , itemWidths , effectiveHideRoot ] )
173
242
174
243
// Determine final children to render
175
244
const finalChildren = React . useMemo ( ( ) => {
@@ -194,15 +263,15 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo
194
263
</ li >
195
264
) )
196
265
197
- // Position menu based on hideRoot setting and visible items
198
- if ( hideRoot ) {
266
+ // Position menu based on effective hideRoot setting and visible items
267
+ if ( effectiveHideRoot ) {
199
268
// Show: [overflow menu, leaf breadcrumb]
200
269
return [ menuElement , ...visibleElements ]
201
270
} else {
202
271
// Show: [root breadcrumb, overflow menu, leaf breadcrumb]
203
272
return [ visibleElements [ 0 ] , menuElement , ...visibleElements . slice ( 1 ) ]
204
273
}
205
- } , [ overflow , menuItems , visibleItems , hideRoot ] )
274
+ } , [ overflow , menuItems , visibleItems , effectiveHideRoot ] )
206
275
207
276
return (
208
277
< BoxWithFallback
0 commit comments