diff --git a/.changeset/proud-candles-mix.md b/.changeset/proud-candles-mix.md new file mode 100644 index 00000000000..8cfc63e8b11 --- /dev/null +++ b/.changeset/proud-candles-mix.md @@ -0,0 +1,5 @@ +--- +"@primer/react": patch +--- + +`UnderlineNav`: Fix icon flickering on some screen sizes before initial render/hydration diff --git a/packages/react/src/UnderlineNav/UnderlineNav.module.css b/packages/react/src/UnderlineNav/UnderlineNav.module.css index 329bb9e0897..7612548b008 100644 --- a/packages/react/src/UnderlineNav/UnderlineNav.module.css +++ b/packages/react/src/UnderlineNav/UnderlineNav.module.css @@ -2,15 +2,13 @@ /* Progressive enhancement: Detect overflow using scroll-based animations. The idiomatic way would be a scroll-state container query but browser support is slightly better for animations. */ - animation: detect-overflow linear; - animation-timeline: scroll(self block); + animation: + detect-overflow linear, + delay-visibility forwards 0ms 5ms; + animation-timeline: scroll(self block), auto; --UnderlineNav_moreButton-visibility: hidden; - --UnderlineNav_icons-display: inline; - - &[data-hide-icons='true'] { - --UnderlineNav_icons-display: none; - } + --UnderlineNav_hide-icons-play-state: paused; &[data-has-overflow='true'] { --UnderlineNav_moreButton-visibility: visible; @@ -21,12 +19,45 @@ 0%, 100% { --UnderlineNav_moreButton-visibility: visible; - --UnderlineNav_icons-display: none; + --UnderlineNav_hide-icons-play-state: running; } } .ItemsList [data-component='icon'] { - display: var(--UnderlineNav_icons-display); + /* Unlike the more button, the icons are removed from the layout when hidden. This can cause the container to no + longer overflow, which would cause a flickering loop if we just drove the visibility directly from the scroll state. + So instead we drive an animation that can only play forwards so the icons stay hidden for the life of the page, even + if the container is no longer overflowing. We can't just make the scroll-driven animation itself play forwards, + because scroll-driven animations are conditionally applied and revert when not scrollable. */ + animation-name: hide-icons; + animation-fill-mode: forwards; + animation-timing-function: linear; + animation-play-state: var(--UnderlineNav_hide-icons-play-state); + animation-duration: 0.1ms; /* must be greater than 0 */ +} + +/* Slow devices will still flicker on initial paint as we figure out if we have room to render icons or not, +so we delay the tabs visibility by a very short time to allow things to settle. */ +@keyframes delay-visibility { + 0%, + 99.9% { + visibility: hidden; + } + + 100% { + visibility: visible; + } +} + +@keyframes hide-icons { + 0% { + display: inline; + } + + 0.1%, + 100% { + display: none; + } } .MoreButtonContainer { diff --git a/packages/react/src/UnderlineNav/UnderlineNav.tsx b/packages/react/src/UnderlineNav/UnderlineNav.tsx index 5a9507cd890..a0c50f07a92 100644 --- a/packages/react/src/UnderlineNav/UnderlineNav.tsx +++ b/packages/react/src/UnderlineNav/UnderlineNav.tsx @@ -95,9 +95,8 @@ export const UnderlineNav = forwardRef( ref={navRef} data-variant={variant} data-overflow-mode="wrap" - // Force icons to stay hidden, avoiding flickering as icons create/remove overflow + // Ensure icons are hidden and button is shown on browsers that don't support scroll-driven animations data-hide-icons={hasEverOverflowed ? 'true' : undefined} - // Ensure button is shown (after initial render) on browsers that don't support scroll-driven animations data-has-overflow={isOverflowing ? 'true' : undefined} > @@ -115,7 +114,7 @@ export const UnderlineNav = forwardRef( variant="invisible" data-component="overflow-menu-button" data-current={overflowingCurrentItem ? 'true' : undefined} - aria-label={overflowingCurrentItem ? `More items, including current item` : undefined} + aria-label={overflowingCurrentItem ? 'More items, including current item' : undefined} > More items diff --git a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css index d1852babf0c..2ee18447239 100644 --- a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css +++ b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css @@ -29,7 +29,7 @@ .UnderlineItemList { flex-wrap: wrap; - overflow: hidden; + flex: 1; } } }