diff --git a/.changeset/four-cheetahs-hang.md b/.changeset/four-cheetahs-hang.md
new file mode 100644
index 00000000000..817c9e03bad
--- /dev/null
+++ b/.changeset/four-cheetahs-hang.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': minor
+---
+
+UnderlineNav2: Introducing loading states for counters
diff --git a/docs/content/drafts/UnderlineNav2.mdx b/docs/content/drafts/UnderlineNav2.mdx
index dab8814d770..e66ede78c2a 100644
--- a/docs/content/drafts/UnderlineNav2.mdx
+++ b/docs/content/drafts/UnderlineNav2.mdx
@@ -23,7 +23,7 @@ import {UnderlineNav} from '@primer/react/drafts'
```
-### With icons
+### With Icons
```jsx live drafts
@@ -50,7 +50,7 @@ import {UnderlineNav} from '@primer/react/drafts'
When overflow occurs, the component first hides icons if present to optimize for space and show as many items as possible. (Only for fine pointer devices)
-#### Items without Icons
+#### Items Without Icons
```jsx live drafts
@@ -78,7 +78,7 @@ When overflow occurs, the component first hides icons if present to optimize for
```
-#### Display `More` menu
+#### Display `More` Menu
If there is still overflow, the component will behave depending on the pointer.
@@ -111,6 +111,18 @@ If there is still overflow, the component will behave depending on the pointer.
```
+### Loading state for counters
+
+```jsx live drafts
+
+
+ Item 1
+
+ Item 2
+ Item 3
+
+```
+
## Props
### UnderlineNav
@@ -119,6 +131,12 @@ If there is still overflow, the component will behave depending on the pointer.
+
{
expect(counter?.className).toContain('CounterLabel')
expect(counter?.textContent).toBe('8')
})
+ test('respect loadingCounters prop', () => {
+ const {getByText} = render(
+
+
+ Item 1
+
+ Item 2
+ Item 3
+
+ )
+ const item = getByText('Item 1').closest('a')
+ const loadingCounter = item?.getElementsByTagName('span')[2]
+ expect(loadingCounter?.className).toContain('LoadingCounter')
+ expect(loadingCounter?.textContent).toBe('')
+ })
})
diff --git a/src/UnderlineNav2/UnderlineNav.tsx b/src/UnderlineNav2/UnderlineNav.tsx
index 50900cc5fa1..23e3aee5091 100644
--- a/src/UnderlineNav2/UnderlineNav.tsx
+++ b/src/UnderlineNav2/UnderlineNav.tsx
@@ -15,6 +15,7 @@ import {ChildWidthArray, ResponsiveProps, OnScrollWithButtonEventType} from './t
import {moreBtnStyles, getDividerStyle, getNavStyles, ulStyles, scrollStyles, moreMenuStyles} from './styles'
import {LeftArrowButton, RightArrowButton} from './UnderlineNavArrowButton'
import styled from 'styled-components'
+import {LoadingCounter} from './LoadingCounter'
export type UnderlineNavProps = {
label: string
@@ -22,6 +23,10 @@ export type UnderlineNavProps = {
align?: 'right'
sx?: SxProp
variant?: 'default' | 'small'
+ /**
+ * loading state for all counters (to prevent multiple layout shifts)
+ */
+ loadingCounters?: boolean
afterSelect?: (event: React.MouseEvent | React.KeyboardEvent) => void
children: React.ReactNode
}
@@ -133,7 +138,16 @@ const calculatePossibleItems = (childWidthArray: ChildWidthArray, navWidth: numb
export const UnderlineNav = forwardRef(
(
- {as = 'nav', align, label, sx: sxProp = {}, afterSelect, variant = 'default', children}: UnderlineNavProps,
+ {
+ as = 'nav',
+ align,
+ label,
+ sx: sxProp = {},
+ afterSelect,
+ variant = 'default',
+ loadingCounters = false,
+ children
+ }: UnderlineNavProps,
forwardedRef
) => {
const backupRef = useRef(null)
@@ -248,6 +262,7 @@ export const UnderlineNav = forwardRef(
setSelectedLink,
afterSelect: afterSelectHandler,
variant,
+ loadingCounters,
iconsVisible
}}
>
@@ -282,7 +297,12 @@ export const UnderlineNav = forwardRef(
{actionElementChildren}
- {actionElementProps.counter}
+
+ {loadingCounters ? (
+
+ ) : (
+ {actionElementProps.counter}
+ )}
)
diff --git a/src/UnderlineNav2/UnderlineNavContext.tsx b/src/UnderlineNav2/UnderlineNavContext.tsx
index 6f614aa0864..9e810efc652 100644
--- a/src/UnderlineNav2/UnderlineNavContext.tsx
+++ b/src/UnderlineNav2/UnderlineNavContext.tsx
@@ -7,6 +7,7 @@ export const UnderlineNavContext = createContext<{
setSelectedLink: (ref: RefObject) => void
afterSelect?: (event: React.MouseEvent | React.KeyboardEvent) => void
variant: 'default' | 'small'
+ loadingCounters: boolean
iconsVisible: boolean
}>({
setChildrenWidth: () => null,
@@ -14,5 +15,6 @@ export const UnderlineNavContext = createContext<{
selectedLink: undefined,
setSelectedLink: () => null,
variant: 'default',
+ loadingCounters: false,
iconsVisible: true
})
diff --git a/src/UnderlineNav2/UnderlineNavItem.tsx b/src/UnderlineNav2/UnderlineNavItem.tsx
index 65df8b14409..3e50524f315 100644
--- a/src/UnderlineNav2/UnderlineNavItem.tsx
+++ b/src/UnderlineNav2/UnderlineNavItem.tsx
@@ -7,6 +7,7 @@ import {UnderlineNavContext} from './UnderlineNavContext'
import CounterLabel from '../CounterLabel'
import {useTheme} from '../ThemeProvider'
import {getLinkStyles, wrapperStyles, iconWrapStyles, counterStyles} from './styles'
+import {LoadingCounter} from './LoadingCounter'
// adopted from React.AnchorHTMLAttributes
type LinkProps = {
@@ -70,6 +71,7 @@ export const UnderlineNavItem = forwardRef(
setSelectedLink,
afterSelect,
variant,
+ loadingCounters,
iconsVisible
} = useContext(UnderlineNavContext)
const {theme} = useTheme()
@@ -109,6 +111,7 @@ export const UnderlineNavItem = forwardRef(
},
[onSelect, afterSelect, ref, setSelectedLink]
)
+
return (
- {counter}
+ {loadingCounters ? : {counter}}
)}
diff --git a/src/UnderlineNav2/examples.stories.tsx b/src/UnderlineNav2/examples.stories.tsx
index 3485f0b8cab..e0394ed8de6 100644
--- a/src/UnderlineNav2/examples.stories.tsx
+++ b/src/UnderlineNav2/examples.stories.tsx
@@ -103,3 +103,23 @@ export const InternalResponsiveNav = (args: UnderlineNavProps) => {
)
}
+
+export const CountersLoadingState = (args: UnderlineNavProps) => {
+ const [selectedIndex, setSelectedIndex] = React.useState(1)
+
+ return (
+
+ {items.map((item, index) => (
+ setSelectedIndex(index)}
+ counter={item.counter}
+ >
+ {item.navigation}
+
+ ))}
+
+ )
+}
diff --git a/src/UnderlineNav2/styles.ts b/src/UnderlineNav2/styles.ts
index 2754d725f87..dd05e995b25 100644
--- a/src/UnderlineNav2/styles.ts
+++ b/src/UnderlineNav2/styles.ts
@@ -25,7 +25,9 @@ const defaultVariantLinkStyles = {
}
export const counterStyles = {
- marginLeft: 2
+ marginLeft: 2,
+ display: 'flex',
+ alignItems: 'center'
}
export const getNavStyles = (theme?: Theme, props?: Partial>) => ({