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>) => ({