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
6 changes: 6 additions & 0 deletions packages/core/src/common/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,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 Expand Up @@ -168,6 +173,7 @@ export const OVERFLOW_LIST_SPACER = `${OVERFLOW_LIST}-spacer`;

export const OVERLAY = `${NS}-overlay`;
export const OVERLAY_BACKDROP = `${OVERLAY}-backdrop`;
export const OVERLAY_CONTAINER = `${OVERLAY}-container`;
export const OVERLAY_CONTENT = `${OVERLAY}-content`;
export const OVERLAY_INLINE = `${OVERLAY}-inline`;
export const OVERLAY_OPEN = `${OVERLAY}-open`;
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
134 changes: 134 additions & 0 deletions packages/core/src/components/drawer/_drawer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// 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;

$drawer-default-size: 50%;

.#{$ns}-drawer {
display: flex;
flex-direction: column;
margin: 0;
box-shadow: $pt-elevation-shadow-4;
background: $white;
padding: 0;

&:focus {
outline: 0;
}

&:not(.#{$ns}-vertical) {
@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: "&"
);

top: 0;
right: 0;
bottom: 0;
width: $drawer-default-size;
}

&.#{$ns}-vertical {
@include react-transition-phase(
"#{$ns}-overlay",
"enter",
(transform: (translateY(100%), translateY(0))),
$pt-transition-duration * 2,
$pt-transition-ease,
$before: "&"
);
@include react-transition-phase(
"#{$ns}-overlay",
"exit",
(transform: (translateY(100%), translateY(0))),
$pt-transition-duration,
$before: "&"
);

right: 0;
bottom: 0;
left: 0;
height: $drawer-default-size;
}

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

.#{$ns}-drawer-header {
display: flex;
flex: 0 0 auto;
align-items: center;
position: relative;
border-radius: 0;
box-shadow: 0 1px 0 $pt-divider-black;
min-height: $pt-icon-size-large + $dialog-padding;
padding: $dialog-padding / 4;
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;

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

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

.#{$ns}-drawer-footer {
flex: 0 0 auto;
position: relative;
box-shadow: inset 0 1px 0 $pt-divider-black;
padding: $dialog-padding/2 $dialog-padding;

.#{$ns}-dark & {
box-shadow: inset 0 1px 0 $pt-dark-divider-black;
}
}
21 changes: 21 additions & 0 deletions packages/core/src/components/drawer/drawer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
tag: new
---

@# Drawer
giladgray marked this conversation as resolved.
Show resolved Hide resolved

Drawers overlay content over existing parts of the UI and are anchored to the edge of the screen.

@reactExample DrawerExample

@## Props

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

Use the `size` prop to set the size of the `Drawer`. This prop sets CSS `width` if `vertical={false}` (default) and `height` otherwise. Constants are available for common sizes:

- `Drawer.SIZE_SMALL = 360px`
- `Drawer.SIZE_STANDARD = 50%` (default)
- `Drawer.SIZE_LARGE = 90%`

@interface IDrawerProps
146 changes: 146 additions & 0 deletions packages/core/src/components/drawer/drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* 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, MaybeElement } from "../../common/props";
import { Button } from "../button/buttons";
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 {
/**
* Name of a Blueprint UI icon (or an icon element) to render in the
* drawer's header. Note that the header will only be rendered if `title` is
* provided.
*/
icon?: IconName | MaybeElement;

/**
* 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;

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

/**
* CSS size of the drawer. This sets `width` if `vertical={false}` (default)
* and `height` otherwise.
*
* Constants are available for common sizes:
* - `Drawer.SIZE_SMALL = 360px`
* - `Drawer.SIZE_STANDARD = 50%`
* - `Drawer.SIZE_LARGE = 90%`
*
* @default Drawer.SIZE_STANDARD = "50%"
*/
size?: number | string;

/**
* 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;

/**
* Whether the drawer should appear with vertical styling.
* @default false
*/
vertical?: boolean;
}

export class Drawer extends AbstractPureComponent<IDrawerProps, {}> {
public static displayName = `${DISPLAYNAME_PREFIX}.Drawer`;
public static defaultProps: IDrawerProps = {
canOutsideClickClose: true,
isCloseButtonShown: true,
isOpen: false,
style: {},
vertical: false,
};

public static readonly SIZE_SMALL = "360px";
public static readonly SIZE_STANDARD = "50%";
public static readonly SIZE_LARGE = "90%";

public render() {
const { size, style, vertical } = this.props;
const classes = classNames(Classes.DRAWER, { [Classes.VERTICAL]: vertical }, this.props.className);
const styleProp = size == null ? style : { ...style, [vertical ? "height" : "width"]: size };
return (
<Overlay {...this.props} className={Classes.OVERLAY_CONTAINER}>
<div className={classes} style={styleProp}>
{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() {
if (this.props.isCloseButtonShown) {
return (
<Button
aria-label="Close"
className={Classes.DIALOG_CLOSE_BUTTON}
icon={<Icon icon="small-cross" iconSize={Icon.SIZE_LARGE} />}
minimal={true}
onClick={this.props.onClose}
/>
);
} else {
return null;
}
}

private maybeRenderHeader() {
const { icon, title } = this.props;
if (title == null) {
return null;
}
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 @@ -27,6 +27,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
13 changes: 13 additions & 0 deletions packages/core/src/components/overlay/_overlay.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ body.#{$ns}-overlay-open {
pointer-events: none;
}

&.#{$ns}-overlay-container {
// container covers the entire viewport
position: fixed;
overflow: hidden;

&.#{$ns}-overlay-inline {
// when rendered inline, we want the overlay to position itself relative to
// its parent container, not relative to the whole window. thus, we change
// to position:absolute.
position: absolute;
}
}

&.#{$ns}-overlay-scroll-container {
// scroll container covers the entire viewport
position: fixed;
Expand Down
Loading