Skip to content

Commit 15824db

Browse files
Remove sx from UnderlinePanels (#6874)
Co-authored-by: Marie Lucca <francinelucca@github.com> Co-authored-by: Marie Lucca <40550942+francinelucca@users.noreply.github.com>
1 parent 86b3e60 commit 15824db

File tree

7 files changed

+162
-82
lines changed

7 files changed

+162
-82
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": major
3+
---
4+
5+
Remove sx from UnderlinePanels

packages/react/src/UnderlineNav/UnderlineNav.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {MutableRefObject, RefObject} from 'react'
1+
import type {RefObject} from 'react'
22
import React, {useRef, forwardRef, useCallback, useState, useEffect} from 'react'
33
import {UnderlineNavContext} from './UnderlineNavContext'
44
import type {ResizeObserverEntry} from '../hooks/useResizeObserver'
@@ -144,7 +144,7 @@ export const UnderlineNav = forwardRef(
144144
forwardedRef,
145145
) => {
146146
const backupRef = useRef<HTMLElement>(null)
147-
const navRef = (forwardedRef ?? backupRef) as MutableRefObject<HTMLElement>
147+
const navRef = (forwardedRef ?? backupRef) as RefObject<HTMLElement>
148148
const listRef = useRef<HTMLUListElement>(null)
149149
const moreMenuRef = useRef<HTMLLIElement>(null)
150150
const moreMenuBtnRef = useRef<HTMLButtonElement>(null)
@@ -205,7 +205,7 @@ export const UnderlineNav = forwardRef(
205205
const widthToFitIntoList = getItemsWidth(prospectiveListItem.props.children)
206206
// Check if there is any empty space on the right side of the list
207207
const availableSpace =
208-
navRef.current.getBoundingClientRect().width - (listRef.current?.getBoundingClientRect().width ?? 0)
208+
(navRef.current?.getBoundingClientRect().width ?? 0) - (listRef.current?.getBoundingClientRect().width ?? 0)
209209

210210
// Calculate how many items need to be pulled in to the menu to make room for the selected menu item
211211
// I.e. if we need to pull 2 items in (index 0 and index 1), breakpoint (index) will return 1.

packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,14 @@ import React, {
1111
import {TabContainerElement} from '@github/tab-container-element'
1212
import type {IconProps} from '@primer/octicons-react'
1313
import {createComponent} from '../../utils/create-component'
14-
import {
15-
UnderlineItemList,
16-
UnderlineWrapper,
17-
UnderlineItem,
18-
type UnderlineItemProps,
19-
} from '../../internal/components/UnderlineTabbedInterface'
20-
import {type BoxProps} from '../../Box'
14+
import {UnderlineItemList, UnderlineWrapper, UnderlineItem} from '../../internal/components/UnderlineTabbedInterface'
2115
import {useId} from '../../hooks'
2216
import {invariant} from '../../utils/invariant'
2317
import {type SxProp} from '../../sx'
2418
import {useResizeObserver, type ResizeObserverEntry} from '../../hooks/useResizeObserver'
2519
import useIsomorphicLayoutEffect from '../../utils/useIsomorphicLayoutEffect'
2620
import classes from './UnderlinePanels.module.css'
2721
import {clsx} from 'clsx'
28-
import {BoxWithFallback} from '../../internal/components/BoxWithFallback'
2922

3023
export type UnderlinePanelsProps = {
3124
/**
@@ -74,7 +67,7 @@ export type TabProps = PropsWithChildren<{
7467
}> &
7568
SxProp
7669

77-
export type PanelProps = Omit<BoxProps, 'as'>
70+
export type PanelProps = React.HTMLAttributes<HTMLDivElement>
7871

7972
const TabContainerComponent = createComponent(TabContainerElement, 'tab-container')
8073

@@ -104,23 +97,44 @@ const UnderlinePanels: FC<UnderlinePanelsProps> = ({
10497
let panelIndex = 0
10598

10699
const childrenWithProps = Children.map(children, child => {
107-
if (isValidElement<UnderlineItemProps>(child) && child.type === Tab) {
100+
if (
101+
isValidElement(child) &&
102+
(typeof child.type === 'function' || typeof child.type === 'object') &&
103+
'displayName' in child.type &&
104+
child.type.displayName === 'UnderlinePanels.Tab'
105+
) {
108106
return cloneElement(child, {id: `${parentId}-tab-${tabIndex++}`, loadingCounters, iconsVisible})
109107
}
110108

111-
if (isValidElement<PanelProps>(child) && child.type === Panel) {
112-
return cloneElement(child, {'aria-labelledby': `${parentId}-tab-${panelIndex++}`})
109+
if (
110+
isValidElement(child) &&
111+
(typeof child.type === 'function' || typeof child.type === 'object') &&
112+
'displayName' in child.type &&
113+
child.type.displayName === 'UnderlinePanels.Panel'
114+
) {
115+
const childPanel = child as React.ReactElement<PanelProps>
116+
return cloneElement(childPanel, {'aria-labelledby': `${parentId}-tab-${panelIndex++}`})
113117
}
114118
return child
115119
})
116120

117121
const newTabs = Children.toArray(childrenWithProps).filter(child => {
118-
return isValidElement(child) && child.type === Tab
122+
return (
123+
isValidElement(child) &&
124+
(typeof child.type === 'function' || typeof child.type === 'object') &&
125+
'displayName' in child.type &&
126+
child.type.displayName === 'UnderlinePanels.Tab'
127+
)
119128
})
120129

121-
const newTabPanels = Children.toArray(childrenWithProps).filter(
122-
child => isValidElement(child) && child.type === Panel,
123-
)
130+
const newTabPanels = Children.toArray(childrenWithProps).filter(child => {
131+
return (
132+
isValidElement(child) &&
133+
(typeof child.type === 'function' || typeof child.type === 'object') &&
134+
'displayName' in child.type &&
135+
child.type.displayName === 'UnderlinePanels.Panel'
136+
)
137+
})
124138

125139
setTabs(newTabs)
126140
setTabPanels(newTabPanels)
@@ -221,8 +235,12 @@ const Tab: FC<TabProps> = ({'aria-selected': ariaSelected, onSelect, ...props})
221235

222236
Tab.displayName = 'UnderlinePanels.Tab'
223237

224-
const Panel: FC<PanelProps> = props => {
225-
return <BoxWithFallback as="div" role="tabpanel" {...props} />
238+
const Panel: FC<PanelProps> = ({children, ...rest}) => {
239+
return (
240+
<div role="tabpanel" {...rest}>
241+
{children}
242+
</div>
243+
)
226244
}
227245

228246
Panel.displayName = 'UnderlinePanels.Panel'
Lines changed: 44 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,37 @@
11
// Used for UnderlineNav and UnderlinePanels components
22

3-
import type React from 'react'
4-
import {forwardRef, type FC, type PropsWithChildren} from 'react'
3+
import React from 'react'
4+
import {type ForwardedRef, forwardRef, type FC, type PropsWithChildren, type ElementType} from 'react'
55
import {isElement} from 'react-is'
66
import type {IconProps} from '@primer/octicons-react'
77
import CounterLabel from '../../CounterLabel'
8-
import {type SxProp} from '../../sx'
98
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../../utils/polymorphic'
109

1110
import classes from './UnderlineTabbedInterface.module.css'
1211
import {clsx} from 'clsx'
13-
import {BoxWithFallback} from './BoxWithFallback'
1412

1513
// The gap between the list items. It is a constant because the gap is used to calculate the possible number of items that can fit in the container.
1614
export const GAP = 8
1715

18-
type UnderlineWrapperProps = {
16+
type UnderlineWrapperProps<As extends React.ElementType> = {
1917
slot?: string
20-
as?: React.ElementType
18+
as?: As
2119
className?: string
22-
ref?: React.Ref<unknown>
23-
} & SxProp
20+
ref?: React.Ref<HTMLElement>
21+
}
2422

25-
export const UnderlineWrapper = forwardRef(
26-
({children, className, ...rest}: PropsWithChildren<UnderlineWrapperProps>, forwardedRef) => {
27-
return (
28-
<BoxWithFallback className={clsx(classes.UnderlineWrapper, className)} ref={forwardedRef} {...rest}>
29-
{children}
30-
</BoxWithFallback>
31-
)
32-
},
33-
)
23+
export const UnderlineWrapper = forwardRef((props, ref) => {
24+
const {children, className, as: Component = 'nav', ...rest} = props
25+
return (
26+
<Component
27+
className={clsx(classes.UnderlineWrapper, className)}
28+
ref={ref as ForwardedRef<HTMLDivElement>}
29+
{...rest}
30+
>
31+
{children}
32+
</Component>
33+
)
34+
}) as PolymorphicForwardRefComponent<ElementType, UnderlineWrapperProps<ElementType>>
3435

3536
export const UnderlineItemList = forwardRef(({children, ...rest}: PropsWithChildren, forwardedRef) => {
3637
return (
@@ -44,51 +45,38 @@ export const LoadingCounter = () => {
4445
return <span className={classes.LoadingCounter} />
4546
}
4647

47-
export type UnderlineItemProps = {
48-
as?: React.ElementType | 'a' | 'button'
48+
export type UnderlineItemProps<As extends React.ElementType> = {
49+
as?: As | 'a' | 'button'
4950
className?: string
5051
iconsVisible?: boolean
5152
loadingCounters?: boolean
5253
counter?: number | string
5354
icon?: FC<IconProps> | React.ReactElement
5455
id?: string
5556
ref?: React.Ref<unknown>
56-
} & SxProp
57+
} & React.ComponentPropsWithoutRef<As extends 'a' ? 'a' : As extends 'button' ? 'button' : As>
5758

58-
export const UnderlineItem = forwardRef(
59-
(
60-
{
61-
as = 'a',
62-
children,
63-
counter,
64-
icon: Icon,
65-
iconsVisible,
66-
loadingCounters,
67-
className,
68-
...rest
69-
}: PropsWithChildren<UnderlineItemProps>,
70-
forwardedRef,
71-
) => {
72-
return (
73-
<BoxWithFallback ref={forwardedRef} as={as} className={clsx(classes.UnderlineItem, className)} {...rest}>
74-
{iconsVisible && Icon && <span data-component="icon">{isElement(Icon) ? Icon : <Icon />}</span>}
75-
{children && (
76-
<span data-component="text" data-content={children}>
77-
{children}
59+
export const UnderlineItem = React.forwardRef((props, ref) => {
60+
const {as: Component = 'a', children, counter, icon: Icon, iconsVisible, loadingCounters, className, ...rest} = props
61+
return (
62+
<Component {...rest} ref={ref} className={clsx(classes.UnderlineItem, className)}>
63+
{iconsVisible && Icon && <span data-component="icon">{isElement(Icon) ? Icon : <Icon />}</span>}
64+
{children && (
65+
<span data-component="text" data-content={children}>
66+
{children}
67+
</span>
68+
)}
69+
{counter !== undefined ? (
70+
loadingCounters ? (
71+
<span data-component="counter">
72+
<LoadingCounter />
7873
</span>
79-
)}
80-
{counter !== undefined ? (
81-
loadingCounters ? (
82-
<span data-component="counter">
83-
<LoadingCounter />
84-
</span>
85-
) : (
86-
<span data-component="counter">
87-
<CounterLabel>{counter}</CounterLabel>
88-
</span>
89-
)
90-
) : null}
91-
</BoxWithFallback>
92-
)
93-
},
94-
) as PolymorphicForwardRefComponent<'a', UnderlineItemProps>
74+
) : (
75+
<span data-component="counter">
76+
<CounterLabel>{counter}</CounterLabel>
77+
</span>
78+
)
79+
) : null}
80+
</Component>
81+
)
82+
}) as PolymorphicForwardRefComponent<ElementType, UnderlineItemProps<ElementType>>

packages/styled-react/src/components/TabNav.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,19 @@ const StyledTabNav = styled(PrimerTabNav).withConfig({
1313
${sx}
1414
`
1515

16-
// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error
17-
const TabNavImpl = ({as, ...props}: TabNavProps) => <StyledTabNav forwardedAs={as} {...props} />
16+
const TabNavImpl = ({as, ...props}: TabNavProps) => {
17+
return <StyledTabNav {...props} {...(as ? {forwardedAs: as as React.ElementType} : {})} />
18+
}
1819

1920
const StyledTabNavLink: ForwardRefComponent<'a', TabNavLinkProps> = styled(PrimerTabNav.Link).withConfig({
2021
shouldForwardProp: prop => (prop as keyof TabNavLinkProps) !== 'sx',
2122
})<TabNavLinkProps>`
2223
${sx}
2324
`
2425

25-
// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error
26-
const TabNavLink = ({as, ...props}: TabNavLinkProps) => <StyledTabNavLink forwardedAs={as} {...props} />
26+
const TabNavLink = ({as, ...props}: TabNavLinkProps) => (
27+
<StyledTabNavLink {...props} {...(as ? {forwardedAs: as} : {})} />
28+
)
2729

2830
const TabNav = Object.assign(TabNavImpl, {
2931
Link: TabNavLink,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {
2+
UnderlinePanels as PrimerUnderlinePanels,
3+
type UnderlinePanelsProps as PrimerUnderlinePanelsProps,
4+
type UnderlinePanelsPanelProps as PrimerUnderlinePanelsPanelProps,
5+
type UnderlinePanelsTabProps as PrimerUnderlinePanelsTabProps,
6+
} from '@primer/react/experimental'
7+
import styled from 'styled-components'
8+
import {sx, type SxProp} from '../sx'
9+
10+
type UnderlinePanelsProps = PrimerUnderlinePanelsProps & SxProp
11+
12+
const StyledUnderlinePanels = styled(PrimerUnderlinePanels).withConfig<UnderlinePanelsProps>({
13+
shouldForwardProp: prop => prop !== 'sx',
14+
})`
15+
${sx}
16+
`
17+
18+
// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error
19+
const UnderlinePanelsImpl = ({as, ...props}: UnderlinePanelsProps) => (
20+
<StyledUnderlinePanels forwardedAs={as} {...props} />
21+
)
22+
23+
UnderlinePanelsImpl.displayName = 'UnderlinePanels'
24+
25+
type UnderlinePanelsPanelProps = PrimerUnderlinePanelsPanelProps & SxProp
26+
27+
const StyledUnderlinePanelsPanel = styled(PrimerUnderlinePanels.Panel).withConfig<UnderlinePanelsPanelProps>({
28+
shouldForwardProp: prop => prop !== 'sx',
29+
})`
30+
${sx}
31+
`
32+
33+
// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error
34+
const UnderlinePanelsPanel = ({as, ...props}: UnderlinePanelsPanelProps) => {
35+
return <StyledUnderlinePanelsPanel forwardedAs={as} {...props} />
36+
}
37+
38+
UnderlinePanelsPanel.displayName = 'UnderlinePanels.Panel'
39+
40+
type UnderlinePanelsTabProps = PrimerUnderlinePanelsTabProps & SxProp
41+
42+
const StyledUnderlinePanelsTab = styled(PrimerUnderlinePanels.Tab).withConfig<UnderlinePanelsTabProps>({
43+
shouldForwardProp: prop => prop !== 'sx',
44+
})`
45+
${sx}
46+
`
47+
// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error
48+
const UnderlinePanelsTab = ({as, ...props}: UnderlinePanelsTabProps) => (
49+
<StyledUnderlinePanelsTab forwardedAs={as} {...props} />
50+
)
51+
52+
UnderlinePanelsTab.displayName = 'UnderlinePanels.Tab'
53+
54+
const UnderlinePanels = Object.assign(UnderlinePanelsImpl, {
55+
Tab: UnderlinePanelsTab,
56+
Panel: UnderlinePanelsPanel,
57+
})
58+
59+
export {UnderlinePanels, type UnderlinePanelsProps, type UnderlinePanelsTabProps, type UnderlinePanelsPanelProps}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
export {Dialog, type DialogProps} from './components/Dialog'
2+
23
export {
34
PageHeader,
45
type PageHeaderProps,
56
type PageHeaderActionsProps,
67
type PageHeaderTitleProps,
78
} from './components/PageHeader'
8-
export {Table, Tooltip, UnderlinePanels} from '@primer/react/experimental'
9+
10+
export {
11+
UnderlinePanels,
12+
type UnderlinePanelsProps,
13+
type UnderlinePanelsTabProps,
14+
type UnderlinePanelsPanelProps,
15+
} from './components/UnderlinePanels'
16+
export {Table, Tooltip} from '@primer/react/experimental'

0 commit comments

Comments
 (0)