Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] feat: Overlay2 component #6656

Merged
merged 24 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions packages/core/src/common/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,6 @@ export const DIALOG_WARN_NO_HEADER_CLOSE_BUTTON =

export const DRAWER_ANGLE_POSITIONS_ARE_CASTED =
ns + ` <Drawer> all angle positions are casted into pure position (TOP, BOTTOM, LEFT or RIGHT)`;

export const OVERLAY_CHILD_REF_REQUIRES_SINGLE_CHILD =
ns + ` <Overlay2> cannot have more than one child when using the childRef prop`;
2 changes: 1 addition & 1 deletion packages/core/src/components/alert/alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
import { Button } from "../button/buttons";
import { Dialog } from "../dialog/dialog";
import { Icon, type IconName } from "../icon/icon";
import type { OverlayLifecycleProps } from "../overlay/overlay";
import type { OverlayLifecycleProps } from "../overlay/overlayProps";

export interface AlertProps extends OverlayLifecycleProps, Props {
/**
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/components/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
@## Overlays

@page overlay
@page overlay2
@page portal
@page alert
@page context-menu
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import type { OverlayLifecycleProps } from "../overlay/overlay";
import type { OverlayLifecycleProps } from "../overlay/overlayProps";
import type { PopoverProps } from "../popover/popover";

export type Offset = {
Expand Down
40 changes: 30 additions & 10 deletions packages/core/src/components/dialog/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@ import * as React from "react";

import { type IconName, IconSize, SmallCross } from "@blueprintjs/icons";

import { AbstractPureComponent, Classes, DISPLAYNAME_PREFIX, type MaybeElement, type Props } from "../../common";
import {
AbstractPureComponent,
Classes,
DISPLAYNAME_PREFIX,
type MaybeElement,
mergeRefs,
type Props,
} from "../../common";
import * as Errors from "../../common/errors";
import { uniqueId } from "../../common/utils";
import { Button } from "../button/buttons";
import { H6 } from "../html/html";
import { Icon } from "../icon/icon";
import { type BackdropProps, Overlay, type OverlayableProps } from "../overlay/overlay";
import type { BackdropProps, OverlayableProps } from "../overlay/overlayProps";
import { Overlay2 } from "../overlay2/overlay2";

export interface DialogProps extends OverlayableProps, BackdropProps, Props {
/** Dialog contents. */
Expand Down Expand Up @@ -77,7 +85,7 @@ export interface DialogProps extends OverlayableProps, BackdropProps, Props {
transitionName?: string;

/**
* Ref supplied to the `Classes.DIALOG_CONTAINER` element.
* Ref attached to the `Classes.DIALOG_CONTAINER` element.
*/
containerRef?: React.Ref<HTMLDivElement>;

Expand Down Expand Up @@ -106,6 +114,8 @@ export class Dialog extends AbstractPureComponent<DialogProps> {
isOpen: false,
};

private childRef = React.createRef<HTMLDivElement>();

private titleId: string;

public static displayName = `${DISPLAYNAME_PREFIX}.Dialog`;
Expand All @@ -118,21 +128,31 @@ export class Dialog extends AbstractPureComponent<DialogProps> {
}

public render() {
const { className, children, containerRef, style, title, ...overlayProps } = this.props;

return (
<Overlay {...this.props} className={Classes.OVERLAY_SCROLL_CONTAINER} hasBackdrop={true}>
<div className={Classes.DIALOG_CONTAINER} ref={this.props.containerRef}>
<Overlay2
{...overlayProps}
className={Classes.OVERLAY_SCROLL_CONTAINER}
childRef={this.childRef}
hasBackdrop={true}
>
<div
className={Classes.DIALOG_CONTAINER}
ref={containerRef === undefined ? this.childRef : mergeRefs(containerRef, this.childRef)}
adidahiya marked this conversation as resolved.
Show resolved Hide resolved
>
<div
className={classNames(Classes.DIALOG, this.props.className)}
className={classNames(Classes.DIALOG, className)}
role="dialog"
aria-labelledby={this.props["aria-labelledby"] || (this.props.title ? this.titleId : undefined)}
aria-labelledby={this.props["aria-labelledby"] || (title ? this.titleId : undefined)}
aria-describedby={this.props["aria-describedby"]}
style={this.props.style}
style={style}
>
{this.maybeRenderHeader()}
{this.props.children}
{children}
</div>
</div>
</Overlay>
</Overlay2>
);
}

Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/components/drawer/drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import { DISPLAYNAME_PREFIX, type MaybeElement } from "../../common/props";
import { Button } from "../button/buttons";
import { H4 } from "../html/html";
import { Icon } from "../icon/icon";
import { type BackdropProps, Overlay, type OverlayableProps } from "../overlay/overlay";
import type { BackdropProps, OverlayableProps } from "../overlay/overlayProps";
import { Overlay2 } from "../overlay2/overlay2";

export enum DrawerSize {
SMALL = "360px",
Expand Down Expand Up @@ -134,12 +135,12 @@ export class Drawer extends AbstractPureComponent<DrawerProps> {
return (
// N.B. the `OVERLAY_CONTAINER` class is a bit of a misnomer since it is only being used by the Drawer
// component, but we keep it for backwards compatibility.
<Overlay {...overlayProps} className={classNames({ [Classes.OVERLAY_CONTAINER]: hasBackdrop })}>
<Overlay2 {...overlayProps} className={classNames({ [Classes.OVERLAY_CONTAINER]: hasBackdrop })}>
<div className={classes} style={styleProp}>
{this.maybeRenderHeader()}
{children}
</div>
</Overlay>
</Overlay2>
);
}

Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export { NavbarGroup, type NavbarGroupProps } from "./navbar/navbarGroup";
export { NavbarHeading, type NavbarHeadingProps } from "./navbar/navbarHeading";
export { NonIdealState, type NonIdealStateProps, NonIdealStateIconSize } from "./non-ideal-state/nonIdealState";
export { OverflowList, type OverflowListProps } from "./overflow-list/overflowList";
export { Overlay, type OverlayLifecycleProps, type OverlayProps, type OverlayableProps } from "./overlay/overlay";
export { Overlay } from "./overlay/overlay";
export type { OverlayLifecycleProps, OverlayProps, OverlayableProps } from "./overlay/overlayProps";
export { Overlay2, type Overlay2Props, type OverlayInstance } from "./overlay2/overlay2";
export { Text, type TextProps } from "./text/text";
// eslint-disable-next-line deprecation/deprecation
export { PanelStack, type PanelStackProps } from "./panel-stack/panelStack";
Expand Down
25 changes: 19 additions & 6 deletions packages/core/src/components/overlay/overlay.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
@# Overlay

__Overlay__ is a generic low-level component for rendering content _on top of_ its siblings or above the entire
<div class="@ns-callout @ns-intent-danger @ns-icon-error @ns-callout-has-body-content">
<h5 class="@ns-heading">

Deprecated: use [**Overlay2**](#core/components/overlay2)

</h5>

This component is **deprecated since @blueprintjs/core v5.9.0** in favor of the new
**Overlay2** component which is compatible with React 18 strict mode. You should migrate to the
new API which will become the standard in Blueprint v6.
adidahiya marked this conversation as resolved.
Show resolved Hide resolved

</div>

**Overlay** is a generic low-level component for rendering content _on top of_ its siblings or above the entire
application.

It combines the functionality of the [__Portal__](#core/components/portal) component (which allows React elements to
escape their current DOM hierarchy) with a [__CSSTransitionGroup__](https://reactcommunity.org/react-transition-group/)
It combines the functionality of the [**Portal**](#core/components/portal) component (which allows React elements to
escape their current DOM hierarchy) with a [**CSSTransitionGroup**](https://reactcommunity.org/react-transition-group/)
(to show elegant enter and leave transitions).

An optional "backdrop" element can be rendered behind the overlaid children to provide modal behavior, whereby the
overlay prevents interaction with anything behind it.

__Overlay__ is the backbone of all the components listed in the **Overlays** group in the sidebar. Using __Overlay__
**Overlay** is the backbone of all the components listed in the **Overlays** group in the sidebar. Using **Overlay**
directly should be rare in your application; it should only be necessary if no existing component meets your needs.

@reactExample OverlayExample

@## Usage

__Overlay__ is a controlled component that renders its children only when `isOpen={true}`. The optional backdrop element
**Overlay** is a controlled component that renders its children only when `isOpen={true}`. The optional backdrop element
will be inserted before the children if `hasBackdrop={true}`.

The `onClose` callback prop is invoked when user interaction causes the overlay to close, but your application is
Expand Down Expand Up @@ -50,7 +63,7 @@ scrollable, so any overflowing content will be hidden. Fortunately, making an ov
<Overlay className={Classes.OVERLAY_SCROLL_CONTAINER} />
```

Note that the [__Dialog__](https://blueprintjs.com/docs/#core/components/dialog) component applies this CSS class
Note that the [**Dialog**](https://blueprintjs.com/docs/#core/components/dialog) component applies this CSS class
automatically.

@## Props interface
Expand Down
177 changes: 2 additions & 175 deletions packages/core/src/components/overlay/overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,184 +19,11 @@ import * as React from "react";
import { CSSTransition, TransitionGroup } from "react-transition-group";

import { AbstractPureComponent, Classes } from "../../common";
import { DISPLAYNAME_PREFIX, type HTMLDivProps, type Props } from "../../common/props";
import { DISPLAYNAME_PREFIX, type HTMLDivProps } from "../../common/props";
import { getActiveElement, isFunction } from "../../common/utils";
import { Portal } from "../portal/portal";

export interface OverlayableProps extends OverlayLifecycleProps {
/**
* Whether the overlay should acquire application focus when it first opens.
*
* @default true
*/
autoFocus?: boolean;

/**
* Whether pressing the `esc` key should invoke `onClose`.
*
* @default true
*/
canEscapeKeyClose?: boolean;

/**
* Whether the overlay should prevent focus from leaving itself. That is, if the user attempts
* to focus an element outside the overlay and this prop is enabled, then the overlay will
* immediately bring focus back to itself. If you are nesting overlay components, either disable
* this prop on the "outermost" overlays or mark the nested ones `usePortal={false}`.
*
* @default true
*/
enforceFocus?: boolean;

/**
* If `true` and `usePortal={true}`, the `Portal` containing the children is created and attached
* to the DOM when the overlay is opened for the first time; otherwise this happens when the
* component mounts. Lazy mounting provides noticeable performance improvements if you have lots
* of overlays at once, such as on each row of a table.
*
* @default true
*/
lazy?: boolean;

/**
* Whether the application should return focus to the last active element in the
* document after this overlay closes.
*
* @default true
*/
shouldReturnFocusOnClose?: boolean;

/**
* Indicates how long (in milliseconds) the overlay's enter/leave transition takes.
* This is used by React `CSSTransition` to know when a transition completes and must match
* the duration of the animation in CSS. Only set this prop if you override Blueprint's default
* transitions with new transitions of a different length.
*
* @default 300
*/
transitionDuration?: number;

/**
* Whether the overlay should be wrapped in a `Portal`, which renders its contents in a new
* element attached to `portalContainer` prop.
*
* This prop essentially determines which element is covered by the backdrop: if `false`,
* then only its parent is covered; otherwise, the entire page is covered (because the parent
* of the `Portal` is the `<body>` itself).
*
* Set this prop to `false` on nested overlays (such as `Dialog` or `Popover`) to ensure that they
* are rendered above their parents.
*
* @default true
*/
usePortal?: boolean;

/**
* Space-delimited string of class names applied to the `Portal` element if
* `usePortal={true}`.
*/
portalClassName?: string;

/**
* The container element into which the overlay renders its contents, when `usePortal` is `true`.
* This prop is ignored if `usePortal` is `false`.
*
* @default document.body
*/
portalContainer?: HTMLElement;

/**
* A list of DOM events which should be stopped from propagating through the Portal.
* This prop is ignored if `usePortal` is `false`.
*
* @deprecated this prop's implementation no longer works in React v17+
* @see https://legacy.reactjs.org/docs/portals.html#event-bubbling-through-portals
* @see https://github.com/palantir/blueprint/issues/6124
* @see https://github.com/palantir/blueprint/issues/6580
*/
portalStopPropagationEvents?: Array<keyof HTMLElementEventMap>;

/**
* A callback that is invoked when user interaction causes the overlay to close, such as
* clicking on the overlay or pressing the `esc` key (if enabled).
*
* Receives the event from the user's interaction, if there was an event (generally either a
* mouse or key event). Note that, since this component is controlled by the `isOpen` prop, it
* will not actually close itself until that prop becomes `false`.
*/
onClose?: (event: React.SyntheticEvent<HTMLElement>) => void;
}

export interface OverlayLifecycleProps {
/**
* Lifecycle method invoked just before the CSS _close_ transition begins on
* a child. Receives the DOM element of the child being closed.
*/
onClosing?: (node: HTMLElement) => void;

/**
* Lifecycle method invoked just after the CSS _close_ transition ends but
* before the child has been removed from the DOM. Receives the DOM element
* of the child being closed.
*/
onClosed?: (node: HTMLElement) => void;

/**
* Lifecycle method invoked just after mounting the child in the DOM but
* just before the CSS _open_ transition begins. Receives the DOM element of
* the child being opened.
*/
onOpening?: (node: HTMLElement) => void;

/**
* Lifecycle method invoked just after the CSS _open_ transition ends.
* Receives the DOM element of the child being opened.
*/
onOpened?: (node: HTMLElement) => void;
}

export interface BackdropProps {
/** CSS class names to apply to backdrop element. */
backdropClassName?: string;

/** HTML props for the backdrop element. */
backdropProps?: React.HTMLProps<HTMLDivElement>;

/**
* Whether clicking outside the overlay element (either on backdrop when present or on document)
* should invoke `onClose`.
*
* @default true
*/
canOutsideClickClose?: boolean;

/**
* Whether a container-spanning backdrop element should be rendered behind the contents.
* When `false`, users will be able to scroll through and interact with overlaid content.
*
* @default true
*/
hasBackdrop?: boolean;
}

export interface OverlayProps extends OverlayableProps, BackdropProps, Props {
/** Element to overlay. */
children?: React.ReactNode;

/**
* Toggles the visibility of the overlay and its children.
* This prop is required because the component is controlled.
*/
isOpen: boolean;

/**
* Name of the transition for internal `CSSTransition`.
* Providing your own name here will require defining new CSS transition properties.
*
* @default Classes.OVERLAY
*/
transitionName?: string;
}
import type { OverlayProps } from "./overlayProps";

export interface OverlayState {
hasEverOpened?: boolean;
Expand Down
Loading