diff --git a/.changeset/clever-geese-cover.md b/.changeset/clever-geese-cover.md new file mode 100644 index 00000000000..9b609eebcca --- /dev/null +++ b/.changeset/clever-geese-cover.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +UnderlineNav: Adds `overflow: hidden` when the "More" button isn't present diff --git a/packages/react/src/UnderlineNav/UnderlineNav.tsx b/packages/react/src/UnderlineNav/UnderlineNav.tsx index c0a252d3ffe..14c52ee9683 100644 --- a/packages/react/src/UnderlineNav/UnderlineNav.tsx +++ b/packages/react/src/UnderlineNav/UnderlineNav.tsx @@ -47,11 +47,12 @@ const overflowEffect = ( childArray: Array>, childWidthArray: ChildWidthArray, noIconChildWidthArray: ChildWidthArray, - updateListAndMenu: (props: ResponsiveProps, iconsVisible: boolean) => void, + updateListAndMenu: (props: ResponsiveProps, iconsVisible: boolean, overflowMeasured: boolean) => void, ) => { let iconsVisible = true if (childWidthArray.length === 0) { - updateListAndMenu({items: childArray, menuItems: []}, iconsVisible) + updateListAndMenu({items: childArray, menuItems: []}, iconsVisible, false) + return } const numberOfItemsPossible = calculatePossibleItems(childWidthArray, navWidth) const numberOfItemsWithoutIconPossible = calculatePossibleItems(noIconChildWidthArray, navWidth) @@ -104,7 +105,7 @@ const overflowEffect = ( } } } - updateListAndMenu({items, menuItems}, iconsVisible) + updateListAndMenu({items, menuItems}, iconsVisible, true) } export const getValidChildren = (children: React.ReactNode) => { @@ -166,6 +167,8 @@ export const UnderlineNav = forwardRef( const [iconsVisible, setIconsVisible] = useState(true) const [childWidthArray, setChildWidthArray] = useState([]) const [noIconChildWidthArray, setNoIconChildWidthArray] = useState([]) + // Track whether the initial overflow calculation is complete to prevent CLS + const [isOverflowMeasured, setIsOverflowMeasured] = useState(false) const validChildren = getValidChildren(children) @@ -209,7 +212,7 @@ export const UnderlineNav = forwardRef( prospectiveListItem: React.ReactElement, indexOfProspectiveListItem: number, event: React.MouseEvent | React.KeyboardEvent, - callback: (props: ResponsiveProps, displayIcons: boolean) => void, + callback: (props: ResponsiveProps, displayIcons: boolean, overflowMeasured: boolean) => void, ) => { // get the selected menu item's width const widthToFitIntoList = getItemsWidth(prospectiveListItem.props.children) @@ -229,7 +232,7 @@ export const UnderlineNav = forwardRef( const updatedMenuItems = [...menuItems] // Add itemsToAddToMenu array's items to the menu at the index of the prospectiveListItem and remove 1 count of items (prospectiveListItem) updatedMenuItems.splice(indexOfProspectiveListItem, 1, ...itemsToAddToMenu) - callback({items: updatedItemList, menuItems: updatedMenuItems}, false) + callback({items: updatedItemList, menuItems: updatedMenuItems}, false, true) } // How many items do we need to pull in to the menu to make room for the selected menu item. function getBreakpointForItemSwapping(widthToFitIntoList: number, availableSpace: number) { @@ -245,10 +248,17 @@ export const UnderlineNav = forwardRef( return breakpoint } - const updateListAndMenu = useCallback((props: ResponsiveProps, displayIcons: boolean) => { - setResponsiveProps(props) - setIconsVisible(displayIcons) - }, []) + const updateListAndMenu = useCallback( + (props: ResponsiveProps, displayIcons: boolean, overflowMeasured: boolean) => { + setResponsiveProps(props) + setIconsVisible(displayIcons) + + if (overflowMeasured) { + setIsOverflowMeasured(true) + } + }, + [], + ) const setChildrenWidth = useCallback((size: ChildSize) => { setChildWidthArray(arr => { const newArr = [...arr, size] @@ -330,7 +340,14 @@ export const UnderlineNav = forwardRef( }} > {ariaLabel && {`${ariaLabel} navigation`}} - + {listItems} {menuItems.length > 0 && ( diff --git a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css index 85214c5c317..83cdd50d81f 100644 --- a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css +++ b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css @@ -12,6 +12,17 @@ /* stylelint-disable-next-line primer/box-shadow */ box-shadow: inset 0 -1px var(--borderColor-muted); + /* Hide overflow until calculation is complete to prevent CLS */ + overflow: visible; + + &[data-overflow-measured='false'] { + overflow: hidden; + } + + &[data-overflow-measured='true'] { + overflow: visible; + } + &[data-variant='flush'] { /* stylelint-disable-next-line primer/spacing */ padding-inline: unset;