Skip to content

Commit 61d0458

Browse files
authored
Merge dbdafb0 into ece5160
2 parents ece5160 + dbdafb0 commit 61d0458

18 files changed

Lines changed: 112 additions & 94 deletions

File tree

packages/react/src/ActionList/List.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import React, {type JSX} from 'react'
1+
import React, {useRef, type JSX} from 'react'
22
import {fixedForwardRef} from '../utils/modern-polymorphic'
33
import {ActionListContainerContext} from './ActionListContainerContext'
44
import {useSlots} from '../hooks/useSlots'
55
import {Heading} from './Heading'
66
import {useId} from '../hooks/useId'
77
import {ListContext, type ActionListProps} from './shared'
8-
import {useProvidedRefOrCreate} from '../hooks'
8+
import {useCombinedRefs} from '../hooks'
99
import {FocusKeys, useFocusZone} from '../hooks/useFocusZone'
1010
import {clsx} from 'clsx'
1111
import classes from './ActionList.module.css'
@@ -41,7 +41,8 @@ const UnwrappedList = <As extends React.ElementType = 'ul'>(
4141

4242
const ariaLabelledBy = slots.heading ? (slots.heading.props.id ?? headingId) : listLabelledBy
4343
const listRole = role || listRoleFromContainer
44-
const listRef = useProvidedRefOrCreate(forwardedRef as React.RefObject<HTMLUListElement>)
44+
const listRef = useRef<HTMLElement>(null)
45+
const combinedRef = useCombinedRefs(forwardedRef, listRef)
4546

4647
let enableFocusZone = false
4748
if (enableFocusZoneFromContainer !== undefined) enableFocusZone = enableFocusZoneFromContainer
@@ -66,12 +67,11 @@ const UnwrappedList = <As extends React.ElementType = 'ul'>(
6667
}}
6768
>
6869
{slots.heading}
69-
{/* @ts-expect-error ref needs a non nullable ref */}
7070
<Component
7171
className={clsx(classes.ActionList, className)}
7272
role={listRole}
7373
aria-labelledby={ariaLabelledBy}
74-
ref={listRef}
74+
ref={combinedRef}
7575
data-dividers={showDividers}
7676
data-variant={variant}
7777
{...restProps}

