Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions packages/react/src/ActionList/List.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, {type JSX} from 'react'
import React, {useRef, type JSX} from 'react'
import {fixedForwardRef} from '../utils/modern-polymorphic'
import {ActionListContainerContext} from './ActionListContainerContext'
import {useSlots} from '../hooks/useSlots'
import {Heading} from './Heading'
import {useId} from '../hooks/useId'
import {ListContext, type ActionListProps} from './shared'
import {useProvidedRefOrCreate} from '../hooks'
import {useMergedRefs} from '../hooks'
import {FocusKeys, useFocusZone} from '../hooks/useFocusZone'
import {clsx} from 'clsx'
import classes from './ActionList.module.css'
Expand Down Expand Up @@ -41,7 +41,8 @@ const UnwrappedList = <As extends React.ElementType = 'ul'>(

const ariaLabelledBy = slots.heading ? (slots.heading.props.id ?? headingId) : listLabelledBy
const listRole = role || listRoleFromContainer
const listRef = useProvidedRefOrCreate(forwardedRef as React.RefObject<HTMLUListElement>)
const listRef = useRef<HTMLElement>(null)
const mergedRef = useMergedRefs(forwardedRef, listRef)

let enableFocusZone = false
if (enableFocusZoneFromContainer !== undefined) enableFocusZone = enableFocusZoneFromContainer
Expand Down Expand Up @@ -69,12 +70,11 @@ const UnwrappedList = <As extends React.ElementType = 'ul'>(
return (
<ListContext.Provider value={listContextValue}>
{slots.heading}
{/* @ts-expect-error ref needs a non nullable ref */}
<Component
className={clsx(classes.ActionList, className)}
role={listRole}
aria-labelledby={ariaLabelledBy}
ref={listRef}
ref={mergedRef}
data-dividers={showDividers}
data-variant={variant}
{...restProps}
Expand Down
21 changes: 3 additions & 18 deletions packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {FocusTrapHookSettings} from '../hooks/useFocusTrap'
import {useFocusTrap} from '../hooks/useFocusTrap'
import type {FocusZoneHookSettings} from '../hooks/useFocusZone'
import {useFocusZone} from '../hooks/useFocusZone'
import {useAnchoredPosition, useProvidedRefOrCreate, useRenderForcingRef} from '../hooks'
import {useAnchoredPosition, useMergedRefs, useProvidedRefOrCreate, useRenderForcingRef} from '../hooks'
import {useId} from '../hooks/useId'
import type {AnchorPosition, PositionSettings} from '@primer/behaviors'
import {type ResponsiveValue} from '../hooks/useResponsiveValue'
Expand Down Expand Up @@ -175,6 +175,7 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
const cssAnchorPositioning = useFeatureFlag('primer_react_css_anchor_positioning')
const anchorRef = useProvidedRefOrCreate(externalAnchorRef)
const [overlayRef, updateOverlayRef] = useRenderForcingRef<HTMLDivElement>()
const mergedOverlayRef = useMergedRefs(updateOverlayRef, overlayProps?.ref)
const anchorId = useId(externalAnchorId)

const onClickOutside = useCallback(() => onClose?.('click-outside'), [onClose])
Expand Down Expand Up @@ -284,12 +285,7 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
preventOverflow={preventOverflow}
data-component="AnchoredOverlay"
{...restOverlayProps}
ref={node => {
if (overlayProps?.ref) {
assignRef(overlayProps.ref, node)
}
updateOverlayRef(node)
}}
ref={mergedOverlayRef}
data-anchor-position={cssAnchorPositioning}
data-side={cssAnchorPositioning ? side : position?.anchorSide}
>
Expand Down Expand Up @@ -324,15 +320,4 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
return innerContent
}

function assignRef<T>(
ref: React.MutableRefObject<T | null> | ((instance: T | null) => void) | null | undefined,
value: T | null,
) {
if (typeof ref === 'function') {
ref(value)
} else if (ref) {
ref.current = value
}
}

AnchoredOverlay.displayName = 'AnchoredOverlay'
10 changes: 5 additions & 5 deletions packages/react/src/ButtonGroup/ButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, {type PropsWithChildren} from 'react'
import React, {useRef, type PropsWithChildren} from 'react'
import classes from './ButtonGroup.module.css'
import {clsx} from 'clsx'
import {FocusKeys, useFocusZone} from '../hooks/useFocusZone'
import {useProvidedRefOrCreate} from '../hooks'
import {useMergedRefs} from '../hooks'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'

export type ButtonGroupProps = PropsWithChildren<{
Expand All @@ -17,7 +17,8 @@ const ButtonGroup = React.forwardRef(function ButtonGroup(
forwardRef,
) {
const buttons = React.Children.map(children, (child, index) => <div key={index}>{child}</div>)
const buttonRef = useProvidedRefOrCreate(forwardRef as React.RefObject<HTMLDivElement | null>)
const buttonRef = useRef<HTMLDivElement>(null)
const mergedRef = useMergedRefs(buttonRef, forwardRef)

useFocusZone({
containerRef: buttonRef,
Expand All @@ -27,8 +28,7 @@ const ButtonGroup = React.forwardRef(function ButtonGroup(
})

return (
//@ts-expect-error it needs a non nullable ref
<BaseComponent ref={buttonRef} className={clsx(className, classes.ButtonGroup)} role={role} {...rest}>
<BaseComponent ref={mergedRef} className={clsx(className, classes.ButtonGroup)} role={role} {...rest}>
{buttons}
</BaseComponent>
)
Expand Down
18 changes: 13 additions & 5 deletions packages/react/src/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import {clsx} from 'clsx'
import {useProvidedRefOrCreate} from '../hooks'
import React, {useContext, useEffect, type ChangeEventHandler, type InputHTMLAttributes, type ReactElement} from 'react'
import {useMergedRefs} from '../hooks'
import React, {
useContext,
useEffect,
useRef,
type ChangeEventHandler,
type InputHTMLAttributes,
type ReactElement,
} from 'react'
import useLayoutEffect from '../utils/useIsomorphicLayoutEffect'
import type {FormValidationStatus} from '../utils/types/FormValidationStatus'
import {CheckboxGroupContext} from '../CheckboxGroup/CheckboxGroupContext'
Expand Down Expand Up @@ -45,7 +52,8 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
ref,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): ReactElement<any> => {
const checkboxRef = useProvidedRefOrCreate(ref as React.RefObject<HTMLInputElement>)
const checkboxRef = useRef<HTMLInputElement>(null)
const mergedRef = useMergedRefs(checkboxRef, ref)
const checkboxGroupContext = useContext(CheckboxGroupContext)
const handleOnChange: ChangeEventHandler<HTMLInputElement> = e => {
checkboxGroupContext.onChange && checkboxGroupContext.onChange(e)
Expand All @@ -54,7 +62,7 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
const inputProps = {
type: 'checkbox',
disabled,
ref: checkboxRef,
ref: mergedRef,
checked: indeterminate ? false : checked,
defaultChecked,
required,
Expand Down Expand Up @@ -84,7 +92,7 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
checkbox.setAttribute('aria-checked', checkbox.checked ? 'true' : 'false')
}
})
// @ts-expect-error inputProp needs a non nullable ref

return <input {...inputProps} className={clsx(className, sharedClasses.Input, classes.Checkbox)} />
},
)
Expand Down
8 changes: 4 additions & 4 deletions packages/react/src/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, {useCallback, useEffect, useRef, useState, type SyntheticEvent} from 'react'
import type {ButtonProps} from '../Button'
import {Button, IconButton} from '../Button'
import {useOnEscapePress, useProvidedRefOrCreate} from '../hooks'
import {useMergedRefs, useOnEscapePress} from '../hooks'
import {useFocusTrap} from '../hooks/useFocusTrap'
import {XIcon} from '@primer/octicons-react'
import {useFocusZone} from '../hooks/useFocusZone'
Expand Down Expand Up @@ -428,7 +428,8 @@ const Footer = React.forwardRef<HTMLDivElement, StyledFooterProps>(function Foot
Footer.displayName = 'Dialog.Footer'

const Buttons: React.FC<React.PropsWithChildren<{buttons: DialogButtonProps[]}>> = ({buttons}) => {
const autoFocusRef = useProvidedRefOrCreate<HTMLButtonElement>(buttons.find(button => button.autoFocus)?.ref)
const autoFocusRef = useRef<HTMLButtonElement>(null)
const mergedRef = useMergedRefs(autoFocusRef, buttons.find(button => button.autoFocus)?.ref)
let autoFocusCount = 0
const [hasRendered, setHasRendered] = useState(0)
useEffect(() => {
Expand All @@ -450,8 +451,7 @@ const Buttons: React.FC<React.PropsWithChildren<{buttons: DialogButtonProps[]}>>
{...buttonProps}
// 'normal' value is equivalent to 'default', this is used for backwards compatibility
variant={buttonType === 'normal' ? 'default' : buttonType}
// @ts-expect-error it needs a non nullable ref
ref={autoFocus && autoFocusCount === 0 ? (autoFocusCount++, autoFocusRef) : null}
ref={autoFocus && autoFocusCount === 0 ? (autoFocusCount++, mergedRef) : null}
>
{content}
</Button>
Expand Down
17 changes: 8 additions & 9 deletions packages/react/src/FilteredActionList/FilteredActionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {ActionList, type ActionListProps} from '../ActionList'
import type {GroupedListProps, ListPropsBase, ItemInput, RenderItemFn} from './'
import {useFocusZone} from '../hooks/useFocusZone'
import {useId} from '../hooks/useId'
import {useProvidedRefOrCreate} from '../hooks/useProvidedRefOrCreate'
import {useProvidedStateOrCreate} from '../hooks/useProvidedStateOrCreate'
import useScrollFlash from '../hooks/useScrollFlash'
import {VisuallyHidden} from '../VisuallyHidden'
Expand All @@ -22,6 +21,7 @@ import {isValidElementType} from 'react-is'
import {useAnnouncements} from './useAnnouncements'
import {clsx} from 'clsx'
import {useVirtualizer} from '@tanstack/react-virtual'
import {useMergedRefs} from '../hooks'

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

Expand Down Expand Up @@ -189,10 +189,11 @@ export function FilteredActionList({
const inputAndListContainerRef = useRef<HTMLDivElement>(null)
const listRef = useRef<HTMLUListElement>(null)

const scrollContainerRef = useProvidedRefOrCreate<HTMLDivElement>(
providedScrollContainerRef as React.RefObject<HTMLDivElement>,
)
const inputRef = useProvidedRefOrCreate<HTMLInputElement>(providedInputRef)
const scrollContainerRef = useRef<HTMLDivElement>(null)
const combinedScrollContainerRef = useMergedRefs(scrollContainerRef, providedScrollContainerRef)

const inputRef = useRef<HTMLInputElement>(null)
const combinedInputRef = useMergedRefs(inputRef, providedInputRef)

const usingRovingTabindex = _PrivateFocusManagement === 'roving-tabindex'
const [listContainerElement, setListContainerElement] = useState<HTMLUListElement | null>(null)
Expand Down Expand Up @@ -548,8 +549,7 @@ export function FilteredActionList({
<div ref={inputAndListContainerRef} className={clsx(className, classes.Root)} data-testid="filtered-action-list">
<div className={classes.Header}>
<TextInput
// @ts-expect-error it needs a non nullable ref
ref={inputRef}
ref={combinedInputRef}
block
width="auto"
color="fg.default"
Expand Down Expand Up @@ -585,8 +585,7 @@ export function FilteredActionList({
</label>
</div>
)}
{/* @ts-expect-error div needs a non nullable ref */}
<div ref={scrollContainerRef} className={classes.Container}>
<div ref={combinedScrollContainerRef} className={classes.Container}>
{getBodyContent()}
</div>
</div>
Expand Down
13 changes: 6 additions & 7 deletions packages/react/src/PageHeader/PageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useEffect} from 'react'
import React, {useEffect, useRef} from 'react'
import type {ResponsiveValue} from '../hooks/useResponsiveValue'
import {isResponsiveValue} from '../hooks/useResponsiveValue'
import Heading from '../Heading'
Expand All @@ -10,7 +10,7 @@ import {getResponsiveAttributes} from '../internal/utils/getResponsiveAttributes
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import {areAllValuesTheSame, haveRegularAndWideSameValue} from '../utils/getBreakpointDeclarations'
import {warning} from '../utils/warning'
import {useProvidedRefOrCreate} from '../hooks'
import {useMergedRefs} from '../hooks'
import type {AriaRole, FCWithSlotMarker} from '../utils/types'
import {clsx} from 'clsx'

Expand Down Expand Up @@ -49,7 +49,8 @@ export type PageHeaderProps = {

const Root = React.forwardRef<HTMLDivElement, React.PropsWithChildren<PageHeaderProps>>(
({children, className, as: BaseComponent = 'div', 'aria-label': ariaLabel, role, hasBorder}, forwardedRef) => {
const rootRef = useProvidedRefOrCreate<HTMLDivElement>(forwardedRef as React.RefObject<HTMLDivElement>)
const rootRef = useRef<HTMLDivElement>(null)
const mergedRef = useMergedRefs(rootRef, forwardedRef)

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

return (
<BaseComponent
ref={rootRef}
ref={mergedRef}
className={clsx(classes.PageHeader, className)}
data-has-border={hasBorder ? 'true' : undefined}
aria-label={ariaLabel}
Expand Down Expand Up @@ -205,12 +206,10 @@ export type TitleAreaProps = {

const TitleArea = React.forwardRef<HTMLDivElement, React.PropsWithChildren<TitleAreaProps>>(
({children, className, hidden = false, variant = 'medium'}, forwardedRef) => {
const titleAreaRef = useProvidedRefOrCreate<HTMLDivElement>(forwardedRef as React.RefObject<HTMLDivElement>)
return (
<div
className={clsx(classes.TitleArea, className)}
// @ts-expect-error it needs a non nullable ref
ref={titleAreaRef}
ref={forwardedRef}
data-component="TitleArea"
{...getResponsiveAttributes('size-variant', variant)}
{...getHiddenDataAttributes(hidden)}
Expand Down
8 changes: 4 additions & 4 deletions packages/react/src/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {AlertFillIcon} from '@primer/octicons-react'

import classes from './TextInput.module.css'
import TextInputInnerVisualSlot from '../internal/components/TextInputInnerVisualSlot'
import {useProvidedRefOrCreate} from '../hooks'
import type {Merge} from '../utils/types'
import type {StyledWrapperProps} from '../internal/components/TextInputWrapper'
import TextInputWrapper from '../internal/components/TextInputWrapper'
Expand All @@ -16,6 +15,7 @@ import UnstyledTextInput from '../internal/components/UnstyledTextInput'
import VisuallyHidden from '../_VisuallyHidden'
import {CharacterCounter} from '../utils/character-counter'
import Text from '../Text'
import {useMergedRefs} from '../hooks'

export type TextInputNonPassthroughProps = {
/** @deprecated Use `leadingVisual` or `trailingVisual` prop instead */
Expand Down Expand Up @@ -103,7 +103,8 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
ref,
) => {
const [isInputFocused, setIsInputFocused] = useState<boolean>(false)
const inputRef = useProvidedRefOrCreate(ref as React.RefObject<HTMLInputElement | null>)
const inputRef = useRef<HTMLInputElement>(null)
const mergedRef = useMergedRefs(inputRef, ref)
const [characterCount, setCharacterCount] = useState<string>('')
const [isOverLimit, setIsOverLimit] = useState<boolean>(false)
const [screenReaderMessage, setScreenReaderMessage] = useState<string>('')
Expand Down Expand Up @@ -258,8 +259,7 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
{typeof LeadingVisual !== 'string' && isValidElementType(LeadingVisual) ? <LeadingVisual /> : LeadingVisual}
</TextInputInnerVisualSlot>
<UnstyledTextInput
// @ts-expect-error it needs a non nullable ref
ref={inputRef}
ref={mergedRef}
disabled={disabled}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
Expand Down
12 changes: 6 additions & 6 deletions packages/react/src/TooltipV2/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {Children, useEffect, useRef, useState, useMemo, type ForwardRefExoticComponent} from 'react'
import {useId, useProvidedRefOrCreate, useOnEscapePress, useIsMacOS} from '../hooks'
import React, {Children, useEffect, useState, useMemo, type ForwardRefExoticComponent, useRef} from 'react'
import {useId, useOnEscapePress, useIsMacOS, useMergedRefs} from '../hooks'
import {invariant} from '../utils/invariant'
import {warning} from '../utils/warning'
import {getAnchoredPosition} from '@primer/behaviors'
Expand Down Expand Up @@ -50,7 +50,7 @@ type TriggerPropsType = Pick<
| 'onTouchCancel'
| 'onTouchEnd'
> & {
ref?: React.RefObject<HTMLElement>
ref?: React.Ref<HTMLElement>
}

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

const [calculatedDirection, setCalculatedDirection] = useState<TooltipDirection>(direction)
Expand Down Expand Up @@ -284,8 +285,7 @@ export const Tooltip: ForwardRefExoticComponent<
{React.isValidElement(child) &&
// eslint-disable-next-line react-hooks/refs
React.cloneElement(child as React.ReactElement<TriggerPropsType>, {
// @ts-expect-error it needs a non nullable ref
ref: triggerRef,
ref: mergedTriggerRef,
// If it is a type description, we use tooltip to describe the trigger
'aria-describedby': (() => {
// If tooltip is not a description type, keep the original aria-describedby
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const CustomTabList = (props: React.PropsWithChildren) => {

return (
<div style={{width: '200px'}}>
{/* @ts-expect-error it needs a non nullable ref */}
<ActionList {...tabListProps}>{props.children}</ActionList>
</div>
)
Expand Down
Loading
Loading