Skip to content
Draft
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
5 changes: 0 additions & 5 deletions .changeset/anchored-position-scroll-recalculation.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/bumpy-paws-end.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/combined-refs-hook.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/graduate-css-has-selector-perf-flag.md

This file was deleted.

11 changes: 0 additions & 11 deletions .changeset/improve-theme-provider-perf.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/soft-pianos-carry.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/stack-spacing-options.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/tough-ears-drive.md

This file was deleted.

4 changes: 2 additions & 2 deletions examples/codesandbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.3",
"@primer/react": "38.18.0",
"@primer/styled-react": "1.0.4",
"@primer/react": "38.19.0",
"@primer/styled-react": "1.0.5",
"styled-components": "5.x",
"typescript": "^5.9.2",
"vite": "^7.1.11"
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"@primer/react": "38.18.0",
"@primer/styled-react": "1.0.4",
"@primer/react": "38.19.0",
"@primer/styled-react": "1.0.5",
"next": "^16.1.7",
"react": "^19.2.0",
"react-dom": "^19.2.0",
Expand Down
4 changes: 2 additions & 2 deletions examples/theming/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
},
"dependencies": {
"@primer/octicons-react": "^19.21.0",
"@primer/react": "38.18.0",
"@primer/styled-react": "1.0.4",
"@primer/react": "38.19.0",
"@primer/styled-react": "1.0.5",
"clsx": "^2.1.1",
"next": "^16.1.7",
"react": "^19.2.0",
Expand Down
27 changes: 27 additions & 0 deletions packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# @primer/react

## 38.19.0

### Minor Changes

- [#7677](https://github.com/primer/react/pull/7677) [`c1a81b1`](https://github.com/primer/react/commit/c1a81b178742ba547b85a3df3ed3c27bcff6b7c5) Thanks [@TylerJDev](https://github.com/TylerJDev)! - AnchoredOverlay: Add Popover API to AnchoredOverlay (behind `primer_react_css_anchor_positioning` feature flag)

- [#7697](https://github.com/primer/react/pull/7697) [`990ce7b`](https://github.com/primer/react/commit/990ce7b625bcf90ef3867e93086d0c74835a9068) Thanks [@hectahertz](https://github.com/hectahertz)! - **Stack**: Add `tight` (4px) and `cozy` (12px) spacing values to `gap` and `padding` props. Add `paddingBlock` and `paddingInline` props for directional padding control.

### Patch Changes

- [#7652](https://github.com/primer/react/pull/7652) [`5d19e2b`](https://github.com/primer/react/commit/5d19e2bb5bbd444f2e82d98eef300c221863941c) Thanks [@owenniblock](https://github.com/owenniblock)! - useAnchoredPosition: recalculate overlay position when any scrollable ancestor (or the window) is scrolled.

- [#7707](https://github.com/primer/react/pull/7707) [`6431bfe`](https://github.com/primer/react/commit/6431bfecd72e24db1dfa90b02f8a42834e63843b) Thanks [@liuliu-dev](https://github.com/liuliu-dev)! - Fix anchor-name not being set on the anchor element when SelectPanel is opened in Copilot code agent.

- [#7638](https://github.com/primer/react/pull/7638) [`f04e85d`](https://github.com/primer/react/commit/f04e85df536cd6e8047f70b070bd3cf7c0961f92) Thanks [@iansan5653](https://github.com/iansan5653)! - Update internal implementations of combined refs to improve performance and add support for React 19 callback refs

- [#7633](https://github.com/primer/react/pull/7633) [`a107d39`](https://github.com/primer/react/commit/a107d398e0574b5f8085485c96b27a168061eb50) Thanks [@mattcosta7](https://github.com/mattcosta7)! - Graduate `primer_react_css_has_selector_perf` feature flag: the CSS `:has()` performance optimization (`body[data-dialog-scroll-disabled]`) is now the default behavior for Dialog scroll disabling

- [#7695](https://github.com/primer/react/pull/7695) [`780fc3d`](https://github.com/primer/react/commit/780fc3d7b52fd0f9b63f313af6355398180a0118) Thanks [@mattcosta7](https://github.com/mattcosta7)! - perf(ThemeProvider): Reduce unnecessary renders and effect cascades

- Replace `useState` + `useEffect` SSR hydration handoff with `useSyncExternalStore` — eliminates post-hydration re-render
- Replace `useState` + `useEffect` in `useSystemColorMode` with `useSyncExternalStore` — eliminates effect gap and stale-then-update flicker
- Cache `getServerHandoff` DOM read + JSON.parse per ID (runs once, not on every call)
- Memoize context value object to prevent unnecessary re-renders of all consumers

- [#7706](https://github.com/primer/react/pull/7706) [`fd8910a`](https://github.com/primer/react/commit/fd8910abff851f43d19805ceaa439a9a18c4f226) Thanks [@liuliu-dev](https://github.com/liuliu-dev)! - ActionList.Item: fix inline descriptions being referenced via `aria-labelledby` instead of `aria-describedby`

## 38.18.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@primer/react",
"type": "module",
"version": "38.18.0",
"version": "38.19.0",
"description": "An implementation of GitHub's Primer Design System using React",
"main": "./dist/index.js",
"module": "./dist/index.js",
Expand Down
6 changes: 3 additions & 3 deletions packages/react/src/ActionBar/ActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {ActionMenu} from '../ActionMenu'
import {useFocusZone, FocusKeys} from '../hooks/useFocusZone'
import styles from './ActionBar.module.css'
import {clsx} from 'clsx'
import {useMergedRefs} from '../hooks'
import {useRefObjectAsForwardedRef} from '../hooks'
import {createDescendantRegistry} from '../utils/descendant-registry'

const ACTIONBAR_ITEM_GAP = 8
Expand Down Expand Up @@ -470,7 +470,7 @@ function useWidth(ref: React.RefObject<HTMLElement | null>) {
export const ActionBarIconButton = forwardRef(
({disabled, onClick, ...props}: ActionBarIconButtonProps, forwardedRef) => {
const ref = useRef<HTMLButtonElement>(null)
const mergedRef = useMergedRefs(ref, forwardedRef)
useRefObjectAsForwardedRef(forwardedRef, ref)

const {size, isVisibleChild} = React.useContext(ActionBarContext)
const {groupId} = React.useContext(ActionBarGroupContext)
Expand Down Expand Up @@ -507,7 +507,7 @@ export const ActionBarIconButton = forwardRef(
return (
<IconButton
aria-disabled={disabled}
ref={mergedRef}
ref={ref}
size={size}
onClick={clickHandler}
{...props}
Expand Down
6 changes: 3 additions & 3 deletions packages/react/src/ActionList/Heading.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {forwardRef} from 'react'
import {useMergedRefs} from '../hooks'
import {useRefObjectAsForwardedRef} from '../hooks'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import {default as HeadingComponent} from '../Heading'
import {ListContext} from './shared'
Expand All @@ -21,7 +21,7 @@ export type ActionListHeadingProps = {

export const Heading = forwardRef(({as, size, children, visuallyHidden = false, className, ...props}, forwardedRef) => {
const innerRef = React.useRef<HTMLHeadingElement>(null)
const mergedRef = useMergedRefs(forwardedRef, innerRef)
useRefObjectAsForwardedRef(forwardedRef, innerRef)

const {headingId: headingId, variant: listVariant} = React.useContext(ListContext)
const {container} = React.useContext(ActionListContainerContext)
Expand All @@ -37,7 +37,7 @@ export const Heading = forwardRef(({as, size, children, visuallyHidden = false,
<HeadingComponent
as={as}
variant={size}
ref={mergedRef}
ref={innerRef}
// use custom id if it is provided. Otherwise, use the id from the context
id={props.id ?? headingId}
className={clsx(className, classes.ActionListHeader)}
Expand Down
1 change: 0 additions & 1 deletion packages/react/src/Autocomplete/Autocomplete.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ describe('Autocomplete', () => {
const inputNode = getByLabelText(AUTOCOMPLETE_LABEL)

expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
inputNode.focus()
fireEvent.click(inputNode)
fireEvent.keyDown(inputNode, {key: 'ArrowDown'})

Expand Down
6 changes: 3 additions & 3 deletions packages/react/src/Autocomplete/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, {useCallback, useContext, useEffect, useState} from 'react'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import {AutocompleteContext, AutocompleteInputContext} from './AutocompleteContext'
import TextInput from '../TextInput'
import {useMergedRefs} from '../hooks/useMergedRefs'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import type {ComponentProps} from '../utils/types'
import useSafeTimeout from '../hooks/useSafeTimeout'

Expand Down Expand Up @@ -43,7 +43,7 @@ const AutocompleteInput = React.forwardRef(
}
const {activeDescendantRef, id, inputRef, setInputValue, setShowMenu, showMenu} = autocompleteContext
const {autocompleteSuggestion = '', inputValue = '', isMenuDirectlyActivated} = inputContext
const mergedRef = useMergedRefs(forwardedRef, inputRef)
useRefObjectAsForwardedRef(forwardedRef, inputRef)
const [highlightRemainingText, setHighlightRemainingText] = useState<boolean>(true)
const {safeSetTimeout} = useSafeTimeout()

Expand Down Expand Up @@ -160,7 +160,7 @@ const AutocompleteInput = React.forwardRef(
onKeyDown={handleInputKeyDown}
onKeyPress={onInputKeyPress}
onKeyUp={handleInputKeyUp}
ref={mergedRef}
ref={inputRef}
aria-controls={`${id}-listbox`}
aria-autocomplete="both"
role="combobox"
Expand Down
6 changes: 3 additions & 3 deletions packages/react/src/Autocomplete/AutocompleteOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {OverlayProps} from '../Overlay'
import Overlay from '../Overlay'
import type {ComponentProps} from '../utils/types'
import {AutocompleteContext} from './AutocompleteContext'
import {useMergedRefs} from '../hooks/useMergedRefs'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import VisuallyHidden from '../_VisuallyHidden'

import classes from './AutocompleteOverlay.module.css'
Expand Down Expand Up @@ -57,7 +57,7 @@ function AutocompleteOverlay({
[showMenu, selectedItemLength],
)

const mergedRef = useMergedRefs(scrollContainerRef, floatingElementRef)
useRefObjectAsForwardedRef(scrollContainerRef, floatingElementRef)

const closeOptionList = useCallback(() => {
setShowMenu(false)
Expand All @@ -73,7 +73,7 @@ function AutocompleteOverlay({
preventFocusOnOpen={true}
onClickOutside={closeOptionList}
onEscape={closeOptionList}
ref={mergedRef}
ref={floatingElementRef as React.RefObject<HTMLDivElement>}
top={position?.top}
left={position?.left}
className={clsx(classes.Overlay, className)}
Expand Down
7 changes: 4 additions & 3 deletions packages/react/src/Button/ButtonBase.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, {forwardRef, type JSX} from 'react'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import type {ButtonProps} from './types'
import {useMergedRefs} from '../hooks/useMergedRefs'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {VisuallyHidden} from '../VisuallyHidden'
import Spinner from '../Spinner'
import CounterLabel from '../CounterLabel'
Expand Down Expand Up @@ -51,7 +51,7 @@ const ButtonBase = forwardRef(({children, as: Component = 'button', ...props}, f
} = props

const innerRef = React.useRef<HTMLButtonElement>(null)
const combinedRefs = useMergedRefs(forwardedRef, innerRef)
useRefObjectAsForwardedRef(forwardedRef, innerRef)

const uuid = useId(id)
const loadingAnnouncementID = `${uuid}-loading-announcement`
Expand Down Expand Up @@ -87,7 +87,8 @@ const ButtonBase = forwardRef(({children, as: Component = 'button', ...props}, f
<Component
aria-disabled={loading ? true : undefined}
{...rest}
ref={combinedRefs}
// @ts-ignore temporary disable as we migrate to css modules, until we remove PolymorphicForwardRefComponent
ref={innerRef}
className={clsx(classes.ButtonBase, className)}
data-block={block ? 'block' : null}
data-inactive={inactive ? true : undefined}
Expand Down
6 changes: 3 additions & 3 deletions packages/react/src/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {XIcon} from '@primer/octicons-react'
import {useFocusZone} from '../hooks/useFocusZone'
import {FocusKeys} from '@primer/behaviors'
import Portal from '../Portal'
import {useMergedRefs} from '../hooks/useMergedRefs'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import {useId} from '../hooks/useId'
import {ScrollableRegion} from '../ScrollableRegion'
import type {ResponsiveValue} from '../hooks/useResponsiveValue'
Expand Down Expand Up @@ -299,7 +299,7 @@ const _Dialog = React.forwardRef<HTMLDivElement, React.PropsWithChildren<DialogP
})

const dialogRef = useRef<HTMLDivElement>(null)
const mergedRef = useMergedRefs(forwardedRef, dialogRef)
useRefObjectAsForwardedRef(forwardedRef, dialogRef)
const backdropRef = useRef<HTMLDivElement>(null)

useFocusTrap({
Expand Down Expand Up @@ -388,7 +388,7 @@ const _Dialog = React.forwardRef<HTMLDivElement, React.PropsWithChildren<DialogP
}}
>
<div
ref={mergedRef}
ref={dialogRef}
role={role}
aria-labelledby={dialogLabelId}
aria-describedby={dialogDescriptionId}
Expand Down
6 changes: 3 additions & 3 deletions packages/react/src/Heading/Heading.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {clsx} from 'clsx'
import React, {forwardRef, useEffect} from 'react'
import {useMergedRefs} from '../hooks'
import {useRefObjectAsForwardedRef} from '../hooks'
import type {ComponentProps} from '../utils/types'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import classes from './Heading.module.css'
Expand All @@ -14,7 +14,7 @@ type StyledHeadingProps = {

const Heading = forwardRef(({as: Component = 'h2', className, variant, ...props}, forwardedRef) => {
const innerRef = React.useRef<HTMLHeadingElement>(null)
const mergedRef = useMergedRefs(forwardedRef, innerRef)
useRefObjectAsForwardedRef(forwardedRef, innerRef)

if (__DEV__) {
/**
Expand All @@ -32,7 +32,7 @@ const Heading = forwardRef(({as: Component = 'h2', className, variant, ...props}
}, [innerRef])
}

return <Component className={clsx(className, classes.Heading)} data-variant={variant} {...props} ref={mergedRef} />
return <Component className={clsx(className, classes.Heading)} data-variant={variant} {...props} ref={innerRef} />
}) as PolymorphicForwardRefComponent<HeadingLevels, StyledHeadingProps>

Heading.displayName = 'Heading'
Expand Down
7 changes: 4 additions & 3 deletions packages/react/src/Link/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {clsx} from 'clsx'
import React, {useEffect, type ForwardedRef, type ElementRef} from 'react'
import {useMergedRefs} from '../hooks'
import {useRefObjectAsForwardedRef} from '../hooks'
import classes from './Link.module.css'
import type {ComponentProps} from '../utils/types'
import {type PolymorphicProps, fixedForwardRef} from '../utils/modern-polymorphic'
Expand All @@ -20,7 +20,7 @@ export const UnwrappedLink = <As extends React.ElementType = 'a'>(
) => {
const {as: Component = 'a', className, inline, muted, hoverColor, ...restProps} = props
const innerRef = React.useRef<ElementRef<As>>(null)
const mergedRef = useMergedRefs(ref, innerRef)
useRefObjectAsForwardedRef(ref, innerRef)

if (__DEV__) {
/**
Expand Down Expand Up @@ -53,7 +53,8 @@ export const UnwrappedLink = <As extends React.ElementType = 'a'>(
data-inline={inline}
data-hover-color={hoverColor}
{...restProps}
ref={mergedRef}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ref={innerRef as any}
/>
)
}
Expand Down
6 changes: 3 additions & 3 deletions packages/react/src/Overlay/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {AriaRole, Merge} from '../utils/types'
import type {TouchOrMouseEvent} from '../hooks'
import {useOverlay} from '../hooks'
import Portal from '../Portal'
import {useMergedRefs} from '../hooks/useMergedRefs'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
import type {AnchorSide} from '@primer/behaviors'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import classes from './Overlay.module.css'
Expand Down Expand Up @@ -194,7 +194,7 @@ const Overlay = React.forwardRef<HTMLDivElement, internalOverlayProps>(
): ReactElement<any> => {
const featureFlagMaxHeightClampToViewport = useFeatureFlag('primer_react_overlay_max_height_clamp_to_viewport')
const overlayRef = useRef<HTMLDivElement>(null)
const mergedRef = useMergedRefs(forwardedRef, overlayRef)
useRefObjectAsForwardedRef(forwardedRef, overlayRef)
const slideAnimationDistance = 8 // var(--base-size-8), hardcoded to do some math
const slideAnimationEasing = 'cubic-bezier(0.33, 1, 0.68, 1)'
const cssAnchorPositioning = useFeatureFlag('primer_react_css_anchor_positioning')
Expand Down Expand Up @@ -239,7 +239,7 @@ const Overlay = React.forwardRef<HTMLDivElement, internalOverlayProps>(
role={role}
width={width}
data-reflow-container={!preventOverflow ? true : undefined}
ref={mergedRef}
ref={overlayRef}
left={leftPosition}
right={right}
height={height}
Expand Down
Loading
Loading