Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions packages/react/src/ActionList/Group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import groupClasses from './Group.module.css'
import type {FCWithSlotMarker} from '../utils/types/Slots'
import {GroupHeadingTrailingAction} from './GroupHeadingTrailingAction'
import {useFeatureFlag} from '../FeatureFlags'
import {GroupContext} from './GroupContext'

const GROUP_HEADING_TRAILING_ACTION_FEATURE_FLAG = 'primer_react_action_list_group_heading_trailing_action'

Expand Down Expand Up @@ -66,12 +67,6 @@ export type ActionListGroupProps = React.HTMLAttributes<HTMLLIElement> & {
selectionVariant?: ActionListProps['selectionVariant'] | false
}

type ContextProps = Pick<ActionListGroupProps, 'selectionVariant'> & {groupHeadingId: string | undefined}
export const GroupContext = React.createContext<ContextProps>({
groupHeadingId: undefined,
selectionVariant: undefined,
})

export const Group: FCWithSlotMarker<React.PropsWithChildren<ActionListGroupProps>> = ({
title,
variant = 'subtle',
Expand Down
9 changes: 9 additions & 0 deletions packages/react/src/ActionList/GroupContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react'
import type {ActionListGroupProps} from './Group'

type ContextProps = Pick<ActionListGroupProps, 'selectionVariant'> & {groupHeadingId: string | undefined}

export const GroupContext = React.createContext<ContextProps>({
groupHeadingId: undefined,
selectionVariant: undefined,
})
4 changes: 2 additions & 2 deletions packages/react/src/ActionList/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {useId} from '../hooks/useId'
import {useSlots} from '../hooks/useSlots'
import {ActionListContainerContext} from './ActionListContainerContext'
import {Description} from './Description'
import {GroupContext} from './Group'
import {GroupContext} from './GroupContext'
import type {ActionListItemProps, ActionListProps} from './shared'
import {Selection} from './Selection'
import {LeadingVisual, TrailingVisual, VisualOrIndicator} from './Visuals'
Expand All @@ -16,7 +16,7 @@ import classes from './ActionList.module.css'
import {clsx} from 'clsx'
import {fixedForwardRef} from '../utils/modern-polymorphic'
import {Tooltip} from '../TooltipV2'
import {TooltipContext} from '../TooltipV2/Tooltip'
import {TooltipContext} from '../TooltipV2/TooltipContext'

type ActionListSubItemProps = {
children?: React.ReactNode
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/ActionList/Selection.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import {CheckIcon} from '@primer/octicons-react'
import type {ActionListGroupProps} from './Group'
import {GroupContext} from './Group'
import {GroupContext} from './GroupContext'
import {type ActionListProps, type ActionListItemProps, ListContext} from './shared'
import {VisualContainer} from './Visuals'
import classes from './ActionList.module.css'
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/ActionList/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {List} from './List'
import {Group, GroupContext, GroupHeading} from './Group'
import {Group, GroupHeading} from './Group'
import {GroupContext} from './GroupContext'
import {Item} from './Item'
import {LinkItem} from './LinkItem'
import {Divider} from './Divider'
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/ActionMenu/ActionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import styles from './ActionMenu.module.css'
import {useResponsiveValue, type ResponsiveValue} from '../hooks/useResponsiveValue'
import {isSlot} from '../utils/is-slot'
import type {FCWithSlotMarker, WithSlotMarker} from '../utils/types/Slots'
import {DialogContext} from '../Dialog/Dialog'
import {DialogContext} from '../Dialog/DialogContext'

export type MenuCloseHandler = (
gesture: 'anchor-click' | 'click-outside' | 'escape' | 'tab' | 'item-select' | 'arrow-left' | 'close',
Expand Down
5 changes: 3 additions & 2 deletions packages/react/src/Button/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import React, {forwardRef, type JSX} from 'react'
import type {IconButtonProps} from './types'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import {ButtonBase} from './ButtonBase'
import {TooltipContext, Tooltip} from '../TooltipV2/Tooltip'
import {TooltipContext as TooltipContextV1} from '../Tooltip/Tooltip'
import {Tooltip} from '../TooltipV2/Tooltip'
import {TooltipContext} from '../TooltipV2/TooltipContext'
import {TooltipContext as TooltipContextV1} from '../Tooltip/TooltipContext'
import classes from './ButtonBase.module.css'
import {clsx} from 'clsx'

Expand Down
3 changes: 1 addition & 2 deletions packages/react/src/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import classes from './Dialog.module.css'
import {clsx} from 'clsx'
import {useSlots} from '../hooks/useSlots'
import {useResizeObserver} from '../hooks/useResizeObserver'
import {DialogContext} from './DialogContext'

/* Dialog Version 2 */

Expand Down Expand Up @@ -270,8 +271,6 @@ const defaultFooterButtons: Array<DialogButtonProps> = []
// Minimum room needed for body content before forcing footer buttons into horizontal scroll.
const MIN_BODY_HEIGHT = 48

// useful to determine whether we're inside a Dialog from a nested component
export const DialogContext = React.createContext<object | undefined>(undefined)
const DIALOG_CONTEXT_VALUE = Object.freeze({})

const _Dialog = React.forwardRef<HTMLDivElement, React.PropsWithChildren<DialogProps>>((props, forwardedRef) => {
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/Dialog/DialogContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import React from 'react'

export const DialogContext = React.createContext<object | undefined>(undefined)
4 changes: 3 additions & 1 deletion packages/react/src/Portal/Portal.features.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, {useEffect} from 'react'
import type {Meta} from '@storybook/react-vite'
import {Portal, PortalContext, registerPortalRoot} from './Portal'
import {Portal} from './Portal'
import {PortalContext} from './PortalContext'
import {registerPortalRoot} from './portalRoot'
import classes from './Portal.stories.module.css'
import {clsx} from 'clsx'

Expand Down
53 changes: 3 additions & 50 deletions packages/react/src/Portal/Portal.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,8 @@
import React, {useContext} from 'react'
import {createPortal} from 'react-dom'
import useLayoutEffect from '../utils/useIsomorphicLayoutEffect'

const PRIMER_PORTAL_ROOT_ID = '__primerPortalRoot__'
const DEFAULT_PORTAL_CONTAINER_NAME = '__default__'

const portalRootRegistry: Partial<Record<string, Element>> = {}

/**
* Register a container to serve as a portal root.
* @param root The element that will be the root for portals created in this container
* @param name The name of the container, to be used with the `containerName` prop on the Portal Component.
* If name is not specified, registers the default portal root.
*/
export function registerPortalRoot(root: Element, name = DEFAULT_PORTAL_CONTAINER_NAME): void {
portalRootRegistry[name] = root
}

// Ensures that a default portal root exists and is registered. If a DOM element exists
// with id __primerPortalRoot__, allow that element to serve as the default portal root.
// Otherwise, create that element and attach it to the end of document.body.
function ensureDefaultPortal() {
const existingDefaultPortalContainer = portalRootRegistry[DEFAULT_PORTAL_CONTAINER_NAME]
if (!existingDefaultPortalContainer || !document.body.contains(existingDefaultPortalContainer)) {
let defaultPortalContainer = document.getElementById(PRIMER_PORTAL_ROOT_ID)
if (!(defaultPortalContainer instanceof Element)) {
defaultPortalContainer = document.createElement('div')
defaultPortalContainer.setAttribute('id', PRIMER_PORTAL_ROOT_ID)
defaultPortalContainer.style.position = 'absolute'
defaultPortalContainer.style.top = '0'
defaultPortalContainer.style.left = '0'
defaultPortalContainer.style.width = '100%'
const suitablePortalRoot = document.querySelector('[data-portal-root]')
if (suitablePortalRoot) {
suitablePortalRoot.appendChild(defaultPortalContainer)
} else {
document.body.appendChild(defaultPortalContainer)
}
}

registerPortalRoot(defaultPortalContainer)
}
}

/**
* Provides the ability for component trees to override the portal root container for a sub-set of the experience.
* The portal will prioritize the context value unless overridden by their own `containerName` prop, and fallback to the default root if neither are specified
*/
export const PortalContext = React.createContext<{
portalContainerName?: string
}>({})
import {PortalContext} from './PortalContext'
import {DEFAULT_PORTAL_CONTAINER_NAME, ensureDefaultPortal, getPortalRoot} from './portalRoot'

export interface PortalProps {
/**
Expand Down Expand Up @@ -96,7 +49,7 @@ export const Portal: React.FC<React.PropsWithChildren<PortalProps>> = ({
containerName = DEFAULT_PORTAL_CONTAINER_NAME
ensureDefaultPortal()
}
const parentElement = portalRootRegistry[containerName]
const parentElement = getPortalRoot(containerName)

if (!parentElement) {
throw new Error(
Expand Down
5 changes: 5 additions & 0 deletions packages/react/src/Portal/PortalContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react'

export const PortalContext = React.createContext<{
portalContainerName?: string
}>({})
4 changes: 3 additions & 1 deletion packages/react/src/Portal/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type {PortalProps} from './Portal'
import {Portal, registerPortalRoot, PortalContext} from './Portal'
import {Portal} from './Portal'
import {PortalContext} from './PortalContext'
import {registerPortalRoot} from './portalRoot'

export default Portal
export {registerPortalRoot}
Expand Down
41 changes: 41 additions & 0 deletions packages/react/src/Portal/portalRoot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const PRIMER_PORTAL_ROOT_ID = '__primerPortalRoot__'
export const DEFAULT_PORTAL_CONTAINER_NAME = '__default__'

const portalRootRegistry: Partial<Record<string, Element>> = {}

/**
* Register a container to serve as a portal root.
* @param root The element that will be the root for portals created in this container
* @param name The name of the container, to be used with the `containerName` prop on the Portal Component.
* If name is not specified, registers the default portal root.
*/
export function registerPortalRoot(root: Element, name = DEFAULT_PORTAL_CONTAINER_NAME): void {
portalRootRegistry[name] = root
}

export function ensureDefaultPortal() {
const existingDefaultPortalContainer = portalRootRegistry[DEFAULT_PORTAL_CONTAINER_NAME]
if (!existingDefaultPortalContainer || !document.body.contains(existingDefaultPortalContainer)) {
let defaultPortalContainer = document.getElementById(PRIMER_PORTAL_ROOT_ID)
if (!(defaultPortalContainer instanceof Element)) {
defaultPortalContainer = document.createElement('div')
defaultPortalContainer.setAttribute('id', PRIMER_PORTAL_ROOT_ID)
defaultPortalContainer.style.position = 'absolute'
defaultPortalContainer.style.top = '0'
defaultPortalContainer.style.left = '0'
defaultPortalContainer.style.width = '100%'
const suitablePortalRoot = document.querySelector('[data-portal-root]')
if (suitablePortalRoot) {
suitablePortalRoot.appendChild(defaultPortalContainer)
} else {
document.body.appendChild(defaultPortalContainer)
}
}

registerPortalRoot(defaultPortalContainer)
}
}

export function getPortalRoot(name: string) {
return portalRootRegistry[name]
}
2 changes: 1 addition & 1 deletion packages/react/src/Radio/Radio.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {ChangeEventHandler, InputHTMLAttributes, ReactElement} from 'react'
import React, {useContext} from 'react'
import {RadioGroupContext} from '../RadioGroup/RadioGroup'
import {RadioGroupContext} from '../RadioGroup/RadioGroupContext'
import {clsx} from 'clsx'
import sharedClasses from '../Checkbox/shared.module.css'
import classes from './Radio.module.css'
Expand Down
8 changes: 1 addition & 7 deletions packages/react/src/RadioGroup/RadioGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type {ChangeEvent, ChangeEventHandler, FC} from 'react'
import type React from 'react'
import {createContext} from 'react'
import type {CheckboxOrRadioGroupProps} from '../internal/components/CheckboxOrRadioGroup'
import CheckboxOrRadioGroup from '../internal/components/CheckboxOrRadioGroup'
import CheckboxOrRadioGroupCaption from '../internal/components/CheckboxOrRadioGroup/CheckboxOrRadioGroupCaption'
import CheckboxOrRadioGroupLabel from '../internal/components/CheckboxOrRadioGroup/CheckboxOrRadioGroupLabel'
import CheckboxOrRadioGroupValidation from '../internal/components/CheckboxOrRadioGroup/CheckboxOrRadioGroupValidation'
import {useRenderForcingRef} from '../hooks'
import {RadioGroupContext} from './RadioGroupContext'

export type RadioGroupProps = {
/**
Expand All @@ -19,12 +19,6 @@ export type RadioGroupProps = {
name: string
} & CheckboxOrRadioGroupProps

export const RadioGroupContext = createContext<{
disabled?: boolean
onChange?: ChangeEventHandler<HTMLInputElement>
name: string
} | null>(null)

const RadioGroup: FC<React.PropsWithChildren<RadioGroupProps>> = ({children, disabled, onChange, name, ...rest}) => {
const [selectedRadioValue, setSelectedRadioValue] = useRenderForcingRef<string | null>(null)

Expand Down
7 changes: 7 additions & 0 deletions packages/react/src/RadioGroup/RadioGroupContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {type ChangeEventHandler, createContext} from 'react'

export const RadioGroupContext = createContext<{
disabled?: boolean
onChange?: ChangeEventHandler<HTMLInputElement>
name: string
} | null>(null)
3 changes: 2 additions & 1 deletion packages/react/src/RadioGroup/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export {default, RadioGroupContext} from './RadioGroup'
export {default} from './RadioGroup'
export {RadioGroupContext} from './RadioGroupContext'

export type {RadioGroupProps} from './RadioGroup'
3 changes: 1 addition & 2 deletions packages/react/src/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, {useMemo} from 'react'
import {useId} from '../hooks'
import classes from './Tooltip.module.css'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import {TooltipContext} from './TooltipContext'

/* Tooltip v1 */

Expand All @@ -17,8 +18,6 @@ export type TooltipProps = {
wrap?: boolean
} & React.ComponentProps<'span'>

export const TooltipContext = React.createContext<{tooltipId?: string}>({})

/**
* @deprecated
*/
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/Tooltip/TooltipContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import React from 'react'

export const TooltipContext = React.createContext<{tooltipId?: string}>({})
3 changes: 1 addition & 2 deletions packages/react/src/TooltipV2/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {usePlatform} from '../KeybindingHint/platform'
import VisuallyHidden from '../_VisuallyHidden'
import useSafeTimeout from '../hooks/useSafeTimeout'
import type {SlotMarker} from '../utils/types'
import {TooltipContext} from './TooltipContext'

export type TooltipDirection = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w'
export type TooltipProps = React.PropsWithChildren<{
Expand Down Expand Up @@ -102,8 +103,6 @@ const isInteractive = (element: HTMLElement) => {
(element.hasAttribute('role') && element.getAttribute('role') === 'button')
)
}
export const TooltipContext = React.createContext<{tooltipId?: string}>({})

const emptyKeybindingHints: Array<KeybindingHintProps['keys']> = []

export const Tooltip: ForwardRefExoticComponent<
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/TooltipV2/TooltipContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import React from 'react'

export const TooltipContext = React.createContext<{tooltipId?: string}>({})
Loading