Skip to content

Commit

Permalink
[UnderlineNav] : Introduce loading counters (#2378)
Browse files Browse the repository at this point in the history
* loading state for counters - only for UnderlineNav

* CR feedback & changeset & docs and tests

* Update docs/content/drafts/UnderlineNav2.mdx

Co-authored-by: Cole Bemis <colebemis@github.com>

* prop rename to loadingCounters

Co-authored-by: Cole Bemis <colebemis@github.com>
  • Loading branch information
broccolinisoup and colebemis committed Sep 28, 2022
1 parent e4af7a7 commit c35c2ac
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-cheetahs-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

UnderlineNav2: Introducing loading states for counters
24 changes: 21 additions & 3 deletions docs/content/drafts/UnderlineNav2.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {UnderlineNav} from '@primer/react/drafts'
</UnderlineNav>
```

### With icons
### With Icons

```jsx live drafts
<UnderlineNav>
Expand All @@ -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
<UnderlineNav>
Expand Down Expand Up @@ -78,7 +78,7 @@ When overflow occurs, the component first hides icons if present to optimize for
</UnderlineNav>
```

#### Display `More` menu
#### Display `More` Menu

If there is still overflow, the component will behave depending on the pointer.

Expand Down Expand Up @@ -111,6 +111,18 @@ If there is still overflow, the component will behave depending on the pointer.
</UnderlineNav>
```

### Loading state for counters

```jsx live drafts
<UnderlineNav loadingCounters={true}>
<UnderlineNav.Item counter={4} selected>
Item 1
</UnderlineNav.Item>
<UnderlineNav.Item counter={44}>Item 2</UnderlineNav.Item>
<UnderlineNav.Item>Item 3</UnderlineNav.Item>
</UnderlineNav>
```

## Props

### UnderlineNav
Expand All @@ -119,6 +131,12 @@ If there is still overflow, the component will behave depending on the pointer.
<PropsTableRow name="aria-label" type="string" />
<PropsTableRow name="aria-labelledby" type="string" />
<PropsTableRow name="aria-describedby" type="string" />
<PropsTableRow
name="loadingCounters"
type="boolean"
defaultValue="false"
description="Whether all of the counters are in loading state"
/>
<PropsTableRow
name="afterSelect"
type="(event) => void"
Expand Down
17 changes: 17 additions & 0 deletions src/UnderlineNav2/LoadingCounter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import styled, {keyframes} from 'styled-components'
import {get} from '../constants'

const loading = keyframes`
from { opacity: 0.4; }
to { opacity: 0.8; }
`

export const LoadingCounter = styled.span`
animation: ${loading} 1.2s linear infinite alternate;
background-color: ${get('colors.neutral.emphasis')};
border-color: ${get('colors.border.default')};
width: 1.5rem;
height: 1rem; // 16px
display: inline-block;
border-radius: 20px;
`
15 changes: 15 additions & 0 deletions src/UnderlineNav2/UnderlineNav.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,19 @@ describe('UnderlineNav', () => {
expect(counter?.className).toContain('CounterLabel')
expect(counter?.textContent).toBe('8')
})
test('respect loadingCounters prop', () => {
const {getByText} = render(
<UnderlineNav label="Test nav" loadingCounters={true}>
<UnderlineNav.Item selected counter={4}>
Item 1
</UnderlineNav.Item>
<UnderlineNav.Item>Item 2</UnderlineNav.Item>
<UnderlineNav.Item>Item 3</UnderlineNav.Item>
</UnderlineNav>
)
const item = getByText('Item 1').closest('a')
const loadingCounter = item?.getElementsByTagName('span')[2]
expect(loadingCounter?.className).toContain('LoadingCounter')
expect(loadingCounter?.textContent).toBe('')
})
})
24 changes: 22 additions & 2 deletions src/UnderlineNav2/UnderlineNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ 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
as?: React.ElementType
align?: 'right'
sx?: SxProp
variant?: 'default' | 'small'
/**
* loading state for all counters (to prevent multiple layout shifts)
*/
loadingCounters?: boolean
afterSelect?: (event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>) => void
children: React.ReactNode
}
Expand Down Expand Up @@ -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<HTMLElement>(null)
Expand Down Expand Up @@ -248,6 +262,7 @@ export const UnderlineNav = forwardRef(
setSelectedLink,
afterSelect: afterSelectHandler,
variant,
loadingCounters,
iconsVisible
}}
>
Expand Down Expand Up @@ -282,7 +297,12 @@ export const UnderlineNav = forwardRef(
<ActionList.Item key={index} {...actionElementProps}>
<Box as="span" sx={{display: 'flex', alignItems: 'center', justifyContent: 'space-between'}}>
{actionElementChildren}
<CounterLabel>{actionElementProps.counter}</CounterLabel>

{loadingCounters ? (
<LoadingCounter />
) : (
<CounterLabel>{actionElementProps.counter}</CounterLabel>
)}
</Box>
</ActionList.Item>
)
Expand Down
2 changes: 2 additions & 0 deletions src/UnderlineNav2/UnderlineNavContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ export const UnderlineNavContext = createContext<{
setSelectedLink: (ref: RefObject<HTMLElement>) => void
afterSelect?: (event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>) => void
variant: 'default' | 'small'
loadingCounters: boolean
iconsVisible: boolean
}>({
setChildrenWidth: () => null,
setNoIconChildrenWidth: () => null,
selectedLink: undefined,
setSelectedLink: () => null,
variant: 'default',
loadingCounters: false,
iconsVisible: true
})
5 changes: 4 additions & 1 deletion src/UnderlineNav2/UnderlineNavItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -70,6 +71,7 @@ export const UnderlineNavItem = forwardRef(
setSelectedLink,
afterSelect,
variant,
loadingCounters,
iconsVisible
} = useContext(UnderlineNavContext)
const {theme} = useTheme()
Expand Down Expand Up @@ -109,6 +111,7 @@ export const UnderlineNavItem = forwardRef(
},
[onSelect, afterSelect, ref, setSelectedLink]
)

return (
<Box as="li" sx={{display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
<Box
Expand Down Expand Up @@ -139,7 +142,7 @@ export const UnderlineNavItem = forwardRef(
)}
{counter && (
<Box as="span" data-component="counter" sx={counterStyles}>
<CounterLabel>{counter}</CounterLabel>
{loadingCounters ? <LoadingCounter /> : <CounterLabel>{counter}</CounterLabel>}
</Box>
)}
</Box>
Expand Down
20 changes: 20 additions & 0 deletions src/UnderlineNav2/examples.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,23 @@ export const InternalResponsiveNav = (args: UnderlineNavProps) => {
</UnderlineNav>
)
}

export const CountersLoadingState = (args: UnderlineNavProps) => {
const [selectedIndex, setSelectedIndex] = React.useState<number | null>(1)

return (
<UnderlineNav {...args} loadingCounters={true}>
{items.map((item, index) => (
<UnderlineNav.Item
key={item.navigation}
icon={item.icon}
selected={index === selectedIndex}
onSelect={() => setSelectedIndex(index)}
counter={item.counter}
>
{item.navigation}
</UnderlineNav.Item>
))}
</UnderlineNav>
)
}
4 changes: 3 additions & 1 deletion src/UnderlineNav2/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ const defaultVariantLinkStyles = {
}

export const counterStyles = {
marginLeft: 2
marginLeft: 2,
display: 'flex',
alignItems: 'center'
}

export const getNavStyles = (theme?: Theme, props?: Partial<Pick<UnderlineNavProps, 'align'>>) => ({
Expand Down

0 comments on commit c35c2ac

Please sign in to comment.