diff --git a/.changeset/afraid-papayas-allow.md b/.changeset/afraid-papayas-allow.md
new file mode 100644
index 00000000000..d0b12f17f56
--- /dev/null
+++ b/.changeset/afraid-papayas-allow.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": patch
+---
+
+chore: Migrate from toggleSxComponent to BoxWithFallback helper
diff --git a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx
index e252f1b4e78..fe47e2307ec 100644
--- a/packages/react/src/Breadcrumbs/Breadcrumbs.tsx
+++ b/packages/react/src/Breadcrumbs/Breadcrumbs.tsx
@@ -5,7 +5,7 @@ import type {SxProp} from '../sx'
import type {ComponentProps} from '../utils/types'
import classes from './Breadcrumbs.module.css'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
-import {toggleSxComponent} from '../internal/utils/toggleSxComponent'
+import {BoxWithFallback} from '../internal/components/BoxWithFallback'
const SELECTED_CLASS = 'selected'
@@ -19,13 +19,12 @@ const BreadcrumbsList = ({children}: React.PropsWithChildren) => {
return
{children}
}
-const BreadcrumbsBaseComponent = toggleSxComponent('nav') as React.ComponentType
function Breadcrumbs({className, children, sx: sxProp}: BreadcrumbsProps) {
const wrappedChildren = React.Children.map(children, child => {child})
return (
-
+
{wrappedChildren}
-
+
)
}
@@ -37,10 +36,10 @@ type StyledBreadcrumbsItemProps = {
React.HTMLAttributes &
React.ComponentPropsWithRef<'a'>
-const BreadcrumbsItemBaseComponent = toggleSxComponent('a') as React.ComponentType
const BreadcrumbsItem = React.forwardRef(({selected, className, ...rest}, ref) => {
return (
- >
->
const Root = React.forwardRef>(
({children, className, sx = defaultSxProp, as = 'div', 'aria-label': ariaLabel, role, hasBorder}, forwardedRef) => {
const rootRef = useProvidedRefOrCreate(forwardedRef as React.RefObject)
@@ -109,7 +105,7 @@ const Root = React.forwardRef
{children}
-
+
)
},
) as PolymorphicForwardRefComponent<'div', PageHeaderProps>
-const ContextAreaBaseComponent = toggleSxComponent('div') as React.ComponentType<
- React.PropsWithChildren>
->
// PageHeader.ContextArea : Only visible on narrow viewports by default to provide user context of where they are at their journey. `hidden` prop available
// to manage their custom visibility but consumers should be careful if they choose to hide this on narrow viewports.
// PageHeader.ContextArea Sub Components: PageHeader.ParentLink, PageHeader.ContextBar, PageHeader.ContextAreaActions
@@ -138,13 +131,9 @@ const ContextArea: React.FC> = ({
sx: sxProp = defaultSxProp,
}) => {
return (
-
+
{children}
-
+
)
}
type LinkProps = Pick<
@@ -182,7 +171,7 @@ const ParentLink = React.forwardRef(
href={href}
>
- {children}
+ {children}
>
)
@@ -194,9 +183,6 @@ ParentLink.displayName = 'ParentLink'
// Generic slot for any component above the title region. Use it for custom breadcrumbs and other navigation elements instead of ParentLink.
// ---------------------------------------------------------------------
-const ContextBarBaseComponent = toggleSxComponent('div') as React.ComponentType<
- React.PropsWithChildren>
->
const ContextBar: React.FC> = ({
children,
className,
@@ -204,19 +190,12 @@ const ContextBar: React.FC> = ({
hidden = hiddenOnRegularAndWide,
}) => {
return (
-
+
{children}
-
+
)
}
-const ContextAreaActionsBaseComponent = toggleSxComponent('div') as React.ComponentType<
- React.PropsWithChildren>
->
// ContextAreaActions
// ---------------------------------------------------------------------
const ContextAreaActions: React.FC> = ({
@@ -226,14 +205,14 @@ const ContextAreaActions: React.FC> =
hidden = hiddenOnRegularAndWide,
}) => {
return (
-
{children}
-
+
)
}
@@ -244,15 +223,12 @@ type TitleAreaProps = {
// PageHeader.TitleArea Sub Components: PageHeader.LeadingVisual, PageHeader.Title, PageTitle.TrailingVisual
// ---------------------------------------------------------------------
-const TitleAreaBaseComponent = toggleSxComponent('div') as React.ComponentType<
- React.PropsWithChildren>
->
const TitleArea = React.forwardRef>(
({children, className, sx: sxProp = defaultSxProp, hidden = false, variant = 'medium'}, forwardedRef) => {
const titleAreaRef = useProvidedRefOrCreate(forwardedRef as React.RefObject)
const currentVariant = useResponsiveValue(variant, 'medium')
return (
-
{children}
-
+
)
},
) as PolymorphicForwardRefComponent<'div', TitleAreaProps>
TitleArea.displayName = 'TitleArea'
-const LeadingActionBaseComponent = toggleSxComponent('div') as React.ComponentType<
- React.PropsWithChildren> &
- React.HtmlHTMLAttributes
->
// PageHeader.LeadingAction and PageHeader.TrailingAction should only be visible on regular viewports.
// So they come as hidden on narrow viewports by default and their visibility can be managed by their `hidden` prop.
const LeadingAction: React.FC> = ({
@@ -284,7 +256,7 @@ const LeadingAction: React.FC> = ({
const {height} = sxProp
if (height) style['--custom-height'] = height
return (
- > = ({
{...getHiddenDataAttributes(hidden)}
>
{children}
-
+
)
}
-const BreadcrumbsBaseComponent = toggleSxComponent('div') as React.ComponentType<
- React.PropsWithChildren>
->
// This is reserved for only breadcrumbs.
const Breadcrumbs: React.FC> = ({
children,
@@ -307,22 +276,17 @@ const Breadcrumbs: React.FC> = ({
hidden = false,
}) => {
return (
-
{children}
-
+
)
}
-const LeadingVisualBaseComponent = toggleSxComponent('div') as React.ComponentType<
- React.PropsWithChildren<
- ChildrenPropTypes & React.RefAttributes & React.HtmlHTMLAttributes
- >
->
// PageHeader.LeadingVisual and PageHeader.TrailingVisual should remain visible on narrow viewports.
const LeadingVisual: React.FC> = ({
children,
@@ -335,7 +299,7 @@ const LeadingVisual: React.FC> = ({
const {height} = sxProp
if (height) style['--custom-height'] = height
return (
- > = ({
{...getHiddenDataAttributes(hidden)}
>
{children}
-
+
)
}
@@ -380,11 +344,6 @@ const Title: React.FC> = ({
)
}
-const TrailingVisualBaseComponent = toggleSxComponent('div') as React.ComponentType<
- React.PropsWithChildren<
- ChildrenPropTypes & React.RefAttributes & React.HtmlHTMLAttributes
- >
->
// PageHeader.LeadingVisual and PageHeader.TrailingVisual should remain visible on narrow viewports.
const TrailingVisual: React.FC> = ({
children,
@@ -397,7 +356,7 @@ const TrailingVisual: React.FC> = ({
const {height} = sxProp
if (height) style['--custom-height'] = height
return (
- > = ({
{...getHiddenDataAttributes(hidden)}
>
{children}
-
+
)
}
-const TrailingActionBaseComponent = toggleSxComponent('div') as React.ComponentType<
- React.PropsWithChildren<
- ChildrenPropTypes & React.RefAttributes & React.HtmlHTMLAttributes
- >
->
const TrailingAction: React.FC> = ({
children,
className,
@@ -425,7 +379,7 @@ const TrailingAction: React.FC> = ({
const {height} = sxProp
if (height) style['--custom-height'] = height
return (
- > = ({
{...getHiddenDataAttributes(hidden)}
>
{children}
-
+
)
}
-const ActionsBaseComponent = toggleSxComponent('div') as React.ComponentType<
- React.PropsWithChildren<
- ChildrenPropTypes & React.RefAttributes & React.HtmlHTMLAttributes
- >
->
const Actions: React.FC> = ({
children,
className,
@@ -453,7 +402,7 @@ const Actions: React.FC> = ({
const {height} = sxProp
if (height) style['--custom-height'] = height
return (
- > = ({
{...getHiddenDataAttributes(hidden)}
>
{children}
-
+
)
}
-const DescriptionBaseComponent = toggleSxComponent('div') as React.ComponentType<
- React.PropsWithChildren>
->
-
// PageHeader.Description: The description area of the header. Visible on all viewports
const Description: React.FC> = ({
children,
@@ -477,13 +422,9 @@ const Description: React.FC> = ({
hidden = false,
}) => {
return (
-
+
{children}
-
+
)
}
@@ -493,10 +434,6 @@ export type NavigationProps = {
'aria-labelledby'?: React.AriaAttributes['aria-labelledby']
} & ChildrenPropTypes
-const NavigationBaseComponent = toggleSxComponent('div') as React.ComponentType<
- React.PropsWithChildren>
->
-
// PageHeader.Navigation: The local navigation area of the header. Visible on all viewports
const Navigation: React.FC> = ({
children,
@@ -513,7 +450,7 @@ const Navigation: React.FC> = ({
)
return (
- > = ({
{...getHiddenDataAttributes(hidden)}
>
{children}
-
+
)
}
diff --git a/packages/react/src/Popover/Popover.tsx b/packages/react/src/Popover/Popover.tsx
index 53147dba303..259846d5ea8 100644
--- a/packages/react/src/Popover/Popover.tsx
+++ b/packages/react/src/Popover/Popover.tsx
@@ -3,7 +3,7 @@ import type {SxProp} from '../sx'
import classes from './Popover.module.css'
import type {HTMLProps} from 'react'
import React from 'react'
-import {toggleSxComponent} from '../internal/utils/toggleSxComponent'
+import {BoxWithFallback} from '../internal/components/BoxWithFallback'
type CaretPosition =
| 'top'
@@ -31,18 +31,16 @@ type StyledPopoverProps = {
export type PopoverProps = {
/** Class name for custom styling */
className?: string
+ as?: React.ElementType
} & StyledPopoverProps &
HTMLProps
-const PopoverBaseComponent = toggleSxComponent('div') as React.ComponentType<
- PopoverProps & React.RefAttributes
->
const Popover = React.forwardRef(function Popover(
{className, caret = 'top', open, relative, ...props},
forwardRef,
) {
return (
- (function Popover(
})
Popover.displayName = 'Popover'
-export type PopoverContentProps = {className?: string} & StyledPopoverProps & HTMLProps
+export type PopoverContentProps = {className?: string; as?: React.ElementType} & StyledPopoverProps &
+ HTMLProps
-const PopoverContentBaseComponent = toggleSxComponent('div') as React.ComponentType
const PopoverContent: React.FC> = ({className, ...props}) => {
- return
+ return
}
PopoverContent.displayName = 'Popover.Content'
diff --git a/packages/react/src/Text/Text.tsx b/packages/react/src/Text/Text.tsx
index 50043559b51..26a9cd1f41f 100644
--- a/packages/react/src/Text/Text.tsx
+++ b/packages/react/src/Text/Text.tsx
@@ -5,7 +5,7 @@ import type {SystemCommonProps, SystemTypographyProps} from '../constants'
import type {SxProp} from '../sx'
import {useRefObjectAsForwardedRef} from '../hooks'
import classes from './Text.module.css'
-import {toggleSxComponent} from '../internal/utils/toggleSxComponent'
+import {BoxWithFallback} from '../internal/components/BoxWithFallback'
type StyledTextProps = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -17,13 +17,12 @@ type StyledTextProps = {
SxProp &
React.HTMLAttributes
-const TextBaseComponent = toggleSxComponent('span') as React.ComponentType
const Text = forwardRef(({as: Component = 'span', className, size, weight, ...props}, forwardedRef) => {
const innerRef = React.useRef(null)
useRefObjectAsForwardedRef(forwardedRef, innerRef)
return (
- {
}
export const TooltipContext = React.createContext<{tooltipId?: string}>({})
-const BaseComponent = toggleSxComponent('span') as React.ComponentType<
- SxProp & React.HTMLAttributes & React.RefAttributes
->
-
export const Tooltip = React.forwardRef(
(
{direction = 's', text, type = 'description', children, id, className, keybindingHint, ...rest}: TooltipProps,
@@ -302,7 +298,8 @@ export const Tooltip = React.forwardRef(
child.props.onMouseLeave?.(event)
},
})}
-
+
>
)
diff --git a/packages/react/src/internal/utils/__tests__/toggleSxComponent.test.tsx b/packages/react/src/internal/utils/__tests__/toggleSxComponent.test.tsx
deleted file mode 100644
index a0b732e46f9..00000000000
--- a/packages/react/src/internal/utils/__tests__/toggleSxComponent.test.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import {render} from '@testing-library/react'
-import type React from 'react'
-import {describe, expect, test} from 'vitest'
-import {toggleSxComponent} from '../toggleSxComponent'
-
-const customSx = {color: 'red', p: 2}
-
-describe('toggleSxComponent', () => {
- test('renders the plain component when no sx', () => {
- const TestComponent = toggleSxComponent('span')
- const {container} = render()
- expect(container.firstChild).toBeInstanceOf(HTMLSpanElement)
- })
-
- test('renders Box with `as` if `sx` is provided', () => {
- const TestComponent = toggleSxComponent('div')
- const {container} = render()
-
- expect(container.firstChild).toBeInstanceOf(HTMLButtonElement)
- expect(container.firstChild).toHaveStyle('color: rgb(255, 0, 0)')
- })
-
- test('swaps out component if `sx` is not the default', () => {
- const Label = toggleSxComponent('label') as React.ComponentType<{htmlFor: string}>
- const {container} = render()
-
- expect(container.firstChild).toBeInstanceOf(HTMLLabelElement)
- expect(container.firstChild).toHaveAttribute('for', 'bloop')
- })
-
- test('passes down other props', () => {
- const TestComponent = toggleSxComponent('div')
- const {container} = render()
-
- expect(container.firstChild).toBeInstanceOf(HTMLButtonElement)
- expect(container.firstChild).toHaveStyle('color: rgb(255, 0, 0)')
- expect(container.firstChild).toHaveAttribute('data-foo', 'bar')
- })
-})
diff --git a/packages/react/src/internal/utils/toggleSxComponent.tsx b/packages/react/src/internal/utils/toggleSxComponent.tsx
deleted file mode 100644
index 488c6dbf507..00000000000
--- a/packages/react/src/internal/utils/toggleSxComponent.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react'
-import Box from '../../Box'
-import {defaultSxProp} from '../../utils/defaultSxProp'
-import {includesSystemProps} from '../../utils/includeSystemProps'
-
-type CSSModulesProps = {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- as?: string | React.ComponentType
- sx?: React.CSSProperties
-}
-
-/**
- * Utility to toggle rendering a Box component that receives sx props
- * or a "plain" component based on the provided `as` prop. Other props will be passed through to an element or component created with the `as` prop.
- *
- * @param sx - the sx prop to check against the default sx prop
- * @param defaultAs - the default component to use when `as` is not provided
- */
-export function toggleSxComponent(
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- defaultAs: string | React.ComponentType,
-) {
- const Wrapper = React.forwardRef(function Wrapper(
- {as: BaseComponent = defaultAs, sx: sxProp = defaultSxProp, ...rest},
- ref,
- ) {
- if (sxProp !== defaultSxProp || includesSystemProps(rest)) {
- return
- }
- return
- })
-
- return Wrapper
-}
diff --git a/script/styled-components-migration-status.mts b/script/styled-components-migration-status.mts
index 51fed9b05f6..aac4c66426d 100644
--- a/script/styled-components-migration-status.mts
+++ b/script/styled-components-migration-status.mts
@@ -121,9 +121,5 @@ function hasStyledComponents(contents: string): boolean {
return true
}
- if (contents.match(/toggleSxComponent/)) {
- return true
- }
-
return false
}