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

New Drawer component #2957

Merged
merged 19 commits into from
Feb 7, 2019
Merged
5 changes: 5 additions & 0 deletions packages/core/src/common/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ export const DIALOG_HEADER = `${DIALOG}-header`;

export const DIVIDER = `${NS}-divider`;

export const DRAWER = `${NS}-drawer`;
export const DRAWER_BODY = `${DRAWER}-body`;
export const DRAWER_FOOTER = `${DRAWER}-footer`;
export const DRAWER_HEADER = `${DRAWER}-header`;

export const EDITABLE_TEXT = `${NS}-editable-text`;
export const EDITABLE_TEXT_CONTENT = `${EDITABLE_TEXT}-content`;
export const EDITABLE_TEXT_EDITING = `${EDITABLE_TEXT}-editing`;
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@import "context-menu/context-menu";
@import "divider/divider";
@import "dialog/dialog";
@import "drawer/drawer";
@import "editable-text/editable-text";
@import "forms/index";
@import "html-select/html-select";
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 @@ -54,6 +54,7 @@
@page alert
@page context-menu
@page dialog
@page drawer
@page popover
@page toast
@page tooltip
101 changes: 101 additions & 0 deletions packages/core/src/components/drawer/_drawer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2018 Palantir Technologies, Inc. All rights reserved.
// Licensed under the terms of the LICENSE file distributed with this project.

@import "~@blueprintjs/icons/src/icons";
@import "../../common/mixins";
@import "../../common/react-transition";
@import "../../common/variables";

$drawer-margin: ($pt-grid-size * 3) 0 !default;
$drawer-padding: $pt-grid-size * 2 !default;

.#{$ns}-drawer {
@include react-transition-phase(
"#{$ns}-overlay",
"enter",
(transform: (translateX(100%), translateX(0))),
$pt-transition-duration * 2,
$pt-transition-ease,
$before: "&"
);
@include react-transition-phase(
"#{$ns}-overlay",
"exit",
(transform: (translateX(100%), translateX(0))),
$pt-transition-duration,
$before: "&"
);
display: flex;
flex-direction: column;
top: 0;
right: 0;
bottom: 0;
margin: 0;
box-shadow: $pt-elevation-shadow-4;
background: $white;
width: 50vw;
giladgray marked this conversation as resolved.
Show resolved Hide resolved
min-width: $pt-grid-size * 50;
max-width: $pt-grid-size * 80;
padding: 0;

&:focus {
outline: 0;
}

&.#{$ns}-dark,
.#{$ns}-dark & {
box-shadow: $pt-dark-dialog-box-shadow;
background: $pt-dark-app-background-color;
color: $pt-dark-text-color;
}
}

.#{$ns}-drawer-header {
display: flex;
flex: 0 0 auto;
align-items: center;
border-radius: 0;
box-shadow: 0 1px 0 $pt-divider-black;
background: $white;
min-height: $pt-icon-size-large + $dialog-padding;
padding-left: $dialog-padding;

.#{$ns}-icon-large,
.#{$ns}-icon {
flex: 0 0 auto;
margin-right: $dialog-padding / 2;
color: $pt-icon-color;
}

.#{$ns}-heading {
@include overflow-ellipsis();
flex: 1 1 auto;
margin: 0;
line-height: inherit;

&:last-child {
margin-right: $dialog-padding;
}
}

.#{$ns}-dark & {
box-shadow: 0 1px 0 $pt-dark-divider-black;
background: $dark-gray4;

.#{$ns}-icon-large,
.#{$ns}-icon {
color: $pt-dark-icon-color;
}
}
}

.#{$ns}-drawer-body {
flex: 1 1 auto;
margin: $drawer-padding;
line-height: $pt-grid-size * 1.8;
}

.#{$ns}-drawer-footer {
flex: 0 0 auto;
margin: 0 $drawer-padding;
}
11 changes: 11 additions & 0 deletions packages/core/src/components/drawer/drawer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@# Drawer
giladgray marked this conversation as resolved.
Show resolved Hide resolved

[description]

@reactExample DrawerExample

@## Props

`Drawer` is a stateless React component controlled by the `isOpen` prop.