packages/react/src/ActionMenu/ActionMenu.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import React, {useCallback, useContext, useMemo, useEffect, useState} from 'react'
1+
import React, {useCallback, useContext, useMemo, useEffect, useState, useRef} from 'react'
22
import {TriangleDownIcon, ChevronRightIcon} from '@primer/octicons-react'
33
import type {AnchoredOverlayProps} from '../AnchoredOverlay'
44
import {AnchoredOverlay} from '../AnchoredOverlay'
55
import type {OverlayProps} from '../Overlay'
6-
import {useProvidedRefOrCreate, useProvidedStateOrCreate, useMenuKeyboardNavigation} from '../hooks'
6+
import {useProvidedStateOrCreate, useMenuKeyboardNavigation, useCombinedRefs} from '../hooks'
77
import {Divider} from '../ActionList/Divider'
88
import {ActionListContainerContext} from '../ActionList/ActionListContainerContext'
99
import type {ButtonProps} from '../Button'
@@ -110,7 +110,9 @@ const Menu: FCWithSlotMarker<React.PropsWithChildren<ActionMenuProps>> = ({
110110
)
111111
const menuButtonChildId = React.isValidElement(menuButtonChild) ? menuButtonChild.props.id : undefined
112112

113-
const anchorRef = useProvidedRefOrCreate(externalAnchorRef)
113+
const anchorRef = useRef<HTMLElement>(null)
114+
const combinedRef = useCombinedRefs(anchorRef, externalAnchorRef)
115+
114116
const anchorId = useId(menuButtonChildId)
115117
let renderAnchor: AnchoredOverlayProps['renderAnchor'] = null
116118
// 🚨 Hack for good API!
@@ -130,7 +132,7 @@ const Menu: FCWithSlotMarker<React.PropsWithChildren<ActionMenuProps>> = ({
130132
anchorChildren,
131133
mergeAnchorHandlers({...anchorProps}, anchorChildren.props),
132134
)
133-
return React.cloneElement(child, {children: triggerButton, ref: anchorRef})
135+
return React.cloneElement(child, {children: triggerButton, ref: combinedRef})
134136
}
135137
}
136138
return null
@@ -149,7 +151,7 @@ const Menu: FCWithSlotMarker<React.PropsWithChildren<ActionMenuProps>> = ({
149151
mergeAnchorHandlers({...anchorProps}, tooltipTrigger.props),
150152
)
151153
const tooltip = React.cloneElement(anchorChildren, {children: tooltipTriggerEl})
152-
return React.cloneElement(child, {children: tooltip, ref: anchorRef})
154+
return React.cloneElement(child, {children: tooltip, ref: combinedRef})
153155
}
154156
}
155157
} else {
@@ -278,7 +280,7 @@ const Overlay: FCWithSlotMarker<React.PropsWithChildren<MenuOverlayProps>> = ({
278280
// we typecast anchorRef as required instead of optional
279281
// because we know that we're setting it in context in Menu
280282
const {
281-
anchorRef,
283+
anchorRef: contextAnchorRef,
282284
renderAnchor,
283285
anchorId,
284286
open,
@@ -287,6 +289,9 @@ const Overlay: FCWithSlotMarker<React.PropsWithChildren<MenuOverlayProps>> = ({
287289
isSubmenu = false,
288290
} = React.useContext(MenuContext) as MandateProps<MenuContextProps, 'anchorRef'>
289291

292+
const anchorRef = useRef<HTMLElement>(null)
293+
const combinedAnchorRef = useCombinedRefs(anchorRef, contextAnchorRef)
294+
290295
const containerRef = React.useRef<HTMLDivElement>(null)
291296
const isNarrow = useResponsiveValue({narrow: true}, false)
292297

@@ -328,7 +333,7 @@ const Overlay: FCWithSlotMarker<React.PropsWithChildren<MenuOverlayProps>> = ({
328333

329334
return (
330335
<AnchoredOverlay
331-
anchorRef={anchorRef}
336+
anchorRef={combinedAnchorRef}
332337
renderAnchor={renderAnchor}
333338
anchorId={anchorId}
334339
open={open}

packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type React from 'react'
2-
import {useCallback, useEffect, type JSX} from 'react'
2+
import {useCallback, useEffect, useRef, type JSX} from 'react'
33
import type {OverlayProps} from '../Overlay'
44
import Overlay from '../Overlay'
55
import type {FocusTrapHookSettings} from '../hooks/useFocusTrap'
66
import {useFocusTrap} from '../hooks/useFocusTrap'
77
import type {FocusZoneHookSettings} from '../hooks/useFocusZone'
88
import {useFocusZone} from '../hooks/useFocusZone'
9-
import {useAnchoredPosition, useProvidedRefOrCreate, useRenderForcingRef} from '../hooks'
9+
import {useAnchoredPosition, useCombinedRefs, useRenderForcingRef} from '../hooks'
1010
import {useId} from '../hooks/useId'
1111
import type {AnchorPosition, PositionSettings} from '@primer/behaviors'
1212
import {type ResponsiveValue} from '../hooks/useResponsiveValue'
@@ -27,7 +27,7 @@ interface AnchoredOverlayPropsWithAnchor {
2727
/**
2828
* An override to the internal ref that will be spread on to the renderAnchor
2929
*/
30-
anchorRef?: React.RefObject<HTMLElement | null>
30+
anchorRef?: React.Ref<HTMLElement | null>
3131

3232
/**
3333
* An override to the internal id that will be spread on to the renderAnchor
@@ -46,7 +46,7 @@ interface AnchoredOverlayPropsWithoutAnchor {
4646
* An override to the internal renderAnchor ref that will be used to position the overlay.
4747
* When renderAnchor is null this can be used to make an anchor that is detached from ActionMenu.
4848
*/
49-
anchorRef: React.RefObject<HTMLElement | null>
49+
anchorRef: React.Ref<HTMLElement | null>
5050
/**
5151
* An override to the internal id that will be spread on to the renderAnchor
5252
*/
@@ -160,8 +160,12 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
160160
displayCloseButton = true,
161161
closeButtonProps = defaultCloseButtonProps,
162162
}) => {
163-
const anchorRef = useProvidedRefOrCreate(externalAnchorRef)
163+
const anchorRef = useRef<HTMLElement>(null)
164+
const combinedRef = useCombinedRefs(anchorRef, externalAnchorRef)
165+
164166
const [overlayRef, updateOverlayRef] = useRenderForcingRef<HTMLDivElement>()
167+
const combinedOverlayRef = useCombinedRefs(updateOverlayRef, overlayProps?.ref)
168+
165169
const anchorId = useId(externalAnchorId)
166170

167171
const onClickOutside = useCallback(() => onClose?.('click-outside'), [onClose])
@@ -235,7 +239,7 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
235239
<>
236240
{renderAnchor &&
237241
renderAnchor({
238-
ref: anchorRef,
242+
ref: combinedRef,
239243
id: anchorId,
240244
'aria-haspopup': 'true',
241245
'aria-expanded': open,
@@ -261,12 +265,7 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
261265
preventOverflow={preventOverflow}
262266
data-component="AnchoredOverlay"
263267
{...overlayProps}
264-
ref={node => {
265-
if (overlayProps?.ref) {
266-
assignRef(overlayProps.ref, node)
267-
}
268-
updateOverlayRef(node)
269-
}}
268+
ref={combinedOverlayRef}
270269
>
271270
{showXIcon ? (
272271
<div className={classes.ResponsiveCloseButtonContainer}>
@@ -293,15 +292,4 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
293292
)
294293
}
295294

296-
function assignRef<T>(
297-
ref: React.MutableRefObject<T | null> | ((instance: T | null) => void) | null | undefined,
298-
value: T | null,
299-
) {
300-
if (typeof ref === 'function') {
301-
ref(value)
302-
} else if (ref) {
303-
ref.current = value
304-
}
305-
}
306-
307295
AnchoredOverlay.displayName = 'AnchoredOverlay'

packages/react/src/ButtonGroup/ButtonGroup.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import React, {type PropsWithChildren} from 'react'
1+
import React, {useRef, type PropsWithChildren} from 'react'
22
import classes from './ButtonGroup.module.css'
33
import {clsx} from 'clsx'
44
import {FocusKeys, useFocusZone} from '../hooks/useFocusZone'
5-
import {useProvidedRefOrCreate} from '../hooks'
5+
import {useCombinedRefs} from '../hooks'
66
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
77

88
export type ButtonGroupProps = PropsWithChildren<{
@@ -17,7 +17,8 @@ const ButtonGroup = React.forwardRef(function ButtonGroup(
1717
forwardRef,
1818
) {
1919
const buttons = React.Children.map(children, (child, index) => <div key={index}>{child}</div>)
20-
const buttonRef = useProvidedRefOrCreate(forwardRef as React.RefObject<HTMLDivElement | null>)
20+
const buttonRef = useRef<HTMLDivElement>(null)
21+
const combinedRef = useCombinedRefs(buttonRef, forwardRef)
2122

2223
useFocusZone({
2324
containerRef: buttonRef,
@@ -27,8 +28,7 @@ const ButtonGroup = React.forwardRef(function ButtonGroup(
2728
})
2829

2930
return (
30-
//@ts-expect-error it needs a non nullable ref
31-
<BaseComponent ref={buttonRef} className={clsx(className, classes.ButtonGroup)} role={role} {...rest}>
31+
<BaseComponent ref={combinedRef} className={clsx(className, classes.ButtonGroup)} role={role} {...rest}>
3232
{buttons}
3333
</BaseComponent>
3434
)

packages/react/src/Checkbox/Checkbox.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import {clsx} from 'clsx'
2-
import {useProvidedRefOrCreate} from '../hooks'
3-
import React, {useContext, useEffect, type ChangeEventHandler, type InputHTMLAttributes, type ReactElement} from 'react'
2+
import {useCombinedRefs} from '../hooks'
3+
import React, {
4+
useContext,
5+
useEffect,
6+
useRef,
7+
type ChangeEventHandler,
8+
type InputHTMLAttributes,
9+
type ReactElement,
10+
} from 'react'
411
import useLayoutEffect from '../utils/useIsomorphicLayoutEffect'
512
import type {FormValidationStatus} from '../utils/types/FormValidationStatus'
613
import {CheckboxGroupContext} from '../CheckboxGroup/CheckboxGroupContext'
@@ -45,7 +52,8 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
4552
ref,
4653
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4754
): ReactElement<any> => {
48-
const checkboxRef = useProvidedRefOrCreate(ref as React.RefObject<HTMLInputElement>)
55+
const checkboxRef = useRef<HTMLInputElement>(null)
56+
const combinedRef = useCombinedRefs(checkboxRef, ref as React.RefObject<HTMLInputElement>)
4957
const checkboxGroupContext = useContext(CheckboxGroupContext)
5058
const handleOnChange: ChangeEventHandler<HTMLInputElement> = e => {
5159
checkboxGroupContext.onChange && checkboxGroupContext.onChange(e)
@@ -54,7 +62,7 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
5462
const inputProps = {
5563
type: 'checkbox',
5664
disabled,
57-
ref: checkboxRef,
65+
ref: combinedRef,
5866
checked: indeterminate ? false : checked,
5967
defaultChecked,
6068
required,
@@ -84,7 +92,7 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
8492
checkbox.setAttribute('aria-checked', checkbox.checked ? 'true' : 'false')
8593
}
8694
})
87-
// @ts-expect-error inputProp needs a non nullable ref
95+
8896
return <input {...inputProps} className={clsx(className, sharedClasses.Input, classes.Checkbox)} />
8997
},
9098
)

packages/react/src/Dialog/Dialog.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, {useCallback, useEffect, useRef, useState, type SyntheticEvent} from 'react'
22
import type {ButtonProps} from '../Button'
33
import {Button, IconButton} from '../Button'
4-
import {useOnEscapePress, useProvidedRefOrCreate} from '../hooks'
4+
import {useOnEscapePress} from '../hooks'
55
import {useFocusTrap} from '../hooks/useFocusTrap'
66
import {XIcon} from '@primer/octicons-react'
77
import {useFocusZone} from '../hooks/useFocusZone'
@@ -426,7 +426,8 @@ const Footer = React.forwardRef<HTMLDivElement, StyledFooterProps>(function Foot
426426
Footer.displayName = 'Dialog.Footer'
427427

428428
const Buttons: React.FC<React.PropsWithChildren<{buttons: DialogButtonProps[]}>> = ({buttons}) => {
429-
const autoFocusRef = useProvidedRefOrCreate<HTMLButtonElement>(buttons.find(button => button.autoFocus)?.ref)
429+
const autoFocusRef = useRef<HTMLButtonElement>(null)
430+
const combinedRef = useCombinedRefs(autoFocusRef, buttons.find(button => button.autoFocus)?.ref)
430431
let autoFocusCount = 0
431432
const [hasRendered, setHasRendered] = useState(0)
432433
useEffect(() => {
@@ -448,8 +449,7 @@ const Buttons: React.FC<React.PropsWithChildren<{buttons: DialogButtonProps[]}>>
448449
{...buttonProps}
449450
// 'normal' value is equivalent to 'default', this is used for backwards compatibility
450451
variant={buttonType === 'normal' ? 'default' : buttonType}
451-
// @ts-expect-error it needs a non nullable ref
452-
ref={autoFocus && autoFocusCount === 0 ? (autoFocusCount++, autoFocusRef) : null}
452+
ref={autoFocus && autoFocusCount === 0 ? (autoFocusCount++, combinedRef) : null}
453453
>
454454
{content}
455455
</Button>

packages/react/src/FilteredActionList/FilteredActionList.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {ActionList, type ActionListProps} from '../ActionList'
99
import type {GroupedListProps, ListPropsBase, ItemInput, RenderItemFn} from './'
1010
import {useFocusZone} from '../hooks/useFocusZone'
1111
import {useId} from '../hooks/useId'
12-
import {useProvidedRefOrCreate} from '../hooks/useProvidedRefOrCreate'
1312
import {useProvidedStateOrCreate} from '../hooks/useProvidedStateOrCreate'
1413
import useScrollFlash from '../hooks/useScrollFlash'
1514
import {VisuallyHidden} from '../VisuallyHidden'
@@ -22,6 +21,7 @@ import {isValidElementType} from 'react-is'
2221
import {useAnnouncements} from './useAnnouncements'
2322
import {clsx} from 'clsx'
2423
import {useVirtualizer} from '@tanstack/react-virtual'
24+
import {useCombinedRefs} from '../hooks'
2525

2626
const menuScrollMargins: ScrollIntoViewOptions = {startMargin: 0, endMargin: 8}
2727

@@ -189,10 +189,11 @@ export function FilteredActionList({
189189
const inputAndListContainerRef = useRef<HTMLDivElement>(null)
190190
const listRef = useRef<HTMLUListElement>(null)
191191

192-
const scrollContainerRef = useProvidedRefOrCreate<HTMLDivElement>(
193-
providedScrollContainerRef as React.RefObject<HTMLDivElement>,
194-
)
195-
const inputRef = useProvidedRefOrCreate<HTMLInputElement>(providedInputRef)
192+
const scrollContainerRef = useRef<HTMLDivElement>(null)
193+
const combinedScrollContainerRef = useCombinedRefs(scrollContainerRef, providedScrollContainerRef)
194+
195+
const inputRef = useRef<HTMLInputElement>(null)
196+
const combinedInputRef = useCombinedRefs(inputRef, providedInputRef)
196197

197198
const usingRovingTabindex = _PrivateFocusManagement === 'roving-tabindex'
198199
const [listContainerElement, setListContainerElement] = useState<HTMLUListElement | null>(null)
@@ -548,8 +549,7 @@ export function FilteredActionList({
548549
<div ref={inputAndListContainerRef} className={clsx(className, classes.Root)} data-testid="filtered-action-list">
549550
<div className={classes.Header}>
550551
<TextInput
551-
// @ts-expect-error it needs a non nullable ref
552-
ref={inputRef}
552+
ref={combinedInputRef}
553553
block
554554
width="auto"
555555
color="fg.default"
@@ -585,8 +585,7 @@ export function FilteredActionList({
585585
</label>
586586
</div>
587587
)}
588-
{/* @ts-expect-error div needs a non nullable ref */}
589-
<div ref={scrollContainerRef} className={classes.Container}>
588+
<div ref={combinedScrollContainerRef} className={classes.Container}>
590589
{getBodyContent()}
591590
</div>
592591
</div>

packages/react/src/PageHeader/PageHeader.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useEffect} from 'react'
1+
import React, {useEffect, useRef} from 'react'
22
import type {ResponsiveValue} from '../hooks/useResponsiveValue'
33
import {isResponsiveValue} from '../hooks/useResponsiveValue'
44
import Heading from '../Heading'
@@ -10,7 +10,7 @@ import {getResponsiveAttributes} from '../internal/utils/getResponsiveAttributes
1010
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
1111
import {areAllValuesTheSame, haveRegularAndWideSameValue} from '../utils/getBreakpointDeclarations'
1212
import {warning} from '../utils/warning'
13-
import {useProvidedRefOrCreate} from '../hooks'
13+
import {useCombinedRefs} from '../hooks'
1414
import type {AriaRole, FCWithSlotMarker} from '../utils/types'
1515
import {clsx} from 'clsx'
1616

@@ -49,7 +49,8 @@ export type PageHeaderProps = {
4949

5050
const Root = React.forwardRef<HTMLDivElement, React.PropsWithChildren<PageHeaderProps>>(
5151
({children, className, as: BaseComponent = 'div', 'aria-label': ariaLabel, role, hasBorder}, forwardedRef) => {
52-
const rootRef = useProvidedRefOrCreate<HTMLDivElement>(forwardedRef as React.RefObject<HTMLDivElement>)
52+
const rootRef = useRef<HTMLDivElement>(null)
53+
const combinedRef = useCombinedRefs(rootRef, forwardedRef)
5354

5455
const isInteractive = (element: HTMLElement) => {
5556
return (
@@ -105,7 +106,7 @@ const Root = React.forwardRef<HTMLDivElement, React.PropsWithChildren<PageHeader
105106

106107
return (
107108
<BaseComponent
108-
ref={rootRef}
109+
ref={combinedRef}
109110
className={clsx(classes.PageHeader, className)}
110111
data-has-border={hasBorder ? 'true' : undefined}
111112
aria-label={ariaLabel}
@@ -205,12 +206,10 @@ export type TitleAreaProps = {
205206

206207
const TitleArea = React.forwardRef<HTMLDivElement, React.PropsWithChildren<TitleAreaProps>>(
207208
({children, className, hidden = false, variant = 'medium'}, forwardedRef) => {
208-
const titleAreaRef = useProvidedRefOrCreate<HTMLDivElement>(forwardedRef as React.RefObject<HTMLDivElement>)
209209
return (
210210
<div
211211
className={clsx(classes.TitleArea, className)}
212-
// @ts-expect-error it needs a non nullable ref
213-
ref={titleAreaRef}
212+
ref={forwardedRef}
214213
data-component="TitleArea"
215214
{...getResponsiveAttributes('size-variant', variant)}
216215
{...getHiddenDataAttributes(hidden)}

0 commit comments

Comments
 (0)