From a6dbb988905c0ac87ce9449d119fd37db6006bfa Mon Sep 17 00:00:00 2001 From: Marie Lucca Date: Mon, 13 Oct 2025 14:37:18 -0400 Subject: [PATCH 1/3] chore: add missing slot checks --- packages/react/src/ActionMenu/ActionMenu.tsx | 10 ++++++---- packages/react/src/PageHeader/PageHeader.tsx | 16 +++++++++++----- .../src/SegmentedControl/SegmentedControl.tsx | 5 ++++- packages/react/src/TreeView/TreeView.tsx | 9 ++++++--- .../experimental/SelectPanel2/SelectPanel.tsx | 9 ++++++--- .../CheckboxOrRadioGroup.tsx | 15 ++++++++++++--- 6 files changed, 45 insertions(+), 19 deletions(-) diff --git a/packages/react/src/ActionMenu/ActionMenu.tsx b/packages/react/src/ActionMenu/ActionMenu.tsx index aa6f4ae808c..2041d27f6e4 100644 --- a/packages/react/src/ActionMenu/ActionMenu.tsx +++ b/packages/react/src/ActionMenu/ActionMenu.tsx @@ -101,7 +101,9 @@ const Menu: FCWithSlotMarker> = ({ ) const menuButtonChild = React.Children.toArray(children).find( - child => React.isValidElement(child) && (child.type === MenuButton || child.type === Anchor), + child => + React.isValidElement(child) && + (child.type === MenuButton || child.type === Anchor || isSlot(child, Anchor) || isSlot(child, MenuButton)), ) const menuButtonChildId = React.isValidElement(menuButtonChild) ? menuButtonChild.props.id : undefined @@ -117,7 +119,7 @@ const Menu: FCWithSlotMarker> = ({ if (child.type === Tooltip || isSlot(child, Tooltip)) { // tooltip trigger const anchorChildren = child.props.children - if (anchorChildren.type === MenuButton) { + if (anchorChildren.type === MenuButton || isSlot(anchorChildren, MenuButton)) { // eslint-disable-next-line react-compiler/react-compiler renderAnchor = anchorProps => { // We need to attach the anchor props to the tooltip trigger (ActionMenu.Button's grandchild) not the tooltip itself. @@ -129,7 +131,7 @@ const Menu: FCWithSlotMarker> = ({ } } return null - } else if (child.type === Anchor) { + } else if (child.type === Anchor || isSlot(child, Anchor)) { const anchorChildren = child.props.children const isWrappedWithTooltip = anchorChildren !== undefined ? anchorChildren.type === Tooltip || isSlot(anchorChildren, Tooltip) : false @@ -151,7 +153,7 @@ const Menu: FCWithSlotMarker> = ({ renderAnchor = anchorProps => React.cloneElement(child, anchorProps) } return null - } else if (child.type === MenuButton) { + } else if (child.type === MenuButton || isSlot(child, MenuButton)) { renderAnchor = anchorProps => React.cloneElement(child, mergeAnchorHandlers(anchorProps, child.props)) return null } else { diff --git a/packages/react/src/PageHeader/PageHeader.tsx b/packages/react/src/PageHeader/PageHeader.tsx index af3e250160f..2289585a79d 100644 --- a/packages/react/src/PageHeader/PageHeader.tsx +++ b/packages/react/src/PageHeader/PageHeader.tsx @@ -10,10 +10,11 @@ import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../uti import {areAllValuesTheSame, haveRegularAndWideSameValue} from '../utils/getBreakpointDeclarations' import {warning} from '../utils/warning' import {useProvidedRefOrCreate} from '../hooks' -import type {AriaRole} from '../utils/types' +import type {AriaRole, FCWithSlotMarker} from '../utils/types' import {clsx} from 'clsx' import classes from './PageHeader.module.css' +import {isSlot} from '../utils/is-slot' // Types that are shared between PageHeader children components export type ChildrenPropTypes = { @@ -74,10 +75,10 @@ const Root = React.forwardRef> = ({ +const ContextArea: FCWithSlotMarker> = ({ children, className, hidden = hiddenOnRegularAndWide, @@ -130,6 +131,9 @@ const ContextArea: React.FC> = ({ ) } + +ContextArea.__SLOT__ = Symbol('PageHeader.ContextArea') + type LinkProps = Pick< React.AnchorHTMLAttributes & BaseLinkProps, 'download' | 'href' | 'hrefLang' | 'media' | 'ping' | 'rel' | 'target' | 'type' | 'referrerPolicy' | 'as' @@ -219,7 +223,7 @@ TitleArea.displayName = 'TitleArea' // 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> = ({ +const LeadingAction: FCWithSlotMarker> = ({ children, className, hidden = hiddenOnNarrow, @@ -235,6 +239,8 @@ const LeadingAction: React.FC> = ({ ) } +LeadingAction.__SLOT__ = Symbol('PageHeader.LeadingAction') + // This is reserved for only breadcrumbs. const Breadcrumbs: React.FC> = ({children, className, hidden = false}) => { return ( diff --git a/packages/react/src/SegmentedControl/SegmentedControl.tsx b/packages/react/src/SegmentedControl/SegmentedControl.tsx index f613ae4f00a..eda9bbf5aeb 100644 --- a/packages/react/src/SegmentedControl/SegmentedControl.tsx +++ b/packages/react/src/SegmentedControl/SegmentedControl.tsx @@ -90,7 +90,10 @@ const Root: React.FC> = ({ return null } const getChildText = (childArg: React.ReactNode) => { - if (React.isValidElement(childArg) && childArg.type === Button) { + if ( + React.isValidElement(childArg) && + (childArg.type === Button || isSlot(childArg, Button)) + ) { return childArg.props.children } diff --git a/packages/react/src/TreeView/TreeView.tsx b/packages/react/src/TreeView/TreeView.tsx index ccb08352102..f1cc0b21cf5 100644 --- a/packages/react/src/TreeView/TreeView.tsx +++ b/packages/react/src/TreeView/TreeView.tsx @@ -27,6 +27,8 @@ import {ActionList} from '../ActionList' import {getAccessibleKeybindingHintString} from '../KeybindingHint' import {useIsMacOS} from '../hooks' import {Tooltip} from '../TooltipV2' +import {isSlot} from '../utils/is-slot' +import type {FCWithSlotMarker} from '../utils/types' // ---------------------------------------------------------------------------- // Context @@ -457,7 +459,7 @@ export type TreeViewSubTreeProps = { 'aria-label'?: string } -const SubTree: React.FC = ({count, state, children, 'aria-label': ariaLabel}) => { +const SubTree: FCWithSlotMarker = ({count, state, children, 'aria-label': ariaLabel}) => { const {announceUpdate} = React.useContext(RootContext) const {itemId, isExpanded, isSubTreeEmpty, setIsSubTreeEmpty} = React.useContext(ItemContext) const loadingItemRef = React.useRef(null) @@ -573,6 +575,7 @@ const SubTree: React.FC = ({count, state, children, 'aria- } SubTree.displayName = 'TreeView.SubTree' +SubTree.__SLOT__ = Symbol('TreeView.SubTree') function usePreviousValue(value: T): T { const ref = React.useRef(value) @@ -638,11 +641,11 @@ const EmptyItem = React.forwardRef((props, ref) => { function useSubTree(children: React.ReactNode) { return React.useMemo(() => { const subTree = React.Children.toArray(children).find( - child => React.isValidElement(child) && child.type === SubTree, + child => React.isValidElement(child) && (child.type === SubTree || isSlot(child, SubTree)), ) const childrenWithoutSubTree = React.Children.toArray(children).filter( - child => !(React.isValidElement(child) && child.type === SubTree), + child => !(React.isValidElement(child) && (child.type === SubTree || isSlot(child, SubTree))), ) return { diff --git a/packages/react/src/experimental/SelectPanel2/SelectPanel.tsx b/packages/react/src/experimental/SelectPanel2/SelectPanel.tsx index 5d97938d2be..04667c2b5d2 100644 --- a/packages/react/src/experimental/SelectPanel2/SelectPanel.tsx +++ b/packages/react/src/experimental/SelectPanel2/SelectPanel.tsx @@ -27,7 +27,8 @@ import {clsx} from 'clsx' import classes from './SelectPanel.module.css' import type {PositionSettings} from '@primer/behaviors' -import type {FCWithSlotMarker} from '../../utils/types' +import type {FCWithSlotMarker, WithSlotMarker} from '../../utils/types' +import {isSlot} from '../../utils/is-slot' const SelectPanelContext = React.createContext<{ title: string @@ -124,7 +125,7 @@ const Panel: React.FC = ({ } const contents = React.Children.map(props.children, child => { - if (React.isValidElement(child) && child.type === SelectPanelButton) { + if (React.isValidElement(child) && (child.type === SelectPanelButton || isSlot(child, SelectPanelButton))) { // eslint-disable-next-line react-compiler/react-compiler Anchor = React.cloneElement(child, { // @ts-ignore TODO @@ -339,7 +340,9 @@ const SelectPanelButton = React.forwardRef((prop } else { return