@interface IDrawerProps
126 changes: 126 additions & 0 deletions packages/core/src/components/drawer/drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright 2018 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the terms of the LICENSE file distributed with this project.
*/

import classNames from "classnames";
import * as React from "react";

import { AbstractPureComponent } from "../../common/abstractPureComponent";
import * as Classes from "../../common/classes";
import * as Errors from "../../common/errors";
import { DISPLAYNAME_PREFIX, IProps } from "../../common/props";
import { H4 } from "../html/html";
import { Icon, IconName } from "../icon/icon";
import { IBackdropProps, IOverlayableProps, Overlay } from "../overlay/overlay";

export interface IDrawerProps extends IOverlayableProps, IBackdropProps, IProps {
/**
* Toggles the visibility of the overlay and its children.
* This prop is required because the component is controlled.
*/
isOpen: boolean;

/**
* Drawer always has a backdrop so this prop is excluded from the public API.
* @internal
*/
hasBackdrop?: boolean;
giladgray marked this conversation as resolved.
Show resolved Hide resolved

/**
* Name of a Blueprint UI icon (or an icon element) to render in the
* dialog's header. Note that the header will only be rendered if `title` is
* provided.
*/
icon?: IconName | JSX.Element;

/**
* Whether to show the close button in the dialog's header.
* Note that the header will only be rendered if `title` is provided.
* @default true
*/
isCloseButtonShown?: boolean;

/**
* CSS styles to apply to the dialog.
* @default {}
*/
style?: React.CSSProperties;

/**
* Title of the dialog. If provided, an element with `Classes.DIALOG_HEADER`
* will be rendered inside the dialog before any children elements.
*/
title?: React.ReactNode;

/**
* Name of the transition for internal `CSSTransition`. Providing your own
* name here will require defining new CSS transition properties.
*/
transitionName?: string;
}

export class Drawer extends AbstractPureComponent<IDrawerProps, {}> {
public static defaultProps: IDrawerProps = {
canOutsideClickClose: true,
isOpen: false,
};

public static displayName = `${DISPLAYNAME_PREFIX}.Drawer`;

public render() {
return (
<Overlay {...this.props} className={Classes.OVERLAY_SCROLL_CONTAINER} hasBackdrop={true}>
<div className={classNames(Classes.DRAWER, this.props.className)} style={this.props.style}>
{this.maybeRenderHeader()}
{this.props.children}
</div>
</Overlay>
);
}

protected validateProps(props: IDrawerProps) {
if (props.title == null) {
if (props.icon != null) {
console.warn(Errors.DIALOG_WARN_NO_HEADER_ICON);
}
if (props.isCloseButtonShown != null) {
console.warn(Errors.DIALOG_WARN_NO_HEADER_CLOSE_BUTTON);
}
}
}

private maybeRenderCloseButton() {
// show close button if prop is undefined or null
// this gives us a behavior as if the default value were `true`
if (this.props.isCloseButtonShown !== false) {
return (
<button
giladgray marked this conversation as resolved.
Show resolved Hide resolved
aria-label="Close"
className={Classes.DIALOG_CLOSE_BUTTON}
onClick={this.props.onClose}
type="button"
>
<Icon icon="small-cross" iconSize={Icon.SIZE_LARGE} />
</button>
);
} else {
return undefined;
}
}

private maybeRenderHeader() {
const { icon, title } = this.props;
if (title == null) {
return undefined;
}
return (
<div className={Classes.DRAWER_HEADER}>
<Icon icon={icon} iconSize={Icon.SIZE_LARGE} />
<H4>{title}</H4>
{this.maybeRenderCloseButton()}
</div>
);
}
}
1 change: 1 addition & 0 deletions packages/core/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export * from "./collapsible-list/collapsibleList";
export * from "./context-menu/contextMenuTarget";
export * from "./dialog/dialog";
export * from "./divider/divider";
export * from "./drawer/drawer";
export * from "./editable-text/editableText";
export * from "./forms/controlGroup";
export * from "./forms/controls";
Expand Down
104 changes: 104 additions & 0 deletions packages/docs-app/src/examples/core-examples/drawerExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright 2018 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the terms of the LICENSE file distributed with this project.
*/

import * as React from "react";

