Skip to content

Commit 2ec5bf2

Browse files
feat: support custom slots (#6976)
Co-authored-by: Adam Dierkens <[email protected]>
1 parent f896b37 commit 2ec5bf2

File tree

64 files changed

+795
-82
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+795
-82
lines changed

.changeset/lucky-walls-jog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@primer/react": minor
3+
"@primer/styled-react": patch
4+
---
5+
6+
feat: support custom slots

packages/react/src/ActionList/Description.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Truncate from '../Truncate'
33
import {ItemContext} from './shared'
44
import classes from './ActionList.module.css'
55
import {clsx} from 'clsx'
6+
import type {FCWithSlotMarker} from '../utils/types/Slots'
67

78
export type ActionListDescriptionProps = {
89
/**
@@ -21,7 +22,7 @@ export type ActionListDescriptionProps = {
2122
truncate?: boolean
2223
}
2324

24-
export const Description: React.FC<React.PropsWithChildren<ActionListDescriptionProps>> = ({
25+
export const Description: FCWithSlotMarker<React.PropsWithChildren<ActionListDescriptionProps>> = ({
2526
variant = 'inline',
2627
className,
2728
truncate,
@@ -70,3 +71,5 @@ export const Description: React.FC<React.PropsWithChildren<ActionListDescription
7071
)
7172
}
7273
}
74+
75+
Description.__SLOT__ = Symbol('ActionList.Description')

packages/react/src/ActionList/Divider.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type React from 'react'
22
import {clsx} from 'clsx'
33
import classes from './ActionList.module.css'
4+
import type {FCWithSlotMarker} from '../utils/types/Slots'
45

56
export type ActionListDividerProps = {
67
className?: string
@@ -10,7 +11,7 @@ export type ActionListDividerProps = {
1011
/**
1112
* Visually separates `Items` or `Groups` in an `ActionList`.
1213
*/
13-
export const Divider: React.FC<React.PropsWithChildren<ActionListDividerProps>> = ({className, style}) => {
14+
export const Divider: FCWithSlotMarker<React.PropsWithChildren<ActionListDividerProps>> = ({className, style}) => {
1415
return (
1516
<li
1617
className={clsx(className, classes.Divider)}
@@ -20,3 +21,5 @@ export const Divider: React.FC<React.PropsWithChildren<ActionListDividerProps>>
2021
/>
2122
)
2223
}
24+
25+
Divider.__SLOT__ = Symbol('ActionList.Divider')

packages/react/src/ActionList/Group.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {invariant} from '../utils/invariant'
77
import {clsx} from 'clsx'
88
import classes from './ActionList.module.css'
99
import groupClasses from './Group.module.css'
10+
import type {FCWithSlotMarker} from '../utils/types/Slots'
1011

1112
type HeadingProps = {
1213
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
@@ -67,7 +68,7 @@ export const GroupContext = React.createContext<ContextProps>({
6768
selectionVariant: undefined,
6869
})
6970

70-
export const Group: React.FC<React.PropsWithChildren<ActionListGroupProps>> = ({
71+
export const Group: FCWithSlotMarker<React.PropsWithChildren<ActionListGroupProps>> = ({
7172
title,
7273
variant = 'subtle',
7374
auxiliaryText,
@@ -138,7 +139,7 @@ export type ActionListGroupHeadingProps = Pick<ActionListGroupProps, 'variant' |
138139
* hidden from the accessibility tree due to the limitation of listbox children. https://w3c.github.io/aria/#listbox
139140
* groups under menu or listbox are labelled by `aria-label`
140141
*/
141-
export const GroupHeading: React.FC<React.PropsWithChildren<ActionListGroupHeadingProps>> = ({
142+
export const GroupHeading: FCWithSlotMarker<React.PropsWithChildren<ActionListGroupHeadingProps>> = ({
142143
as,
143144
variant = 'subtle',
144145
// We are not recommending this prop to be used, it should only be used internally for incremental rollout.
@@ -210,3 +211,6 @@ export const GroupHeading: React.FC<React.PropsWithChildren<ActionListGroupHeadi
210211

211212
GroupHeading.displayName = 'ActionList.GroupHeading'
212213
Group.displayName = 'ActionList.Group'
214+
215+
Group.__SLOT__ = Symbol('ActionList.Group')
216+
GroupHeading.__SLOT__ = Symbol('ActionList.GroupHeading')

packages/react/src/ActionList/Heading.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,5 @@ export const Heading = forwardRef(({as, size, children, visuallyHidden = false,
5151
}) as PolymorphicForwardRefComponent<HeadingLevels, ActionListHeadingProps>
5252

5353
Heading.displayName = 'ActionList.Heading'
54+
55+
Heading.__SLOT__ = Symbol('ActionList.Heading')

packages/react/src/ActionList/Item.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,9 @@ const UnwrappedItem = <As extends React.ElementType = 'li'>(
314314
)
315315
}
316316

317-
const Item = fixedForwardRef(UnwrappedItem)
318-
319-
Object.assign(Item, {displayName: 'ActionList.Item'})
317+
const Item = Object.assign(fixedForwardRef(UnwrappedItem), {
318+
displayName: 'ActionList.Item',
319+
__SLOT__: Symbol('ActionList.Item'),
320+
})
320321

321322
export {Item}

packages/react/src/ActionList/LinkItem.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,5 @@ export const LinkItem = React.forwardRef(
5656
) as PolymorphicForwardRefComponent<'a', ActionListLinkItemProps>
5757

5858
LinkItem.displayName = 'ActionList.LinkItem'
59+
60+
LinkItem.__SLOT__ = Symbol('ActionList.LinkItem')

packages/react/src/ActionList/TrailingAction.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,5 @@ export const TrailingAction = forwardRef(
6666
)
6767
},
6868
) as PolymorphicForwardRefComponent<'button' | 'a', ActionListTrailingActionProps>
69+
70+
TrailingAction.__SLOT__ = Symbol('ActionList.TrailingAction')

packages/react/src/ActionList/Visuals.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {ItemContext} from './shared'
55
import {Tooltip, type TooltipProps} from '../TooltipV2'
66
import {clsx} from 'clsx'
77
import classes from './ActionList.module.css'
8+
import type {FCWithSlotMarker} from '../utils/types/Slots'
89

910
export type VisualProps = React.HTMLAttributes<HTMLSpanElement>
1011

@@ -13,7 +14,7 @@ export const VisualContainer: React.FC<React.PropsWithChildren<VisualProps>> = (
1314
}
1415

1516
export type ActionListLeadingVisualProps = VisualProps
16-
export const LeadingVisual: React.FC<React.PropsWithChildren<VisualProps>> = ({className, ...props}) => {
17+
export const LeadingVisual: FCWithSlotMarker<React.PropsWithChildren<VisualProps>> = ({className, ...props}) => {
1718
return (
1819
<VisualContainer className={clsx(className, classes.LeadingVisual)} {...props}>
1920
{props.children}
@@ -22,7 +23,7 @@ export const LeadingVisual: React.FC<React.PropsWithChildren<VisualProps>> = ({c
2223
}
2324

2425
export type ActionListTrailingVisualProps = VisualProps
25-
export const TrailingVisual: React.FC<React.PropsWithChildren<VisualProps>> = ({className, ...props}) => {
26+
export const TrailingVisual: FCWithSlotMarker<React.PropsWithChildren<VisualProps>> = ({className, ...props}) => {
2627
const {trailingVisualId} = React.useContext(ItemContext)
2728
return (
2829
<VisualContainer className={clsx(className, classes.TrailingVisual)} id={trailingVisualId} {...props}>
@@ -78,3 +79,6 @@ export const VisualOrIndicator: React.FC<
7879

7980
LeadingVisual.displayName = 'ActionList.LeadingVisual'
8081
TrailingVisual.displayName = 'ActionList.TrailingVisual'
82+
83+
LeadingVisual.__SLOT__ = Symbol('ActionList.LeadingVisual')
84+
TrailingVisual.__SLOT__ = Symbol('ActionList.TrailingVisual')

packages/react/src/ActionMenu/ActionMenu.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../uti
1515
import {Tooltip} from '../TooltipV2/Tooltip'
1616
import styles from './ActionMenu.module.css'
1717
import {useResponsiveValue, type ResponsiveValue} from '../hooks/useResponsiveValue'
18+
import {isSlot} from '../utils/is-slot'
19+
import type {FCWithSlotMarker, WithSlotMarker} from '../utils/types/Slots'
1820

1921
export type MenuCloseHandler = (
2022
gesture: 'anchor-click' | 'click-outside' | 'escape' | 'tab' | 'item-select' | 'arrow-left' | 'close',
@@ -71,7 +73,7 @@ const mergeAnchorHandlers = (anchorProps: React.HTMLAttributes<HTMLElement>, but
7173
return mergedAnchorProps
7274
}
7375

74-
const Menu: React.FC<React.PropsWithChildren<ActionMenuProps>> = ({
76+
const Menu: FCWithSlotMarker<React.PropsWithChildren<ActionMenuProps>> = ({
7577
anchorRef: externalAnchorRef,
7678
open,
7779
onOpenChange,
@@ -112,7 +114,7 @@ const Menu: React.FC<React.PropsWithChildren<ActionMenuProps>> = ({
112114
// 🚨 Accounting for Tooltip wrapping ActionMenu.Button or being a direct child of ActionMenu.Anchor.
113115
const contents = React.Children.map(children, child => {
114116
// Is ActionMenu.Button wrapped with Tooltip? If this is the case, our anchor is the tooltip's trigger (ActionMenu.Button's grandchild)
115-
if (child.type === Tooltip) {
117+
if (child.type === Tooltip || isSlot(child, Tooltip)) {
116118
// tooltip trigger
117119
const anchorChildren = child.props.children
118120
if (anchorChildren.type === MenuButton) {
@@ -129,7 +131,8 @@ const Menu: React.FC<React.PropsWithChildren<ActionMenuProps>> = ({
129131
return null
130132
} else if (child.type === Anchor) {
131133
const anchorChildren = child.props.children
132-
const isWrappedWithTooltip = anchorChildren !== undefined ? anchorChildren.type === Tooltip : false
134+
const isWrappedWithTooltip =
135+
anchorChildren !== undefined ? anchorChildren.type === Tooltip || isSlot(anchorChildren, Tooltip) : false
133136
if (isWrappedWithTooltip) {
134137
if (anchorChildren.props.children !== null) {
135138
renderAnchor = anchorProps => {
@@ -175,7 +178,15 @@ const Menu: React.FC<React.PropsWithChildren<ActionMenuProps>> = ({
175178
}
176179

177180
export type ActionMenuAnchorProps = {children: React.ReactElement; id?: string} & React.HTMLAttributes<HTMLElement>
178-
const Anchor = React.forwardRef<HTMLElement, ActionMenuAnchorProps>(({children: child, ...anchorProps}, anchorRef) => {
181+
const Anchor: WithSlotMarker<
182+
React.ForwardRefExoticComponent<
183+
{
184+
children: React.ReactElement
185+
id?: string
186+
} & React.HTMLAttributes<HTMLElement> &
187+
React.RefAttributes<HTMLElement>
188+
>
189+
> = React.forwardRef<HTMLElement, ActionMenuAnchorProps>(({children: child, ...anchorProps}, anchorRef) => {
179190
const {onOpen, isSubmenu} = React.useContext(MenuContext)
180191

181192
const openSubmenuOnRightArrow: React.KeyboardEventHandler<HTMLElement> = useCallback(
@@ -247,7 +258,7 @@ type MenuOverlayProps = Partial<OverlayProps> &
247258
children: React.ReactNode
248259
onPositionChange?: ({position}: {position: AnchorPosition}) => void
249260
}
250-
const Overlay: React.FC<React.PropsWithChildren<MenuOverlayProps>> = ({
261+
const Overlay: FCWithSlotMarker<React.PropsWithChildren<MenuOverlayProps>> = ({
251262
children,
252263
align = 'start',
253264
side,
@@ -323,4 +334,10 @@ const Overlay: React.FC<React.PropsWithChildren<MenuOverlayProps>> = ({
323334
}
324335

325336
Menu.displayName = 'ActionMenu'
337+
338+
Menu.__SLOT__ = Symbol('ActionMenu')
339+
MenuButton.__SLOT__ = Symbol('ActionMenu.Button')
340+
Anchor.__SLOT__ = Symbol('ActionMenu.Anchor')
341+
Overlay.__SLOT__ = Symbol('ActionMenu.Overlay')
342+
326343
export const ActionMenu = Object.assign(Menu, {Button: MenuButton, Anchor, Overlay, Divider})

0 commit comments

Comments
 (0)