Skip to content

Commit b92c507

Browse files
authored
Merge 538f208 into 2755680
2 parents 2755680 + 538f208 commit b92c507

14 files changed

Lines changed: 81 additions & 86 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 {useMergedRefs} 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 mergedRef = useMergedRefs(forwardedRef, listRef)
4546

4647
let enableFocusZone = false
4748
if (enableFocusZoneFromContainer !== undefined) enableFocusZone = enableFocusZoneFromContainer
@@ -69,12 +70,11 @@ const UnwrappedList = <As extends React.ElementType = 'ul'>(
6970
return (
7071
<ListContext.Provider value={listContextValue}>
7172
{slots.heading}
72-
{/* @ts-expect-error ref needs a non nullable ref */}
7373
<Component
7474
className={clsx(classes.ActionList, className)}
7575
role={listRole}
7676
aria-labelledby={ariaLabelledBy}
77-
ref={listRef}
77+
ref={mergedRef}
7878
data-dividers={showDividers}
7979
data-variant={variant}
8080
{...restProps}

packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {FocusTrapHookSettings} from '../hooks/useFocusTrap'
77
import {useFocusTrap} from '../hooks/useFocusTrap'
88
import type {FocusZoneHookSettings} from '../hooks/useFocusZone'
99
import {useFocusZone} from '../hooks/useFocusZone'
10-
import {useAnchoredPosition, useProvidedRefOrCreate, useRenderForcingRef} from '../hooks'
10+
import {useAnchoredPosition, useMergedRefs, useProvidedRefOrCreate, useRenderForcingRef} from '../hooks'
1111
import {useId} from '../hooks/useId'
1212
import type {AnchorPosition, PositionSettings} from '@primer/behaviors'
1313
import {type ResponsiveValue} from '../hooks/useResponsiveValue'
@@ -176,6 +176,7 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
176176
const cssAnchorPositioning = useFeatureFlag('primer_react_css_anchor_positioning')
177177
const anchorRef = useProvidedRefOrCreate(externalAnchorRef)
178178
const [overlayRef, updateOverlayRef] = useRenderForcingRef<HTMLDivElement>()
179+
const mergedOverlayRef = useMergedRefs(updateOverlayRef, overlayProps?.ref)
179180
const anchorId = useId(externalAnchorId)
180181

181182
const onClickOutside = useCallback(() => onClose?.('click-outside'), [onClose])
@@ -331,12 +332,7 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
331332
{...(cssAnchorPositioning ? {popover: 'manual'} : {})}
332333
{...restOverlayProps}
333334
{...(cssAnchorPositioning ? {id: popoverId} : {})}
334-
ref={node => {
335-
if (overlayProps?.ref) {
336-
assignRef(overlayProps.ref, node)
337-
}
338-
updateOverlayRef(node)
339-
}}
335+
ref={mergedOverlayRef}
340336
data-anchor-position={cssAnchorPositioning}
341337
data-side={cssAnchorPositioning ? side : position?.anchorSide}
342338
>
@@ -365,15 +361,4 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
365361
)
366362
}
367363

