@atlaskit/popup
Version:
A popup displays brief content in an overlay.
313 lines (312 loc) • 12 kB
TypeScript
import { type ComponentType, type CSSProperties, type Dispatch, type PropsWithChildren, type default as React, type ReactNode, type Ref, type SetStateAction } from 'react';
import { type StrictXCSSProp } from '@atlaskit/css';
import { type Modifier, type Placement, type PopperChildrenProps } from '@atlaskit/popper';
export interface TriggerProps {
/**
* React ref that will be attached to the trigger element.
*/
ref: Ref<any>;
/**
* Identifies the popup element that the trigger controls.
* Should match the `id` of the popup content for screen readers to understand the relationship.
*/
'aria-controls'?: string;
/**
* Announces to assistive technology whether the popup is currently open or closed.
*/
'aria-expanded': boolean;
/**
* Informs assistive technology that this element triggers a popup.
*/
'aria-haspopup': boolean | 'dialog';
'data-ds--level'?: string;
}
export type PopupRef = HTMLDivElement | null;
export type TriggerRef = HTMLElement | HTMLButtonElement | null;
export interface ContentProps {
/**
* This will reposition the popup if any of the content has changed.
* This is useful when positions change, and the popup wasn't aware.
*/
update: PopperChildrenProps['update'];
/**
* Passed through from the parent popup.
*/
isOpen: boolean;
/**
* Passed through from the parent popup.
*/
onClose?: BaseProps['onClose'];
/**
* Escape hatch to set the initial focus for a specific element, when the popup is opened.
*
* The specified element needs to be available in the DOM as soon as the popup is opened, otherwise it
* will not be focused upon. This means it will not work for content that renders into the popup content
* after it has already opened (e.g. async or lazy loaded content).
*/
setInitialFocusRef: Dispatch<SetStateAction<HTMLElement | null>>;
}
export interface PopupComponentProps {
/**
* Children passed through by the parent popup.
*/
children: ReactNode;
/**
* Placement passed through by the parent popup.
*/
'data-placement': Placement;
/**
* Test ID passed through by the parent popup.
*/
'data-testid'?: string;
/**
* ID passed through by the parent popup.
*/
id?: string;
/**
* Style that should be assigned to the root element.
*/
style: CSSProperties;
xcss?: StrictXCSSProp<'padding' | 'paddingInline' | 'paddingBlock' | 'width', never>;
/**
* Tab index passed through by the parent popup.
*/
tabIndex: number | undefined;
/**
* Controls whether the popup is rendered inline within its parent component or in a portal at the document root.
* `true` renders the popup in the DOM node closest to the trigger; focus is not trapped inside the element.
* `false` renders the popup in React.Portal and focus is trapped inside the element.
* The default is `false`.
*/
shouldRenderToParent?: boolean;
/**
* This fits the popup width to its parent's width.
* When set to `true`, the trigger and popup elements will be wrapped in a `div` with `position: relative`.
* The popup will be rendered as a sibling to the trigger element, and will be full width.
* The default is `false`.
*/
shouldFitContainer?: boolean;
/**
* Determines if the popup will have a `max-width` and `max-height` set to
* constrain it to the viewport.
*/
shouldFitViewport?: boolean;
/**
* The "default" appearance is used for standard popups.
* The "UNSAFE_modal-below-sm" appearance makes the popup appear as a modal when the viewport is smaller than "sm".
* When the feature gate `platform_dst_nav4_flyout_menu_slots_close_button` is enabled, the appearance should only
* be set to "UNSAFE_modal-below-sm" if the provided popup includes a close button.
*/
appearance?: 'default' | 'UNSAFE_modal-below-sm';
/**
* Class name to apply to the popup container element.
*/
className?: string;
/**
* Boolean to indicate if the reference element is hidden.
*/
isReferenceHidden?: boolean;
/**
* Use this to set the accessibility role for the popup.
* We strongly recommend using only `menu` or `dialog`.
*/
role?: string;
}
interface BaseProps {
/**
* The "default" appearance is used for standard popups.
* The "UNSAFE_modal-below-sm" appearance makes the popup appear as a modal when the viewport is smaller than "sm".
*/
appearance?: 'default' | 'UNSAFE_modal-below-sm';
/**
* Bounded style overrides.
*/
xcss?: StrictXCSSProp<'padding' | 'paddingInline' | 'paddingBlock' | 'paddingInlineStart' | 'paddingInlineEnd' | 'paddingBlockStart' | 'paddingBlockEnd' | 'width', never>;
/**
* Use this to either show or hide the popup.
* When set to `false` the popup will not render anything to the DOM.
*/
isOpen: boolean;
/**
* Render props for content that is displayed inside the popup.
*/
content: (props: ContentProps) => React.ReactNode;
/**
* ID that is assigned to the popup container element.
*/
id?: string;
/**
* The distance the popup should be offset from the reference in the format of [along, away] (units in px).
* The default is `[0, 8]`, which means the popup will be `8px` away from the edge of the reference specified
* by the `placement` prop.
*/
offset?: [number, number];
/**
* Placement of where the popup should be displayed relative to the trigger element.
* The default is `"auto"`.
*/
placement?: Placement;
/**
* This is a list of backup placements for the popup to try.
* When the preferred placement doesn't have enough space,
* the modifier will test the ones provided in the list, and use the first suitable one.
* If no fallback placements are suitable, it reverts back to the original placement.
*/
fallbackPlacements?: Placement[];
/**
* The boundary element that the popup will check for overflow.
* The default is `"clippingParents"` which are parent scroll containers,
* but can be set to any element.
*/
boundary?: 'clippingParents' | HTMLElement;
/**
* The root boundary that the popup will check for overflow.
* The default is `"viewport"` but it can be set to `"document"`.
*/
rootBoundary?: 'viewport' | 'document';
/**
* Allows the popup to be placed on the opposite side of its trigger if it doesn't fit in the viewport.
* The default is `true`.
*/
shouldFlip?: boolean;
/**
* A `testId` prop is provided for specified elements,
* which is a unique string that appears as a data attribute `data-testid` in the rendered code,
* serving as a hook for automated tests.
*/
testId?: string;
/**
* Handler that is called when the popup wants to close itself.
* This can happen when:
* - the user clicks away from the popup
* - the user presses the escape key
* - the popup is closed programatically. In this case, the `event` argument will be `null`.
*
* You'll want to use this to set open state accordingly, and then pump it back into the `isOpen` prop.
*/
onClose?(
/**
* The event that triggered the close.
* The argument value will be `null` when the popup is closed programatically and has no corresponding event.
*/
event: Event | React.MouseEvent | React.KeyboardEvent | null, currentLevel?: number | any): void;
/**
* The element that is shown when `isOpen` prop is `true`.
* The result of the `content` prop will be placed as children here.
* The default is an element with an elevation of `e200` with _no padding_.
*/
popupComponent?: ComponentType<PopupComponentProps> | React.ForwardRefExoticComponent<React.PropsWithoutRef<PopupComponentProps> & React.RefAttributes<HTMLDivElement>>;
/**
* This controls whether the popup takes focus when opening.
* This changes the `popupComponent` component tabIndex to `null`.
* The default is `true`.
*/
autoFocus?: boolean;
/**
* This controls if the event which handles clicks outside the popup is be bound with
* `capture: true`.
*/
shouldUseCaptureOnOutsideClick?: boolean;
/**
* The root element where the popup should be rendered.
* Defaults to `false`.
*/
shouldRenderToParent?: boolean;
/**
* This fits the popup width to its parent's width.
* When set to `true`, the trigger and popup elements will be wrapped in a `div` with `position: relative`.
* The popup will be rendered as a sibling to the trigger element, and will be full width.
* The default is `false`.
*/
shouldFitContainer?: boolean;
/**
* This makes the popup close on Tab key press. It will only work when `shouldRenderToParent` is `true`.
* The default is `false`.
*/
shouldDisableFocusLock?: boolean;
/**
* This determines whether the popup trigger will be focused when the popup content closes.
* The default is `true`.
*/
shouldReturnFocus?: boolean;
/**
* This controls the positioning strategy to use. Can vary between `absolute` and `fixed`.
* The default is `fixed`.
*/
strategy?: 'absolute' | 'fixed';
/**
* Use this to set the accessibility role for the popup.
* We strongly recommend using only `menu` or `dialog`.
* Must be used along with `label` or `titleId`.
*/
role?: string;
/**
* Refers to an `aria-label` attribute. Sets an accessible name for the popup to announce it to users of assistive technology.
* Usage of either this, or the `titleId` attribute is strongly recommended.
*/
label?: string;
/**
* Id referenced by the popup `aria-labelledby` attribute.
* Usage of either this, or the `label` attribute is strongly recommended.
*/
titleId?: string;
/**
* Additional modifiers and modifier overwrites.
* for more details - https://popper.js.org/docs/v1/#modifiers
*/
modifiers?: Partial<Modifier<string, object>>[];
/**
* Determines if the popup will have a `max-width` and `max-height` set to
* constrain it to the viewport.
*/
shouldFitViewport?: boolean;
}
interface InternalPopupProps extends BaseProps {
/**
* Render props used to anchor the popup to your content.
* Make this an interactive element,
* such as an `@atlaskit/button` component.
*/
trigger: (props: TriggerProps) => React.ReactNode;
/**
* Z-index that the popup should be displayed in.
* This is passed to the portal component.
* The default is 400.
*/
zIndex?: number;
}
type StandardPopupProps = InternalPopupProps & {
shouldFitContainer?: false;
};
type ShouldFitContainerPopupProps = InternalPopupProps & {
shouldFitContainer: true;
shouldRenderToParent?: true;
strategy?: 'absolute';
};
export type PopupProps = StandardPopupProps | ShouldFitContainerPopupProps;
export interface PopperWrapperProps extends BaseProps {
triggerRef: TriggerRef;
}
export type CloseManagerHook = Pick<PopupProps, 'isOpen' | 'onClose'> & {
popupRef: PopupRef;
triggerRef: TriggerRef;
shouldUseCaptureOnOutsideClick?: boolean;
shouldCloseOnTab?: boolean;
shouldDisableFocusTrap: boolean;
shouldRenderToParent?: boolean;
autoFocus: boolean;
};
export type FocusManagerHook = {
initialFocusRef: HTMLElement | null;
popupRef: PopupRef;
shouldCloseOnTab?: boolean;
triggerRef: TriggerRef;
autoFocus: boolean;
shouldDisableFocusTrap: boolean;
shouldReturnFocus: boolean;
shouldRenderToParent?: boolean;
};
export type RepositionOnUpdateProps = PropsWithChildren<{
update: PopperChildrenProps['update'];
}>;
export {};