Skip to content

Commit 173f76d

Browse files
chore: add missing slot checks (#6996)
1 parent 2d37fab commit 173f76d

File tree

7 files changed

+50
-19
lines changed

7 files changed

+50
-19
lines changed

.changeset/quick-queens-rescue.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": patch
3+
---
4+
5+
chore: add missing slot checks to CheckboxOrRadioGroup, SelectPanel, ActionMenu, Treeview, SegmentedControl and PageHeader

packages/react/src/ActionMenu/ActionMenu.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@ const Menu: FCWithSlotMarker<React.PropsWithChildren<ActionMenuProps>> = ({
101101
)
102102

103103
const menuButtonChild = React.Children.toArray(children).find(
104-
child => React.isValidElement<ActionMenuButtonProps>(child) && (child.type === MenuButton || child.type === Anchor),
104+
child =>
105+
React.isValidElement<ActionMenuButtonProps>(child) &&
106+
(child.type === MenuButton || child.type === Anchor || isSlot(child, Anchor) || isSlot(child, MenuButton)),
105107
)
106108
const menuButtonChildId = React.isValidElement(menuButtonChild) ? menuButtonChild.props.id : undefined
107109

@@ -117,7 +119,7 @@ const Menu: FCWithSlotMarker<React.PropsWithChildren<ActionMenuProps>> = ({
117119
if (child.type === Tooltip || isSlot(child, Tooltip)) {
118120
// tooltip trigger
119121
const anchorChildren = child.props.children
120-
if (anchorChildren.type === MenuButton) {
122+
if (anchorChildren.type === MenuButton || isSlot(anchorChildren, MenuButton)) {
121123
// eslint-disable-next-line react-compiler/react-compiler
122124
renderAnchor = anchorProps => {
123125
// 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<React.PropsWithChildren<ActionMenuProps>> = ({
129131
}
130132
}
131133
return null
132-
} else if (child.type === Anchor) {
134+
} else if (child.type === Anchor || isSlot(child, Anchor)) {
133135
const anchorChildren = child.props.children
134136
const isWrappedWithTooltip =
135137
anchorChildren !== undefined ? anchorChildren.type === Tooltip || isSlot(anchorChildren, Tooltip) : false
@@ -151,7 +153,7 @@ const Menu: FCWithSlotMarker<React.PropsWithChildren<ActionMenuProps>> = ({
151153
renderAnchor = anchorProps => React.cloneElement(child, anchorProps)
152154
}
153155
return null
154-
} else if (child.type === MenuButton) {
156+
} else if (child.type === MenuButton || isSlot(child, MenuButton)) {
155157
renderAnchor = anchorProps => React.cloneElement(child, mergeAnchorHandlers(anchorProps, child.props))
156158
return null
157159
} else {

packages/react/src/PageHeader/PageHeader.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../uti
1010
import {areAllValuesTheSame, haveRegularAndWideSameValue} from '../utils/getBreakpointDeclarations'
1111
import {warning} from '../utils/warning'
1212
import {useProvidedRefOrCreate} from '../hooks'
13-
import type {AriaRole} from '../utils/types'
13+
import type {AriaRole, FCWithSlotMarker} from '../utils/types'
1414
import {clsx} from 'clsx'
1515

1616
import classes from './PageHeader.module.css'
17+
import {isSlot} from '../utils/is-slot'
1718

1819
// Types that are shared between PageHeader children components
1920
export type ChildrenPropTypes = {
@@ -74,10 +75,10 @@ const Root = React.forwardRef<HTMLDivElement, React.PropsWithChildren<PageHeader
7475
if (!titleArea) return
7576

7677
for (const child of React.Children.toArray(children)) {
77-
if (React.isValidElement(child) && child.type === ContextArea) {
78+
if (React.isValidElement(child) && (child.type === ContextArea || isSlot(child, ContextArea))) {
7879
hasContextArea = true
7980
}
80-
if (React.isValidElement(child) && child.type === LeadingAction) {
81+
if (React.isValidElement(child) && (child.type === LeadingAction || isSlot(child, LeadingAction))) {
8182
hasLeadingAction = true
8283
}
8384
}
@@ -119,7 +120,7 @@ const Root = React.forwardRef<HTMLDivElement, React.PropsWithChildren<PageHeader
119120
// to manage their custom visibility but consumers should be careful if they choose to hide this on narrow viewports.
120121
// PageHeader.ContextArea Sub Components: PageHeader.ParentLink, PageHeader.ContextBar, PageHeader.ContextAreaActions
121122
// ---------------------------------------------------------------------
122-
const ContextArea: React.FC<React.PropsWithChildren<ChildrenPropTypes>> = ({
123+
const ContextArea: FCWithSlotMarker<React.PropsWithChildren<ChildrenPropTypes>> = ({
123124
children,
124125
className,
125126
hidden = hiddenOnRegularAndWide,
@@ -130,6 +131,9 @@ const ContextArea: React.FC<React.PropsWithChildren<ChildrenPropTypes>> = ({
130131
</div>
131132
)
132133
}
134+
135+
ContextArea.__SLOT__ = Symbol('PageHeader.ContextArea')
136+
133137
type LinkProps = Pick<
134138
React.AnchorHTMLAttributes<HTMLAnchorElement> & BaseLinkProps,
135139
'download' | 'href' | 'hrefLang' | 'media' | 'ping' | 'rel' | 'target' | 'type' | 'referrerPolicy' | 'as'
@@ -219,7 +223,7 @@ TitleArea.displayName = 'TitleArea'
219223

220224
// PageHeader.LeadingAction and PageHeader.TrailingAction should only be visible on regular viewports.
221225
// So they come as hidden on narrow viewports by default and their visibility can be managed by their `hidden` prop.
222-
const LeadingAction: React.FC<React.PropsWithChildren<ChildrenPropTypes>> = ({
226+
const LeadingAction: FCWithSlotMarker<React.PropsWithChildren<ChildrenPropTypes>> = ({
223227
children,
224228
className,
225229
hidden = hiddenOnNarrow,
@@ -235,6 +239,8 @@ const LeadingAction: React.FC<React.PropsWithChildren<ChildrenPropTypes>> = ({
235239
)
236240
}
237241

242+
LeadingAction.__SLOT__ = Symbol('PageHeader.LeadingAction')
243+
238244
// This is reserved for only breadcrumbs.
239245
const Breadcrumbs: React.FC<React.PropsWithChildren<ChildrenPropTypes>> = ({children, className, hidden = false}) => {
240246
return (

packages/react/src/SegmentedControl/SegmentedControl.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ const Root: React.FC<React.PropsWithChildren<SegmentedControlProps>> = ({
9090
return null
9191
}
9292
const getChildText = (childArg: React.ReactNode) => {
93-
if (React.isValidElement<SegmentedControlButtonProps>(childArg) && childArg.type === Button) {
93+
if (
94+
React.isValidElement<SegmentedControlButtonProps>(childArg) &&
95+
(childArg.type === Button || isSlot(childArg, Button))
96+
) {
9497
return childArg.props.children
9598
}
9699

packages/react/src/TreeView/TreeView.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import {ActionList} from '../ActionList'
2727
import {getAccessibleKeybindingHintString} from '../KeybindingHint'
2828
import {useIsMacOS} from '../hooks'
2929
import {Tooltip} from '../TooltipV2'
30+
import {isSlot} from '../utils/is-slot'
31+
import type {FCWithSlotMarker} from '../utils/types'
3032

3133
// ----------------------------------------------------------------------------
3234
// Context
@@ -457,7 +459,7 @@ export type TreeViewSubTreeProps = {
457459
'aria-label'?: string
458460
}
459461

460-
const SubTree: React.FC<TreeViewSubTreeProps> = ({count, state, children, 'aria-label': ariaLabel}) => {
462+
const SubTree: FCWithSlotMarker<TreeViewSubTreeProps> = ({count, state, children, 'aria-label': ariaLabel}) => {
461463
const {announceUpdate} = React.useContext(RootContext)
462464
const {itemId, isExpanded, isSubTreeEmpty, setIsSubTreeEmpty} = React.useContext(ItemContext)
463465
const loadingItemRef = React.useRef<HTMLElement>(null)
@@ -573,6 +575,7 @@ const SubTree: React.FC<TreeViewSubTreeProps> = ({count, state, children, 'aria-
573575
}
574576

575577
SubTree.displayName = 'TreeView.SubTree'
578+
SubTree.__SLOT__ = Symbol('TreeView.SubTree')
576579

577580
function usePreviousValue<T>(value: T): T {
578581
const ref = React.useRef(value)
@@ -638,11 +641,11 @@ const EmptyItem = React.forwardRef<HTMLElement>((props, ref) => {
638641
function useSubTree(children: React.ReactNode) {
639642
return React.useMemo(() => {
640643
const subTree = React.Children.toArray(children).find(
641-
child => React.isValidElement(child) && child.type === SubTree,
644+
child => React.isValidElement(child) && (child.type === SubTree || isSlot(child, SubTree)),
642645
)
643646

644647
const childrenWithoutSubTree = React.Children.toArray(children).filter(
645-
child => !(React.isValidElement(child) && child.type === SubTree),
648+
child => !(React.isValidElement(child) && (child.type === SubTree || isSlot(child, SubTree))),
646649
)
647650

648651
return {

packages/react/src/experimental/SelectPanel2/SelectPanel.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import {clsx} from 'clsx'
2727

2828
import classes from './SelectPanel.module.css'
2929
import type {PositionSettings} from '@primer/behaviors'
30-
import type {FCWithSlotMarker} from '../../utils/types'
30+
import type {FCWithSlotMarker, WithSlotMarker} from '../../utils/types'
31+
import {isSlot} from '../../utils/is-slot'
3132

3233
const SelectPanelContext = React.createContext<{
3334
title: string
@@ -124,7 +125,7 @@ const Panel: React.FC<SelectPanelProps> = ({
124125
}
125126

126127
const contents = React.Children.map(props.children, child => {
127-
if (React.isValidElement(child) && child.type === SelectPanelButton) {
128+
if (React.isValidElement(child) && (child.type === SelectPanelButton || isSlot(child, SelectPanelButton))) {
128129
// eslint-disable-next-line react-compiler/react-compiler
129130
Anchor = React.cloneElement(child, {
130131
// @ts-ignore TODO
@@ -339,7 +340,9 @@ const SelectPanelButton = React.forwardRef<HTMLButtonElement, ButtonProps>((prop
339340
} else {
340341
return <Button ref={anchorRef} {...props} />
341342
}
342-
})
343+
}) as WithSlotMarker<React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>>
344+
345+
SelectPanelButton.__SLOT__ = Symbol('SelectPanel.Button')
343346

344347
const SelectPanelHeader: FCWithSlotMarker<React.ComponentPropsWithoutRef<'div'> & {onBack?: () => void}> = ({
345348
children,

packages/react/src/internal/components/CheckboxOrRadioGroup/CheckboxOrRadioGroup.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import VisuallyHidden from '../../../_VisuallyHidden'
99
import {useSlots} from '../../../hooks/useSlots'
1010
import classes from './CheckboxOrRadioGroup.module.css'
1111
import {clsx} from 'clsx'
12+
import {isSlot} from '../../../utils/is-slot'
1213

1314
export type CheckboxOrRadioGroupProps = {
1415
/** Class name for custom styling */
@@ -46,13 +47,21 @@ const CheckboxOrRadioGroup: React.FC<React.PropsWithChildren<CheckboxOrRadioGrou
4647
validation: CheckboxOrRadioGroupValidation,
4748
})
4849
const labelChild = React.Children.toArray(children).find(
49-
child => React.isValidElement(child) && child.type === CheckboxOrRadioGroupLabel,
50+
child =>
51+
React.isValidElement(child) &&
52+
(child.type === CheckboxOrRadioGroupLabel || isSlot(child, CheckboxOrRadioGroupLabel)),
5053
)
5154
const validationChild = React.Children.toArray(children).find(child =>
52-
React.isValidElement(child) && child.type === CheckboxOrRadioGroupValidation ? child : null,
55+
React.isValidElement(child) &&
56+
(child.type === CheckboxOrRadioGroupValidation || isSlot(child, CheckboxOrRadioGroupValidation))
57+
? child
58+
: null,
5359
)
5460
const captionChild = React.Children.toArray(children).find(child =>
55-
React.isValidElement(child) && child.type === CheckboxOrRadioGroupCaption ? child : null,
61+
React.isValidElement(child) &&
62+
(child.type === CheckboxOrRadioGroupCaption || isSlot(child, CheckboxOrRadioGroupCaption))
63+
? child
64+
: null,
5665
)
5766
const id = useId(idProp)
5867
const validationMessageId = validationChild ? `${id}-validationMessage` : undefined

0 commit comments

Comments
 (0)