368-
function assignRef<T>(
369-
ref: React.MutableRefObject<T | null> | ((instance: T | null) => void) | null | undefined,
370-
value: T | null,
371-
) {
372-
if (typeof ref === 'function') {
373-
ref(value)
374-
} else if (ref) {
375-
ref.current = value
376-
}
377-
}
378-
379364
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 {useMergedRefs} 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 mergedRef = useMergedRefs(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={mergedRef} 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 {useMergedRefs} 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 mergedRef = useMergedRefs(checkboxRef, ref)
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: mergedRef,
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 & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
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 {useMergedRefs, useOnEscapePress} from '../hooks'
55
import {useFocusTrap} from '../hooks/useFocusTrap'
66
import {XIcon} from '@primer/octicons-react'
77
import {useFocusZone} from '../hooks/useFocusZone'
88
import {FocusKeys} from '@primer/behaviors'
99
import Portal from '../Portal'
10-
import {useMergedRefs} from '../hooks/useMergedRefs'
1110
import {useId} from '../hooks/useId'
1211
import {ScrollableRegion} from '../ScrollableRegion'
1312
import type {ResponsiveValue} from '../hooks/useResponsiveValue'
@@ -455,7 +454,8 @@ const Footer = React.forwardRef<HTMLDivElement, StyledFooterProps>(function Foot
455454
Footer.displayName = 'Dialog.Footer'
456455

457456
const Buttons: React.FC<React.PropsWithChildren<{buttons: DialogButtonProps[]}>> = ({buttons}) => {
458-
const autoFocusRef = useProvidedRefOrCreate<HTMLButtonElement>(buttons.find(button => button.autoFocus)?.ref)
457+
const autoFocusRef = useRef<HTMLButtonElement>(null)
458+
const mergedRef = useMergedRefs(autoFocusRef, buttons.find(button => button.autoFocus)?.ref)
459459
let autoFocusCount = 0
460460
const [hasRendered, setHasRendered] = useState(0)
461461
useEffect(() => {
@@ -477,8 +477,7 @@ const Buttons: React.FC<React.PropsWithChildren<{buttons: DialogButtonProps[]}>>
477477
{...buttonProps}
478478
// 'normal' value is equivalent to 'default', this is used for backwards compatibility
479479
variant={buttonType === 'normal' ? 'default' : buttonType}
480-
// @ts-expect-error it needs a non nullable ref
481-
ref={autoFocus && autoFocusCount === 0 ? (autoFocusCount++, autoFocusRef) : null}
480+
ref={autoFocus && autoFocusCount === 0 ? (autoFocusCount++, mergedRef) : null}
482481
>
483482
{content}
484483
</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 {useMergedRefs} 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 = useMergedRefs(scrollContainerRef, providedScrollContainerRef)
194+
195+
const inputRef = useRef<HTMLInputElement>(null)
196+
const combinedInputRef = useMergedRefs(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 {useMergedRefs} 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 mergedRef = useMergedRefs(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={mergedRef}
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)}

packages/react/src/TextInput/TextInput.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {AlertFillIcon} from '@primer/octicons-react'
77

88
import classes from './TextInput.module.css'
99
import TextInputInnerVisualSlot from '../internal/components/TextInputInnerVisualSlot'
10-
import {useProvidedRefOrCreate} from '../hooks'
1110
import type {Merge} from '../utils/types'
1211
import type {StyledWrapperProps} from '../internal/components/TextInputWrapper'
1312
import TextInputWrapper from '../internal/components/TextInputWrapper'
@@ -16,6 +15,7 @@ import UnstyledTextInput from '../internal/components/UnstyledTextInput'
1615
import VisuallyHidden from '../_VisuallyHidden'
1716
import {CharacterCounter} from '../utils/character-counter'
1817
import Text from '../Text'
18+
import {useMergedRefs} from '../hooks'
1919

2020
export type TextInputNonPassthroughProps = {
2121
/** @deprecated Use `leadingVisual` or `trailingVisual` prop instead */
@@ -103,7 +103,8 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
103103
ref,
104104
) => {
105105
const [isInputFocused, setIsInputFocused] = useState<boolean>(false)
106-
const inputRef = useProvidedRefOrCreate(ref as React.RefObject<HTMLInputElement | null>)
106+
const inputRef = useRef<HTMLInputElement>(null)
107+
const mergedRef = useMergedRefs(inputRef, ref)
107108
const [characterCount, setCharacterCount] = useState<string>('')
108109
const [isOverLimit, setIsOverLimit] = useState<boolean>(false)
109110
const [screenReaderMessage, setScreenReaderMessage] = useState<string>('')
@@ -258,8 +259,7 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
258259
{typeof LeadingVisual !== 'string' && isValidElementType(LeadingVisual) ? <LeadingVisual /> : LeadingVisual}
259260
</TextInputInnerVisualSlot>
260261
<UnstyledTextInput
261-
// @ts-expect-error it needs a non nullable ref
262-
ref={inputRef}
262+
ref={mergedRef}
263263
disabled={disabled}
264264
onFocus={handleInputFocus}
265265
onBlur={handleInputBlur}

packages/react/src/TooltipV2/Tooltip.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, {Children, useEffect, useRef, useState, useMemo, type ForwardRefExoticComponent} from 'react'
2-
import {useId, useProvidedRefOrCreate, useOnEscapePress, useIsMacOS} from '../hooks'
1+
import React, {Children, useEffect, useState, useMemo, type ForwardRefExoticComponent, useRef} from 'react'
2+
import {useId, useOnEscapePress, useIsMacOS, useMergedRefs} from '../hooks'
33
import {invariant} from '../utils/invariant'
44
import {warning} from '../utils/warning'
55
import {getAnchoredPosition} from '@primer/behaviors'
@@ -50,7 +50,7 @@ type TriggerPropsType = Pick<
5050
| 'onTouchCancel'
5151
| 'onTouchEnd'
5252
> & {
53-
ref?: React.RefObject<HTMLElement>
53+
ref?: React.Ref<HTMLElement>
5454
}
5555

5656
// map tooltip direction to anchoredPosition props
@@ -126,7 +126,8 @@ export const Tooltip: ForwardRefExoticComponent<
126126
) => {
127127
const tooltipId = useId(id)
128128
const child = Children.only(children)
129-
const triggerRef = useProvidedRefOrCreate(forwardedRef as React.RefObject<HTMLElement>)
129+
const triggerRef = useRef<HTMLElement>(null)
130+
const mergedTriggerRef = useMergedRefs(triggerRef, forwardedRef)
130131
const tooltipElRef = useRef<HTMLDivElement>(null)
131132

132133
const [calculatedDirection, setCalculatedDirection] = useState<TooltipDirection>(direction)
@@ -284,8 +285,7 @@ export const Tooltip: ForwardRefExoticComponent<
284285
{React.isValidElement(child) &&
285286
// eslint-disable-next-line react-hooks/refs
286287
React.cloneElement(child as React.ReactElement<TriggerPropsType>, {
287-
// @ts-expect-error it needs a non nullable ref
288-
ref: triggerRef,
288+
ref: mergedTriggerRef,
289289
// If it is a type description, we use tooltip to describe the trigger
290290
'aria-describedby': (() => {
291291
// If tooltip is not a description type, keep the original aria-describedby

packages/react/src/experimental/Tabs/Tabs.examples.stories.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const CustomTabList = (props: React.PropsWithChildren) => {
1717

1818
return (
1919
<div style={{width: '200px'}}>
20-
{/* @ts-expect-error it needs a non nullable ref */}
2120
<ActionList {...tabListProps}>{props.children}</ActionList>
2221
</div>
2322
)

0 commit comments

Comments
 (0)