import { Button, Classes, Code, Drawer, H5, Switch } from "@blueprintjs/core";
import { Example, handleBooleanChange, IExampleProps } from "@blueprintjs/docs-theme";
import { IBlueprintExampleData } from "../../tags/reactExamples";

export interface IDrawerExampleState {
autoFocus: boolean;
canEscapeKeyClose: boolean;
canOutsideClickClose: boolean;
enforceFocus: boolean;
isOpen: boolean;
usePortal: boolean;
}
export class DrawerExample extends React.PureComponent<IExampleProps<IBlueprintExampleData>, IDrawerExampleState> {
public state: IDrawerExampleState = {
autoFocus: true,
canEscapeKeyClose: true,
canOutsideClickClose: true,
enforceFocus: true,
isOpen: true,
usePortal: true,
};

private handleAutoFocusChange = handleBooleanChange(autoFocus => this.setState({ autoFocus }));
private handleEnforceFocusChange = handleBooleanChange(enforceFocus => this.setState({ enforceFocus }));
private handleEscapeKeyChange = handleBooleanChange(canEscapeKeyClose => this.setState({ canEscapeKeyClose }));
private handleUsePortalChange = handleBooleanChange(usePortal => this.setState({ usePortal }));
private handleOutsideClickChange = handleBooleanChange(val => this.setState({ canOutsideClickClose: val }));

public render() {
return (
<Example options={this.renderOptions()} {...this.props}>
<Button onClick={this.handleOpen}>Show Drawer</Button>
<Drawer
className={this.props.data.themeName}
icon="info-sign"
onClose={this.handleClose}
title="Palantir Foundry"
{...this.state}
>
<div className={Classes.DRAWER_BODY}>
<p>
<strong>
Data integration is the seminal problem of the digital age. For over ten years, we’ve
helped the world’s premier organizations rise to the challenge.
</strong>
</p>
<p>
Palantir Foundry radically reimagines the way enterprises interact with data by amplifying
and extending the power of data integration. With Foundry, anyone can source, fuse, and
transform data into any shape they desire. Business analysts become data engineers — and
leaders in their organization’s data revolution.
</p>
<p>
Foundry’s back end includes a suite of best-in-class data integration capabilities: data
provenance, git-style versioning semantics, granular access controls, branching,
transformation authoring, and more. But these powers are not limited to the back-end IT
shop.
</p>
<p>
In Foundry, tables, applications, reports, presentations, and spreadsheets operate as data
integrations in their own right. Access controls, transformation logic, and data quality
flow from original data source to intermediate analysis to presentation in real time. Every
end product created in Foundry becomes a new data source that other users can build upon.
And the enterprise data foundation goes where the business drives it.
</p>
<p>Start the revolution. Unleash the power of data integration with Palantir Foundry.</p>
</div>
<div className={Classes.DRAWER_FOOTER}>footer</div>
</Drawer>
</Example>
);
}

private renderOptions() {
const { autoFocus, enforceFocus, canEscapeKeyClose, canOutsideClickClose, usePortal } = this.state;
return (
<>
<H5>Props</H5>
<Switch checked={autoFocus} label="Auto focus" onChange={this.handleAutoFocusChange} />
<Switch checked={enforceFocus} label="Enforce focus" onChange={this.handleEnforceFocusChange} />
<Switch checked={usePortal} onChange={this.handleUsePortalChange}>
Use <Code>Portal</Code>
</Switch>
<Switch
checked={canOutsideClickClose}
label="Click outside to close"
onChange={this.handleOutsideClickChange}
/>
<Switch checked={canEscapeKeyClose} label="Escape key to close" onChange={this.handleEscapeKeyChange} />
</>
);
}

private handleOpen = () => this.setState({ isOpen: true });
private handleClose = () => this.setState({ isOpen: false });
}
3 changes: 2 additions & 1 deletion packages/docs-app/src/examples/core-examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ export * from "./checkboxExample";
export * from "./collapseExample";
export * from "./cardExample";
export * from "./collapsibleListExample";
export * from "./contextMenuExample";
export * from "./controlGroupExample";
export * from "./dialogExample";
export * from "./dividerExample";
export * from "./contextMenuExample";
export * from "./drawerExample";
export * from "./dropdownMenuExample";
export * from "./editableTextExample";
export * from "./focusExample";
Expand Down