From a3c1fb9a5f79af4df236306d45baa882d1a28e25 Mon Sep 17 00:00:00 2001 From: Marie Lucca Date: Fri, 26 Sep 2025 13:54:24 -0400 Subject: [PATCH 01/21] use forwardedAs prop in components --- .changeset/afraid-eyes-serve.md | 5 +++++ .../styled-react/src/components/Header.tsx | 11 ++++++++-- .../src/components/PageHeader.tsx | 22 ++++++++++++++----- .../src/components/StateLabelProps.tsx | 10 --------- .../src/components/UnderlineNav.tsx | 13 +++++++++-- 5 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 .changeset/afraid-eyes-serve.md delete mode 100644 packages/styled-react/src/components/StateLabelProps.tsx diff --git a/.changeset/afraid-eyes-serve.md b/.changeset/afraid-eyes-serve.md new file mode 100644 index 00000000000..ff70b8317cf --- /dev/null +++ b/.changeset/afraid-eyes-serve.md @@ -0,0 +1,5 @@ +--- +"@primer/styled-react": patch +--- + +chore: use forwardedAs prop in styled-react diff --git a/packages/styled-react/src/components/Header.tsx b/packages/styled-react/src/components/Header.tsx index b7266645a53..7533950240a 100644 --- a/packages/styled-react/src/components/Header.tsx +++ b/packages/styled-react/src/components/Header.tsx @@ -3,6 +3,7 @@ import { type HeaderItemProps as PrimerHeaderItemProps, type HeaderLinkProps as PrimerHeaderLinkProps, Header as PrimerHeader, + type HeaderLinkProps, } from '@primer/react' import {forwardRef} from 'react' import {Box} from './Box' @@ -11,20 +12,26 @@ import type {SxProp} from '../sx' type HeaderProps = PrimerHeaderProps & SxProp -const HeaderImpl = forwardRef(function Header(props, ref) { +const StyledHeader = forwardRef(function Header(props, ref) { return }) as ForwardRefComponent<'header', HeaderProps> +// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error +const HeaderImpl = ({as, ...props}: HeaderProps) => + type HeaderItemProps = PrimerHeaderItemProps & SxProp const HeaderItem = forwardRef(function HeaderItem(props, ref) { return }) -const HeaderLink = forwardRef(function HeaderLink(props, ref) { +const StyledHeaderLink = forwardRef(function HeaderLink(props, ref) { return }) +// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error +const HeaderLink = ({as, ...props}: HeaderLinkProps) => + const Header = Object.assign(HeaderImpl, { Item: HeaderItem, Link: HeaderLink, diff --git a/packages/styled-react/src/components/PageHeader.tsx b/packages/styled-react/src/components/PageHeader.tsx index db23410c7c7..73b4471161b 100644 --- a/packages/styled-react/src/components/PageHeader.tsx +++ b/packages/styled-react/src/components/PageHeader.tsx @@ -10,10 +10,11 @@ import {sx, type SxProp} from '../sx' import type {ForwardRefComponent} from '../polymorphic' import {Box} from './Box' import type {PropsWithChildren} from 'react' +import React from 'react' type PageHeaderProps = PrimerPageHeaderProps & SxProp -const PageHeaderImpl: ForwardRefComponent<'div', PageHeaderProps> = styled( +const StyledPageHeader: ForwardRefComponent<'div', PageHeaderProps> = styled( PrimerPageHeader, ).withConfig({ shouldForwardProp: prop => prop !== 'sx', @@ -21,9 +22,14 @@ const PageHeaderImpl: ForwardRefComponent<'div', PageHeaderProps> = styled( ${sx} ` +const PageHeaderImpl = React.forwardRef(({as, ...props}, ref) => ( + // @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error + +)) as ForwardRefComponent<'div', PageHeaderProps> + type PageHeaderActionsProps = PrimerPageHeaderActionsProps & SxProp -function PageHeaderActions({sx, ...rest}: PageHeaderActionsProps) { +function StyledPageHeaderActions({sx, ...rest}: PageHeaderActionsProps) { const style: CSSCustomProperties = {} if (sx) { // @ts-ignore sx has height attribute @@ -37,13 +43,16 @@ function PageHeaderActions({sx, ...rest}: PageHeaderActionsProps) { return } +// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error +const PageHeaderActions = ({as, ...props}: PageHeaderProps) => + type PageHeaderTitleProps = PropsWithChildren & SxProp type CSSCustomProperties = { [key: `--${string}`]: string | number } -function PageHeaderTitle({sx, ...rest}: PageHeaderTitleProps) { +function StyledPageHeaderTitle({sx, ...rest}: PageHeaderTitleProps) { const style: CSSCustomProperties = {} if (sx) { // @ts-ignore sx can have color attribute @@ -65,6 +74,9 @@ function PageHeaderTitle({sx, ...rest}: PageHeaderTitleProps) { return } +// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error +const PageHeaderTitle = ({as, ...props}: PageHeaderTitleProps) => + type PageHeaderTitleAreaProps = PropsWithChildren & SxProp const PageHeaderTitleArea: ForwardRefComponent<'div', PageHeaderTitleAreaProps> = styled( @@ -75,7 +87,7 @@ const PageHeaderTitleArea: ForwardRefComponent<'div', PageHeaderTitleAreaProps> ${sx} ` -type PageHeaderComponent = ForwardRefComponent<'div', PageHeaderProps> & { +type PageHeaderComponentType = ForwardRefComponent<'div', PageHeaderProps> & { Actions: typeof PageHeaderActions ContextArea: typeof PrimerPageHeader.ContextArea ParentLink: typeof PrimerPageHeader.ParentLink @@ -91,7 +103,7 @@ type PageHeaderComponent = ForwardRefComponent<'div', PageHeaderProps> & { TrailingAction: typeof PrimerPageHeader.TrailingAction } -const PageHeader: PageHeaderComponent = Object.assign(PageHeaderImpl, { +const PageHeader: PageHeaderComponentType = Object.assign(PageHeaderImpl, { Actions: PageHeaderActions, ContextArea: PrimerPageHeader.ContextArea, ParentLink: PrimerPageHeader.ParentLink, diff --git a/packages/styled-react/src/components/StateLabelProps.tsx b/packages/styled-react/src/components/StateLabelProps.tsx deleted file mode 100644 index 2d08f8c79d4..00000000000 --- a/packages/styled-react/src/components/StateLabelProps.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import {StateLabel as PrimerStateLabel, type StateLabelProps as PrimerStateLabelProps} from '@primer/react' -import {Box} from './Box' -import {forwardRef} from 'react' -import {type SxProp} from '../sx' - -export type StateLabelProps = PrimerStateLabelProps & SxProp - -export const StateLabel = forwardRef(function StateLabel(props, ref) { - return -}) diff --git a/packages/styled-react/src/components/UnderlineNav.tsx b/packages/styled-react/src/components/UnderlineNav.tsx index e4bec07908b..bd69bec4f2d 100644 --- a/packages/styled-react/src/components/UnderlineNav.tsx +++ b/packages/styled-react/src/components/UnderlineNav.tsx @@ -11,19 +11,28 @@ import {sx, type SxProp} from '../sx' export type UnderlineNavProps = PrimerUnderlineNavProps & SxProp -const UnderlineNavImpl = forwardRef(function UnderlineNav(props, ref) { +const StyledUnderlineNav = forwardRef(function UnderlineNav(props, ref) { return }) +export const UnderlineNavImpl = ({as, ...props}: UnderlineNavProps) => ( + // @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error + +) + export type UnderlineNavItemProps = PrimerUnderlineNavItemProps & SxProp -const UnderlineNavItem: ForwardRefComponent<'a', UnderlineNavItemProps> = styled( +const StyledUnderlineNavItem: ForwardRefComponent<'a', UnderlineNavItemProps> = styled( PrimerUnderlineNav.Item, ).withConfig({ shouldForwardProp: prop => prop !== 'sx', })` ${sx} ` +export const UnderlineNavItem = ({as, ...props}: UnderlineNavItemProps) => ( + // @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error + +) export const UnderlineNav = Object.assign(UnderlineNavImpl, { Item: UnderlineNavItem, From cf445d8ce5033a1237ffac222b85fb11033a436d Mon Sep 17 00:00:00 2001 From: Marie Lucca Date: Fri, 26 Sep 2025 16:50:23 -0400 Subject: [PATCH 02/21] type fix --- packages/styled-react/src/components/PageHeader.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/styled-react/src/components/PageHeader.tsx b/packages/styled-react/src/components/PageHeader.tsx index 73b4471161b..dd49ab77f5c 100644 --- a/packages/styled-react/src/components/PageHeader.tsx +++ b/packages/styled-react/src/components/PageHeader.tsx @@ -29,7 +29,7 @@ const PageHeaderImpl = React.forwardRef(({as, . type PageHeaderActionsProps = PrimerPageHeaderActionsProps & SxProp -function StyledPageHeaderActions({sx, ...rest}: PageHeaderActionsProps) { +function PageHeaderActions({sx, ...rest}: PageHeaderActionsProps) { const style: CSSCustomProperties = {} if (sx) { // @ts-ignore sx has height attribute @@ -43,9 +43,6 @@ function StyledPageHeaderActions({sx, ...rest}: PageHeaderActionsProps) { return } -// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error -const PageHeaderActions = ({as, ...props}: PageHeaderProps) => - type PageHeaderTitleProps = PropsWithChildren & SxProp type CSSCustomProperties = { From 1aae62bfc537b09e2be1c2edc401fe292a4b5032 Mon Sep 17 00:00:00 2001 From: Marie Lucca Date: Mon, 29 Sep 2025 12:33:04 -0400 Subject: [PATCH 03/21] fix ts error --- packages/styled-react/src/components/Header.tsx | 8 ++++---- packages/styled-react/src/components/PageHeader.tsx | 8 ++++---- packages/styled-react/src/components/UnderlineNav.tsx | 6 ++---- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/styled-react/src/components/Header.tsx b/packages/styled-react/src/components/Header.tsx index 7533950240a..1c4462ddda0 100644 --- a/packages/styled-react/src/components/Header.tsx +++ b/packages/styled-react/src/components/Header.tsx @@ -16,8 +16,7 @@ const StyledHeader = forwardRef(function Header(props, ref) { return }) as ForwardRefComponent<'header', HeaderProps> -// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error -const HeaderImpl = ({as, ...props}: HeaderProps) => +const HeaderImpl = ({as, ...props}: HeaderProps) => type HeaderItemProps = PrimerHeaderItemProps & SxProp @@ -29,8 +28,9 @@ const StyledHeaderLink = forwardRef(fu return }) -// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error -const HeaderLink = ({as, ...props}: HeaderLinkProps) => +const HeaderLink = ({as, ...props}: HeaderLinkProps) => ( + +) const Header = Object.assign(HeaderImpl, { Item: HeaderItem, diff --git a/packages/styled-react/src/components/PageHeader.tsx b/packages/styled-react/src/components/PageHeader.tsx index dd49ab77f5c..4b099c64d1c 100644 --- a/packages/styled-react/src/components/PageHeader.tsx +++ b/packages/styled-react/src/components/PageHeader.tsx @@ -23,8 +23,7 @@ const StyledPageHeader: ForwardRefComponent<'div', PageHeaderProps> = styled( ` const PageHeaderImpl = React.forwardRef(({as, ...props}, ref) => ( - // @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error - + )) as ForwardRefComponent<'div', PageHeaderProps> type PageHeaderActionsProps = PrimerPageHeaderActionsProps & SxProp @@ -71,8 +70,9 @@ function StyledPageHeaderTitle({sx, ...rest}: PageHeaderTitleProps) { return } -// @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error -const PageHeaderTitle = ({as, ...props}: PageHeaderTitleProps) => +const PageHeaderTitle = ({as, ...props}: PageHeaderTitleProps) => ( + +) type PageHeaderTitleAreaProps = PropsWithChildren & SxProp diff --git a/packages/styled-react/src/components/UnderlineNav.tsx b/packages/styled-react/src/components/UnderlineNav.tsx index bd69bec4f2d..0157d8be16f 100644 --- a/packages/styled-react/src/components/UnderlineNav.tsx +++ b/packages/styled-react/src/components/UnderlineNav.tsx @@ -16,8 +16,7 @@ const StyledUnderlineNav = forwardRef(function U }) export const UnderlineNavImpl = ({as, ...props}: UnderlineNavProps) => ( - // @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error - + ) export type UnderlineNavItemProps = PrimerUnderlineNavItemProps & SxProp @@ -30,8 +29,7 @@ const StyledUnderlineNavItem: ForwardRefComponent<'a', UnderlineNavItemProps> = ${sx} ` export const UnderlineNavItem = ({as, ...props}: UnderlineNavItemProps) => ( - // @ts-ignore forwardedAs is valid here but I don't know how to fix the typescript error - + ) export const UnderlineNav = Object.assign(UnderlineNavImpl, { From 2cfa66a3f71ed5dcb8adc7fa3ec212283b885680 Mon Sep 17 00:00:00 2001 From: Marie Lucca Date: Mon, 29 Sep 2025 13:29:27 -0400 Subject: [PATCH 04/21] prop type fix --- packages/react/src/UnderlineNav/UnderlineNavItem.tsx | 3 ++- .../styled-react/src/__tests__/primer-react.browser.test.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react/src/UnderlineNav/UnderlineNavItem.tsx b/packages/react/src/UnderlineNav/UnderlineNavItem.tsx index 963f7c033f1..16af060ded4 100644 --- a/packages/react/src/UnderlineNav/UnderlineNavItem.tsx +++ b/packages/react/src/UnderlineNav/UnderlineNavItem.tsx @@ -45,7 +45,8 @@ export type UnderlineNavItemProps = { * Counter */ counter?: number | string -} & LinkProps +} & LinkProps & + React.HTMLAttributes export const UnderlineNavItem = forwardRef( ( diff --git a/packages/styled-react/src/__tests__/primer-react.browser.test.tsx b/packages/styled-react/src/__tests__/primer-react.browser.test.tsx index ef1f7b70823..02a3e986dd1 100644 --- a/packages/styled-react/src/__tests__/primer-react.browser.test.tsx +++ b/packages/styled-react/src/__tests__/primer-react.browser.test.tsx @@ -476,7 +476,7 @@ describe('@primer/react', () => { test('UnderlineNav supports `sx` prop', () => { render( - test + test , ) expect(window.getComputedStyle(screen.getByLabelText('navigation')).backgroundColor).toBe('rgb(255, 0, 0)') From 3e993d9a75e561a7c03f2869e81249037155c9a5 Mon Sep 17 00:00:00 2001 From: Marie Lucca Date: Mon, 29 Sep 2025 14:05:29 -0400 Subject: [PATCH 05/21] path of least resistance --- packages/react/src/UnderlineNav/UnderlineNavItem.tsx | 3 +-- packages/styled-react/src/components/UnderlineNav.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/react/src/UnderlineNav/UnderlineNavItem.tsx b/packages/react/src/UnderlineNav/UnderlineNavItem.tsx index 16af060ded4..963f7c033f1 100644 --- a/packages/react/src/UnderlineNav/UnderlineNavItem.tsx +++ b/packages/react/src/UnderlineNav/UnderlineNavItem.tsx @@ -45,8 +45,7 @@ export type UnderlineNavItemProps = { * Counter */ counter?: number | string -} & LinkProps & - React.HTMLAttributes +} & LinkProps export const UnderlineNavItem = forwardRef( ( diff --git a/packages/styled-react/src/components/UnderlineNav.tsx b/packages/styled-react/src/components/UnderlineNav.tsx index 0157d8be16f..8ee49674cac 100644 --- a/packages/styled-react/src/components/UnderlineNav.tsx +++ b/packages/styled-react/src/components/UnderlineNav.tsx @@ -9,7 +9,7 @@ import {forwardRef} from 'react' import styled from 'styled-components' import {sx, type SxProp} from '../sx' -export type UnderlineNavProps = PrimerUnderlineNavProps & SxProp +export type UnderlineNavProps = PrimerUnderlineNavProps & SxProp & React.HTMLAttributes const StyledUnderlineNav = forwardRef(function UnderlineNav(props, ref) { return From a5ee699a6619cec025e9953da175d6cb76cc0d79 Mon Sep 17 00:00:00 2001 From: Marie Lucca Date: Mon, 29 Sep 2025 14:12:10 -0400 Subject: [PATCH 06/21] wrong proptpye --- packages/styled-react/src/components/UnderlineNav.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/styled-react/src/components/UnderlineNav.tsx b/packages/styled-react/src/components/UnderlineNav.tsx index 8ee49674cac..025f42a0cb9 100644 --- a/packages/styled-react/src/components/UnderlineNav.tsx +++ b/packages/styled-react/src/components/UnderlineNav.tsx @@ -9,7 +9,7 @@ import {forwardRef} from 'react' import styled from 'styled-components' import {sx, type SxProp} from '../sx' -export type UnderlineNavProps = PrimerUnderlineNavProps & SxProp & React.HTMLAttributes +export type UnderlineNavProps = PrimerUnderlineNavProps & SxProp const StyledUnderlineNav = forwardRef(function UnderlineNav(props, ref) { return @@ -19,7 +19,7 @@ export const UnderlineNavImpl = ({as, ...props}: UnderlineNavProps) => ( ) -export type UnderlineNavItemProps = PrimerUnderlineNavItemProps & SxProp +export type UnderlineNavItemProps = PrimerUnderlineNavItemProps & SxProp & React.HTMLAttributes const StyledUnderlineNavItem: ForwardRefComponent<'a', UnderlineNavItemProps> = styled( PrimerUnderlineNav.Item, From b4ed960d6411dad3ce11a13090e057928e6adbb6 Mon Sep 17 00:00:00 2001 From: Marie Lucca Date: Mon, 29 Sep 2025 15:48:23 -0400 Subject: [PATCH 07/21] add forwardedAs to link --- packages/styled-react/src/components/Link.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/styled-react/src/components/Link.tsx b/packages/styled-react/src/components/Link.tsx index c87a2629684..ab25a390243 100644 --- a/packages/styled-react/src/components/Link.tsx +++ b/packages/styled-react/src/components/Link.tsx @@ -4,9 +4,11 @@ import {sx, type SxProp} from '../sx' type LinkProps = PrimerLinkProps & SxProp -const Link = styled(PrimerLink).withConfig({ +const StyledLink = styled(PrimerLink).withConfig({ shouldForwardProp: prop => prop !== 'sx', })` ${sx} ` +const Link = ({as, ...props}: LinkProps) => + export {Link, type LinkProps} From 30ffc4eb6e6289cab95607ef073f26267a5a9340 Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Mon, 29 Sep 2025 12:48:47 -0700 Subject: [PATCH 08/21] Add browser tests for polymorphic `as` prop behavior in styled-react components (#6911) Co-authored-by: Marie Lucca <40550942+francinelucca@users.noreply.github.com> --- .../primer-react-deprecated.browser.test.tsx | 1 + ...primer-react-experimental.browser.test.tsx | 3 +- .../__tests__/primer-react.browser.test.tsx | 80 ++++++++++++++----- 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/packages/styled-react/src/__tests__/primer-react-deprecated.browser.test.tsx b/packages/styled-react/src/__tests__/primer-react-deprecated.browser.test.tsx index dd0a9696a04..1913123d5f0 100644 --- a/packages/styled-react/src/__tests__/primer-react-deprecated.browser.test.tsx +++ b/packages/styled-react/src/__tests__/primer-react-deprecated.browser.test.tsx @@ -21,6 +21,7 @@ describe('@primer/react/deprecated', () => { test('TabNav.Link supports `sx` prop', () => { render() + expect(screen.getByTestId('component')).toHaveAttribute('role', 'tab') expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)') expect(window.getComputedStyle(screen.getByRole('tab')).backgroundColor).toBe('rgb(255, 0, 0)') expect(screen.getByRole('tab').tagName).toBe('BUTTON') diff --git a/packages/styled-react/src/__tests__/primer-react-experimental.browser.test.tsx b/packages/styled-react/src/__tests__/primer-react-experimental.browser.test.tsx index 4e2a8fa64fe..86ea2df3afd 100644 --- a/packages/styled-react/src/__tests__/primer-react-experimental.browser.test.tsx +++ b/packages/styled-react/src/__tests__/primer-react-experimental.browser.test.tsx @@ -9,7 +9,8 @@ describe('@primer/react/experimental', () => { }) test('PageHeader supports `sx` prop', () => { - const {container} = render() + const {container} = render() + expect(container.firstElementChild!).toHaveAttribute('role', 'article') expect(window.getComputedStyle(container.firstElementChild!).backgroundColor).toBe('rgb(255, 0, 0)') }) diff --git a/packages/styled-react/src/__tests__/primer-react.browser.test.tsx b/packages/styled-react/src/__tests__/primer-react.browser.test.tsx index 02a3e986dd1..33ed2c72e22 100644 --- a/packages/styled-react/src/__tests__/primer-react.browser.test.tsx +++ b/packages/styled-react/src/__tests__/primer-react.browser.test.tsx @@ -47,8 +47,9 @@ import { describe('@primer/react', () => { test('ActionList supports `sx` prop', () => { - render() + render() expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)') + expect(screen.getByTestId('component')).toHaveAttribute('data-variant', 'inset') }) test('ActionMenu.Button supports `sx` prop', () => { @@ -109,7 +110,7 @@ describe('@primer/react', () => { }) test('Box supports `sx` prop', () => { - render() + render() expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)') }) @@ -119,13 +120,15 @@ describe('@primer/react', () => { }) test('Breadcrumbs.Item supports `sx` prop', () => { - render() + render() expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)') + expect(screen.getByTestId('component').className.includes('selected')).toBe(true) }) test('Button supports `sx` prop', () => { - render(