From a7208d425b4895d42e2585c0fb89ae1f2bc7030f Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 11 Aug 2025 17:06:41 +1000 Subject: [PATCH 01/54] Spike for breadcrumbs overflow --- .../src/Breadcrumbs/Breadcrumbs.module.css | 6 + .../src/Breadcrumbs/Breadcrumbs.stories.tsx | 116 ++++++++++ .../react/src/Breadcrumbs/Breadcrumbs.tsx | 200 +++++++++++++++++- 3 files changed, 317 insertions(+), 5 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index 96c61e7072c..92f1c9c0ab3 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -9,6 +9,12 @@ margin-bottom: 0; } +/* Prevent wrapping when using menu overflow */ +[data-overflow='menu'] .BreadcrumbsList { + white-space: nowrap; + overflow: hidden; +} + .ItemWrapper { display: inline-block; font-size: var(--text-body-size-medium); diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx index 16be2bd7bff..af02fde2c1e 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx @@ -16,3 +16,119 @@ export const Default = () => ( ) + +export const OverflowWrap = () => ( + + Home + Products + Category + Subcategory + Item + Details + + Current Page + + +) + +export const OverflowMenu = () => ( + + Home + Products + Category + Subcategory + Item + Details + + Current Page + + +) + +export const OverflowMenuHideRoot = () => ( + + Home + Products + Category + Subcategory + Item + Details + + Current Page + + +) + +export const OverflowMenuShowRoot = () => ( + + Home + Products + Category + Subcategory + Item + Details + + Current Page + + +) + +export const OverflowMenuFewItems = () => ( + + Home + About + + Team + + +) + +export const OverflowMenuManyItems = () => ( + + Home + Level 1 + Level 2 + Level 3 + Level 4 + Level 5 + Level 6 + Level 7 + Level 8 + + Current Page + + +) + +export const OverflowMenuLongWords = () => ( + + SupercalifragilisticexpialidociousRepository + AnticonstitutionnellementConfiguration + PneumonoultramicroscopicsilicovolcanoconiosisDocumentation + + HippopotomonstrosesquippedaliophobiaCurrentPage + + +) + +export const OverflowMenuLongWordsHideRoot = () => ( + + SupercalifragilisticexpialidociousRepository + AnticonstitutionnellementConfiguration + PneumonoultramicroscopicsilicovolcanoconiosisDocumentation + + HippopotomonstrosesquippedaliophobiaCurrentPage + + +) + +export const OverflowMenuLongWordsShowRoot = () => ( + + SupercalifragilisticexpialidociousRepository + AnticonstitutionnellementConfiguration + PneumonoultramicroscopicsilicovolcanoconiosisDocumentation + + HippopotomonstrosesquippedaliophobiaCurrentPage + + +) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index fe47e2307ec..c42f55d1af1 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -1,17 +1,23 @@ import {clsx} from 'clsx' import type {To} from 'history' -import React from 'react' +import React, {useState, useRef, useCallback, useEffect} from 'react' import type {SxProp} from '../sx' import type {ComponentProps} from '../utils/types' import classes from './Breadcrumbs.module.css' import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' import {BoxWithFallback} from '../internal/components/BoxWithFallback' +import {ActionMenu} from '../ActionMenu' +import {ActionList} from '../ActionList' +import {useResizeObserver} from '../hooks/useResizeObserver' +import type {ResizeObserverEntry} from '../hooks/useResizeObserver' const SELECTED_CLASS = 'selected' export type BreadcrumbsProps = React.PropsWithChildren< { className?: string + overflow?: 'wrap' | 'menu' + hideRoot?: boolean } & SxProp > @@ -19,11 +25,195 @@ const BreadcrumbsList = ({children}: React.PropsWithChildren) => { return
    {children}
} -function Breadcrumbs({className, children, sx: sxProp}: BreadcrumbsProps) { - const wrappedChildren = React.Children.map(children, child =>
  • {child}
  • ) +type BreadcrumbsMenuItemProps = { + items: React.ReactElement[] + 'aria-label'?: string +} + +const BreadcrumbsMenuItem = React.forwardRef( + ({items, 'aria-label': ariaLabel, ...rest}, ref) => { + return ( + + + … + + + + {items.map((item, index) => { + const href = item.props.href + const children = item.props.children + const selected = item.props.selected + return ( + + {children} + + ) + })} + + + + ) + }, +) + +BreadcrumbsMenuItem.displayName = 'Breadcrumbs.MenuItem' + +function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRoot = true}: BreadcrumbsProps) { + const containerRef = useRef(null) + const [containerWidth, setContainerWidth] = useState(0) + const [visibleItems, setVisibleItems] = useState([]) + const [menuItems, setMenuItems] = useState([]) + const [itemWidths, setItemWidths] = useState([]) + const previousWidthsRef = useRef('') + + const childArray = React.Children.toArray(children).filter(child => + React.isValidElement(child), + ) as React.ReactElement[] + + // Initialize visible items to show all items initially for measurement + useEffect(() => { + if (visibleItems.length === 0 && childArray.length > 0) { + setVisibleItems(childArray) + } + }, [childArray, visibleItems.length]) + + const handleResize = useCallback((entries: ResizeObserverEntry[]) => { + if (entries[0]) { + setContainerWidth(entries[0].contentRect.width) + } + }, []) + + useResizeObserver(handleResize, containerRef) + + // Calculate item widths from rendered items using parent container + useEffect(() => { + if (containerRef.current && overflow === 'menu') { + const listElement = containerRef.current.querySelector('ol') + if (listElement && listElement.children.length > 0) { + // Only measure widths when all original items are visible (no overflow menu yet) + if (listElement.children.length === childArray.length) { + const widths = Array.from(listElement.children).map(child => (child as HTMLElement).offsetWidth) + const widthsString = JSON.stringify(widths) + // Only update if widths have actually changed to prevent infinite loops + if (widthsString !== previousWidthsRef.current) { + previousWidthsRef.current = widthsString + setItemWidths(widths) + } + } + } + } + }, [childArray, overflow, visibleItems]) + + // Calculate which items are visible vs in menu + useEffect(() => { + if (overflow === 'wrap') { + setVisibleItems(childArray) + setMenuItems([]) + return + } + + // For 'menu' overflow mode + const lastItem = childArray[childArray.length - 1] // Leaf breadcrumb + const firstItem = childArray[0] // Root breadcrumb + + // First check: if more than 5 items, always use overflow + if (childArray.length > 5) { + if (hideRoot) { + // Show only overflow menu and leaf breadcrumb + const itemsToHide = childArray.slice(0, -1) // All except last + setMenuItems(itemsToHide) + setVisibleItems([lastItem]) + } else { + // Show root breadcrumb, overflow menu, and leaf breadcrumb + const itemsToHide = childArray.slice(1, -1) // All except first and last + setMenuItems(itemsToHide) + setVisibleItems([firstItem, lastItem]) + } + return + } + + // Second check: if we have measured widths and container width, check if items fit + if (containerWidth > 0 && itemWidths.length === childArray.length && itemWidths.length > 0) { + const totalItemsWidth = itemWidths.reduce((sum, width) => sum + width, 0) + // Add some buffer for the ellipsis menu button (approximately 50px) + const bufferWidth = 50 + + if (totalItemsWidth + bufferWidth > containerWidth) { + // Items don't fit, need to overflow + if (hideRoot) { + // Show only overflow menu and leaf breadcrumb + const itemsToHide = childArray.slice(0, -1) // All except last + setMenuItems(itemsToHide) + setVisibleItems([lastItem]) + } else { + // Show root breadcrumb, overflow menu, and leaf breadcrumb + const itemsToHide = childArray.slice(1, -1) // All except first and last + setMenuItems(itemsToHide) + setVisibleItems([firstItem, lastItem]) + } + return + } + } + + // No overflow needed - show all items + setVisibleItems(childArray) + setMenuItems([]) + }, [childArray, overflow, containerWidth, hideRoot, itemWidths]) + + // Determine final children to render + const finalChildren = React.useMemo(() => { + if (overflow === 'wrap' || menuItems.length === 0) { + return visibleItems.map(child => ( +
  • + {child} +
  • + )) + } + + // Create menu item and combine with visible items + const menuElement = ( +
  • + +
  • + ) + + const visibleElements = visibleItems.map(child => ( +
  • + {child} +
  • + )) + + // Position menu based on hideRoot setting and visible items + if (hideRoot) { + // Show: [overflow menu, leaf breadcrumb] + return [menuElement, ...visibleElements] + } else { + // Show: [root breadcrumb, overflow menu, leaf breadcrumb] + return [visibleElements[0], menuElement, ...visibleElements.slice(1)] + } + }, [overflow, menuItems, visibleItems, hideRoot]) + return ( - - {wrappedChildren} + + {finalChildren} ) } From 54c9148a38eeb4c4bb8cc6cda4d362437a497f18 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 11 Aug 2025 22:44:15 +1000 Subject: [PATCH 02/54] Add changeset for breadcrumbs overflow --- .changeset/good-cougars-hug.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/good-cougars-hug.md diff --git a/.changeset/good-cougars-hug.md b/.changeset/good-cougars-hug.md new file mode 100644 index 00000000000..27786935bdc --- /dev/null +++ b/.changeset/good-cougars-hug.md @@ -0,0 +1,5 @@ +--- +"@primer/react": patch +--- + +Spike for breadcrumbs overflow From dc5dac1794ffa5476d391a7504a9768b56ed2d0c Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Tue, 12 Aug 2025 15:32:20 +1000 Subject: [PATCH 03/54] Add review comments and change behavior --- .../src/Breadcrumbs/Breadcrumbs.stories.tsx | 84 ++------- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 159 +++++++++++++----- 2 files changed, 126 insertions(+), 117 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx index af02fde2c1e..f0a6451f607 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx @@ -45,20 +45,6 @@ export const OverflowMenu = () => ( ) -export const OverflowMenuHideRoot = () => ( - - Home - Products - Category - Subcategory - Item - Details - - Current Page - - -) - export const OverflowMenuShowRoot = () => ( Home @@ -73,62 +59,16 @@ export const OverflowMenuShowRoot = () => ( ) -export const OverflowMenuFewItems = () => ( - - Home - About - - Team - - -) - -export const OverflowMenuManyItems = () => ( - - Home - Level 1 - Level 2 - Level 3 - Level 4 - Level 5 - Level 6 - Level 7 - Level 8 - - Current Page - - -) - -export const OverflowMenuLongWords = () => ( - - SupercalifragilisticexpialidociousRepository - AnticonstitutionnellementConfiguration - PneumonoultramicroscopicsilicovolcanoconiosisDocumentation - - HippopotomonstrosesquippedaliophobiaCurrentPage - - -) - -export const OverflowMenuLongWordsHideRoot = () => ( - - SupercalifragilisticexpialidociousRepository - AnticonstitutionnellementConfiguration - PneumonoultramicroscopicsilicovolcanoconiosisDocumentation - - HippopotomonstrosesquippedaliophobiaCurrentPage - - -) - -export const OverflowMenuLongWordsShowRoot = () => ( - - SupercalifragilisticexpialidociousRepository - AnticonstitutionnellementConfiguration - PneumonoultramicroscopicsilicovolcanoconiosisDocumentation - - HippopotomonstrosesquippedaliophobiaCurrentPage - - +export const OverflowMenuNarrowContainer = () => ( +
    + + Home + Products + Category + Subcategory + + Current Page + + +
    ) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index c42f55d1af1..13a8e0f9e51 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -36,15 +36,18 @@ const BreadcrumbsMenuItem = React.forwardRef - + {items.map((item, index) => { const href = item.props.href const children = item.props.children @@ -53,6 +56,7 @@ const BreadcrumbsMenuItem = React.forwardRef @@ -75,6 +79,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo const [visibleItems, setVisibleItems] = useState([]) const [menuItems, setMenuItems] = useState([]) const [itemWidths, setItemWidths] = useState([]) + const [effectiveHideRoot, setEffectiveHideRoot] = useState(hideRoot) const previousWidthsRef = useRef('') const childArray = React.Children.toArray(children).filter(child => @@ -120,56 +125,120 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo if (overflow === 'wrap') { setVisibleItems(childArray) setMenuItems([]) + setEffectiveHideRoot(hideRoot) return } // For 'menu' overflow mode - const lastItem = childArray[childArray.length - 1] // Leaf breadcrumb - const firstItem = childArray[0] // Root breadcrumb - - // First check: if more than 5 items, always use overflow - if (childArray.length > 5) { - if (hideRoot) { - // Show only overflow menu and leaf breadcrumb - const itemsToHide = childArray.slice(0, -1) // All except last - setMenuItems(itemsToHide) - setVisibleItems([lastItem]) - } else { - // Show root breadcrumb, overflow menu, and leaf breadcrumb - const itemsToHide = childArray.slice(1, -1) // All except first and last - setMenuItems(itemsToHide) - setVisibleItems([firstItem, lastItem]) + // Helper function to calculate visible items and menu items with progressive hiding + const calculateOverflow = (availableWidth: number) => { + const MENU_BUTTON_WIDTH = 50 // Approximate width of "..." button + + let currentVisibleItems = [...childArray] + let currentMenuItems: React.ReactElement[] = [] + + // If more than 5 items, start by reducing to 5 visible items (including menu) + if (childArray.length > 5) { + // Target: 4 visible items + 1 menu = 5 total + const itemsToHide = childArray.slice(0, childArray.length - 4) + currentMenuItems = itemsToHide + currentVisibleItems = childArray.slice(childArray.length - 4) } - return - } - // Second check: if we have measured widths and container width, check if items fit - if (containerWidth > 0 && itemWidths.length === childArray.length && itemWidths.length > 0) { - const totalItemsWidth = itemWidths.reduce((sum, width) => sum + width, 0) - // Add some buffer for the ellipsis menu button (approximately 50px) - const bufferWidth = 50 - - if (totalItemsWidth + bufferWidth > containerWidth) { - // Items don't fit, need to overflow - if (hideRoot) { - // Show only overflow menu and leaf breadcrumb - const itemsToHide = childArray.slice(0, -1) // All except last - setMenuItems(itemsToHide) - setVisibleItems([lastItem]) - } else { - // Show root breadcrumb, overflow menu, and leaf breadcrumb - const itemsToHide = childArray.slice(1, -1) // All except first and last - setMenuItems(itemsToHide) - setVisibleItems([firstItem, lastItem]) + // Now check if current visible items fit in available width + if (availableWidth > 0 && itemWidths.length === childArray.length) { + let visibleItemsWidthTotal = currentVisibleItems + .map(item => { + const index = childArray.findIndex(child => child.key === item.key) + return index !== -1 ? itemWidths[index] : 0 + }) + .reduce((sum, width) => sum + width, 0) + + // Add menu button width if we have hidden items + if (currentMenuItems.length > 0) { + visibleItemsWidthTotal += MENU_BUTTON_WIDTH + } + + // Progressive hiding: keep moving items to menu until they fit + let effectiveHideRoot = hideRoot + + while (visibleItemsWidthTotal > availableWidth && currentVisibleItems.length > 1) { + // Determine which item to hide based on hideRoot setting + let itemToHide: React.ReactElement + + if (effectiveHideRoot) { + // Hide from start when hideRoot is true + itemToHide = currentVisibleItems[0] + currentVisibleItems = currentVisibleItems.slice(1) + } else { + // Try to hide second item (keep root and leaf) when hideRoot is false + itemToHide = currentVisibleItems[1] + currentVisibleItems = [currentVisibleItems[0], ...currentVisibleItems.slice(2)] + } + + currentMenuItems = [itemToHide, ...currentMenuItems] + + // Recalculate width + visibleItemsWidthTotal = currentVisibleItems + .map(item => { + const index = childArray.findIndex(child => child.key === item.key) + return index !== -1 ? itemWidths[index] : 0 + }) + .reduce((sum, width) => sum + width, 0) + + // Add menu button width + if (currentMenuItems.length > 0) { + visibleItemsWidthTotal += MENU_BUTTON_WIDTH + } + + // If hideRoot is false but we still don't fit with root + menu + leaf, + // fallback to hideRoot=true behavior (menu + leaf only) + if ( + !hideRoot && + !effectiveHideRoot && + currentVisibleItems.length === 2 && + visibleItemsWidthTotal > availableWidth + ) { + effectiveHideRoot = true + // Move the root item to menu as well + const rootItem = currentVisibleItems[0] + currentVisibleItems = currentVisibleItems.slice(1) + currentMenuItems = [rootItem, ...currentMenuItems] + + // Recalculate width one more time + visibleItemsWidthTotal = currentVisibleItems + .map(item => { + const index = childArray.findIndex(child => child.key === item.key) + return index !== -1 ? itemWidths[index] : 0 + }) + .reduce((sum, width) => sum + width, 0) + + if (currentMenuItems.length > 0) { + visibleItemsWidthTotal += MENU_BUTTON_WIDTH + } + } + } + + // Final check: if even the leaf breadcrumb + menu doesn't fit, just show them anyway + // The CSS will handle truncation of the leaf breadcrumb + if (visibleItemsWidthTotal > availableWidth && currentVisibleItems.length === 1) { + // Keep the current configuration - CSS will handle truncation } - return + } + + return { + visibleItems: currentVisibleItems, + menuItems: currentMenuItems, + effectiveHideRoot, } } - // No overflow needed - show all items - setVisibleItems(childArray) - setMenuItems([]) - }, [childArray, overflow, containerWidth, hideRoot, itemWidths]) + // Apply the overflow calculation + const result = calculateOverflow(containerWidth) + setVisibleItems(result.visibleItems) + setMenuItems(result.menuItems) + setEffectiveHideRoot(result.effectiveHideRoot) + }, [childArray, overflow, containerWidth, hideRoot, itemWidths, effectiveHideRoot]) // Determine final children to render const finalChildren = React.useMemo(() => { @@ -194,15 +263,15 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo )) - // Position menu based on hideRoot setting and visible items - if (hideRoot) { + // Position menu based on effective hideRoot setting and visible items + if (effectiveHideRoot) { // Show: [overflow menu, leaf breadcrumb] return [menuElement, ...visibleElements] } else { // Show: [root breadcrumb, overflow menu, leaf breadcrumb] return [visibleElements[0], menuElement, ...visibleElements.slice(1)] } - }, [overflow, menuItems, visibleItems, hideRoot]) + }, [overflow, menuItems, visibleItems, effectiveHideRoot]) return ( Date: Wed, 13 Aug 2025 16:53:19 +1000 Subject: [PATCH 04/54] Fix up some issues. --- .../src/Breadcrumbs/Breadcrumbs.stories.tsx | 8 +-- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 67 ++++++------------- 2 files changed, 23 insertions(+), 52 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx index f0a6451f607..f16a3e74004 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx @@ -36,9 +36,9 @@ export const OverflowMenu = () => ( Home Products Category - Subcategory - Item - Details + SubcategorySubcategorySubcategorySubcategory + ItemItemItemItemItemItemItem + DetailsDetailsDetailsDetailsDetailsDetailsDetails Current Page @@ -60,7 +60,7 @@ export const OverflowMenuShowRoot = () => ( ) export const OverflowMenuNarrowContainer = () => ( -
    +
    Home Products diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 13a8e0f9e51..9824bfb7d09 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -40,6 +40,7 @@ const BreadcrumbsMenuItem = React.forwardRef
    ) + +// Wrapper components to test that BreadcrumbsItem works when wrapped +const StyledWrapper = ({children}: {children: React.ReactNode}) => ( + {children} +) + +const ConditionalWrapper = ({children, condition}: {children: React.ReactNode; condition: boolean}) => { + return condition ? {children} : <>{children} +} + +const DataAttributeWrapper = ({children}: {children: React.ReactNode}) => ( + + {children} + +) + +export const WrappedBreadcrumbItemsWithOverflow = () => ( + + + Wrapped Home + + + Products + + + Category + + + Subcategory + + + Item + + + Details + + + Current Page + + +) From 68eba14cdeb6ae2099ceefe7a6c55e6cd4f152a7 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Fri, 15 Aug 2025 18:44:33 +1000 Subject: [PATCH 12/54] Fix for ssr and child key --- packages/react/src/Breadcrumbs/Breadcrumbs.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 2719e155e63..27f2933d57e 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -210,8 +210,8 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo // Determine final children to render const finalChildren = React.useMemo(() => { if (overflow === 'wrap' || menuItems.length === 0) { - return visibleItems.map(child => ( -
  • + return visibleItems.map((child, index) => ( +
  • {child}
  • )) @@ -249,7 +249,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo ref={containerRef} data-overflow={overflow} > - {finalChildren} + {finalChildren.length > 0 ? finalChildren : children} ) } From f4bba96e221e432729bbd5afbe7ed17262a98cc2 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Fri, 15 Aug 2025 19:31:10 +1000 Subject: [PATCH 13/54] Add IconButton --- .../src/Breadcrumbs/Breadcrumbs.module.css | 8 +++++ .../react/src/Breadcrumbs/Breadcrumbs.tsx | 31 ++++++++++--------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index 92f1c9c0ab3..9fd7ff977d1 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -64,3 +64,11 @@ text-decoration: none; } } + +.MenuButton { + display: inline-flex; + align-items: flex-end; + justify-content: center; + height: 0.75rem; + color: var(--fgColor-link); +} diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 27f2933d57e..746184126ef 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -8,8 +8,11 @@ import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../uti import {BoxWithFallback} from '../internal/components/BoxWithFallback' import {ActionMenu} from '../ActionMenu' import {ActionList} from '../ActionList' +import {IconButton} from '../Button/IconButton' +import {KebabHorizontalIcon} from '@primer/octicons-react' import {useResizeObserver} from '../hooks/useResizeObserver' import type {ResizeObserverEntry} from '../hooks/useResizeObserver' +import useLayoutEffect from '../utils/useIsomorphicLayoutEffect' const SELECTED_CLASS = 'selected' @@ -34,18 +37,18 @@ const BreadcrumbsMenuItem = React.forwardRef { return ( - + + {items.map((item, index) => { @@ -87,7 +90,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo const childArray = useMemo(() => getValidChildren(children), [children]) - useEffect(() => { + useLayoutEffect(() => { if (visibleItems.length === 0 && childArray.length > 0) { setVisibleItems(childArray) } @@ -249,7 +252,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo ref={containerRef} data-overflow={overflow} > - {finalChildren.length > 0 ? finalChildren : children} + {finalChildren} ) } From b55755cf1f78029d9e5c12361b4a2b525fde4ed3 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 18 Aug 2025 15:41:41 +1000 Subject: [PATCH 14/54] Fix for SSR --- packages/react/src/Breadcrumbs/Breadcrumbs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 746184126ef..4b817f0c533 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -104,7 +104,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo useResizeObserver(handleResize, containerRef) - useEffect(() => { + useLayoutEffect(() => { if (childArray.length > 0) { if (overflow === 'wrap') { setVisibleItems(childArray) From 9477af78021131150b2632a00bb7578710ac8e38 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 18 Aug 2025 16:29:53 +1000 Subject: [PATCH 15/54] Fix for SSR --- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 4b817f0c533..c3e065b50fe 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -1,6 +1,6 @@ import {clsx} from 'clsx' import type {To} from 'history' -import React, {useState, useRef, useCallback, useEffect, useMemo} from 'react' +import React, {useState, useRef, useCallback, useMemo} from 'react' import type {SxProp} from '../sx' import type {ComponentProps} from '../utils/types' import classes from './Breadcrumbs.module.css' @@ -12,7 +12,7 @@ import {IconButton} from '../Button/IconButton' import {KebabHorizontalIcon} from '@primer/octicons-react' import {useResizeObserver} from '../hooks/useResizeObserver' import type {ResizeObserverEntry} from '../hooks/useResizeObserver' -import useLayoutEffect from '../utils/useIsomorphicLayoutEffect' +import useIsomorphicLayoutEffect from '../utils/useIsomorphicLayoutEffect' const SELECTED_CLASS = 'selected' @@ -83,18 +83,22 @@ const getValidChildren = (children: React.ReactNode) => { function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRoot = true}: BreadcrumbsProps) { const containerRef = useRef(null) const [containerWidth, setContainerWidth] = useState(0) - const [visibleItems, setVisibleItems] = useState([]) - const [menuItems, setMenuItems] = useState([]) const [itemWidths, setItemWidths] = useState([]) const [effectiveHideRoot, setEffectiveHideRoot] = useState(hideRoot) const childArray = useMemo(() => getValidChildren(children), [children]) - useLayoutEffect(() => { - if (visibleItems.length === 0 && childArray.length > 0) { + // Initialize visibleItems based on childArray for SSR compatibility + const [visibleItems, setVisibleItems] = useState(() => childArray) + const [menuItems, setMenuItems] = useState([]) + + // Sync visibleItems when childArray changes (for when children prop updates) + useIsomorphicLayoutEffect(() => { + if (overflow === 'wrap') { setVisibleItems(childArray) + setMenuItems([]) } - }, [childArray, visibleItems.length]) + }, [childArray, overflow]) const handleResize = useCallback((entries: ResizeObserverEntry[]) => { if (entries[0]) { @@ -104,7 +108,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo useResizeObserver(handleResize, containerRef) - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { if (childArray.length > 0) { if (overflow === 'wrap') { setVisibleItems(childArray) From df9aa0f591f59271c74ac02a813225a5a0cd534b Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 18 Aug 2025 16:42:35 +1000 Subject: [PATCH 16/54] Final changes for SSR --- packages/react/src/Breadcrumbs/Breadcrumbs.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index c3e065b50fe..d2efd1c2ad2 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -1,6 +1,6 @@ import {clsx} from 'clsx' import type {To} from 'history' -import React, {useState, useRef, useCallback, useMemo} from 'react' +import React, {useState, useRef, useCallback, useEffect, useMemo} from 'react' import type {SxProp} from '../sx' import type {ComponentProps} from '../utils/types' import classes from './Breadcrumbs.module.css' @@ -12,7 +12,6 @@ import {IconButton} from '../Button/IconButton' import {KebabHorizontalIcon} from '@primer/octicons-react' import {useResizeObserver} from '../hooks/useResizeObserver' import type {ResizeObserverEntry} from '../hooks/useResizeObserver' -import useIsomorphicLayoutEffect from '../utils/useIsomorphicLayoutEffect' const SELECTED_CLASS = 'selected' @@ -92,8 +91,12 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo const [visibleItems, setVisibleItems] = useState(() => childArray) const [menuItems, setMenuItems] = useState([]) + // SSR friendly + if (typeof window !== 'undefined') { + overflow = 'wrap' + } // Sync visibleItems when childArray changes (for when children prop updates) - useIsomorphicLayoutEffect(() => { + useEffect(() => { if (overflow === 'wrap') { setVisibleItems(childArray) setMenuItems([]) @@ -108,7 +111,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo useResizeObserver(handleResize, containerRef) - useIsomorphicLayoutEffect(() => { + useEffect(() => { if (childArray.length > 0) { if (overflow === 'wrap') { setVisibleItems(childArray) From fcd8a8e959841ab5826663fec91092e3439ae76b Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Tue, 19 Aug 2025 21:14:10 +1000 Subject: [PATCH 17/54] Rework calculations --- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 214 ++++++++---------- 1 file changed, 97 insertions(+), 117 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index d2efd1c2ad2..1bed67f31f7 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -81,174 +81,154 @@ const getValidChildren = (children: React.ReactNode) => { function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRoot = true}: BreadcrumbsProps) { const containerRef = useRef(null) - const [containerWidth, setContainerWidth] = useState(0) - const [itemWidths, setItemWidths] = useState([]) const [effectiveHideRoot, setEffectiveHideRoot] = useState(hideRoot) + let effectiveOverflow = 'wrap' const childArray = useMemo(() => getValidChildren(children), [children]) - // Initialize visibleItems based on childArray for SSR compatibility + const rootItem = childArray[0] + const [visibleItems, setVisibleItems] = useState(() => childArray) + const [childArrayWidths, setChildArrayWidths] = useState(() => []) + const [menuItems, setMenuItems] = useState([]) + const [rootItemWidth, setRootItemWidth] = useState(0) // SSR friendly if (typeof window !== 'undefined') { - overflow = 'wrap' + effectiveOverflow = overflow } - // Sync visibleItems when childArray changes (for when children prop updates) - useEffect(() => { - if (overflow === 'wrap') { - setVisibleItems(childArray) - setMenuItems([]) - } - }, [childArray, overflow]) + const MIN_VISIBLE_ITEMS = !effectiveHideRoot ? 3 : 4 - const handleResize = useCallback((entries: ResizeObserverEntry[]) => { - if (entries[0]) { - setContainerWidth(entries[0].contentRect.width) + useEffect(() => { + const listElement = containerRef.current?.querySelector('ol') + if (listElement && listElement.children.length > 0) { + const listElementArray = Array.from(listElement.children) as HTMLElement[] + const widths = listElementArray.map(child => child.offsetWidth) + setChildArrayWidths(widths) + setRootItemWidth(listElementArray[0].offsetWidth) } - }, []) + }, [childArray.length]) - useResizeObserver(handleResize, containerRef) + const calculateOverflow = useCallback( + (availableWidth: number) => { + const MENU_BUTTON_WIDTH = 50 // Approximate width of "..." button - useEffect(() => { - if (childArray.length > 0) { - if (overflow === 'wrap') { - setVisibleItems(childArray) - setMenuItems([]) - setEffectiveHideRoot(hideRoot) - return + const calculateVisibleItemsWidth = (w: number[]) => { + const widths = w.reduce((sum, width) => sum + width + 16, 0) + return !effectiveHideRoot ? rootItemWidth + widths : widths } - // For 'menu' overflow mode - // Helper function to calculate visible items and menu items with progressive hiding - const calculateOverflow = (availableWidth: number) => { - const listElement = containerRef.current?.querySelector('ol') - if (listElement && listElement.children.length > 0 && itemWidths.length === 0) { - const widths = Array.from(listElement.children).map(child => (child as HTMLElement).offsetWidth) - setItemWidths(widths) - } - const MENU_BUTTON_WIDTH = 50 // Approximate width of "..." button - - // Helper function to calculate total width of visible items - const calculateVisibleItemsWidth = (items: React.ReactElement[]) => { - return items - .map((item, index) => { - return itemWidths[index] - }) - .reduce((sum, width) => sum + width, 0) - } + let currentVisibleItems = [...childArray] + let currentVisibleItemWidths = [...childArrayWidths] + let currentMenuItems: React.ReactElement[] = [] + let currentMenuItemsWidths: number[] = [] + let eHideRoot = effectiveHideRoot - let currentVisibleItems = [...childArray] - let currentMenuItems: React.ReactElement[] = [] + if (availableWidth > 0 && currentVisibleItemWidths.length > 0) { + let visibleItemsWidthTotal = calculateVisibleItemsWidth(currentVisibleItemWidths) - // If more than 5 items, start by reducing to 5 visible items (including menu) - if (childArray.length > 5) { - // Target: 4 visible items + 1 menu = 5 total - const itemsToHide = childArray.slice(0, childArray.length - 4) - currentMenuItems = itemsToHide - currentVisibleItems = childArray.slice(childArray.length - 4) + // Add menu button width if we have hidden items + if (currentMenuItems.length > 0) { + visibleItemsWidthTotal += MENU_BUTTON_WIDTH } - let eHideRoot = hideRoot - // Now check if current visible items fit in available width - if (availableWidth > 0) { - let visibleItemsWidthTotal = calculateVisibleItemsWidth(currentVisibleItems) - - // Add menu button width if we have hidden items + while ( + overflow === 'menu' && + (visibleItemsWidthTotal > availableWidth || currentVisibleItems.length > MIN_VISIBLE_ITEMS) + ) { + // Remove the last visible item + const itemToHide = currentVisibleItems.slice(0)[0] + const itemToHideWidth = currentVisibleItemWidths.slice(0)[0] + currentMenuItems = [...currentMenuItems, itemToHide] + currentMenuItemsWidths = [...currentMenuItemsWidths, itemToHideWidth] + currentVisibleItems = [...currentVisibleItems.slice(1)] + currentVisibleItemWidths = [...currentVisibleItemWidths.slice(1)] + + visibleItemsWidthTotal = calculateVisibleItemsWidth(currentVisibleItemWidths) + + // Add menu button width if (currentMenuItems.length > 0) { visibleItemsWidthTotal += MENU_BUTTON_WIDTH } - while (visibleItemsWidthTotal > availableWidth && currentVisibleItems.length > 1) { - // Determine which item to hide based on hideRoot setting - let itemToHide: React.ReactElement - - if (eHideRoot) { - // Hide from start when hideRoot is true - itemToHide = currentVisibleItems[0] - currentVisibleItems = currentVisibleItems.slice(1) - } else { - // Try to hide second item (keep root and leaf) when hideRoot is false - itemToHide = currentVisibleItems[1] - currentVisibleItems = [currentVisibleItems[0], ...currentVisibleItems.slice(2)] - } - - currentMenuItems = [itemToHide, ...currentMenuItems] - - visibleItemsWidthTotal = calculateVisibleItemsWidth(currentVisibleItems) - - // Add menu button width - if (currentMenuItems.length > 0) { - visibleItemsWidthTotal += MENU_BUTTON_WIDTH - } - - // If hideRoot is false but we still don't fit with root + menu + leaf, - // fallback to hideRoot=true behavior (menu + leaf only) - if ( - !hideRoot && - !eHideRoot && - currentVisibleItems.length === 2 && - visibleItemsWidthTotal > availableWidth - ) { - eHideRoot = true - const rootItem = currentVisibleItems[0] - currentVisibleItems = currentVisibleItems.slice(1) - currentMenuItems = [rootItem, ...currentMenuItems] - - visibleItemsWidthTotal = calculateVisibleItemsWidth(currentVisibleItems) - - if (currentMenuItems.length > 0) { - visibleItemsWidthTotal += MENU_BUTTON_WIDTH - } - } + // If hideRoot is false but we still don't fit with root + menu + leaf, + // fallback to hideRoot=true behavior (menu + leaf only) + if (!hideRoot && currentVisibleItems.length === 1 && visibleItemsWidthTotal > availableWidth) { + eHideRoot = true + break + } else { + eHideRoot = hideRoot } } - return { - visibleItems: currentVisibleItems, - menuItems: currentMenuItems, - effectiveHideRoot: eHideRoot, - } } + return { + visibleItems: [...currentVisibleItems], + menuItems: [...currentMenuItems], + effectiveHideRoot: eHideRoot, + } + }, + [MIN_VISIBLE_ITEMS, childArray, childArrayWidths, effectiveHideRoot, hideRoot, overflow, rootItemWidth], + ) - const result = calculateOverflow(containerWidth) - setVisibleItems(result.visibleItems) - setMenuItems(result.menuItems) - setEffectiveHideRoot(result.effectiveHideRoot) - } - }, [childArray, overflow, containerWidth, hideRoot, itemWidths]) + const handleResize = useCallback( + (entries: ResizeObserverEntry[]) => { + if (entries[0]) { + const containerWidth = entries[0].contentRect.width + const result = calculateOverflow(containerWidth) + setVisibleItems(result.visibleItems) + setMenuItems(result.menuItems) + setEffectiveHideRoot(result.effectiveHideRoot) + } + }, + [calculateOverflow], + ) + + useResizeObserver(handleResize, containerRef) // Determine final children to render const finalChildren = React.useMemo(() => { - if (overflow === 'wrap' || menuItems.length === 0) { + if (effectiveOverflow === 'wrap' || menuItems.length === 0) { return visibleItems.map((child, index) => ( -
  • +
  • {child}
  • )) } - // Create menu item and combine with visible items + let effectiveMenuItems = [...menuItems] + if (!effectiveHideRoot) { + effectiveMenuItems = [...menuItems.slice(1)] + } const menuElement = (
  • - +
  • ) - const visibleElements = visibleItems.map(child => ( -
  • + const visibleElements = visibleItems.map((child, index) => ( +
  • {child}
  • )) + const rootElement = ( +
  • + {rootItem} +
  • + ) + // Position menu based on effective hideRoot setting and visible items if (effectiveHideRoot) { // Show: [overflow menu, leaf breadcrumb] return [menuElement, ...visibleElements] } else { // Show: [root breadcrumb, overflow menu, leaf breadcrumb] - return [visibleElements[0], menuElement, ...visibleElements.slice(1)] + return [rootElement, menuElement, ...visibleElements] } - }, [overflow, menuItems, visibleItems, effectiveHideRoot]) + }, [effectiveOverflow, menuItems, visibleItems, rootItem, effectiveHideRoot]) return ( {finalChildren} From 0a09e45cc1e57783f0795c7eaa27322a7232d659 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Tue, 19 Aug 2025 21:54:38 +1000 Subject: [PATCH 18/54] Tests are passing --- packages/react/src/Breadcrumbs/Breadcrumbs.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 1bed67f31f7..bd2212d9713 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -83,7 +83,6 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo const containerRef = useRef(null) const [effectiveHideRoot, setEffectiveHideRoot] = useState(hideRoot) let effectiveOverflow = 'wrap' - const childArray = useMemo(() => getValidChildren(children), [children]) const rootItem = childArray[0] @@ -94,7 +93,6 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo const [menuItems, setMenuItems] = useState([]) const [rootItemWidth, setRootItemWidth] = useState(0) - // SSR friendly if (typeof window !== 'undefined') { effectiveOverflow = overflow } @@ -185,6 +183,18 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo useResizeObserver(handleResize, containerRef) + // Initial overflow calculation for testing and >5 items + useEffect(() => { + if (overflow === 'menu' && childArray.length > 5) { + // Get actual container width from DOM or use default + const containerWidth = containerRef.current?.offsetWidth || 800 + const result = calculateOverflow(containerWidth) + setVisibleItems(result.visibleItems) + setMenuItems(result.menuItems) + setEffectiveHideRoot(result.effectiveHideRoot) + } + }, [overflow, childArray.length, calculateOverflow]) + // Determine final children to render const finalChildren = React.useMemo(() => { if (effectiveOverflow === 'wrap' || menuItems.length === 0) { From 3b9891a0e5525163b792c91bab89fc2cf1feba70 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Thu, 21 Aug 2025 16:18:02 +1000 Subject: [PATCH 19/54] Small fixes to menu button --- .../Breadcrumbs.features.stories.tsx | 13 +++-- .../src/Breadcrumbs/Breadcrumbs.module.css | 17 ++++--- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 51 ++++++++++++++----- 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx index 872448a2809..0510633a974 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx @@ -38,14 +38,13 @@ export const OverflowMenu = () => ( export const OverflowMenuShowRoot = () => ( - Home - Products - Category - Subcategory - Item - Details + github + Teams + Engineering + core-productivity + collaboration-workflows-flex - Current Page + global-navigation-reviewers ) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index 9fd7ff977d1..277300822ab 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -13,9 +13,17 @@ [data-overflow='menu'] .BreadcrumbsList { white-space: nowrap; overflow: hidden; + display: flex; + flex-direction: row; +} + +[data-overflow='menu'] .BreadcrumbsItem { + display: inline-grid; + grid-auto-flow: column; + align-items: center; } -.ItemWrapper { +.BreadcrumbsItem { display: inline-block; font-size: var(--text-body-size-medium); white-space: nowrap; @@ -25,7 +33,7 @@ display: inline-block; height: 0.8em; /* stylelint-disable-next-line primer/spacing */ - margin: 0 0.5em; + margin: 0.25em 0.5em 0 0.5em; font-size: var(--text-body-size-medium); content: ''; /* stylelint-disable-next-line primer/borders, primer/colors */ @@ -66,9 +74,6 @@ } .MenuButton { - display: inline-flex; - align-items: flex-end; - justify-content: center; - height: 0.75rem; color: var(--fgColor-link); + padding-top: 4px; } diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index bd2212d9713..081fcfc9382 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -81,8 +81,9 @@ const getValidChildren = (children: React.ReactNode) => { function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRoot = true}: BreadcrumbsProps) { const containerRef = useRef(null) + const menuButtonRef = useRef(null) const [effectiveHideRoot, setEffectiveHideRoot] = useState(hideRoot) - let effectiveOverflow = 'wrap' + //let effectiveOverflow = 'wrap' const childArray = useMemo(() => getValidChildren(children), [children]) const rootItem = childArray[0] @@ -93,9 +94,13 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo const [menuItems, setMenuItems] = useState([]) const [rootItemWidth, setRootItemWidth] = useState(0) - if (typeof window !== 'undefined') { - effectiveOverflow = overflow - } + // Menu button width with fallback + const MENU_BUTTON_FALLBACK_WIDTH = 32 // Design system small IconButton + const [menuButtonWidth, setMenuButtonWidth] = useState(MENU_BUTTON_FALLBACK_WIDTH) + + // if (typeof window !== 'undefined') { + // effectiveOverflow = overflow + // } const MIN_VISIBLE_ITEMS = !effectiveHideRoot ? 3 : 4 useEffect(() => { @@ -108,9 +113,19 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo } }, [childArray.length]) + // Measure actual menu button width when it exists + useEffect(() => { + if (menuButtonRef.current) { + const measuredWidth = menuButtonRef.current.offsetWidth + if (measuredWidth > 0) { + setMenuButtonWidth(measuredWidth) + } + } + }, [menuItems.length]) // Re-measure when menu button appears/disappears + const calculateOverflow = useCallback( (availableWidth: number) => { - const MENU_BUTTON_WIDTH = 50 // Approximate width of "..." button + const MENU_BUTTON_WIDTH = menuButtonWidth // Use measured width with fallback const calculateVisibleItemsWidth = (w: number[]) => { const widths = w.reduce((sum, width) => sum + width + 16, 0) @@ -165,7 +180,16 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo effectiveHideRoot: eHideRoot, } }, - [MIN_VISIBLE_ITEMS, childArray, childArrayWidths, effectiveHideRoot, hideRoot, overflow, rootItemWidth], + [ + MIN_VISIBLE_ITEMS, + childArray, + childArrayWidths, + effectiveHideRoot, + hideRoot, + overflow, + rootItemWidth, + menuButtonWidth, + ], ) const handleResize = useCallback( @@ -197,9 +221,9 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo // Determine final children to render const finalChildren = React.useMemo(() => { - if (effectiveOverflow === 'wrap' || menuItems.length === 0) { + if (overflow === 'wrap' || menuItems.length === 0) { return visibleItems.map((child, index) => ( -
  • +
  • {child}
  • )) @@ -210,8 +234,9 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo effectiveMenuItems = [...menuItems.slice(1)] } const menuElement = ( -
  • +
  • @@ -219,13 +244,13 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo ) const visibleElements = visibleItems.map((child, index) => ( -
  • +
  • {child}
  • )) const rootElement = ( -
  • +
  • {rootItem}
  • ) @@ -238,7 +263,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo // Show: [root breadcrumb, overflow menu, leaf breadcrumb] return [rootElement, menuElement, ...visibleElements] } - }, [effectiveOverflow, menuItems, visibleItems, rootItem, effectiveHideRoot]) + }, [overflow, menuItems, effectiveHideRoot, visibleItems, rootItem]) return ( {finalChildren} From 273a0551ca3ea08da4d4c4dc8d940c6cfee2d276 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Thu, 21 Aug 2025 22:32:34 +1000 Subject: [PATCH 20/54] Fix styling issues with button --- .../src/Breadcrumbs/Breadcrumbs.module.css | 34 ++++++++----------- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 13 +++++++ 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index 277300822ab..94914f387a5 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -17,37 +17,23 @@ flex-direction: row; } -[data-overflow='menu'] .BreadcrumbsItem { +.BreadcrumbsItem { display: inline-grid; grid-auto-flow: column; align-items: center; -} - -.BreadcrumbsItem { - display: inline-block; + flex: 0 99999 auto; + min-width: auto; font-size: var(--text-body-size-medium); white-space: nowrap; list-style: none; - &::after { - display: inline-block; - height: 0.8em; - /* stylelint-disable-next-line primer/spacing */ - margin: 0.25em 0.5em 0 0.5em; - font-size: var(--text-body-size-medium); - content: ''; - /* stylelint-disable-next-line primer/borders, primer/colors */ - border-right: 0.1em solid var(--fgColor-muted); - transform: rotate(15deg) translateY(0.0625em); - } - &:first-child { margin-left: 0; } &:last-child { - &::after { - content: none; + .ItemSeparator { + display: none; } } } @@ -75,5 +61,13 @@ .MenuButton { color: var(--fgColor-link); - padding-top: 4px; +} + +.ItemSeparator { + color: var(--fgColor-muted); + display: flex; + align-self: center; + justify-content: center; + white-space: nowrap; + user-select: none; } diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 081fcfc9382..ce8569a055c 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -240,18 +240,21 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo items={effectiveMenuItems} aria-label={`${effectiveMenuItems.length} more breadcrumb items`} /> + ) const visibleElements = visibleItems.map((child, index) => (
  • {child} +
  • )) const rootElement = (
  • {rootItem} +
  • ) @@ -279,6 +282,16 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo ) } +const ItemSeparator = () => { + return ( + + + + ) +} + type StyledBreadcrumbsItemProps = { to?: To selected?: boolean From 08637ae6a1b9c63cf16002f16436ed8af1af32c0 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Fri, 22 Aug 2025 15:57:20 +1000 Subject: [PATCH 21/54] Fix focus states --- .../src/Breadcrumbs/Breadcrumbs.module.css | 18 ++++++------------ packages/react/src/Breadcrumbs/Breadcrumbs.tsx | 2 -- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index 94914f387a5..845f02ff394 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -1,6 +1,7 @@ .BreadcrumbsBase { display: flex; justify-content: space-between; + width: 100%; } .BreadcrumbsList { @@ -43,19 +44,12 @@ font-size: var(--text-body-size-medium); color: var(--fgColor-link); text-decoration: none; + padding-inline: var(--base-size-6); + padding-block: var(--base-size-4); + border-radius: var(--borderRadius-medium); - &:hover, - &:focus { - text-decoration: underline; - } -} - -.ItemSelected { - color: var(--fgColor-default); - pointer-events: none; - - &:focus { - text-decoration: none; + &:hover { + background: var(--control-transparent-bgColor-hover); } } diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index ce8569a055c..f468cdfaf65 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -60,7 +60,6 @@ const BreadcrumbsMenuItem = React.forwardRef {children} @@ -306,7 +305,6 @@ const BreadcrumbsItem = React.forwardRef(({selected, className, ...rest}, ref) = as="a" className={clsx(className, classes.Item, { [SELECTED_CLASS]: selected, - [classes.ItemSelected]: selected, })} aria-current={selected ? 'page' : undefined} ref={ref} From 17a31075eea03db028a9fd8e472ab957e8f77151 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Fri, 22 Aug 2025 15:59:25 +1000 Subject: [PATCH 22/54] Fix focus states --- packages/react/src/Breadcrumbs/Breadcrumbs.module.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index 845f02ff394..1b5803276bd 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -51,6 +51,11 @@ &:hover { background: var(--control-transparent-bgColor-hover); } + &:focus { + box-shadow: none; + outline: 2px solid var(--focus-outlineColor, var(--color-accent-fg)); + outline-offset: -2px; + } } .MenuButton { From ed838f3abab8fbd439a977df26668ca6d2ed4281 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 25 Aug 2025 21:57:13 +1000 Subject: [PATCH 23/54] Make sure old behavior works --- .../src/Breadcrumbs/Breadcrumbs.module.css | 53 ++++++++++++++++++- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 9 ++-- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index 1b5803276bd..d5084c0d65e 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -10,7 +10,6 @@ margin-bottom: 0; } -/* Prevent wrapping when using menu overflow */ [data-overflow='menu'] .BreadcrumbsList { white-space: nowrap; overflow: hidden; @@ -39,7 +38,7 @@ } } -.Item { +[data-overflow='menu'] .Item { display: inline-block; font-size: var(--text-body-size-medium); color: var(--fgColor-link); @@ -70,3 +69,53 @@ white-space: nowrap; user-select: none; } + +.ItemWrapper { + display: inline-block; + font-size: var(--text-body-size-medium); + white-space: nowrap; + list-style: none; + + &::after { + display: inline-block; + height: 0.8em; + /* stylelint-disable-next-line primer/spacing */ + margin: 0 0.5em; + font-size: var(--text-body-size-medium); + content: ''; + /* stylelint-disable-next-line primer/borders, primer/colors */ + border-right: 0.1em solid var(--fgColor-muted); + transform: rotate(15deg) translateY(0.0625em); + } + + &:first-child { + margin-left: 0; + } + + &:last-child { + &::after { + content: none; + } + } +} + +.Item { + display: inline-block; + font-size: var(--text-body-size-medium); + color: var(--fgColor-link); + text-decoration: none; + + &:hover, + &:focus { + text-decoration: underline; + } +} + +.ItemSelected { + color: var(--fgColor-default); + pointer-events: none; + + &:focus { + text-decoration: none; + } +} diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index f468cdfaf65..2f0396b4235 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -221,11 +221,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo // Determine final children to render const finalChildren = React.useMemo(() => { if (overflow === 'wrap' || menuItems.length === 0) { - return visibleItems.map((child, index) => ( -
  • - {child} -
  • - )) + return React.Children.map(children, child =>
  • {child}
  • ) } let effectiveMenuItems = [...menuItems] @@ -265,7 +261,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo // Show: [root breadcrumb, overflow menu, leaf breadcrumb] return [rootElement, menuElement, ...visibleElements] } - }, [overflow, menuItems, effectiveHideRoot, visibleItems, rootItem]) + }, [overflow, menuItems, effectiveHideRoot, visibleItems, rootItem, children]) return ( Date: Mon, 25 Aug 2025 22:56:33 +1000 Subject: [PATCH 24/54] Fix tests and lint --- packages/react/src/Breadcrumbs/Breadcrumbs.module.css | 1 + packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index d5084c0d65e..31bc52059a5 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -50,6 +50,7 @@ &:hover { background: var(--control-transparent-bgColor-hover); } + &:focus { box-shadow: none; outline: 2px solid var(--focus-outlineColor, var(--color-accent-fg)); diff --git a/packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx b/packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx index 41bbf99cbe7..3a9d8d7c063 100644 --- a/packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx +++ b/packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx @@ -276,10 +276,10 @@ describe('Breadcrumbs', () => { expect(screen.getByRole('menuitem', {name: 'Home'})).toBeInTheDocument() expect(screen.getByRole('menuitem', {name: 'Category'})).toBeInTheDocument() expect(screen.getByRole('menuitem', {name: 'Subcategory'})).toBeInTheDocument() + expect(screen.getByRole('menuitem', {name: 'Product'})).toBeInTheDocument() }) // Verify that the last 4 items are visible as regular breadcrumb items (not in menu) - expect(screen.getByRole('link', {name: 'Product'})).toBeInTheDocument() expect(screen.getByRole('link', {name: 'Details'})).toBeInTheDocument() expect(screen.getByRole('link', {name: 'Specifications'})).toBeInTheDocument() expect(screen.getByRole('link', {name: 'Reviews'})).toBeInTheDocument() @@ -322,10 +322,10 @@ describe('Breadcrumbs', () => { expect(screen.getByRole('menuitem', {name: 'Home'})).toBeInTheDocument() expect(screen.getByRole('menuitem', {name: 'Category'})).toBeInTheDocument() expect(screen.getByRole('menuitem', {name: 'Subcategory'})).toBeInTheDocument() + expect(screen.getByRole('menuitem', {name: 'Product'})).toBeInTheDocument() }) // Verify visible breadcrumb items are still accessible (last 4 items) - expect(screen.getByRole('link', {name: 'Product'})).toBeInTheDocument() expect(screen.getByRole('link', {name: 'Details'})).toBeInTheDocument() expect(screen.getByRole('link', {name: 'Specifications'})).toBeInTheDocument() expect(screen.getByRole('link', {name: 'Reviews'})).toBeInTheDocument() From 0eaf106ae4f451a1ad8b4793f61310fae378db7c Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Tue, 26 Aug 2025 09:11:50 +1000 Subject: [PATCH 25/54] Create eighty-queens-tap.md --- .changeset/eighty-queens-tap.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/eighty-queens-tap.md diff --git a/.changeset/eighty-queens-tap.md b/.changeset/eighty-queens-tap.md new file mode 100644 index 00000000000..6d5648d6aae --- /dev/null +++ b/.changeset/eighty-queens-tap.md @@ -0,0 +1,5 @@ +--- +"@primer/react": patch +--- + +Breadcrumbs : Add overflow menu for responsive behavior From 40cb6a9276ee73ef6feee0328d26f5653b2a1cd8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 12:10:45 -0500 Subject: [PATCH 26/54] chore(deps-dev): bump the eslint group with 3 updates (#6657) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 28 ++++++++++++++-------------- package.json | 6 +++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 74f9f0d59b6..e357144c797 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@eslint-react/eslint-plugin": "^1.52.6", "@eslint/compat": "^1.3.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.33.0", + "@eslint/js": "^9.34.0", "@github/axe-github": "0.7.0", "@github/markdownlint-github": "^0.7.0", "@github/mini-throttle": "2.1.1", @@ -32,7 +32,7 @@ "@size-limit/preset-big-lib": "11.2.0", "@vitest/browser": "^3.2.4", "@vitest/eslint-plugin": "^1.3.4", - "eslint": "^9.33.0", + "eslint": "^9.34.0", "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-clsx": "^0.0.10", "eslint-plugin-github": "^6.0.0", @@ -45,7 +45,7 @@ "eslint-plugin-react-compiler": "^19.1.0-rc.2", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-ssr-friendly": "1.3.0", - "eslint-plugin-storybook": "^9.1.2", + "eslint-plugin-storybook": "^9.1.3", "eslint-plugin-testing-library": "^7.6.6", "globals": "^16.2.0", "markdownlint-cli2": "^0.17.2", @@ -3745,9 +3745,9 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", - "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", + "version": "9.34.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz", + "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==", "dev": true, "license": "MIT", "engines": { @@ -11514,9 +11514,9 @@ } }, "node_modules/eslint": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", - "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", + "version": "9.34.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz", + "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", "dev": true, "license": "MIT", "dependencies": { @@ -11526,7 +11526,7 @@ "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.33.0", + "@eslint/js": "9.34.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -12587,9 +12587,9 @@ } }, "node_modules/eslint-plugin-storybook": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-9.1.2.tgz", - "integrity": "sha512-EQa/kChrYrekxv36q3pvW57anqxMlAP4EdPXEDyA/EDrCQJaaTbWEdsMnVZtD744RjPP0M5wzaUjHbMhNooAwQ==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-9.1.3.tgz", + "integrity": "sha512-CR576JrlvxLY2ebJIyR6z/YWy6+iyVsB7ORjPrwM3a9SshlRnAntdEn6hyMYbQmFoPIv7kYcRiDznDXBQ/jitA==", "dev": true, "license": "MIT", "dependencies": { @@ -12600,7 +12600,7 @@ }, "peerDependencies": { "eslint": ">=8", - "storybook": "^9.1.2" + "storybook": "^9.1.3" } }, "node_modules/eslint-plugin-testing-library": { diff --git a/package.json b/package.json index 2906294b9f1..2a821d166ad 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@eslint-react/eslint-plugin": "^1.52.6", "@eslint/compat": "^1.3.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.33.0", + "@eslint/js": "^9.34.0", "@github/axe-github": "0.7.0", "@github/markdownlint-github": "^0.7.0", "@github/mini-throttle": "2.1.1", @@ -60,7 +60,7 @@ "@size-limit/preset-big-lib": "11.2.0", "@vitest/browser": "^3.2.4", "@vitest/eslint-plugin": "^1.3.4", - "eslint": "^9.33.0", + "eslint": "^9.34.0", "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-clsx": "^0.0.10", "eslint-plugin-github": "^6.0.0", @@ -73,7 +73,7 @@ "eslint-plugin-react-compiler": "^19.1.0-rc.2", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-ssr-friendly": "1.3.0", - "eslint-plugin-storybook": "^9.1.2", + "eslint-plugin-storybook": "^9.1.3", "eslint-plugin-testing-library": "^7.6.6", "globals": "^16.2.0", "markdownlint-cli2": "^0.17.2", From 027f4f60c8c8d8b7e03d3da9bc650ddea332bd68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 17:11:25 +0000 Subject: [PATCH 27/54] chore(deps): bump the rollup group with 2 updates (#6659) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 184 ++++++++++++++--------------- package.json | 2 +- packages/mcp/package.json | 2 +- packages/react/package.json | 2 +- packages/styled-react/package.json | 2 +- 5 files changed, 91 insertions(+), 101 deletions(-) diff --git a/package-lock.json b/package-lock.json index e357144c797..ddc061fdfac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ "npm": ">=7" }, "optionalDependencies": { - "@rollup/rollup-linux-x64-gnu": "^4.46.2" + "@rollup/rollup-linux-x64-gnu": "^4.48.0" } }, "examples/codesandbox": { @@ -6832,9 +6832,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.48.0.tgz", + "integrity": "sha512-aVzKH922ogVAWkKiyKXorjYymz2084zrhrZRXtLrA5eEx5SO8Dj0c/4FpCHZyn7MKzhW2pW4tK28vVr+5oQ2xw==", "cpu": [ "arm" ], @@ -6846,9 +6846,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.48.0.tgz", + "integrity": "sha512-diOdQuw43xTa1RddAFbhIA8toirSzFMcnIg8kvlzRbK26xqEnKJ/vqQnghTAajy2Dcy42v+GMPMo6jq67od+Dw==", "cpu": [ "arm64" ], @@ -6860,7 +6860,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.48.0.tgz", + "integrity": "sha512-QhR2KA18fPlJWFefySJPDYZELaVqIUVnYgAOdtJ+B/uH96CFg2l1TQpX19XpUMWUqMyIiyY45wje8K6F4w4/CA==", "cpu": [ "arm64" ], @@ -6872,9 +6874,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.48.0.tgz", + "integrity": "sha512-Q9RMXnQVJ5S1SYpNSTwXDpoQLgJ/fbInWOyjbCnnqTElEyeNvLAB3QvG5xmMQMhFN74bB5ZZJYkKaFPcOG8sGg==", "cpu": [ "x64" ], @@ -6886,9 +6888,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.48.0.tgz", + "integrity": "sha512-3jzOhHWM8O8PSfyft+ghXZfBkZawQA0PUGtadKYxFqpcYlOYjTi06WsnYBsbMHLawr+4uWirLlbhcYLHDXR16w==", "cpu": [ "arm64" ], @@ -6900,9 +6902,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.48.0.tgz", + "integrity": "sha512-NcD5uVUmE73C/TPJqf78hInZmiSBsDpz3iD5MF/BuB+qzm4ooF2S1HfeTChj5K4AV3y19FFPgxonsxiEpy8v/A==", "cpu": [ "x64" ], @@ -6914,9 +6916,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.48.0.tgz", + "integrity": "sha512-JWnrj8qZgLWRNHr7NbpdnrQ8kcg09EBBq8jVOjmtlB3c8C6IrynAJSMhMVGME4YfTJzIkJqvSUSVJRqkDnu/aA==", "cpu": [ "arm" ], @@ -6928,9 +6930,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.48.0.tgz", + "integrity": "sha512-9xu92F0TxuMH0tD6tG3+GtngwdgSf8Bnz+YcsPG91/r5Vgh5LNofO48jV55priA95p3c92FLmPM7CvsVlnSbGQ==", "cpu": [ "arm" ], @@ -6942,9 +6944,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.48.0.tgz", + "integrity": "sha512-NLtvJB5YpWn7jlp1rJiY0s+G1Z1IVmkDuiywiqUhh96MIraC0n7XQc2SZ1CZz14shqkM+XN2UrfIo7JB6UufOA==", "cpu": [ "arm64" ], @@ -6956,9 +6958,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.48.0.tgz", + "integrity": "sha512-QJ4hCOnz2SXgCh+HmpvZkM+0NSGcZACyYS8DGbWn2PbmA0e5xUk4bIP8eqJyNXLtyB4gZ3/XyvKtQ1IFH671vQ==", "cpu": [ "arm64" ], @@ -6970,9 +6972,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.48.0.tgz", + "integrity": "sha512-Pk0qlGJnhILdIC5zSKQnprFjrGmjfDM7TPZ0FKJxRkoo+kgMRAg4ps1VlTZf8u2vohSicLg7NP+cA5qE96PaFg==", "cpu": [ "loong64" ], @@ -6984,9 +6986,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.48.0.tgz", + "integrity": "sha512-/dNFc6rTpoOzgp5GKoYjT6uLo8okR/Chi2ECOmCZiS4oqh3mc95pThWma7Bgyk6/WTEvjDINpiBCuecPLOgBLQ==", "cpu": [ "ppc64" ], @@ -6998,9 +7000,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.48.0.tgz", + "integrity": "sha512-YBwXsvsFI8CVA4ej+bJF2d9uAeIiSkqKSPQNn0Wyh4eMDY4wxuSp71BauPjQNCKK2tD2/ksJ7uhJ8X/PVY9bHQ==", "cpu": [ "riscv64" ], @@ -7012,9 +7014,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.48.0.tgz", + "integrity": "sha512-FI3Rr2aGAtl1aHzbkBIamsQyuauYtTF9SDUJ8n2wMXuuxwchC3QkumZa1TEXYIv/1AUp1a25Kwy6ONArvnyeVQ==", "cpu": [ "riscv64" ], @@ -7026,9 +7028,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.48.0.tgz", + "integrity": "sha512-Dx7qH0/rvNNFmCcIRe1pyQ9/H0XO4v/f0SDoafwRYwc2J7bJZ5N4CHL/cdjamISZ5Cgnon6iazAVRFlxSoHQnQ==", "cpu": [ "s390x" ], @@ -7040,9 +7042,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.1.tgz", - "integrity": "sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.48.0.tgz", + "integrity": "sha512-GUdZKTeKBq9WmEBzvFYuC88yk26vT66lQV8D5+9TgkfbewhLaTHRNATyzpQwwbHIfJvDJ3N9WJ90wK/uR3cy3Q==", "cpu": [ "x64" ], @@ -7053,9 +7055,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.48.0.tgz", + "integrity": "sha512-ao58Adz/v14MWpQgYAb4a4h3fdw73DrDGtaiF7Opds5wNyEQwtO6M9dBh89nke0yoZzzaegq6J/EXs7eBebG8A==", "cpu": [ "x64" ], @@ -7067,9 +7069,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.48.0.tgz", + "integrity": "sha512-kpFno46bHtjZVdRIOxqaGeiABiToo2J+st7Yce+aiAoo1H0xPi2keyQIP04n2JjDVuxBN6bSz9R6RdTK5hIppw==", "cpu": [ "arm64" ], @@ -7081,9 +7083,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.48.0.tgz", + "integrity": "sha512-rFYrk4lLk9YUTIeihnQMiwMr6gDhGGSbWThPEDfBoU/HdAtOzPXeexKi7yU8jO+LWRKnmqPN9NviHQf6GDwBcQ==", "cpu": [ "ia32" ], @@ -7095,9 +7097,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.48.0.tgz", + "integrity": "sha512-sq0hHLTgdtwOPDB5SJOuaoHyiP1qSwg+71TQWk8iDS04bW1wIE0oQ6otPiRj2ZvLYNASLMaTp8QRGUVZ+5OL5A==", "cpu": [ "x64" ], @@ -20211,7 +20213,9 @@ } }, "node_modules/rollup": { - "version": "4.46.2", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.0.tgz", + "integrity": "sha512-BXHRqK1vyt9XVSEHZ9y7xdYtuYbwVod2mLwOMFP7t/Eqoc1pHRlG/WdV2qNeNvZHRQdLedaFycljaYYM96RqJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -20225,26 +20229,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", + "@rollup/rollup-android-arm-eabi": "4.48.0", + "@rollup/rollup-android-arm64": "4.48.0", + "@rollup/rollup-darwin-arm64": "4.48.0", + "@rollup/rollup-darwin-x64": "4.48.0", + "@rollup/rollup-freebsd-arm64": "4.48.0", + "@rollup/rollup-freebsd-x64": "4.48.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.48.0", + "@rollup/rollup-linux-arm-musleabihf": "4.48.0", + "@rollup/rollup-linux-arm64-gnu": "4.48.0", + "@rollup/rollup-linux-arm64-musl": "4.48.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.48.0", + "@rollup/rollup-linux-ppc64-gnu": "4.48.0", + "@rollup/rollup-linux-riscv64-gnu": "4.48.0", + "@rollup/rollup-linux-riscv64-musl": "4.48.0", + "@rollup/rollup-linux-s390x-gnu": "4.48.0", + "@rollup/rollup-linux-x64-gnu": "4.48.0", + "@rollup/rollup-linux-x64-musl": "4.48.0", + "@rollup/rollup-win32-arm64-msvc": "4.48.0", + "@rollup/rollup-win32-ia32-msvc": "4.48.0", + "@rollup/rollup-win32-x64-msvc": "4.48.0", "fsevents": "~2.3.2" } }, @@ -21003,20 +21007,6 @@ "dev": true, "license": "MIT" }, - "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/router": { "version": "2.2.0", "license": "MIT", @@ -25131,7 +25121,7 @@ "@rollup/plugin-node-resolve": "^16.0.1", "@types/turndown": "^5.0.5", "rimraf": "^6.0.1", - "rollup": "^4.46.2", + "rollup": "^4.48.0", "rollup-plugin-typescript2": "^0.36.0", "typescript": "^5.9.2" } @@ -25477,7 +25467,7 @@ "react-test-renderer": "18.3.1", "recast": "0.23.7", "rimraf": "5.0.5", - "rollup": "4.46.2", + "rollup": "4.48.0", "rollup-plugin-import-css": "^0.0.0", "rollup-plugin-postcss": "4.0.2", "rollup-plugin-visualizer": "6.0.3", @@ -26236,7 +26226,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "rimraf": "^6.0.1", - "rollup": "4.46.2", + "rollup": "4.48.0", "rollup-plugin-typescript2": "^0.36.0", "styled-components": "5.3.11", "typescript": "^5.9.2" diff --git a/package.json b/package.json index 2a821d166ad..d2a9192cabd 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "vitest": "^3.2.4" }, "optionalDependencies": { - "@rollup/rollup-linux-x64-gnu": "^4.46.2" + "@rollup/rollup-linux-x64-gnu": "^4.48.0" }, "overrides": { "nwsapi": "2.2.2" diff --git a/packages/mcp/package.json b/packages/mcp/package.json index d3e5d188fa8..429162e2b30 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -51,7 +51,7 @@ "@rollup/plugin-node-resolve": "^16.0.1", "@types/turndown": "^5.0.5", "rimraf": "^6.0.1", - "rollup": "^4.46.2", + "rollup": "^4.48.0", "rollup-plugin-typescript2": "^0.36.0", "typescript": "^5.9.2" } diff --git a/packages/react/package.json b/packages/react/package.json index 04a39306ed7..4381b368550 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -188,7 +188,7 @@ "react-test-renderer": "18.3.1", "recast": "0.23.7", "rimraf": "5.0.5", - "rollup": "4.46.2", + "rollup": "4.48.0", "rollup-plugin-import-css": "^0.0.0", "rollup-plugin-postcss": "4.0.2", "rollup-plugin-visualizer": "6.0.3", diff --git a/packages/styled-react/package.json b/packages/styled-react/package.json index 03eea48ba70..da90c245792 100644 --- a/packages/styled-react/package.json +++ b/packages/styled-react/package.json @@ -37,7 +37,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "rimraf": "^6.0.1", - "rollup": "4.46.2", + "rollup": "4.48.0", "rollup-plugin-typescript2": "^0.36.0", "styled-components": "5.3.11", "typescript": "^5.9.2" From 7b4921cab234ad94b5807c41755b7f6279b9bcb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 17:12:43 +0000 Subject: [PATCH 28/54] chore(deps-dev): bump postcss-mixins from 11.0.1 to 12.1.2 (#6660) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 32 ++++++++++++++------- packages/postcss-preset-primer/package.json | 2 +- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index ddc061fdfac..882be7c1c24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18576,7 +18576,9 @@ } }, "node_modules/postcss-mixins": { - "version": "11.0.1", + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/postcss-mixins/-/postcss-mixins-12.1.2.tgz", + "integrity": "sha512-90pSxmZVfbX9e5xCv7tI5RV1mnjdf16y89CJKbf/hD7GyOz1FCxcYMl8ZYA8Hc56dbApTKKmU9HfvgfWdCxlwg==", "dev": true, "funding": [ { @@ -18592,11 +18594,11 @@ "dependencies": { "postcss-js": "^4.0.1", "postcss-simple-vars": "^7.0.1", - "sugarss": "^4.0.1", - "tinyglobby": "^0.2.6" + "sugarss": "^5.0.0", + "tinyglobby": "^0.2.14" }, "engines": { - "node": "^18.0 || ^ 20.0 || >= 22.0" + "node": "^20.0 || ^22.0 || >=24.0" }, "peerDependencies": { "postcss": "^8.2.14" @@ -22568,15 +22570,23 @@ } }, "node_modules/sugarss": { - "version": "4.0.1", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-5.0.1.tgz", + "integrity": "sha512-ctS5RYCBVvPoZAnzIaX5QSShK8ZiZxD5HUqSxlusvEMC+QZQIPCPOIJg6aceFX+K2rf4+SH89eu++h1Zmsr2nw==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "node": ">=18.0" }, "peerDependencies": { "postcss": "^8.3.3" @@ -25257,7 +25267,7 @@ "cssnano": "^7.0.7", "postcss": "^8.4.41", "postcss-custom-properties-fallback": "^1.0.2", - "postcss-mixins": "^11.0.1", + "postcss-mixins": "^12.1.2", "typescript": "^5.9.2" }, "peerDependencies": { diff --git a/packages/postcss-preset-primer/package.json b/packages/postcss-preset-primer/package.json index 2117b3e29be..a0a8b99e73b 100644 --- a/packages/postcss-preset-primer/package.json +++ b/packages/postcss-preset-primer/package.json @@ -23,7 +23,7 @@ "cssnano": "^7.0.7", "postcss": "^8.4.41", "postcss-custom-properties-fallback": "^1.0.2", - "postcss-mixins": "^11.0.1", + "postcss-mixins": "^12.1.2", "typescript": "^5.9.2" } } From bc2749c5d2640b123c1628751fc257bff6e572d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 17:17:44 +0000 Subject: [PATCH 29/54] chore(deps-dev): bump @github/markdownlint-github from 0.7.0 to 0.8.0 (#6661) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 ++++-- package.json | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 882be7c1c24..79161dc63c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.34.0", "@github/axe-github": "0.7.0", - "@github/markdownlint-github": "^0.7.0", + "@github/markdownlint-github": "^0.8.0", "@github/mini-throttle": "2.1.1", "@github/prettier-config": "0.0.6", "@mdx-js/react": "1.6.22", @@ -4178,7 +4178,9 @@ "license": "MIT" }, "node_modules/@github/markdownlint-github": { - "version": "0.7.0", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@github/markdownlint-github/-/markdownlint-github-0.8.0.tgz", + "integrity": "sha512-079sWT/2Z8EI5v02GTtSfvG06E1m8Q6xjYoQiGdPg6rSKVntpfBw6in79fGs+vc9cYihBHl73vkOoDcyH/Jl8g==", "dev": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index d2a9192cabd..9ff3bfe7316 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.34.0", "@github/axe-github": "0.7.0", - "@github/markdownlint-github": "^0.7.0", + "@github/markdownlint-github": "^0.8.0", "@github/mini-throttle": "2.1.1", "@github/prettier-config": "0.0.6", "@mdx-js/react": "1.6.22", From f9c9a2d4441d7403f1ae01af21e0c8abad426209 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Mon, 25 Aug 2025 12:28:57 -0500 Subject: [PATCH 30/54] feat(mcp): add better primitives output, add coding guidelines tool (#6599) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/mcp/src/primitives.ts | 103 ++++++++ packages/mcp/src/server.ts | 460 +++------------------------------ 2 files changed, 145 insertions(+), 418 deletions(-) create mode 100644 packages/mcp/src/primitives.ts diff --git a/packages/mcp/src/primitives.ts b/packages/mcp/src/primitives.ts new file mode 100644 index 00000000000..493e6b2b0fe --- /dev/null +++ b/packages/mcp/src/primitives.ts @@ -0,0 +1,103 @@ +import baseMotion from '@primer/primitives/dist/docs/base/motion/motion.json' with {type: 'json'} +import baseSize from '@primer/primitives/dist/docs/base/size/size.json' with {type: 'json'} +import baseTypography from '@primer/primitives/dist/docs/base/typography/typography.json' with {type: 'json'} +import functionalSizeBorder from '@primer/primitives/dist/docs/functional/size/border.json' with {type: 'json'} +import functionalSizeCoarse from '@primer/primitives/dist/docs/functional/size/size-coarse.json' with {type: 'json'} +import functionalSizeFine from '@primer/primitives/dist/docs/functional/size/size-fine.json' with {type: 'json'} +import functionalSize from '@primer/primitives/dist/docs/functional/size/size.json' with {type: 'json'} +import light from '@primer/primitives/dist/docs/functional/themes/light.json' with {type: 'json'} +import functionalTypography from '@primer/primitives/dist/docs/functional/typography/typography.json' with {type: 'json'} + +const categories = { + base: { + motion: Object.values(baseMotion).map(token => { + return { + name: token.name, + type: token.type, + value: token.value, + } + }), + size: Object.values(baseSize).map(token => { + return { + name: token.name, + type: token.type, + value: token.value, + } + }), + typography: Object.values(baseTypography).map(token => { + return { + name: token.name, + type: token.type, + value: token.value, + } + }), + }, + functional: { + border: Object.values(functionalSizeBorder).map(token => { + return { + name: token.name, + type: token.type, + value: token.value, + } + }), + sizeCoarse: Object.values(functionalSizeCoarse).map(token => { + return { + name: token.name, + type: token.type, + value: token.value, + } + }), + sizeFine: Object.values(functionalSizeFine).map(token => { + return { + name: token.name, + type: token.type, + value: token.value, + } + }), + size: Object.values(functionalSize).map(token => { + return { + name: token.name, + type: token.type, + value: token.value, + } + }), + themes: { + light: Object.values(light).map(token => { + return { + name: token.name, + type: token.type, + value: token.value, + } + }), + }, + typography: Object.values(functionalTypography).map(token => { + return { + name: token.name, + type: token.type, + value: token.value, + } + }), + }, +} as const + +const tokens = [ + ...categories.base.motion, + ...categories.base.size, + ...categories.base.typography, + ...categories.functional.border, + ...categories.functional.sizeCoarse, + ...categories.functional.sizeFine, + ...categories.functional.size, + ...categories.functional.themes.light, + ...categories.functional.typography, +] + +function serialize(value: typeof tokens): string { + return value + .map(token => { + return `` + }) + .join('\n') +} + +export {categories, tokens, serialize} diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts index 16adb4b36ed..07f179c4ece 100644 --- a/packages/mcp/src/server.ts +++ b/packages/mcp/src/server.ts @@ -4,6 +4,7 @@ import * as cheerio from 'cheerio' import {z} from 'zod' import TurndownService from 'turndown' import {listComponents, listPatterns, listIcons} from './primer' +import {tokens, serialize} from './primitives' import packageJson from '../package.json' with {type: 'json'} const server = new McpServer({ @@ -413,430 +414,14 @@ ${text}`, }, ) -type Token = TokenCategory | string -type TokenCategory = { - category: string - tokens: Array -} - -const tokens: Array = [ - { - category: 'color', - tokens: [ - { - category: 'foreground', - tokens: [ - '--fgColor-accent', - '--fgColor-attention', - '--fgColor-black', - '--fgColor-closed', - '--fgColor-danger', - '--fgColor-default', - '--fgColor-disabled', - '--fgColor-done', - '--fgColor-link', - '--fgColor-muted', - '--fgColor-neutral', - '--fgColor-onEmphasis', - '--fgColor-onInverse', - '--fgColor-open', - '--fgColor-severe', - '--fgColor-sponsors', - '--fgColor-success', - '--fgColor-upsell', - '--fgColor-white', - ], - }, - { - category: 'background', - tokens: [ - '--bgColor-accent-emphasis', - '--bgColor-accent-muted', - '--bgColor-attention-emphasis', - '--bgColor-attention-muted', - '--bgColor-black', - '--bgColor-closed-emphasis', - '--bgColor-closed-muted', - '--bgColor-danger-emphasis', - '--bgColor-danger-muted', - '--bgColor-default', - '--bgColor-disabled', - '--bgColor-done-emphasis', - '--bgColor-done-muted', - '--bgColor-emphasis', - '--bgColor-inset', - '--bgColor-inverse', - '--bgColor-muted', - '--bgColor-neutral-emphasis', - '--bgColor-neutral-muted', - '--bgColor-open-emphasis', - '--bgColor-open-muted', - '--bgColor-severe-emphasis', - '--bgColor-severe-muted', - '--bgColor-sponsors-emphasis', - '--bgColor-sponsors-muted', - '--bgColor-success-emphasis', - '--bgColor-success-muted', - '--bgColor-transparent', - '--bgColor-upsell-emphasis', - '--bgColor-upsell-muted', - '--bgColor-white', - ], - }, - { - category: 'border', - tokens: [ - '--borderColor-accent-emphasis', - '--borderColor-accent-muted', - '--borderColor-attention-emphasis', - '--borderColor-attention-muted', - '--borderColor-closed-emphasis', - '--borderColor-closed-muted', - '--borderColor-danger-emphasis', - '--borderColor-danger-muted', - '--borderColor-default', - '--borderColor-disabled', - '--borderColor-done-emphasis', - '--borderColor-done-muted', - '--borderColor-emphasis', - '--borderColor-muted', - '--borderColor-neutral-emphasis', - '--borderColor-neutral-muted', - '--borderColor-open-emphasis', - '--borderColor-open-muted', - '--borderColor-severe-emphasis', - '--borderColor-severe-muted', - '--borderColor-sponsors-emphasis', - '--borderColor-sponsors-muted', - '--borderColor-success-emphasis', - '--borderColor-success-muted', - '--borderColor-translucent', - '--borderColor-transparent', - '--borderColor-upsell-emphasis', - '--borderColor-upsell-muted', - ], - }, - { - category: 'shadow', - tokens: [ - '--shadow-floating-large', - '--shadow-floating-legacy', - '--shadow-floating-medium', - '--shadow-floating-small', - '--shadow-floating-xlarge', - '--shadow-inset', - '--shadow-resting-medium', - '--shadow-resting-small', - '--shadow-resting-xsmall', - ], - }, - { - category: 'control', - tokens: [ - '--control-bgColor-active', - '--control-bgColor-disabled', - '--control-bgColor-hover', - '--control-bgColor-rest', - '--control-bgColor-selected', - '--control-borderColor-danger', - '--control-borderColor-disabled', - '--control-borderColor-emphasis', - '--control-borderColor-rest', - '--control-borderColor-selected', - '--control-borderColor-success', - '--control-borderColor-warning', - '--control-checked-bgColor-active', - '--control-checked-bgColor-disabled', - '--control-checked-bgColor-hover', - '--control-checked-bgColor-rest', - '--control-checked-borderColor-active', - '--control-checked-borderColor-disabled', - '--control-checked-borderColor-hover', - '--control-checked-borderColor-rest', - '--control-checked-fgColor-disabled', - '--control-checked-fgColor-rest', - '--control-danger-bgColor-active', - '--control-danger-bgColor-hover', - '--control-danger-fgColor-hover', - '--control-danger-fgColor-rest', - '--control-fgColor-disabled', - '--control-fgColor-placeholder', - '--control-fgColor-rest', - '--control-iconColor-rest', - '--control-transparent-bgColor-active', - '--control-transparent-bgColor-disabled', - '--control-transparent-bgColor-hover', - '--control-transparent-bgColor-rest', - '--control-transparent-bgColor-selected', - '--control-transparent-borderColor-active', - '--control-transparent-borderColor-hover', - '--control-transparent-borderColor-rest', - ], - }, - { - category: 'focus', - tokens: ['--focus-outlineColor'], - }, - { - category: 'overlay', - tokens: ['--overlay-background-bgColor', '--overlay-bgColor', '--overlay-borderColor'], - }, - ], - }, - { - category: 'size', - tokens: [ - { - category: 'base', - tokens: [ - '--base-size-2', - '--base-size-4', - '--base-size-6', - '--base-size-8', - '--base-size-12', - '--base-size-16', - '--base-size-20', - '--base-size-24', - '--base-size-28', - '--base-size-32', - '--base-size-36', - '--base-size-40', - '--base-size-44', - '--base-size-48', - '--base-size-64', - '--base-size-80', - '--base-size-96', - '--base-size-112', - '--base-size-128', - ], - }, - { - category: 'border', - tokens: [ - { - category: 'border-size', - tokens: [ - '--boxShadow-thick', - '--boxShadow-thicker', - '--boxShadow-thin', - '--borderWidth-default', - '--borderWidth-thick', - '--borderWidth-thicker', - '--borderWidth-thin', - ], - }, - { - category: 'border-radius', - tokens: [ - '--borderRadius-default', - '--borderRadius-full', - '--borderRadius-large', - '--borderRadius-medium', - '--borderRadius-small', - ], - }, - { - category: 'outline', - tokens: ['--outline-focus-offset', '--outline-focus-width'], - }, - ], - }, - { - category: 'layout', - tokens: [ - { - category: 'stack', - tokens: [ - '--stack-gap-condensed', - '--stack-gap-normal', - '--stack-gap-spacious', - '--stack-padding-condensed', - '--stack-padding-normal', - '--stack-padding-spacious', - ], - }, - { - category: 'controls', - tokens: [ - '--controlStack-large-gap-auto', - '--controlStack-large-gap-condensed', - '--controlStack-large-gap-spacious', - '--controlStack-medium-gap-condensed', - '--controlStack-medium-gap-spacious', - '--controlStack-small-gap-condensed', - '--controlStack-small-gap-spacious', - '--controlStack-medium-gap-auto', - '--controlStack-small-gap-auto', - - '--control-xsmall-gap', - '--control-small-gap', - '--control-medium-gap', - '--control-large-gap', - '--control-xlarge-gap', - '--control-xsmall-lineBoxHeight', - '--control-small-lineBoxHeight', - '--control-medium-lineBoxHeight', - '--control-large-lineBoxHeight', - '--control-xlarge-lineBoxHeight', - '--control-xsmall-paddingBlock', - '--control-small-paddingBlock', - '--control-medium-paddingBlock', - '--control-large-paddingBlock', - '--control-xlarge-paddingBlock', - '--control-xsmall-paddingInline-condensed', - '--control-small-paddingInline-condensed', - '--control-medium-paddingInline-condensed', - '--control-large-paddingInline-condensed', - '--control-xlarge-paddingInline-condensed', - '--control-xsmall-paddingInline-normal', - '--control-small-paddingInline-normal', - '--control-medium-paddingInline-normal', - '--control-large-paddingInline-normal', - '--control-xlarge-paddingInline-normal', - '--control-xsmall-paddingInline-spacious', - '--control-small-paddingInline-spacious', - '--control-medium-paddingInline-spacious', - '--control-large-paddingInline-spacious', - '--control-xlarge-paddingInline-spacious', - '--control-xsmall-size', - '--control-small-size', - '--control-medium-size', - '--control-large-size', - '--control-xlarge-size', - ], - }, - { - category: 'overlay', - tokens: [ - '--overlay-borderRadius', - '--overlay-height-large', - '--overlay-height-medium', - '--overlay-height-small', - '--overlay-height-xlarge', - '--overlay-offset', - '--overlay-padding-condensed', - '--overlay-padding-normal', - '--overlay-paddingBlock-condensed', - '--overlay-paddingBlock-normal', - '--overlay-width-large', - '--overlay-width-medium', - '--overlay-width-small', - '--overlay-width-xlarge', - '--overlay-width-xsmall', - ], - }, - ], - }, - ], - }, - { - category: 'typography', - tokens: [ - { - category: 'weight', - tokens: [ - '--base-text-weight-light', - '--base-text-weight-normal', - '--base-text-weight-medium', - '--base-text-weight-semibold', - ], - }, - { - category: 'font-family', - tokens: [ - '--fontStack-monospace', - '--fontStack-sansSerif', - '--fontStack-sansSerifDisplay', - '--fontStack-system', - ], - }, - { - category: 'font-shorthand', - tokens: [ - '--text-body-shorthand-large', - '--text-body-shorthand-medium', - '--text-body-shorthand-small', - '--text-caption-shorthand', - '--text-codeBlock-shorthand', - '--text-codeInline-shorthand', - '--text-display-shorthand', - '--text-subtitle-shorthand', - '--text-title-shorthand-large', - '--text-title-shorthand-medium', - '--text-title-shorthand-small', - ], - }, - { - category: 'display', - tokens: [ - '--text-display-lineBoxHeight', - '--text-display-lineHeight', - '--text-display-size', - '--text-display-weight', - ], - }, - { - category: 'title-large', - tokens: ['--text-title-lineHeight-large', '--text-title-size-large', '--text-title-weight-large'], - }, - { - category: 'title-medium', - tokens: ['--text-title-lineHeight-medium', '--text-title-size-medium', '--text-title-weight-medium'], - }, - { - category: 'title-small', - tokens: ['--text-title-lineHeight-small', '--text-title-size-small', '--text-title-weight-small'], - }, - { - category: 'subtitle', - tokens: ['--text-subtitle-lineHeight', '--text-subtitle-size', '--text-subtitle-weight'], - }, - { - category: 'body-large', - tokens: ['--text-body-lineHeight-large', '--text-body-size-large'], - }, - { - category: 'body-medium', - tokens: ['--text-body-lineHeight-medium', '--text-body-size-medium'], - }, - { - category: 'body-small', - tokens: ['--text-body-lineHeight-small', '--text-body-size-small'], - }, - { - category: 'caption', - tokens: ['--text-caption-lineHeight', '--text-caption-size', '--text-caption-weight'], - }, - { - category: 'code-block', - tokens: ['--text-codeBlock-lineHeight', '--text-codeBlock-size', '--text-codeBlock-weight'], - }, - { - category: 'inline-code-block', - tokens: ['--text-codeInline-size', '--text-codeInline-weight'], - }, - ], - }, -] as const - -function serialize(token: Token): string { - if (typeof token === 'string') { - return `` - } - return `\n${token.tokens.map(serialize).join('\n')}\n` -} - // ----------------------------------------------------------------------------- // Design Tokens // ----------------------------------------------------------------------------- server.tool('list_tokens', 'List all of the design tokens available from Primer', async () => { let text = - 'Below is a list of all designs tokens available from Primer. They are organized by category. Tokens are used in CSS and CSS Modules. They can also be referred to in JavaScript files using the style attribute or prop in React components. To refer to the CSS Custom Property for a design token, wrap it in var(name-of-token). To learn how to use a specific token, use a corresponding usage tool for the category of the token. For example, if a token is a color token look for the get_color_usage tool. \n\n' + 'Below is a list of all design tokens available from Primer. Tokens are used in CSS and CSS Modules. To refer to the CSS Custom Property for a design token, wrap it in var(--{name-of-token}). To learn how to use a specific token, use a corresponding usage tool for the category of the token. For example, if a token is a color token look for the get_color_usage tool. \n\n' - for (const token of tokens) { - text += serialize(token) - text += '\n' - } + text += serialize(tokens) return { content: [ @@ -1005,4 +590,43 @@ ${text}`, }, ) +// ----------------------------------------------------------------------------- +// Coding guidelines +// ----------------------------------------------------------------------------- +server.tool( + 'primer_coding_guidelines', + 'Get the guidelines when writing code that uses Primer or for UI code that you are creating', + async () => { + return { + content: [ + { + type: 'text', + text: `When writing code that uses Primer, follow these guidelines: + +## Design Tokens + +- Prefer design tokens over hard-coded values. For example, use \`var(--fgColor-default)\` instead of \`#24292f\`. Use the \`list_tokens\` tool to find the design token you need. +- Prefer recommending design tokens in the same group for related CSS properties. For example, when styling background and border color, use tokens from the same group/category + +## Authoring & Using Components + +- Prefer re-using a component from Primer when possible over writing a new component. +- Prefer using existing props for a component for styling instead of adding styling to a component +- Prefer using icons from Primer instead of creating new icons. Use the \`list_icons\` tool to find the icon you need. +- Follow patterns from Primer when creating new components. Use the \`list_patterns\` tool to find the pattern you need, if one exists +- When using a component from Primer, make sure to follow the component's usage and accessibility guidelines + +## Coding guidelines + +The following list of coding guidelines must be followed: + +- Do not use the sx prop for styling components. Instead, use CSS Modules. +- Do not use the Box component for styling components. Instead, use CSS Modules. +`, + }, + ], + } + }, +) + export {server} From 870a8ca5b788eae8c257b2fb4877ca092e97a9d9 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Thu, 28 Aug 2025 16:09:18 +1000 Subject: [PATCH 31/54] Convert menu to disclosure pattern --- .../Breadcrumbs.features.stories.tsx | 25 +++++ .../src/Breadcrumbs/Breadcrumbs.module.css | 38 ++++++- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 103 +++++++++++++++--- 3 files changed, 145 insertions(+), 21 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx index 0510633a974..db9d67b9786 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx @@ -2,6 +2,7 @@ import type {Meta} from '@storybook/react-vite' import type React from 'react' import type {ComponentProps} from '../utils/types' import Breadcrumbs from './Breadcrumbs' +import TextInput from '../TextInput' export default { title: 'Components/Breadcrumbs/Features', @@ -103,3 +104,27 @@ export const WrappedBreadcrumbItemsWithOverflow = () => ( ) + +export const WithEditableNameInput = () => ( + + Home + Documents + Project Alpha + + + + +) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index 31bc52059a5..2b5141bf09a 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -12,7 +12,6 @@ [data-overflow='menu'] .BreadcrumbsList { white-space: nowrap; - overflow: hidden; display: flex; flex-direction: row; } @@ -58,8 +57,41 @@ } } -.MenuButton { - color: var(--fgColor-link); +.MenuSummary { + list-style: none; + display: inline-block; + outline: none; +} + +.MenuSummary::-webkit-details-marker { + display: none; +} + +.MenuDetails { + position: relative; + display: inline-block; +} + +.MenuDetails summary { + list-style: none; + cursor: pointer; + outline: none; +} + +.MenuDetails summary::-webkit-details-marker { + display: none; +} + +.MenuOverlay { + position: absolute; + z-index: 1; + box-shadow: + 0 1px 3px rgba(0, 0, 0, 0.12), + 0 1px 2px rgba(0, 0, 0, 0.24); + border-radius: 12px; + background-color: var(--overlay-bgColor); + list-style: none; + width: max-content; } .ItemSeparator { diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 2f0396b4235..a30dc17222e 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -6,12 +6,15 @@ import type {ComponentProps} from '../utils/types' import classes from './Breadcrumbs.module.css' import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' import {BoxWithFallback} from '../internal/components/BoxWithFallback' -import {ActionMenu} from '../ActionMenu' +import Details from '../Details' import {ActionList} from '../ActionList' import {IconButton} from '../Button/IconButton' import {KebabHorizontalIcon} from '@primer/octicons-react' import {useResizeObserver} from '../hooks/useResizeObserver' import type {ResizeObserverEntry} from '../hooks/useResizeObserver' +import {useOnEscapePress} from '../hooks/useOnEscapePress' +import {useOnOutsideClick} from '../hooks/useOnOutsideClick' +import {getAnchoredPosition} from '@primer/behaviors' const SELECTED_CLASS = 'selected' @@ -32,24 +35,83 @@ type BreadcrumbsMenuItemProps = { 'aria-label'?: string } -const BreadcrumbsMenuItem = React.forwardRef( - ({items, 'aria-label': ariaLabel, ...rest}, ref) => { +const BreadcrumbsMenuItem = React.forwardRef( + ({items, 'aria-label': ariaLabel, ...rest}, detailsRef) => { + const [isOpen, setIsOpen] = useState(false) + const [menuPosition, setMenuPosition] = useState<{top?: number; left?: number; right?: number}>({}) + + const handleSummaryClick = useCallback( + (event: React.MouseEvent) => { + // Prevent the button click from bubbling up and interfering with details toggle + event.preventDefault() + // Manually toggle the details element + if (detailsRef && 'current' in detailsRef && detailsRef.current) { + const newOpenState = !detailsRef.current.open + detailsRef.current.open = newOpenState + setIsOpen(newOpenState) + } + }, + [detailsRef], + ) + + const closeOverlay = useCallback(() => { + if (detailsRef && 'current' in detailsRef && detailsRef.current) { + detailsRef.current.open = false + setIsOpen(false) + } + }, [detailsRef]) + + const focusOnMenuButton = useCallback(() => { + iconButtonRef.current?.focus() + }, []) + + const iconButtonRef = useRef(null) + const menuContainerRef = useRef(null) + const summaryRef = useRef(null) + + // Calculate menu position when opening + useEffect(() => { + if (isOpen && summaryRef.current && menuContainerRef.current) { + const {top, left} = getAnchoredPosition(summaryRef.current, menuContainerRef.current, { + align: 'end', + side: 'outside-bottom', + }) + setMenuPosition({top, left}) + } + }, [isOpen]) + + useOnEscapePress( + (event: KeyboardEvent) => { + if (isOpen) { + event.preventDefault() + closeOverlay() + focusOnMenuButton() + } + }, + [isOpen], + ) + + useOnOutsideClick({ + onClickOutside: closeOverlay, + containerRef: menuContainerRef, + ignoreClickRefs: [iconButtonRef], + }) + return ( - - +
    + +
    + {items.map((item, index) => { const href = item.props.href const children = item.props.children @@ -58,16 +120,16 @@ const BreadcrumbsMenuItem = React.forwardRef {children} ) })} - - +
    +
    ) }, ) @@ -80,7 +142,7 @@ const getValidChildren = (children: React.ReactNode) => { function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRoot = true}: BreadcrumbsProps) { const containerRef = useRef(null) - const menuButtonRef = useRef(null) + const menuButtonRef = useRef(null) const [effectiveHideRoot, setEffectiveHideRoot] = useState(hideRoot) //let effectiveOverflow = 'wrap' const childArray = useMemo(() => getValidChildren(children), [children]) @@ -115,9 +177,14 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo // Measure actual menu button width when it exists useEffect(() => { if (menuButtonRef.current) { - const measuredWidth = menuButtonRef.current.offsetWidth - if (measuredWidth > 0) { - setMenuButtonWidth(measuredWidth) + const iconButtonElement = + menuButtonRef.current.querySelector('button[data-component="IconButton"]') || + menuButtonRef.current.querySelector('button') + if (iconButtonElement) { + const measuredWidth = (iconButtonElement as HTMLElement).offsetWidth + if (measuredWidth > 0) { + setMenuButtonWidth(measuredWidth) + } } } }, [menuItems.length]) // Re-measure when menu button appears/disappears From bc99a1717440ff3a3c02e2ca2b1a5724fc5a5b1b Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Thu, 28 Aug 2025 17:22:15 +1000 Subject: [PATCH 32/54] Fix bugs --- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index a30dc17222e..ec41fcd26be 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -166,13 +166,13 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo useEffect(() => { const listElement = containerRef.current?.querySelector('ol') - if (listElement && listElement.children.length > 0) { + if (listElement && listElement.children.length > 0 && listElement.children.length === childArray.length) { const listElementArray = Array.from(listElement.children) as HTMLElement[] const widths = listElementArray.map(child => child.offsetWidth) setChildArrayWidths(widths) setRootItemWidth(listElementArray[0].offsetWidth) } - }, [childArray.length]) + }, [childArray]) // Measure actual menu button width when it exists useEffect(() => { @@ -187,7 +187,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo } } } - }, [menuItems.length]) // Re-measure when menu button appears/disappears + }, [menuItems]) const calculateOverflow = useCallback( (availableWidth: number) => { @@ -263,12 +263,18 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo if (entries[0]) { const containerWidth = entries[0].contentRect.width const result = calculateOverflow(containerWidth) - setVisibleItems(result.visibleItems) - setMenuItems(result.menuItems) - setEffectiveHideRoot(result.effectiveHideRoot) + if ( + visibleItems.length !== result.visibleItems.length || + menuItems.length !== result.menuItems.length || + effectiveHideRoot !== result.effectiveHideRoot + ) { + setVisibleItems(result.visibleItems) + setMenuItems(result.menuItems) + setEffectiveHideRoot(result.effectiveHideRoot) + } } }, - [calculateOverflow], + [calculateOverflow, effectiveHideRoot, menuItems, visibleItems], ) useResizeObserver(handleResize, containerRef) @@ -283,7 +289,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo setMenuItems(result.menuItems) setEffectiveHideRoot(result.effectiveHideRoot) } - }, [overflow, childArray.length, calculateOverflow]) + }, [overflow, childArray, calculateOverflow]) // Determine final children to render const finalChildren = React.useMemo(() => { From 020f0b70143aedae29b8fe47a1390a797cf38541 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Fri, 29 Aug 2025 23:18:19 +1000 Subject: [PATCH 33/54] Fix up infinite loop at 1 remaining visible item --- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 39 +++++++------------ 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index ec41fcd26be..70bd91631ac 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -67,12 +67,10 @@ const BreadcrumbsMenuItem = React.forwardRef(null) const menuContainerRef = useRef(null) - const summaryRef = useRef(null) - // Calculate menu position when opening useEffect(() => { - if (isOpen && summaryRef.current && menuContainerRef.current) { - const {top, left} = getAnchoredPosition(summaryRef.current, menuContainerRef.current, { + if (isOpen && iconButtonRef.current && menuContainerRef.current) { + const {top, left} = getAnchoredPosition(iconButtonRef.current, menuContainerRef.current, { align: 'end', side: 'outside-bottom', }) @@ -99,7 +97,7 @@ const BreadcrumbsMenuItem = React.forwardRef - + { const listElement = containerRef.current?.querySelector('ol') @@ -174,7 +171,6 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo } }, [childArray]) - // Measure actual menu button width when it exists useEffect(() => { if (menuButtonRef.current) { const iconButtonElement = @@ -191,18 +187,19 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo const calculateOverflow = useCallback( (availableWidth: number) => { - const MENU_BUTTON_WIDTH = menuButtonWidth // Use measured width with fallback + let eHideRoot = effectiveHideRoot + const MENU_BUTTON_WIDTH = menuButtonWidth + const MIN_VISIBLE_ITEMS = !eHideRoot ? 3 : 4 const calculateVisibleItemsWidth = (w: number[]) => { const widths = w.reduce((sum, width) => sum + width + 16, 0) - return !effectiveHideRoot ? rootItemWidth + widths : widths + return !eHideRoot ? rootItemWidth + widths : widths } let currentVisibleItems = [...childArray] let currentVisibleItemWidths = [...childArrayWidths] let currentMenuItems: React.ReactElement[] = [] let currentMenuItemsWidths: number[] = [] - let eHideRoot = effectiveHideRoot if (availableWidth > 0 && currentVisibleItemWidths.length > 0) { let visibleItemsWidthTotal = calculateVisibleItemsWidth(currentVisibleItemWidths) @@ -232,7 +229,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo // If hideRoot is false but we still don't fit with root + menu + leaf, // fallback to hideRoot=true behavior (menu + leaf only) - if (!hideRoot && currentVisibleItems.length === 1 && visibleItemsWidthTotal > availableWidth) { + if (currentVisibleItems.length === 1 && visibleItemsWidthTotal > availableWidth) { eHideRoot = true break } else { @@ -246,16 +243,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo effectiveHideRoot: eHideRoot, } }, - [ - MIN_VISIBLE_ITEMS, - childArray, - childArrayWidths, - effectiveHideRoot, - hideRoot, - overflow, - rootItemWidth, - menuButtonWidth, - ], + [childArray, childArrayWidths, effectiveHideRoot, hideRoot, overflow, rootItemWidth, menuButtonWidth], ) const handleResize = useCallback( @@ -264,9 +252,8 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo const containerWidth = entries[0].contentRect.width const result = calculateOverflow(containerWidth) if ( - visibleItems.length !== result.visibleItems.length || - menuItems.length !== result.menuItems.length || - effectiveHideRoot !== result.effectiveHideRoot + (visibleItems.length !== result.visibleItems.length && menuItems.length !== result.menuItems.length) || + result.effectiveHideRoot !== effectiveHideRoot ) { setVisibleItems(result.visibleItems) setMenuItems(result.menuItems) @@ -281,7 +268,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo // Initial overflow calculation for testing and >5 items useEffect(() => { - if (overflow === 'menu' && childArray.length > 5) { + if (overflow === 'menu' && childArray.length > 5 && menuItems.length === 0) { // Get actual container width from DOM or use default const containerWidth = containerRef.current?.offsetWidth || 800 const result = calculateOverflow(containerWidth) @@ -289,7 +276,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo setMenuItems(result.menuItems) setEffectiveHideRoot(result.effectiveHideRoot) } - }, [overflow, childArray, calculateOverflow]) + }, [overflow, childArray, calculateOverflow, menuItems.length]) // Determine final children to render const finalChildren = React.useMemo(() => { From 8cf263e7d121c11ed6594e9fb3e3583d7b6ccc0e Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Fri, 29 Aug 2025 23:23:03 +1000 Subject: [PATCH 34/54] Remove unnecessary comments --- packages/react/src/Breadcrumbs/Breadcrumbs.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 70bd91631ac..9e1d979f3f7 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -142,7 +142,6 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo const containerRef = useRef(null) const menuButtonRef = useRef(null) const [effectiveHideRoot, setEffectiveHideRoot] = useState(hideRoot) - //let effectiveOverflow = 'wrap' const childArray = useMemo(() => getValidChildren(children), [children]) const rootItem = childArray[0] @@ -153,7 +152,6 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo const [menuItems, setMenuItems] = useState([]) const [rootItemWidth, setRootItemWidth] = useState(0) - // Menu button width with fallback const MENU_BUTTON_FALLBACK_WIDTH = 32 // Design system small IconButton const [menuButtonWidth, setMenuButtonWidth] = useState(MENU_BUTTON_FALLBACK_WIDTH) @@ -204,7 +202,6 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo if (availableWidth > 0 && currentVisibleItemWidths.length > 0) { let visibleItemsWidthTotal = calculateVisibleItemsWidth(currentVisibleItemWidths) - // Add menu button width if we have hidden items if (currentMenuItems.length > 0) { visibleItemsWidthTotal += MENU_BUTTON_WIDTH } @@ -212,7 +209,6 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo overflow === 'menu' && (visibleItemsWidthTotal > availableWidth || currentVisibleItems.length > MIN_VISIBLE_ITEMS) ) { - // Remove the last visible item const itemToHide = currentVisibleItems.slice(0)[0] const itemToHideWidth = currentVisibleItemWidths.slice(0)[0] currentMenuItems = [...currentMenuItems, itemToHide] @@ -222,13 +218,10 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo visibleItemsWidthTotal = calculateVisibleItemsWidth(currentVisibleItemWidths) - // Add menu button width if (currentMenuItems.length > 0) { visibleItemsWidthTotal += MENU_BUTTON_WIDTH } - // If hideRoot is false but we still don't fit with root + menu + leaf, - // fallback to hideRoot=true behavior (menu + leaf only) if (currentVisibleItems.length === 1 && visibleItemsWidthTotal > availableWidth) { eHideRoot = true break @@ -266,10 +259,8 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo useResizeObserver(handleResize, containerRef) - // Initial overflow calculation for testing and >5 items useEffect(() => { if (overflow === 'menu' && childArray.length > 5 && menuItems.length === 0) { - // Get actual container width from DOM or use default const containerWidth = containerRef.current?.offsetWidth || 800 const result = calculateOverflow(containerWidth) setVisibleItems(result.visibleItems) @@ -278,7 +269,6 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo } }, [overflow, childArray, calculateOverflow, menuItems.length]) - // Determine final children to render const finalChildren = React.useMemo(() => { if (overflow === 'wrap' || menuItems.length === 0) { return React.Children.map(children, child =>
  • {child}
  • ) @@ -313,7 +303,6 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo ) - // Position menu based on effective hideRoot setting and visible items if (effectiveHideRoot) { // Show: [overflow menu, leaf breadcrumb] return [menuElement, ...visibleElements] From 23ddd0ecca352cf57ed8fd9ecc94cc8b02936253 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Fri, 29 Aug 2025 23:33:40 +1000 Subject: [PATCH 35/54] Use ref callback for menu width calculation --- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 9e1d979f3f7..c8b358a230d 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -140,7 +140,17 @@ const getValidChildren = (children: React.ReactNode) => { function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRoot = true}: BreadcrumbsProps) { const containerRef = useRef(null) - const menuButtonRef = useRef(null) + + const measureMenuButton = useCallback((element: HTMLDetailsElement | null) => { + if (element) { + const iconButtonElement = element.querySelector('button[data-component="IconButton"]') + if (iconButtonElement) { + const measuredWidth = (iconButtonElement as HTMLElement).offsetWidth + setMenuButtonWidth(measuredWidth) + } + } + }, []) + const [effectiveHideRoot, setEffectiveHideRoot] = useState(hideRoot) const childArray = useMemo(() => getValidChildren(children), [children]) @@ -169,20 +179,6 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo } }, [childArray]) - useEffect(() => { - if (menuButtonRef.current) { - const iconButtonElement = - menuButtonRef.current.querySelector('button[data-component="IconButton"]') || - menuButtonRef.current.querySelector('button') - if (iconButtonElement) { - const measuredWidth = (iconButtonElement as HTMLElement).offsetWidth - if (measuredWidth > 0) { - setMenuButtonWidth(measuredWidth) - } - } - } - }, [menuItems]) - const calculateOverflow = useCallback( (availableWidth: number) => { let eHideRoot = effectiveHideRoot @@ -281,7 +277,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo const menuElement = (
  • @@ -310,7 +306,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo // Show: [root breadcrumb, overflow menu, leaf breadcrumb] return [rootElement, menuElement, ...visibleElements] } - }, [overflow, menuItems, effectiveHideRoot, visibleItems, rootItem, children]) + }, [overflow, menuItems, effectiveHideRoot, visibleItems, rootItem, children, measureMenuButton]) return ( Date: Mon, 1 Sep 2025 11:58:25 +1000 Subject: [PATCH 36/54] Fix up review comments --- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index c8b358a230d..0d487eb5f17 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -14,7 +14,6 @@ import {useResizeObserver} from '../hooks/useResizeObserver' import type {ResizeObserverEntry} from '../hooks/useResizeObserver' import {useOnEscapePress} from '../hooks/useOnEscapePress' import {useOnOutsideClick} from '../hooks/useOnOutsideClick' -import {getAnchoredPosition} from '@primer/behaviors' const SELECTED_CLASS = 'selected' @@ -36,16 +35,24 @@ type BreadcrumbsMenuItemProps = { } const BreadcrumbsMenuItem = React.forwardRef( - ({items, 'aria-label': ariaLabel, ...rest}, detailsRef) => { + ({items, 'aria-label': ariaLabel, ...rest}, menuRefCallback) => { const [isOpen, setIsOpen] = useState(false) - const [menuPosition, setMenuPosition] = useState<{top?: number; left?: number; right?: number}>({}) - + const detailsRef = useRef(null) + const detailsRefCallback = useCallback( + (element: HTMLDetailsElement | null) => { + detailsRef.current = element + if (typeof menuRefCallback === 'function') { + menuRefCallback(element) + } + }, + [menuRefCallback], + ) const handleSummaryClick = useCallback( (event: React.MouseEvent) => { // Prevent the button click from bubbling up and interfering with details toggle event.preventDefault() // Manually toggle the details element - if (detailsRef && 'current' in detailsRef && detailsRef.current) { + if (detailsRef.current) { const newOpenState = !detailsRef.current.open detailsRef.current.open = newOpenState setIsOpen(newOpenState) @@ -55,7 +62,7 @@ const BreadcrumbsMenuItem = React.forwardRef { - if (detailsRef && 'current' in detailsRef && detailsRef.current) { + if (detailsRef.current) { detailsRef.current.open = false setIsOpen(false) } @@ -68,16 +75,6 @@ const BreadcrumbsMenuItem = React.forwardRef(null) const menuContainerRef = useRef(null) - useEffect(() => { - if (isOpen && iconButtonRef.current && menuContainerRef.current) { - const {top, left} = getAnchoredPosition(iconButtonRef.current, menuContainerRef.current, { - align: 'end', - side: 'outside-bottom', - }) - setMenuPosition({top, left}) - } - }, [isOpen]) - useOnEscapePress( (event: KeyboardEvent) => { if (isOpen) { @@ -96,11 +93,12 @@ const BreadcrumbsMenuItem = React.forwardRef +
    -
    +
    {items.map((item, index) => { const href = item.props.href @@ -205,12 +203,12 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo overflow === 'menu' && (visibleItemsWidthTotal > availableWidth || currentVisibleItems.length > MIN_VISIBLE_ITEMS) ) { - const itemToHide = currentVisibleItems.slice(0)[0] - const itemToHideWidth = currentVisibleItemWidths.slice(0)[0] + const itemToHide = currentVisibleItems[0] + const itemToHideWidth = currentVisibleItemWidths[0] currentMenuItems = [...currentMenuItems, itemToHide] currentMenuItemsWidths = [...currentMenuItemsWidths, itemToHideWidth] - currentVisibleItems = [...currentVisibleItems.slice(1)] - currentVisibleItemWidths = [...currentVisibleItemWidths.slice(1)] + currentVisibleItems = currentVisibleItems.slice(1) + currentVisibleItemWidths = currentVisibleItemWidths.slice(1) visibleItemsWidthTotal = calculateVisibleItemsWidth(currentVisibleItemWidths) @@ -227,8 +225,8 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo } } return { - visibleItems: [...currentVisibleItems], - menuItems: [...currentMenuItems], + visibleItems: currentVisibleItems, + menuItems: currentMenuItems, effectiveHideRoot: eHideRoot, } }, From 2e367bf0fa52c83d2e3f8e629f3fa212b11fbc1f Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 1 Sep 2025 12:53:27 +1000 Subject: [PATCH 37/54] Add feature flags --- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 104 ++++++++++-------- .../src/FeatureFlags/DefaultFeatureFlags.ts | 1 + 2 files changed, 59 insertions(+), 46 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 0d487eb5f17..fa927ed6bcf 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -14,6 +14,7 @@ import {useResizeObserver} from '../hooks/useResizeObserver' import type {ResizeObserverEntry} from '../hooks/useResizeObserver' import {useOnEscapePress} from '../hooks/useOnEscapePress' import {useOnOutsideClick} from '../hooks/useOnOutsideClick' +import {useFeatureFlag} from '../FeatureFlags' const SELECTED_CLASS = 'selected' @@ -137,6 +138,8 @@ const getValidChildren = (children: React.ReactNode) => { } function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRoot = true}: BreadcrumbsProps) { + const overflowMenuEnabled = useFeatureFlag('primer_react_breadcrumbs_overflow_menu') + const wrappedChildren = React.Children.map(children, child =>
  • {child}
  • ) const containerRef = useRef(null) const measureMenuButton = useCallback((element: HTMLDetailsElement | null) => { @@ -169,13 +172,18 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo useEffect(() => { const listElement = containerRef.current?.querySelector('ol') - if (listElement && listElement.children.length > 0 && listElement.children.length === childArray.length) { + if ( + overflowMenuEnabled && + listElement && + listElement.children.length > 0 && + listElement.children.length === childArray.length + ) { const listElementArray = Array.from(listElement.children) as HTMLElement[] const widths = listElementArray.map(child => child.offsetWidth) setChildArrayWidths(widths) setRootItemWidth(listElementArray[0].offsetWidth) } - }, [childArray]) + }, [childArray, overflowMenuEnabled]) const calculateOverflow = useCallback( (availableWidth: number) => { @@ -235,7 +243,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo const handleResize = useCallback( (entries: ResizeObserverEntry[]) => { - if (entries[0]) { + if (overflowMenuEnabled && entries[0]) { const containerWidth = entries[0].contentRect.width const result = calculateOverflow(containerWidth) if ( @@ -248,65 +256,67 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo } } }, - [calculateOverflow, effectiveHideRoot, menuItems, visibleItems], + [calculateOverflow, effectiveHideRoot, menuItems.length, overflowMenuEnabled, visibleItems.length], ) useResizeObserver(handleResize, containerRef) useEffect(() => { - if (overflow === 'menu' && childArray.length > 5 && menuItems.length === 0) { + if (overflowMenuEnabled && overflow === 'menu' && childArray.length > 5 && menuItems.length === 0) { const containerWidth = containerRef.current?.offsetWidth || 800 const result = calculateOverflow(containerWidth) setVisibleItems(result.visibleItems) setMenuItems(result.menuItems) setEffectiveHideRoot(result.effectiveHideRoot) } - }, [overflow, childArray, calculateOverflow, menuItems.length]) + }, [overflow, childArray, calculateOverflow, menuItems.length, overflowMenuEnabled]) const finalChildren = React.useMemo(() => { - if (overflow === 'wrap' || menuItems.length === 0) { - return React.Children.map(children, child =>
  • {child}
  • ) - } - - let effectiveMenuItems = [...menuItems] - if (!effectiveHideRoot) { - effectiveMenuItems = [...menuItems.slice(1)] - } - const menuElement = ( -
  • - - -
  • - ) - - const visibleElements = visibleItems.map((child, index) => ( -
  • - {child} - -
  • - )) - - const rootElement = ( -
  • - {rootItem} - -
  • - ) + if (overflowMenuEnabled) { + if (overflow === 'wrap' || menuItems.length === 0) { + return React.Children.map(children, child =>
  • {child}
  • ) + } - if (effectiveHideRoot) { - // Show: [overflow menu, leaf breadcrumb] - return [menuElement, ...visibleElements] - } else { - // Show: [root breadcrumb, overflow menu, leaf breadcrumb] - return [rootElement, menuElement, ...visibleElements] + let effectiveMenuItems = [...menuItems] + if (!effectiveHideRoot) { + effectiveMenuItems = [...menuItems.slice(1)] + } + const menuElement = ( +
  • + + +
  • + ) + + const visibleElements = visibleItems.map((child, index) => ( +
  • + {child} + +
  • + )) + + const rootElement = ( +
  • + {rootItem} + +
  • + ) + + if (effectiveHideRoot) { + // Show: [overflow menu, leaf breadcrumb] + return [menuElement, ...visibleElements] + } else { + // Show: [root breadcrumb, overflow menu, leaf breadcrumb] + return [rootElement, menuElement, ...visibleElements] + } } - }, [overflow, menuItems, effectiveHideRoot, visibleItems, rootItem, children, measureMenuButton]) + }, [overflowMenuEnabled, overflow, menuItems, effectiveHideRoot, measureMenuButton, visibleItems, rootItem, children]) - return ( + return overflowMenuEnabled ? ( {finalChildren} + ) : ( + {wrappedChildren} ) } diff --git a/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts b/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts index 9a387d232bb..f60c9687834 100644 --- a/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts +++ b/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts @@ -2,6 +2,7 @@ import {FeatureFlagScope} from './FeatureFlagScope' export const DefaultFeatureFlags = FeatureFlagScope.create({ primer_react_action_list_item_as_button: false, + primer_react_breadcrumbs_overflow_menu: false, primer_react_overlay_overflow: false, primer_react_segmented_control_tooltip: false, primer_react_select_panel_fullscreen_on_narrow: false, From bb5aa77c36788bd6fe43b33351f60c878a209e40 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 1 Sep 2025 13:01:56 +1000 Subject: [PATCH 38/54] Delete .changeset/good-cougars-hug.md --- .changeset/good-cougars-hug.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/good-cougars-hug.md diff --git a/.changeset/good-cougars-hug.md b/.changeset/good-cougars-hug.md deleted file mode 100644 index 27786935bdc..00000000000 --- a/.changeset/good-cougars-hug.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@primer/react": patch ---- - -Spike for breadcrumbs overflow From b8063b8892614b60f97f519301afda8eff9b140b Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 1 Sep 2025 13:11:39 +1000 Subject: [PATCH 39/54] Add different prop to hideRoot --- .../src/Breadcrumbs/Breadcrumbs.features.stories.tsx | 8 ++++---- packages/react/src/Breadcrumbs/Breadcrumbs.tsx | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx index db9d67b9786..7e86655a813 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx @@ -28,9 +28,9 @@ export const OverflowMenu = () => ( Home Products Category - SubcategorySubcategorySubcategorySubcategory - ItemItemItemItemItemItemItem - DetailsDetailsDetailsDetailsDetailsDetailsDetails + Subcategory + Item + Details Current Page @@ -38,7 +38,7 @@ export const OverflowMenu = () => ( ) export const OverflowMenuShowRoot = () => ( - + github Teams Engineering diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index fa927ed6bcf..43e0c1537d1 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -21,8 +21,7 @@ const SELECTED_CLASS = 'selected' export type BreadcrumbsProps = React.PropsWithChildren< { className?: string - overflow?: 'wrap' | 'menu' - hideRoot?: boolean + overflow?: 'wrap' | 'menu' | 'menu-with-root' } & SxProp > @@ -137,7 +136,7 @@ const getValidChildren = (children: React.ReactNode) => { return React.Children.toArray(children).filter(child => React.isValidElement(child)) as React.ReactElement[] } -function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRoot = true}: BreadcrumbsProps) { +function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap'}: BreadcrumbsProps) { const overflowMenuEnabled = useFeatureFlag('primer_react_breadcrumbs_overflow_menu') const wrappedChildren = React.Children.map(children, child =>
  • {child}
  • ) const containerRef = useRef(null) @@ -152,6 +151,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', hideRo } }, []) + const hideRoot = overflow === 'menu-with-root' const [effectiveHideRoot, setEffectiveHideRoot] = useState(hideRoot) const childArray = useMemo(() => getValidChildren(children), [children]) From c7669d6dd95962b000fc15c674496a753627649d Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 1 Sep 2025 16:08:03 +1000 Subject: [PATCH 40/54] Add tests --- .../src/Breadcrumbs/Breadcrumbs.docs.json | 11 +- .../src/Breadcrumbs/Breadcrumbs.module.css | 7 +- .../src/Breadcrumbs/Breadcrumbs.stories.tsx | 97 ++++++ .../react/src/Breadcrumbs/Breadcrumbs.tsx | 17 +- .../__tests__/Breadcrumbs.test.tsx | 290 +++++++++++++++--- 5 files changed, 360 insertions(+), 62 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.docs.json b/packages/react/src/Breadcrumbs/Breadcrumbs.docs.json index ed4c7d120c3..41321620375 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.docs.json +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.docs.json @@ -25,18 +25,11 @@ }, { "name": "overflow", - "type": "'wrap' | 'menu'", + "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.", + "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": "hideRoot", - "type": "boolean", - "required": false, - "description": "When using overflow='menu', whether to prioritize hiding the root (first) item when space is limited. If false, the root item will be kept visible when possible.", - "defaultValue": "true" - }, { "name": "sx", "type": "SystemStyleObject", diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index 2b5141bf09a..218a11ebce5 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -10,7 +10,8 @@ margin-bottom: 0; } -[data-overflow='menu'] .BreadcrumbsList { +[data-overflow='menu'] .BreadcrumbsList, +[data-overflow='menu-with-root'] .BreadcrumbsList { white-space: nowrap; display: flex; flex-direction: row; @@ -37,7 +38,8 @@ } } -[data-overflow='menu'] .Item { +[data-overflow='menu'] .Item, +[data-overflow='menu-with-root'] .Item { display: inline-block; font-size: var(--text-body-size-medium); color: var(--fgColor-link); @@ -61,6 +63,7 @@ list-style: none; display: inline-block; outline: none; + height: 0.5rem; } .MenuSummary::-webkit-details-marker { diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx index 16be2bd7bff..8480d66eb5c 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.stories.tsx @@ -1,4 +1,5 @@ import type {Meta} from '@storybook/react-vite' +import React, {useState} from 'react' import type {ComponentProps} from '../utils/types' import Breadcrumbs from './Breadcrumbs' @@ -16,3 +17,99 @@ export const Default = () => (
    ) + +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 ( +
    +
    + + + + +
    + +
    +

    Overflow: wrap (default)

    + + {items.map((item, index) => ( + + {item.name} + + ))} + +
    + +
    +

    Overflow: menu

    + + {items.map((item, index) => ( + + {item.name} + + ))} + +
    + +
    +

    Overflow: menu

    + + {items.map((item, index) => ( + + {item.name} + + ))} + +
    + +
    + Current items: {items.length} | Try adding/removing items to see how overflow behavior changes +
    +
    + ) +} diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 43e0c1537d1..8ca176e349c 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -98,6 +98,7 @@ const BreadcrumbsMenuItem = React.forwardRef(hideRoot) const childArray = useMemo(() => getValidChildren(children), [children]) @@ -208,7 +209,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap'}: Bread visibleItemsWidthTotal += MENU_BUTTON_WIDTH } while ( - overflow === 'menu' && + (overflow === 'menu' || overflow === 'menu-with-root') && (visibleItemsWidthTotal > availableWidth || currentVisibleItems.length > MIN_VISIBLE_ITEMS) ) { const itemToHide = currentVisibleItems[0] @@ -262,7 +263,12 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap'}: Bread useResizeObserver(handleResize, containerRef) useEffect(() => { - if (overflowMenuEnabled && overflow === 'menu' && childArray.length > 5 && menuItems.length === 0) { + if ( + overflowMenuEnabled && + (overflow === 'menu' || overflow === 'menu-with-root') && + childArray.length > 5 && + menuItems.length === 0 + ) { const containerWidth = containerRef.current?.offsetWidth || 800 const result = calculateOverflow(containerWidth) setVisibleItems(result.visibleItems) @@ -278,6 +284,7 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap'}: Bread } let effectiveMenuItems = [...menuItems] + // In 'menu-with-root' mode, include the root item inside the menu even if it's visible in the breadcrumbs if (!effectiveHideRoot) { effectiveMenuItems = [...menuItems.slice(1)] } @@ -328,7 +335,9 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap'}: Bread {finalChildren} ) : ( - {wrappedChildren} + + {wrappedChildren} + ) } diff --git a/packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx b/packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx index 3a9d8d7c063..0ef9460e7cc 100644 --- a/packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx +++ b/packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx @@ -1,14 +1,21 @@ import Breadcrumbs from '..' -import type React from 'react' -import {render as HTMLRender, screen, waitFor} from '@testing-library/react' +import {render as HTMLRender, screen, waitFor, within} from '@testing-library/react' import {describe, expect, it, vi} from 'vitest' import userEvent from '@testing-library/user-event' import {ThemeProvider} from '../../ThemeProvider' +import {FeatureFlags} from '../../FeatureFlags' import theme from '../../theme' -// Helper function to render with theme -const renderWithTheme = (component: React.ReactElement) => { - return HTMLRender({component}) +// Helper function to render with theme and feature flags +const renderWithTheme = (component: React.ReactElement, flags?: Record) => { + const wrappedComponent = flags ? ( + + {component} + + ) : ( + {component} + ) + return HTMLRender(wrappedComponent) } // Mock ResizeObserver for tests @@ -23,12 +30,6 @@ globalThis.ResizeObserver = vi.fn().mockImplementation(() => ({ })) describe('Breadcrumbs', () => { - it('should support `className` on the outermost element', () => { - expect(HTMLRender().container.firstChild).toHaveClass( - 'test-class-name', - ) - }) - it('renders a
    -

    Overflow: wrap (default)

    - - {items.map((item, index) => ( - - {item.name} - - ))} - -
    - -
    -

    Overflow: menu

    - - {items.map((item, index) => ( - - {item.name} - - ))} - -
    - -
    -

    Overflow: menu

    +

    + Dynamic breadcrumbs +

    {items.map((item, index) => ( From 92b813e44e20361ed6d7153cd8d923dcb964170f Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 1 Sep 2025 21:31:02 +1000 Subject: [PATCH 44/54] Story color needs changed --- packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx index 4e47d5b05b1..18dfff2137e 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx @@ -199,7 +199,7 @@ export const DynamicChildren = () => {
    -
    +
    Current items: {items.length} | Try adding/removing items to see how overflow behavior changes
    From 2854259e548d86b990b20045240cccf62cf22c74 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Wed, 3 Sep 2025 16:19:43 +1000 Subject: [PATCH 45/54] Some css improvements --- .../src/Breadcrumbs/Breadcrumbs.module.css | 36 +++++++++---------- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 7 ++-- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index 6a33f30660c..dc6f8db5d33 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -42,7 +42,7 @@ [data-overflow='menu-with-root'] .Item { display: inline-block; font-size: var(--text-body-size-medium); - color: var(--fgColor-link); + color: var(--fgColor-default); text-decoration: none; padding-inline: var(--base-size-6); padding-block: var(--base-size-4); @@ -50,49 +50,46 @@ &:hover { background: var(--control-transparent-bgColor-hover); + text-decoration: none; } &:focus { - box-shadow: none; - outline: 2px solid var(--focus-outlineColor, var(--color-accent-fg)); - outline-offset: -2px; + @mixin focusOutline; } } -.MenuSummary { - list-style: none; - display: inline-block; - outline: none; - height: 0.5rem; -} - -.MenuSummary::-webkit-details-marker { - display: none; -} - .MenuDetails { position: relative; display: inline-block; } -.MenuDetails summary { +summary { list-style: none; cursor: pointer; outline: none; } -.MenuDetails summary::-webkit-details-marker { +summary::-webkit-details-marker { display: none; } .MenuOverlay { position: absolute; z-index: 1; - box-shadow: var(--shadow-resting-medium); + box-shadow: var(--shadow-resting-small); border-radius: var(--borderRadius-large); background-color: var(--overlay-bgColor); list-style: none; - width: max-content; + min-width: var(--overlay-width-xsmall); + max-height: 100vh; + max-width: var(--overlay-width-small); + overflow: hidden; +} + +@media (prefers-reduced-motion: no-preference) { + .MenuOverlay { + animation: overlay-appear 200ms cubic-bezier(0.33, 1, 0.68, 1); + } } .ItemSeparator { @@ -107,7 +104,6 @@ .ItemWrapper { display: inline-block; font-size: var(--text-body-size-medium); - white-space: nowrap; list-style: none; &::after { diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 8ca176e349c..a5770e311a7 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -9,6 +9,7 @@ import {BoxWithFallback} from '../internal/components/BoxWithFallback' import Details from '../Details' import {ActionList} from '../ActionList' import {IconButton} from '../Button/IconButton' +import {Tooltip} from '../TooltipV2' import {KebabHorizontalIcon} from '@primer/octicons-react' import {useResizeObserver} from '../hooks/useResizeObserver' import type {ResizeObserverEntry} from '../hooks/useResizeObserver' @@ -94,19 +95,19 @@ const BreadcrumbsMenuItem = React.forwardRef - + - +
    {items.map((item, index) => { From b2e8931e2cb51de2f593e8366fb099a0422cc44d Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Thu, 4 Sep 2025 16:04:26 +1000 Subject: [PATCH 46/54] Fix the button role --- packages/react/src/Breadcrumbs/Breadcrumbs.tsx | 1 + packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index a5770e311a7..1d62fe02400 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -98,6 +98,7 @@ const BreadcrumbsMenuItem = React.forwardRef { // Press Escape key await user.keyboard('{Escape}') // sometimes tooltip swallows this escape - await user.keyboard('{Escape}') // Verify menu is closed await waitFor(() => { expect(menuButton).toHaveAttribute('aria-expanded', 'false') }) - - // Verify focus returns to menu button - expect(menuButton).toHaveFocus() }) it('closes menu when clicking outside', async () => { From c247260204845b0250f861adc5cff2b92aef0680 Mon Sep 17 00:00:00 2001 From: Katie Langerman <18661030+langermank@users.noreply.github.com> Date: Sun, 7 Sep 2025 19:09:44 -0700 Subject: [PATCH 47/54] Breadcrumb overflow styling (#6728) Co-authored-by: Pavithra Kodmad --- .changeset/hot-bears-cry.md | 5 + ...bs-Default-dark-colorblind-focus-linux.png | Bin 7873 -> 6335 bytes ...crumbs-Default-dark-dimmed-focus-linux.png | Bin 7888 -> 6306 bytes .../Breadcrumbs-Default-dark-focus-linux.png | Bin 7873 -> 6335 bytes ...Default-dark-high-contrast-focus-linux.png | Bin 7881 -> 6329 bytes ...bs-Default-dark-tritanopia-focus-linux.png | Bin 7873 -> 6335 bytes ...s-Default-light-colorblind-focus-linux.png | Bin 7693 -> 6274 bytes .../Breadcrumbs-Default-light-focus-linux.png | Bin 7691 -> 6274 bytes ...efault-light-high-contrast-focus-linux.png | Bin 7687 -> 6324 bytes ...s-Default-light-tritanopia-focus-linux.png | Bin 7693 -> 6274 bytes .../Breadcrumbs/Breadcrumbs.dev.stories.tsx | 162 +++++++++++++++ .../Breadcrumbs.features.stories.tsx | 189 +++++------------- .../src/Breadcrumbs/Breadcrumbs.module.css | 189 ++++++++++-------- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 19 +- .../__tests__/Breadcrumbs.test.tsx | 28 +++ .../BreadcrumbsItem.test.tsx.snap | 2 +- 16 files changed, 355 insertions(+), 239 deletions(-) create mode 100644 .changeset/hot-bears-cry.md create mode 100644 packages/react/src/Breadcrumbs/Breadcrumbs.dev.stories.tsx diff --git a/.changeset/hot-bears-cry.md b/.changeset/hot-bears-cry.md new file mode 100644 index 00000000000..b675e41c057 --- /dev/null +++ b/.changeset/hot-bears-cry.md @@ -0,0 +1,5 @@ +--- +"@primer/react": patch +--- + +Breadcrumb overflow styling diff --git a/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-dark-colorblind-focus-linux.png b/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-dark-colorblind-focus-linux.png index 80bf84cd927076b6b5d798dee47bbe2c767052fe..1f2c9c4df145351816af7c6b66c79f8b6ab0b4a8 100644 GIT binary patch literal 6335 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 diff --git a/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-dark-dimmed-focus-linux.png b/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-dark-dimmed-focus-linux.png index aa6bbbcfb2fa82e828c5dcd07627a4847cd0fbf5..9901afc1d1d3e6c14baba40a6f1d674f371e3f2f 100644 GIT binary patch literal 6306 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 Y@s_*$uM%57Lj~|S<>N*?apA^)0p5n5RR910 literal 7888 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 diff --git a/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-dark-focus-linux.png b/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-dark-focus-linux.png index 80bf84cd927076b6b5d798dee47bbe2c767052fe..1f2c9c4df145351816af7c6b66c79f8b6ab0b4a8 100644 GIT binary patch literal 6335 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 diff --git a/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-dark-high-contrast-focus-linux.png b/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-dark-high-contrast-focus-linux.png index e8c5b1e5c32fbe4e48bc1c195c727279ea04aa1f..420702f2e9f761bf38fba764f5a9a67eb3325be0 100644 GIT binary patch literal 6329 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 diff --git a/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-dark-tritanopia-focus-linux.png b/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-dark-tritanopia-focus-linux.png index 80bf84cd927076b6b5d798dee47bbe2c767052fe..1f2c9c4df145351816af7c6b66c79f8b6ab0b4a8 100644 GIT binary patch literal 6335 zcmeHMX;)L{7Csg$(iYL{fR;j*%VnviK)nbEDWMfHDijXle&WOXWv};~ecoq3`&rM~ z?@wR(!z|zX@I3$kEYF`i^ECjNt(q3F^Bq&<-7{Pd0N4$jKl5c!O3{Ke?U%e6^poZK z?z)9{z};cjeQsgEQPfP_@J9;1MDkzEk_|_HV5(HL6;7-Ps=En0c0x4o%2PmC4CMcdK=^r!ZCM z>*FWfJPH641FYY-1HhTxTGO|jmY4xw?2NMlfWv=z0>BS%7!*%7tJE`YzM#_1XQjRX z(4Jfy|N7m@|5kcsy{o3_!6qDZ4fX#EcKLF1P=?}^rLsWN*4;7o(;2KI#$_$h@KLOy z8GJPldR87D8<3Qg1cP;hxw&oWqt}h=r!mpPRko?vU>Um1wrI@Ei&TrA{L=x%@SLqT zold+TnpZHh&@znj@nj*Jn5BD3H20adGUuuobJ4DQwIq#mSy)VafN_Z{Ctwh++=e3w zTzxr3pdC-UnPUOFs9To{V*74bH_$E?E4ocUCFg{Jw0WL8EZy~^%FJDOTr{eFLft|` zwZ<*>`v<#33YKRj3{<1MB)szF(0tobX9c4sbDNhxLJ=GtQ?85i4S_ITnhl@4G3^Ff(_FAytRNb1&<1 zTKm&_l?oc$^VC_1E!q^++vyYR2>{_80&iB7*RVC#Xu4^l-JEcgLUYR z)wuyD!TKw>^B8@Tvm!J~eqz|T#}9s=m6fLCN4bdrT02UAuw0+PC1@CleD*8XZk zUG}qa+eguewLiM?i4q$bgg?v?;~Q)9ET8KuX~k&EpQBU*wyR$DQwHbr686Pj1U!Eixb zM;}Aq6GoKAy_n4Q+SEnLHZ^&wHiNQn@5le)>87YoE#FMEUpH|zITCV6n@MZa{SBUX z^U9)=5)3K56KoMVyEpaad@tDMf`d&yPnS80hUF3GhB6Z8s;u%6Zu_aLe&AVVkvft6 zdw2{E&Ych`ptyosEr_D&FHj;OlncHq5NbwJ6pSFP4D?~+JynZM5RVdLNfe8|vffE7 zuyzvhV$cG11pyk=SBC5mGGkDp+Q!Me8J1TNL?Mo-Ref3EkhW+K&R98jV$wzJ#0*;^ zsru~mlDk~x8d&b2t44Sd&P8xyIwE(ZDr)gRuBD+`>`>5I`S#lbCqq~9v4+uDsDE|9j0KjDz^FEE5 z0~Np&oE^alxs-s|=Jw4aQ#H6Gw~; zsZr1jAL$>!0O~nRw<^6pi-$$gO*GHdiys@VP77`Nj zywF*0g?fOGC2vUf`k&f-gkg>n1bsf7CcnD61Vf*w%1cKCL#?plTC`dr!ckcH`F{6Y zla!K{?La#oYd_7ejN61H&}BU2NE^j)7A|*0(j}vH{03WB4)(5A+?3BtBpC28<4>X= z_Z*257WMT}R}YcneUf@>ozk09uyza3m-jjEqcHcL8A8p;pNWR7@MVFhzHh0&VTXSp zCgPDXD4C==;_ku}ovi~@R+st4n%m@?dWgkP~O?Q$oiRrv2H$Qm}zFd_`=P?Bq$-t z%T_VNgMz_^N9gt+s25a5Qj)Oac^FiEYw>bRxW|lFnu-!n+rv1@>B}L zd3$$+&<=#5>a`_Qx}aVcf?kPy)~&U6LWTqclZ@ThJ!x3O9-NPszwv~@iARh%VDc}L zn@^Sa;*ehqf8btiQl@THYONTL82Ov5io;QE-E`dOWN`FhuGL65d_cgfo8Q%ot9s3F zxN{ULN$t{RSUpD1m=T4BQ>HC(fe3cG?#DgS)+F1L50$e>JbaaVqm86}U4`sXQPZDT z>B;GA`3LTEt=_EaaO3ma!Oo0Fl&P-8bVXN_9pT}eKx-j;v^EAhS%YZ49|x9y06I~Z zF)qnPmB^aw3-g8WK;x9kikUJ!*+vuaw08p$ZcL=$>T;HgZznn7>U?>AXEe=eO=SDn z!l#QkcOrIGPb(lYGYHE_=L}t5W4Kwtg%yyoiR{l;UZarsmjnbI!-kaec{zu-*tCrr zem98N^B27|_udJ8&msc}&QR?QLqiPTw z#ciq+v#QE4yFZlIq>-AsJh8gEJj!TGI&COh%DDq8fJPw6r5k5e;UOWKS;F`yTv%?%%S)#*^<{0DvpV=| z=lk~A``jO6k4L)y+568B2*my9kuSf3K;AcjALPSTV9QTix8p$Jmj6{`ID|8@c@_fk zBOU$nU_xQ7PENX_B#_nfmd~o6{`~WkV>`XJAm97!VDNW8x}i{nmP>zI-N_3lYWKkudVIlSH}W}`YU|2mmbk1 z0bxaCvT>x_)4Uh7^X&oCwiz`)BS&s}9Vo$d$P)!-RF&k9M;Ecv{T1(rPEfGf+00E} zWQrbbK&PNPmxc&>m=h}}SUNn+N#F##UV2BgRcPf(beFag#maYd)xfFFbJwyDY)@*g zk7#cfPZniu*2ICTC-0fH>#ft3q3tT6pbY?uF|lUJOP~ySZ(yE80)8Wk>NQL)r<;@0 zwWK-}x(*z&d94GOi))Z!#(qDHK!}b1`f&*W$_|o^3F2QqkHRFNCO+}|5Y+Q1r&KLA zMqdX2VCvVu7=^Y+AwEH8_CCA}_T4*Y90YQg?K3{KQZRM%S8L1x7JtoduxZf~bKt!5 z-xf-DWr@QeIBmOKjpy#G+Og?}sE=vjyv?!KXij_k@Fypr5|=0(`>M3~mDDSXKQicD9$VcMPztI2qL z#lG2K?1Ss@q|!1EmOD+6x-G{su8~VNNOk#&6Oldo0-)vfV~sxL1k13L!X~}+X90#( zceqQTOc)@O(QN5t82`Av52jVnhMT5U)b@yD^!NckXsZNXg4*G_aM8-ASKfjF4f_EA zLmsedbhFG~w;}bD@KX;ScA0(}(%iUw9Jqg|OjOsQj6!7h(h5Nc?69eL^ zoSv32TeZ)^tYP_3dts*;zZuxTtiZ7(>+Nj)D@7yK-1j-o?O&&ZnwzlJ1;g_+1fro5 z&h~TKi_G_K#jw|TWl3|@)b^`X-TUTotul6^BP2f3K4@6*4L0haPIm+ICYs-@cRXil z=3%a$Mpu;(89#9&bR^d#n#L^MVdw}7)j~G3#QrqdGU{She(y?ZsAM1y)u0OYWsZ(c zLEl~r7L^9qrd*bEyH&i3&3XK=U(|-o!H#X}9WvxH%3hTsn&RjJ7f@E=b1Mw`rX@_r=wmQQiT8Q=)$0CI6E^bX^ z3=;qrUKp{um>gi3I!lzG$<{z^`4@_`ii!%} z+m->16zyV}b$BYP9XF8(cS@=3fyAcUNMVV2B!4@qy-ja>LPj8Xb50xf;ScKwMx}$S z6k6oy($$`vkGukU?T_ArK$6=MqHy^Z~Y&p;95Z8Y)Oji3qTOy7-#NMuci#~5y zcO6EAO~*|KNMukf(A|xtVY4@ewDs2(5<`jFo3IW-$Q;ibX0s<78v^x{l2$fJJ>g;j z?Kw$^iXD+5aODk6O3A=ZwN8_i7>)B6$^;_-158^;#!F)8Wao#-a&MyLe^1yTtcrG1gbU;_Z1PM5W$N4n#@ zW!xnYP0#smYplBKbPk6{)8Ao}CQanp-)BF7+ikWd0+Zu4se>R*SKhrNvCpglO%?`M zRTn?q&HH-k8NYIT0q97aZf>tBUf?Y=*%svColxp?9lID8AZld5V-bi@SQk2v6Pmrz z{w#N5v{~Ac_6=Iv3QLuz@}{cY34?E>y|0`FwbV4e_Pml?AkbPcQxCpJAj~{2l`JiF z%BeAU!+mEMxYF-j*F0odGu={dQ-9xpg&pdk>-%WkD{u#xzRPG zqtHEmi4~vrix-3W0?7_*g!zoTN2x~HrURuj)mf6q(~1P~uDc$Lvw4nN@Vorvt&F$7 z3{-Ze0#1+Z>5!(HROpgG5Iw(abG^A>}{vDbYXA$pBWi=*a} zm3&Wnius#pMi}1%ZlA;8@mx=;2}tG_6Dn|cg9Qq@V&lfuI;_?kz|5Yvh+cJ&@1_~= z`zOX&RI=t*DDz?+Rl+rfw!VUyAEx?q|DILS=6!+2X8RQY}-$&h1gzz@ap zqn2>hz95feJ(=?(9b!SOy01feWgMQ;fF>8~o+}U&bW#_cly|DGaH;?^I(kaH^f*|{ z1X-_ap-sjOr#DJ=3yR245C8a7?NxCDx>!9z`@G32pvDc<8kzg&e z1UolwsK=QO-OVR+i~Val`YAXLA}F`6H)&xotgJ0t$T%4(-uAhMM6#-wK=yc} z*g7(DC}b_Dwt1tYYsisWFqvGZm5S|4Mhj)`lf0oF9SK8^B{eWZ`JvF|%H!j^6n;2W z9so}@s)kwDbGn$Sr`7SFMwD-y>9l6NdVGm6`1f0)^B=6i=qEb~!#u`*(WfA8?zxR| zkuxknRTtC|Ryc3dEN&V{?>>k|@J{u8!CM@Q)Os;`di6b8)P>^Bpqaf}tiE7z1G)Fx zG*%1BcT+69!@6T-s&`PltvGSTiT^|GzXG2X_^iO^UB0HRFlvQSD~wuU)Vp|@Ux_6v vv1BEdti+O)SOUIZ{MY|C|1YEHsB3lbss|aRH_2#l?~tR1kAKMtKY9K?@9s|q diff --git a/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-light-colorblind-focus-linux.png b/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-light-colorblind-focus-linux.png index a95ed49f6ea677426f249162c0bf6326530a1682..c714d8833ec89173dffc6bad80f79e9ae66a8442 100644 GIT binary patch literal 6274 zcmeHL=~vTN8va?TcHGc9lxZk%Y>!nNAyqaZ0&1mH8KAN+CP5g3m_S5!LXuW0RuFN6 zRDv?a0Wk!^7D8AO1pxs8WeeFMVG{@;fh3ZUWWxLl`k`|UAMTg;-1ppj-upby``q{D z&pw_zw}G|+0I>7SPpAC=z+}a^fX_ZLMmC>rb^(Chz?su015%3?1lT+IQy04Abqlsm zoA!ND@{c{I_dtFDT^|ogh%fT7{9(ieRFmB~dI|84irSG4atN#ZHfaCNo3}^5ExhDE zZ?nY#MBTIR>#$E5Q9oDpCYg`H#+bdIN9Jq44EVACdakeB@{X%K9n4BTHeOgw~pp`O+cd*4>w(5`Dqj9QU=A7e{(|rp000 zP!I@Y%bG4T8*w0|zsMfhX~}Y?vCZs2ptDf^-o4}l1C=(h629$M>2ka)marz*jK%K< zfU!Mp`Mj%0mKvYr?M)JdVdgfQ=ECr=gf>VUGB(+-Pu?0%OfDx8!j+YK-G(Z>mIoVN zOG)iF9^*#G#p9(!gnre8rFm;fcgR)GB}M!V0Fr;Rv{uUkM@+F1!iu3F8u>7^3ENA6 zCJfk0dx%x%o7wa-0UkMXZonqAI>Wmd7sgM${B(Iy7qLpFxw<&8qJJW~^773IShbf| zj`{155cZR(DWqGZc=8a=RNDH`oUc_3QdJmeEFKoWG|>nFg9XaA!SEl00|TjF4paLc zWvbEV&IR2P=@;%b!$XcnpTQ0E=bPjIY-e=UgSVJAU!LyhN;zg26XZzf)59tjezb@n zFo5ZX7(Qj>sbNxgU$dy{-L4X97)qBby8W*bgalS?iFn!Mc3ip(L4U2+^HzqG#s+@} z)_j#b8yJkI&kJ)g+Hr_=`ilI(ffi*@-lBk&EWC5_z@v~IaDv3Ux4^dIS*UXow>}>n zjMEMh3fC4Nh+Nfc_e#B3awXxoWM%zj7uu4S9&59*2LBD6roh5Mb|t(AGIe?Q(3J^Y zLXNyF3m@m_eMGM@6Lj{4Db*>2rP*qnu&E3iFP?r%&aTVzEoUn3UVpMh=B&%In%Tj} z*%d3Mc{233i$(-kLQx}!JnMDM-|*aA@E!LGX%;?}RhU#{q;iOup}IMt{rmmZ7el$E z94jk&#v}pqRobA{a-4Lv-Oj~S*U=&#a8gb~;0+jFL0>jSbcutBkF!=Q|7_>g$1L9< z7F#E2O^FbMo4BVU=fyiaBtH7(E5b1YpWcH*IEZ zTJFibI(U-^!+Xq+R$5^vC&J^JCde`vB9PYjDe&AD4V5smlWXiDTgDXDF-U-7d1(U)?lV20HHMK@eX2{=4x{f4oMux<*kDM7rYBnGGp?vKV^ zFm%g^=U1+)bl7^dsEJbzp^Jt+*&}S0UD{;HMBL2Ny3p-Idpf zJ|1-ec6Y%e#3@%j8xTsF8mJ@cqsVSvWX;h=+3%oDYMhP&8PZBu2 zZWvBsS!RBc1;f>QSy4L}bP%q}9D*XQ@HIP~Ng7{@FawpKh3gae3h|9=b+X6d#~pVt z-4aI5X=#Udedlg}zt-KlifouI0@LH#crNrjl(1wKj!jBacFedP#j|~744J;zu+Wua zl|H+#V~FLdTrsLWx_g%}fpB7`htWHF7{qqPyF2c!@)CkD@{747 zt|{+dCq&wwL6$R`ejqDFuyia#^o6>$79Z{NR+nRF*-YpcYg&0uK9rE&;-fMMy`hLv z9cSpztE)r6cXA$Pbor|@Pup*eGi)Rd|H2NDnB1ILyc#~W3qw?i8)3w*cbPnCvy zI`vn+e>Ty@LL4neNaBj9R)R%ii-`_)nAYa_2kGNgbNl9PXQaX!MgB9e*Su|e>-_co z7H*~fJO!h7E|?NGR>p`8U@>tTmGc63KJGj-(gOw)q1+o-@7)~04Ow*Z{df?5(;<2ZFJD(-u84_djWK)_QBi<9Cx85tSb#+ox6GPaQ#k?}U@(HwxsnST8iKmj1`krsnQJYod z$SX?^qmRocF<47J-T$c%U64Ms=Z3m@K>3}y)v1VU{{^NTea`>@ literal 7693 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 literal 7691 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 HjlcX)j5qH3 diff --git a/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-light-high-contrast-focus-linux.png b/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-light-high-contrast-focus-linux.png index 29efc80181af1247a0a310d85c5efbbbe3fa5b1e..4b4fe3ca9916361e80b77f2b9a57a2726ee6c7dc 100644 GIT binary patch literal 6324 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 literal 7687 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 literal 7693 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 { + 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 ( +
    +
    + + + + +
    + +
    +

    + Dynamic breadcrumbs +

    + + {items.map((item, index) => ( + + {item.name} + + ))} + +
    + +
    + Current items: {items.length} | Try adding/removing items to see how overflow behavior changes +
    +
    + ) +} + +export const OverflowMenuNarrowContainer = () => ( +
    + + Home + Products + Category + Subcategory + + Current Page + + +
    +) + +// Wrapper components to test that BreadcrumbsItem works when wrapped +const StyledWrapper = ({children}: {children: React.ReactNode}) => ( + {children} +) + +const ConditionalWrapper = ({children, condition}: {children: React.ReactNode; condition: boolean}) => { + return condition ? {children} : <>{children} +} + +const DataAttributeWrapper = ({children}: {children: React.ReactNode}) => ( + + {children} + +) + +export const WrappedBreadcrumbItemsWithOverflow = () => ( + + + Wrapped Home + + + Products + + + Category + + + Subcategory + + + Item + + + Details + + + Current Page + + +) + +export const WithEditableNameInput = () => ( + + Home + Documents + Project Alpha + + + + +) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx index 18dfff2137e..1e8d695d3a4 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx @@ -1,9 +1,8 @@ import type {Meta} from '@storybook/react-vite' import type React from 'react' -import {useState} from 'react' import type {ComponentProps} from '../utils/types' import Breadcrumbs from './Breadcrumbs' -import TextInput from '../TextInput' +import {FeatureFlags} from '../FeatureFlags' export default { title: 'Components/Breadcrumbs/Features', @@ -24,7 +23,23 @@ export const OverflowWrap = () => ( ) -export const OverflowMenu = () => ( +export const OverflowMenuFeatureFlagEnabled = () => ( + + + Home + Products + Category + Subcategory + Item + Details + + Current Page + + + +) + +export const OverflowMenuFeatureFlagDisabled = () => ( Home Products @@ -38,7 +53,7 @@ export const OverflowMenu = () => ( ) -export const OverflowMenuShowRoot = () => ( +export const OverflowMenuShowRootFeatureFlagDisabled = () => ( github Teams @@ -51,157 +66,47 @@ export const OverflowMenuShowRoot = () => ( ) -export const OverflowMenuNarrowContainer = () => ( -
    - - Home - Products - Category - Subcategory +export const OverflowMenuShowRootFeatureFlagEnabled = () => ( + + + github + Teams + Engineering + core-productivity + collaboration-workflows-flex - Current Page + global-navigation-reviewers -
    + ) -// Wrapper components to test that BreadcrumbsItem works when wrapped -const StyledWrapper = ({children}: {children: React.ReactNode}) => ( - {children} -) - -const ConditionalWrapper = ({children, condition}: {children: React.ReactNode; condition: boolean}) => { - return condition ? {children} : <>{children} -} - -const DataAttributeWrapper = ({children}: {children: React.ReactNode}) => ( - - {children} - -) - -export const WrappedBreadcrumbItemsWithOverflow = () => ( - - - Wrapped Home - - +export const SpaciousVariantWithOverflowMenu = () => ( + + + Home Products - - Category - - Subcategory - - Item - - Details - - - Current Page - - + + Current Page + + + ) -export const WithEditableNameInput = () => ( - +export const SpaciousVariantWithOverflowWrap = () => ( + Home - Documents - Project Alpha - - + Products + Category + Subcategory + Item + Details + + Current Page ) - -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 ( -
    -
    - - - - -
    - -
    -

    - Dynamic breadcrumbs -

    - - {items.map((item, index) => ( - - {item.name} - - ))} - -
    - -
    - Current items: {items.length} | Try adding/removing items to see how overflow behavior changes -
    -
    - ) -} diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index dc6f8db5d33..c8772e56e8d 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -10,85 +10,12 @@ margin-bottom: 0; } -[data-overflow='menu'] .BreadcrumbsList, -[data-overflow='menu-with-root'] .BreadcrumbsList { - white-space: nowrap; - display: flex; - flex-direction: row; -} - -.BreadcrumbsItem { - display: inline-grid; - grid-auto-flow: column; - align-items: center; - flex: 0 99999 auto; - min-width: auto; - font-size: var(--text-body-size-medium); - white-space: nowrap; - list-style: none; - - &:first-child { - margin-left: 0; - } - - &:last-child { - .ItemSeparator { - display: none; - } - } -} - -[data-overflow='menu'] .Item, -[data-overflow='menu-with-root'] .Item { - display: inline-block; - font-size: var(--text-body-size-medium); - color: var(--fgColor-default); - text-decoration: none; - padding-inline: var(--base-size-6); - padding-block: var(--base-size-4); - border-radius: var(--borderRadius-medium); - - &:hover { - background: var(--control-transparent-bgColor-hover); - text-decoration: none; - } - - &:focus { - @mixin focusOutline; - } -} - -.MenuDetails { - position: relative; - display: inline-block; -} - -summary { - list-style: none; - cursor: pointer; - outline: none; -} - -summary::-webkit-details-marker { - display: none; -} - -.MenuOverlay { - position: absolute; - z-index: 1; - box-shadow: var(--shadow-resting-small); - border-radius: var(--borderRadius-large); - background-color: var(--overlay-bgColor); - list-style: none; - min-width: var(--overlay-width-xsmall); - max-height: 100vh; - max-width: var(--overlay-width-small); - overflow: hidden; -} - -@media (prefers-reduced-motion: no-preference) { - .MenuOverlay { - animation: overlay-appear 200ms cubic-bezier(0.33, 1, 0.68, 1); +[data-overflow='menu'], +[data-overflow='menu-with-root'] { + & .BreadcrumbsList { + white-space: nowrap; + display: flex; + flex-direction: row; } } @@ -132,20 +59,106 @@ summary::-webkit-details-marker { .Item { display: inline-block; font-size: var(--text-body-size-medium); - color: var(--fgColor-link); - text-decoration: none; - &:hover, - &:focus { - text-decoration: underline; + &:focus-visible { + @mixin focusOutline; } } -.ItemSelected { - color: var(--fgColor-default); - pointer-events: none; +[data-variant='normal'] { + & .Item { + color: var(--fgColor-link); + text-decoration: none; + + &:not([aria-current]) { + &:hover { + text-decoration: underline; + } + } + + &:focus-visible { + text-decoration: none; + } + + &[aria-current] { + color: var(--fgColor-default); + } + } +} - &:focus { +[data-variant='spacious'] { + & .Item { + color: var(--fgColor-default); text-decoration: none; + padding-inline: var(--base-size-6); + padding-block: var(--base-size-4); + border-radius: var(--borderRadius-medium); + + &:hover { + background: var(--control-transparent-bgColor-hover); + text-decoration: none; + } + + &[aria-current] { + font-weight: var(--base-text-weight-semibold); + } + } +} + +.BreadcrumbsItem { + display: inline-grid; + grid-auto-flow: column; + align-items: center; + flex: 0 99999 auto; + min-width: auto; + white-space: nowrap; + list-style: none; + + /* allow menu items to wrap line */ + &:has(.MenuOverlay) { + white-space: normal; + } + + &:first-child { + margin-left: 0; + } + + &:last-child { + .ItemSeparator { + display: none; + } + } + + .MenuDetails { + position: relative; + display: inline-block; + + & summary { + list-style: none; + cursor: pointer; + outline: none; + + &::-webkit-details-marker { + display: none; + } + } + } + + .MenuOverlay { + position: absolute; + z-index: 1; + box-shadow: var(--shadow-floating-small); + border-radius: var(--borderRadius-large); + background-color: var(--overlay-bgColor); + min-width: var(--overlay-width-xsmall); + max-height: 100vh; + max-width: var(--overlay-width-small); + overflow: hidden; + /* stylelint-disable-next-line primer/spacing */ + top: calc(var(--overlay-offset) + var(--control-small-size)); + + @media (prefers-reduced-motion: no-preference) { + animation: overlay-appear 200ms cubic-bezier(0.33, 1, 0.68, 1); + } } } diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 1d62fe02400..5ca42038863 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -17,12 +17,11 @@ import {useOnEscapePress} from '../hooks/useOnEscapePress' import {useOnOutsideClick} from '../hooks/useOnOutsideClick' import {useFeatureFlag} from '../FeatureFlags' -const SELECTED_CLASS = 'selected' - export type BreadcrumbsProps = React.PropsWithChildren< { className?: string overflow?: 'wrap' | 'menu' | 'menu-with-root' + variant?: 'normal' | 'spacious' } & SxProp > @@ -139,7 +138,7 @@ const getValidChildren = (children: React.ReactNode) => { return React.Children.toArray(children).filter(child => React.isValidElement(child)) as React.ReactElement[] } -function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap'}: BreadcrumbsProps) { +function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', variant = 'normal'}: BreadcrumbsProps) { const overflowMenuEnabled = useFeatureFlag('primer_react_breadcrumbs_overflow_menu') const wrappedChildren = React.Children.map(children, child =>
  • {child}
  • ) const containerRef = useRef(null) @@ -333,11 +332,18 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap'}: Bread sx={sxProp} ref={containerRef} data-overflow={overflow} + data-variant={variant} > {finalChildren} ) : ( - + {wrappedChildren} ) @@ -365,10 +371,7 @@ const BreadcrumbsItem = React.forwardRef(({selected, className, ...rest}, ref) = return ( { expect(menuButton).toHaveFocus() }) }) + + describe('variant prop (feature flag on)', () => { + it('sets data-variant="normal" by default', () => { + const {container} = renderWithTheme( + + Home + + Docs + + , + {primer_react_breadcrumbs_overflow_menu: true}, + ) + expect(container.firstChild).toHaveAttribute('data-variant', 'normal') + }) + + it('sets data-variant="spacious" when variant prop provided', () => { + const {container} = renderWithTheme( + + Home + + Docs + + , + {primer_react_breadcrumbs_overflow_menu: true}, + ) + expect(container.firstChild).toHaveAttribute('data-variant', 'spacious') + }) + }) }) diff --git a/packages/react/src/Breadcrumbs/__tests__/__snapshots__/BreadcrumbsItem.test.tsx.snap b/packages/react/src/Breadcrumbs/__tests__/__snapshots__/BreadcrumbsItem.test.tsx.snap index 1fac9c17f5b..532e0315ca2 100644 --- a/packages/react/src/Breadcrumbs/__tests__/__snapshots__/BreadcrumbsItem.test.tsx.snap +++ b/packages/react/src/Breadcrumbs/__tests__/__snapshots__/BreadcrumbsItem.test.tsx.snap @@ -3,6 +3,6 @@ exports[`Breadcrumbs.Item > respects the "selected" prop 1`] = `
    `; From 7ff66f296660449e1351094423ca5b74d98162db Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 8 Sep 2025 12:11:34 +1000 Subject: [PATCH 48/54] Couple of css tweaks --- packages/react/src/Breadcrumbs/Breadcrumbs.module.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css index c8772e56e8d..b4d53bfef01 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.module.css @@ -61,7 +61,7 @@ font-size: var(--text-body-size-medium); &:focus-visible { - @mixin focusOutline; + @mixin focusOutline 1px; } } @@ -136,7 +136,6 @@ & summary { list-style: none; cursor: pointer; - outline: none; &::-webkit-details-marker { display: none; From 9e839573f1ed56e204f9a8f8c3b08773dc5e634b Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 8 Sep 2025 12:13:42 +1000 Subject: [PATCH 49/54] Remove unnecessary dependencies --- .../react/src/Breadcrumbs/Breadcrumbs.tsx | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 5ca42038863..4f87c017f76 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -47,26 +47,23 @@ const BreadcrumbsMenuItem = React.forwardRef { - // Prevent the button click from bubbling up and interfering with details toggle - event.preventDefault() - // Manually toggle the details element - if (detailsRef.current) { - const newOpenState = !detailsRef.current.open - detailsRef.current.open = newOpenState - setIsOpen(newOpenState) - } - }, - [detailsRef], - ) + const handleSummaryClick = useCallback((event: React.MouseEvent) => { + // Prevent the button click from bubbling up and interfering with details toggle + event.preventDefault() + // Manually toggle the details element + if (detailsRef.current) { + const newOpenState = !detailsRef.current.open + detailsRef.current.open = newOpenState + setIsOpen(newOpenState) + } + }, []) const closeOverlay = useCallback(() => { if (detailsRef.current) { detailsRef.current.open = false setIsOpen(false) } - }, [detailsRef]) + }, []) const focusOnMenuButton = useCallback(() => { iconButtonRef.current?.focus() From 1249ae8a5a86cf435859ba9f4288fcb5059267d4 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 8 Sep 2025 12:22:22 +1000 Subject: [PATCH 50/54] Add some doc comments --- packages/react/src/Breadcrumbs/Breadcrumbs.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 4f87c017f76..c49d4356688 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -16,11 +16,26 @@ import type {ResizeObserverEntry} from '../hooks/useResizeObserver' import {useOnEscapePress} from '../hooks/useOnEscapePress' import {useOnOutsideClick} from '../hooks/useOnOutsideClick' import {useFeatureFlag} from '../FeatureFlags' +import {overflow} from 'styled-system' export type BreadcrumbsProps = React.PropsWithChildren< { + /** + * Optional class name for the breadcrumbs container. + */ className?: string + /** + * Controls the overflow behavior of the breadcrumbs. + * By default all overflowing crumbs will "wrap" in the given space taking up extra height. + * In the "menu" option we'll see the overflowing crumbs as part of a menu like dropdown instead of the root breadcrumb. + * In "menu-with-root" we see that instead of the root, the menu button will take the place of the next breadcrumb. + */ overflow?: 'wrap' | 'menu' | 'menu-with-root' + /** + * Controls the visual variant of the breadcrumbs. + * By default, the breadcrumbs will have a normal appearance. + * In the "spacious" option, the breadcrumbs will have increased padding and a more relaxed layout. + */ variant?: 'normal' | 'spacious' } & SxProp > From f9ec096bfa9659f14f18a8859c39651567a2364a Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 8 Sep 2025 12:22:45 +1000 Subject: [PATCH 51/54] fix --- packages/react/src/Breadcrumbs/Breadcrumbs.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index c49d4356688..4cfce288c30 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -16,7 +16,6 @@ import type {ResizeObserverEntry} from '../hooks/useResizeObserver' import {useOnEscapePress} from '../hooks/useOnEscapePress' import {useOnOutsideClick} from '../hooks/useOnOutsideClick' import {useFeatureFlag} from '../FeatureFlags' -import {overflow} from 'styled-system' export type BreadcrumbsProps = React.PropsWithChildren< { From f43f6e6105104e93209c5a37944579f75c2e1ae4 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 8 Sep 2025 16:01:25 +1000 Subject: [PATCH 52/54] Fix esc button focus --- packages/react/src/Breadcrumbs/Breadcrumbs.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 4cfce288c30..1e1504dd609 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -52,6 +52,8 @@ const BreadcrumbsMenuItem = React.forwardRef { const [isOpen, setIsOpen] = useState(false) const detailsRef = useRef(null) + const menuButtonRef = useRef(null) + const menuContainerRef = useRef(null) const detailsRefCallback = useCallback( (element: HTMLDetailsElement | null) => { detailsRef.current = element @@ -80,12 +82,12 @@ const BreadcrumbsMenuItem = React.forwardRef { - iconButtonRef.current?.focus() + // this menubutton ref doesnt seem to set current element. + // The first child of details is the summary element + detailsRef.current?.firstChild?.focus() + //menuButtonRef.current?.focus() }, []) - const iconButtonRef = useRef(null) - const menuContainerRef = useRef(null) - useOnEscapePress( (event: KeyboardEvent) => { if (isOpen) { @@ -100,7 +102,7 @@ const BreadcrumbsMenuItem = React.forwardRef Date: Mon, 8 Sep 2025 16:51:38 +1000 Subject: [PATCH 53/54] Update snapshots --- ...bs-Default-dark-colorblind-focus-linux.png | Bin 6335 -> 6360 bytes ...crumbs-Default-dark-dimmed-focus-linux.png | Bin 6306 -> 6349 bytes .../Breadcrumbs-Default-dark-focus-linux.png | Bin 6335 -> 6360 bytes ...Default-dark-high-contrast-focus-linux.png | Bin 6329 -> 6381 bytes ...bs-Default-dark-tritanopia-focus-linux.png | Bin 6335 -> 6360 bytes ...s-Default-light-colorblind-focus-linux.png | Bin 6274 -> 6338 bytes .../Breadcrumbs-Default-light-focus-linux.png | Bin 6274 -> 6338 bytes ...efault-light-high-contrast-focus-linux.png | Bin 6324 -> 6339 bytes ...s-Default-light-tritanopia-focus-linux.png | Bin 6274 -> 6338 bytes 9 files changed, 0 insertions(+), 0 deletions(-) diff --git a/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-dark-colorblind-focus-linux.png b/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-dark-colorblind-focus-linux.png index 1f2c9c4df145351816af7c6b66c79f8b6ab0b4a8..abac0d357a0009ffbd3d14cb136305d64dbe1f64 100644 GIT binary patch delta 2534 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 delta 2399 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 literal 6274 zcmeHL=~vTN8va?TcHGc9lxZk%Y>!nNAyqaZ0&1mH8KAN+CP5g3m_S5!LXuW0RuFN6 zRDv?a0Wk!^7D8AO1pxs8WeeFMVG{@;fh3ZUWWxLl`k`|UAMTg;-1ppj-upby``q{D z&pw_zw}G|+0I>7SPpAC=z+}a^fX_ZLMmC>rb^(Chz?su015%3?1lT+IQy04Abqlsm zoA!ND@{c{I_dtFDT^|ogh%fT7{9(ieRFmB~dI|84irSG4atN#ZHfaCNo3}^5ExhDE zZ?nY#MBTIR>#$E5Q9oDpCYg`H#+bdIN9Jq44EVACdakeB@{X%K9n4BTHeOgw~pp`O+cd*4>w(5`Dqj9QU=A7e{(|rp000 zP!I@Y%bG4T8*w0|zsMfhX~}Y?vCZs2ptDf^-o4}l1C=(h629$M>2ka)marz*jK%K< zfU!Mp`Mj%0mKvYr?M)JdVdgfQ=ECr=gf>VUGB(+-Pu?0%OfDx8!j+YK-G(Z>mIoVN zOG)iF9^*#G#p9(!gnre8rFm;fcgR)GB}M!V0Fr;Rv{uUkM@+F1!iu3F8u>7^3ENA6 zCJfk0dx%x%o7wa-0UkMXZonqAI>Wmd7sgM${B(Iy7qLpFxw<&8qJJW~^773IShbf| zj`{155cZR(DWqGZc=8a=RNDH`oUc_3QdJmeEFKoWG|>nFg9XaA!SEl00|TjF4paLc zWvbEV&IR2P=@;%b!$XcnpTQ0E=bPjIY-e=UgSVJAU!LyhN;zg26XZzf)59tjezb@n zFo5ZX7(Qj>sbNxgU$dy{-L4X97)qBby8W*bgalS?iFn!Mc3ip(L4U2+^HzqG#s+@} z)_j#b8yJkI&kJ)g+Hr_=`ilI(ffi*@-lBk&EWC5_z@v~IaDv3Ux4^dIS*UXow>}>n zjMEMh3fC4Nh+Nfc_e#B3awXxoWM%zj7uu4S9&59*2LBD6roh5Mb|t(AGIe?Q(3J^Y zLXNyF3m@m_eMGM@6Lj{4Db*>2rP*qnu&E3iFP?r%&aTVzEoUn3UVpMh=B&%In%Tj} z*%d3Mc{233i$(-kLQx}!JnMDM-|*aA@E!LGX%;?}RhU#{q;iOup}IMt{rmmZ7el$E z94jk&#v}pqRobA{a-4Lv-Oj~S*U=&#a8gb~;0+jFL0>jSbcutBkF!=Q|7_>g$1L9< z7F#E2O^FbMo4BVU=fyiaBtH7(E5b1YpWcH*IEZ zTJFibI(U-^!+Xq+R$5^vC&J^JCde`vB9PYjDe&AD4V5smlWXiDTgDXDF-U-7d1(U)?lV20HHMK@eX2{=4x{f4oMux<*kDM7rYBnGGp?vKV^ zFm%g^=U1+)bl7^dsEJbzp^Jt+*&}S0UD{;HMBL2Ny3p-Idpf zJ|1-ec6Y%e#3@%j8xTsF8mJ@cqsVSvWX;h=+3%oDYMhP&8PZBu2 zZWvBsS!RBc1;f>QSy4L}bP%q}9D*XQ@HIP~Ng7{@FawpKh3gae3h|9=b+X6d#~pVt z-4aI5X=#Udedlg}zt-KlifouI0@LH#crNrjl(1wKj!jBacFedP#j|~744J;zu+Wua zl|H+#V~FLdTrsLWx_g%}fpB7`htWHF7{qqPyF2c!@)CkD@{747 zt|{+dCq&wwL6$R`ejqDFuyia#^o6>$79Z{NR+nRF*-YpcYg&0uK9rE&;-fMMy`hLv z9cSpztE)r6cXA$Pbor|@Pup*eGi)Rd|H2NDnB1ILyc#~W3qw?i8)3w*cbPnCvy zI`vn+e>Ty@LL4neNaBj9R)R%ii-`_)nAYa_2kGNgbNl9PXQaX!MgB9e*Su|e>-_co z7H*~fJO!h7E|?NGR>p`8U@>tTmGc63KJGj-(gOw)q1+o-@7)~04Ow*Z{df?5(;<2ZFJD(-u84_djWK)_QBi<9Cx85tSb#+ox6GPaQ#k?}U@(HwxsnST8iKmj1`krsnQJYod z$SX?^qmRocF<47J-T$c%U64Ms=Z3m@K>3}y)v1VU{{^NTea`>@ diff --git a/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-light-focus-linux.png b/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-light-focus-linux.png index c714d8833ec89173dffc6bad80f79e9ae66a8442..28540b3c2d32df420523b4f9d4daa9fcccd4c417 100644 GIT binary patch literal 6338 zcmeI1TT~P07RSeOlvd@^(*g)9404J`Y0N}&j;!VJ3`F}DvT&khY1?)!?lEv(Y{{ny? ztIFR$t06O0_vBzTMrdaQ0MoGUK`#KvH&yCC`~=qk`0B${69Cv9Rz}^a(=u+^SOnN_ z0>1jiu}GsORU(Iu$e1>nNiSynd-hcvKVW2JgpGua-4a%oTGwC5Jc)(+EuebK3JTr? zE8pD_N{@hjac(WLf~eN;>THqs|!zXjChY>9;?ZC?}qb zH)p1g+^lmWM04H*r^}#qU1+(5Is+QYu4+SrS+9@ydlOj*{x}G;K26R}>xcNwqBY{s z4}ctEjHq907^JZ9JSI|{8Da-kSih-L(5u@|DvkNuuq3XOb34VG!ijV`c&7>Bqr->u z5V6cAi6%+fSl&sacEG<;TDzB7U{E6t+e!>-UJ^yT$CrPNu`aZkxR?k1B4aIolV*kU zVwV_|{+79gQhk(0Vug5!hX%ie)0>wn5(xwXXqRg}4_~|Zn(*N14!TygS%2kxU#|K% z&wfO6cCN`-^nF-*?#%Ot?$t0vf2;<-lafcOcA1^Z&fVXOiwTYu$=He^8 zTe?uB^`e}ew%H+}KS+B|np|Y#y-=^>u_l<=Qdb9i2eqU{5(LU_>Gv;n(!6%E&0Z*~ z?dy>FOQJfvvR8hZz2Q}}qhq}fXYLpgc&;gJ=Fu5n>>=8_8H|l?J%6fmX(spTHoB@5 zpTYkuPMiJ|baVjs0vJ7o=rZgB{fJA{=Pi^q{%f-eiRtl2z=c`|l? zlITH!q;`Yctr@j`KWjwv^hmT8HEXvbqK!@sIx++$fp%SdH4b3+oJB7&t70U%y z{tN9Q3;W`8TWf9EQfUje%o3}zz`M2foH3+0?ejz^UGk8W>IrkJ{DO#{wX15|M~^K_fo`av&`8s0tV(#=1CN>P&OG5sl=?N{9N+2^eMr zQTgd(2nngYH*8%_Q1fWP=CnvQ(FPj&1LMiCaKy~aaZm_y$(AUzp_CCiNT#hNFgku5 zqqvLi9KJY6qRCk#2<}b6Z^ZrDIa_mHW>`xNZ+BsOhmLZ!_w>~o9-}1l;MJ|_6l@#|it5HQfWeS6&ZhYfMlYykDepIr^cwh27Ryt%(WJkRz*t z-{K7=jCR&95#W9Cn8}D>y12>#=lXUS&ryd_liOx*@S-uQtLc4`_4!Vv_mZqyyZQ>P zgW73(riT&`*_Hh+AN<0{&7s^P-A_&Oh)VK@Aw zjyUz~^wqu+wvv8V=k+2UG`hCq1l4xH2LV=0w-nTttPy7&z0L8?p+7sqG2r4vE!JT&a4hPb`(fB&Kx5H9{V}@tBd)&Q_ z#*Q`y)qbiMSj+LTV=Xg*JvTA*>DDWypxLa&pNvgPLp(bPUra^ctC!y#!_;{-9Q#)j9r82V)_RIRQO<5NS3p4R_{>45{>`UtT@#Bdao2?-Np9E5?$O zLB^~$#lxwE(KNza!R>Zq)TNclo>~UOIRc)Awd)TghbVYCG12o`D)OA?apXXpg*<7U z>mm@_@=_HPrfurG(eZKTJQM3Q9tC8USBpmu-VV@GC6fm28H-2`(bc+M_srLc7E7as zs|vJwM{od~GE&?{LLb6bFC)bmXsG48D9 z6PV;DF<4^ko^*b`N!aA+s@KnEoVBugZAM#(``rKlaP{TnHbVe7?)V2^^Fq+8wacp8 z1pxO|Pv2XRYl8J_W_qr6Ci4HD1Ny^@{;Yx8xcx7C&yCb=r0y@YEF0KtVDrzw#xG#c zioRI}zn=yC-9~(a>J6&@8B}kO{MW~68!FyV@rH{3{#bfLNB>VpYqt6y5IP-Tb%LTl d+4A0Y7y!0Dp$M%wt6Coc&iMGBBAqy&`(MfBA+`Vj literal 6274 zcmeHL=~vTN8va?TcHGc9lxZk%Y>!nNAyqaZ0&1mH8KAN+CP5g3m_S5!LXuW0RuFN6 zRDv?a0Wk!^7D8AO1pxs8WeeFMVG{@;fh3ZUWWxLl`k`|UAMTg;-1ppj-upby``q{D z&pw_zw}G|+0I>7SPpAC=z+}a^fX_ZLMmC>rb^(Chz?su015%3?1lT+IQy04Abqlsm zoA!ND@{c{I_dtFDT^|ogh%fT7{9(ieRFmB~dI|84irSG4atN#ZHfaCNo3}^5ExhDE zZ?nY#MBTIR>#$E5Q9oDpCYg`H#+bdIN9Jq44EVACdakeB@{X%K9n4BTHeOgw~pp`O+cd*4>w(5`Dqj9QU=A7e{(|rp000 zP!I@Y%bG4T8*w0|zsMfhX~}Y?vCZs2ptDf^-o4}l1C=(h629$M>2ka)marz*jK%K< zfU!Mp`Mj%0mKvYr?M)JdVdgfQ=ECr=gf>VUGB(+-Pu?0%OfDx8!j+YK-G(Z>mIoVN zOG)iF9^*#G#p9(!gnre8rFm;fcgR)GB}M!V0Fr;Rv{uUkM@+F1!iu3F8u>7^3ENA6 zCJfk0dx%x%o7wa-0UkMXZonqAI>Wmd7sgM${B(Iy7qLpFxw<&8qJJW~^773IShbf| zj`{155cZR(DWqGZc=8a=RNDH`oUc_3QdJmeEFKoWG|>nFg9XaA!SEl00|TjF4paLc zWvbEV&IR2P=@;%b!$XcnpTQ0E=bPjIY-e=UgSVJAU!LyhN;zg26XZzf)59tjezb@n zFo5ZX7(Qj>sbNxgU$dy{-L4X97)qBby8W*bgalS?iFn!Mc3ip(L4U2+^HzqG#s+@} z)_j#b8yJkI&kJ)g+Hr_=`ilI(ffi*@-lBk&EWC5_z@v~IaDv3Ux4^dIS*UXow>}>n zjMEMh3fC4Nh+Nfc_e#B3awXxoWM%zj7uu4S9&59*2LBD6roh5Mb|t(AGIe?Q(3J^Y zLXNyF3m@m_eMGM@6Lj{4Db*>2rP*qnu&E3iFP?r%&aTVzEoUn3UVpMh=B&%In%Tj} z*%d3Mc{233i$(-kLQx}!JnMDM-|*aA@E!LGX%;?}RhU#{q;iOup}IMt{rmmZ7el$E z94jk&#v}pqRobA{a-4Lv-Oj~S*U=&#a8gb~;0+jFL0>jSbcutBkF!=Q|7_>g$1L9< z7F#E2O^FbMo4BVU=fyiaBtH7(E5b1YpWcH*IEZ zTJFibI(U-^!+Xq+R$5^vC&J^JCde`vB9PYjDe&AD4V5smlWXiDTgDXDF-U-7d1(U)?lV20HHMK@eX2{=4x{f4oMux<*kDM7rYBnGGp?vKV^ zFm%g^=U1+)bl7^dsEJbzp^Jt+*&}S0UD{;HMBL2Ny3p-Idpf zJ|1-ec6Y%e#3@%j8xTsF8mJ@cqsVSvWX;h=+3%oDYMhP&8PZBu2 zZWvBsS!RBc1;f>QSy4L}bP%q}9D*XQ@HIP~Ng7{@FawpKh3gae3h|9=b+X6d#~pVt z-4aI5X=#Udedlg}zt-KlifouI0@LH#crNrjl(1wKj!jBacFedP#j|~744J;zu+Wua zl|H+#V~FLdTrsLWx_g%}fpB7`htWHF7{qqPyF2c!@)CkD@{747 zt|{+dCq&wwL6$R`ejqDFuyia#^o6>$79Z{NR+nRF*-YpcYg&0uK9rE&;-fMMy`hLv z9cSpztE)r6cXA$Pbor|@Pup*eGi)Rd|H2NDnB1ILyc#~W3qw?i8)3w*cbPnCvy zI`vn+e>Ty@LL4neNaBj9R)R%ii-`_)nAYa_2kGNgbNl9PXQaX!MgB9e*Su|e>-_co z7H*~fJO!h7E|?NGR>p`8U@>tTmGc63KJGj-(gOw)q1+o-@7)~04Ow*Z{df?5(;<2ZFJD(-u84_djWK)_QBi<9Cx85tSb#+ox6GPaQ#k?}U@(HwxsnST8iKmj1`krsnQJYod z$SX?^qmRocF<47J-T$c%U64Ms=Z3m@K>3}y)v1VU{{^NTea`>@ diff --git a/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-light-high-contrast-focus-linux.png b/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-light-high-contrast-focus-linux.png index 4b4fe3ca9916361e80b77f2b9a57a2726ee6c7dc..5e6d66f8b8fde7d4eb4ee6db6be5970e2158ebb1 100644 GIT binary patch delta 2460 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 diff --git a/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-light-tritanopia-focus-linux.png b/.playwright/snapshots/components/Breadcrumbs.test.ts-snapshots/Breadcrumbs-Default-light-tritanopia-focus-linux.png index c714d8833ec89173dffc6bad80f79e9ae66a8442..28540b3c2d32df420523b4f9d4daa9fcccd4c417 100644 GIT binary patch literal 6338 zcmeI1TT~P07RSeOlvd@^(*g)9404J`Y0N}&j;!VJ3`F}DvT&khY1?)!?lEv(Y{{ny? ztIFR$t06O0_vBzTMrdaQ0MoGUK`#KvH&yCC`~=qk`0B${69Cv9Rz}^a(=u+^SOnN_ z0>1jiu}GsORU(Iu$e1>nNiSynd-hcvKVW2JgpGua-4a%oTGwC5Jc)(+EuebK3JTr? zE8pD_N{@hjac(WLf~eN;>THqs|!zXjChY>9;?ZC?}qb zH)p1g+^lmWM04H*r^}#qU1+(5Is+QYu4+SrS+9@ydlOj*{x}G;K26R}>xcNwqBY{s z4}ctEjHq907^JZ9JSI|{8Da-kSih-L(5u@|DvkNuuq3XOb34VG!ijV`c&7>Bqr->u z5V6cAi6%+fSl&sacEG<;TDzB7U{E6t+e!>-UJ^yT$CrPNu`aZkxR?k1B4aIolV*kU zVwV_|{+79gQhk(0Vug5!hX%ie)0>wn5(xwXXqRg}4_~|Zn(*N14!TygS%2kxU#|K% z&wfO6cCN`-^nF-*?#%Ot?$t0vf2;<-lafcOcA1^Z&fVXOiwTYu$=He^8 zTe?uB^`e}ew%H+}KS+B|np|Y#y-=^>u_l<=Qdb9i2eqU{5(LU_>Gv;n(!6%E&0Z*~ z?dy>FOQJfvvR8hZz2Q}}qhq}fXYLpgc&;gJ=Fu5n>>=8_8H|l?J%6fmX(spTHoB@5 zpTYkuPMiJ|baVjs0vJ7o=rZgB{fJA{=Pi^q{%f-eiRtl2z=c`|l? zlITH!q;`Yctr@j`KWjwv^hmT8HEXvbqK!@sIx++$fp%SdH4b3+oJB7&t70U%y z{tN9Q3;W`8TWf9EQfUje%o3}zz`M2foH3+0?ejz^UGk8W>IrkJ{DO#{wX15|M~^K_fo`av&`8s0tV(#=1CN>P&OG5sl=?N{9N+2^eMr zQTgd(2nngYH*8%_Q1fWP=CnvQ(FPj&1LMiCaKy~aaZm_y$(AUzp_CCiNT#hNFgku5 zqqvLi9KJY6qRCk#2<}b6Z^ZrDIa_mHW>`xNZ+BsOhmLZ!_w>~o9-}1l;MJ|_6l@#|it5HQfWeS6&ZhYfMlYykDepIr^cwh27Ryt%(WJkRz*t z-{K7=jCR&95#W9Cn8}D>y12>#=lXUS&ryd_liOx*@S-uQtLc4`_4!Vv_mZqyyZQ>P zgW73(riT&`*_Hh+AN<0{&7s^P-A_&Oh)VK@Aw zjyUz~^wqu+wvv8V=k+2UG`hCq1l4xH2LV=0w-nTttPy7&z0L8?p+7sqG2r4vE!JT&a4hPb`(fB&Kx5H9{V}@tBd)&Q_ z#*Q`y)qbiMSj+LTV=Xg*JvTA*>DDWypxLa&pNvgPLp(bPUra^ctC!y#!_;{-9Q#)j9r82V)_RIRQO<5NS3p4R_{>45{>`UtT@#Bdao2?-Np9E5?$O zLB^~$#lxwE(KNza!R>Zq)TNclo>~UOIRc)Awd)TghbVYCG12o`D)OA?apXXpg*<7U z>mm@_@=_HPrfurG(eZKTJQM3Q9tC8USBpmu-VV@GC6fm28H-2`(bc+M_srLc7E7as zs|vJwM{od~GE&?{LLb6bFC)bmXsG48D9 z6PV;DF<4^ko^*b`N!aA+s@KnEoVBugZAM#(``rKlaP{TnHbVe7?)V2^^Fq+8wacp8 z1pxO|Pv2XRYl8J_W_qr6Ci4HD1Ny^@{;Yx8xcx7C&yCb=r0y@YEF0KtVDrzw#xG#c zioRI}zn=yC-9~(a>J6&@8B}kO{MW~68!FyV@rH{3{#bfLNB>VpYqt6y5IP-Tb%LTl d+4A0Y7y!0Dp$M%wt6Coc&iMGBBAqy&`(MfBA+`Vj literal 6274 zcmeHL=~vTN8va?TcHGc9lxZk%Y>!nNAyqaZ0&1mH8KAN+CP5g3m_S5!LXuW0RuFN6 zRDv?a0Wk!^7D8AO1pxs8WeeFMVG{@;fh3ZUWWxLl`k`|UAMTg;-1ppj-upby``q{D z&pw_zw}G|+0I>7SPpAC=z+}a^fX_ZLMmC>rb^(Chz?su015%3?1lT+IQy04Abqlsm zoA!ND@{c{I_dtFDT^|ogh%fT7{9(ieRFmB~dI|84irSG4atN#ZHfaCNo3}^5ExhDE zZ?nY#MBTIR>#$E5Q9oDpCYg`H#+bdIN9Jq44EVACdakeB@{X%K9n4BTHeOgw~pp`O+cd*4>w(5`Dqj9QU=A7e{(|rp000 zP!I@Y%bG4T8*w0|zsMfhX~}Y?vCZs2ptDf^-o4}l1C=(h629$M>2ka)marz*jK%K< zfU!Mp`Mj%0mKvYr?M)JdVdgfQ=ECr=gf>VUGB(+-Pu?0%OfDx8!j+YK-G(Z>mIoVN zOG)iF9^*#G#p9(!gnre8rFm;fcgR)GB}M!V0Fr;Rv{uUkM@+F1!iu3F8u>7^3ENA6 zCJfk0dx%x%o7wa-0UkMXZonqAI>Wmd7sgM${B(Iy7qLpFxw<&8qJJW~^773IShbf| zj`{155cZR(DWqGZc=8a=RNDH`oUc_3QdJmeEFKoWG|>nFg9XaA!SEl00|TjF4paLc zWvbEV&IR2P=@;%b!$XcnpTQ0E=bPjIY-e=UgSVJAU!LyhN;zg26XZzf)59tjezb@n zFo5ZX7(Qj>sbNxgU$dy{-L4X97)qBby8W*bgalS?iFn!Mc3ip(L4U2+^HzqG#s+@} z)_j#b8yJkI&kJ)g+Hr_=`ilI(ffi*@-lBk&EWC5_z@v~IaDv3Ux4^dIS*UXow>}>n zjMEMh3fC4Nh+Nfc_e#B3awXxoWM%zj7uu4S9&59*2LBD6roh5Mb|t(AGIe?Q(3J^Y zLXNyF3m@m_eMGM@6Lj{4Db*>2rP*qnu&E3iFP?r%&aTVzEoUn3UVpMh=B&%In%Tj} z*%d3Mc{233i$(-kLQx}!JnMDM-|*aA@E!LGX%;?}RhU#{q;iOup}IMt{rmmZ7el$E z94jk&#v}pqRobA{a-4Lv-Oj~S*U=&#a8gb~;0+jFL0>jSbcutBkF!=Q|7_>g$1L9< z7F#E2O^FbMo4BVU=fyiaBtH7(E5b1YpWcH*IEZ zTJFibI(U-^!+Xq+R$5^vC&J^JCde`vB9PYjDe&AD4V5smlWXiDTgDXDF-U-7d1(U)?lV20HHMK@eX2{=4x{f4oMux<*kDM7rYBnGGp?vKV^ zFm%g^=U1+)bl7^dsEJbzp^Jt+*&}S0UD{;HMBL2Ny3p-Idpf zJ|1-ec6Y%e#3@%j8xTsF8mJ@cqsVSvWX;h=+3%oDYMhP&8PZBu2 zZWvBsS!RBc1;f>QSy4L}bP%q}9D*XQ@HIP~Ng7{@FawpKh3gae3h|9=b+X6d#~pVt z-4aI5X=#Udedlg}zt-KlifouI0@LH#crNrjl(1wKj!jBacFedP#j|~744J;zu+Wua zl|H+#V~FLdTrsLWx_g%}fpB7`htWHF7{qqPyF2c!@)CkD@{747 zt|{+dCq&wwL6$R`ejqDFuyia#^o6>$79Z{NR+nRF*-YpcYg&0uK9rE&;-fMMy`hLv z9cSpztE)r6cXA$Pbor|@Pup*eGi)Rd|H2NDnB1ILyc#~W3qw?i8)3w*cbPnCvy zI`vn+e>Ty@LL4neNaBj9R)R%ii-`_)nAYa_2kGNgbNl9PXQaX!MgB9e*Su|e>-_co z7H*~fJO!h7E|?NGR>p`8U@>tTmGc63KJGj-(gOw)q1+o-@7)~04Ow*Z{df?5(;<2ZFJD(-u84_djWK)_QBi<9Cx85tSb#+ox6GPaQ#k?}U@(HwxsnST8iKmj1`krsnQJYod z$SX?^qmRocF<47J-T$c%U64Ms=Z3m@K>3}y)v1VU{{^NTea`>@ From 9b5c42fa5abd8cc6aeb371594a452aefd147a0cc Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Mon, 8 Sep 2025 17:27:52 +1000 Subject: [PATCH 54/54] Bug has to be fixed in iconbutton --- packages/react/src/Breadcrumbs/Breadcrumbs.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx index 1e1504dd609..72cf11d6301 100644 --- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx @@ -82,10 +82,7 @@ const BreadcrumbsMenuItem = React.forwardRef { - // this menubutton ref doesnt seem to set current element. - // The first child of details is the summary element - detailsRef.current?.firstChild?.focus() - //menuButtonRef.current?.focus() + menuButtonRef.current?.focus() }, []) useOnEscapePress(