diff --git a/README.md b/README.md index 8417172f5b..73fb35455d 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ then [check out the "help wanted" label](https://github.com/palantir/blueprint/l [Lerna](https://lernajs.io/) manages inter-package dependencies in this monorepo. Builds are orchestrated via `lerna run` and NPM scripts. -__Prerequisites__: Node.js v8+, Yarn v1.0+ +__Prerequisites__: Node.js v8+, Yarn v1.10+ ### One-time setup diff --git a/package.json b/package.json index 968c7cde4d..03fe4c8e3f 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,9 @@ "engines": { "node": ">=6.1" }, + "resolutions": { + "node-gyp": "^3.6.3" + }, "repository": { "type": "git", "url": "git@github.com:palantir/blueprint.git" diff --git a/packages/core/package.json b/packages/core/package.json index e4295865c6..9f21fa06c8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/core", - "version": "3.7.0", + "version": "3.9.0", "description": "Core styles & components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", @@ -39,7 +39,7 @@ "verify": "npm-run-all compile -p dist test lint" }, "dependencies": { - "@blueprintjs/icons": "^3.2.0", + "@blueprintjs/icons": "^3.3.0", "@types/dom4": "^2.0.0", "classnames": "^2.2", "dom4": "^2.0.1", @@ -55,8 +55,8 @@ "react-dom": "^15.3.0 || 16" }, "devDependencies": { - "@blueprintjs/karma-build-scripts": "^0.8.0", - "@blueprintjs/node-build-scripts": "^0.7.0", + "@blueprintjs/karma-build-scripts": "*", + "@blueprintjs/node-build-scripts": "*", "@blueprintjs/test-commons": "^0.8.0", "enzyme": "^3.3.0", "karma": "^1.7.1", diff --git a/packages/core/src/_reset.scss b/packages/core/src/_reset.scss index 53d0f44f7c..2d32ae3f6d 100644 --- a/packages/core/src/_reset.scss +++ b/packages/core/src/_reset.scss @@ -21,6 +21,7 @@ html { body { @include base-typography(); color: $pt-text-color; + font-family: $pt-font-family; } p { diff --git a/packages/core/src/common/_flex.scss b/packages/core/src/common/_flex.scss index b44d7db1b4..7dc3090174 100644 --- a/packages/core/src/common/_flex.scss +++ b/packages/core/src/common/_flex.scss @@ -36,8 +36,8 @@ // CSS API support &::before, - // space after all children > * { + // space after all children #{$margin-prop}: $margin; } diff --git a/packages/core/src/common/_mixins.scss b/packages/core/src/common/_mixins.scss index c6af7d7f79..d7230283ee 100644 --- a/packages/core/src/common/_mixins.scss +++ b/packages/core/src/common/_mixins.scss @@ -37,7 +37,6 @@ $pt-dark-intent-text-colors: ( text-transform: none; line-height: $pt-line-height; letter-spacing: 0; - font-family: $pt-font-family; font-size: $pt-font-size; font-weight: 400; } diff --git a/packages/core/src/common/classes.ts b/packages/core/src/common/classes.ts index c801dbb149..e1b76423e2 100644 --- a/packages/core/src/common/classes.ts +++ b/packages/core/src/common/classes.ts @@ -14,7 +14,6 @@ const NS = process.env.BLUEPRINT_NAMESPACE || "bp3"; export const ACTIVE = `${NS}-active`; export const ALIGN_LEFT = `${NS}-align-left`; export const ALIGN_RIGHT = `${NS}-align-right`; -export const CONDENSED = `${NS}-condensed`; export const DARK = `${NS}-dark`; export const DISABLED = `${NS}-disabled`; export const FILL = `${NS}-fill`; @@ -116,8 +115,9 @@ export const HTML_SELECT = `${NS}-html-select`; export const SELECT = `${NS}-select`; export const HTML_TABLE = `${NS}-html-table`; -export const HTML_TABLE_STRIPED = `${HTML_TABLE}-striped`; export const HTML_TABLE_BORDERED = `${HTML_TABLE}-bordered`; +export const HTML_TABLE_CONDENSED = `${HTML_TABLE}-condensed`; +export const HTML_TABLE_STRIPED = `${HTML_TABLE}-striped`; export const INPUT = `${NS}-input`; export const INPUT_GHOST = `${INPUT}-ghost`; diff --git a/packages/core/src/common/props.ts b/packages/core/src/common/props.ts index bbb8ffe206..42f0d57922 100644 --- a/packages/core/src/common/props.ts +++ b/packages/core/src/common/props.ts @@ -23,6 +23,13 @@ export type HTMLDivProps = React.HTMLAttributes; */ export type HTMLInputProps = React.InputHTMLAttributes; +/** + * Alias for a `JSX.Element` or a value that renders nothing. + * + * In React, `boolean`, `null`, and `undefined` do not produce any output. + */ +export type MaybeElement = JSX.Element | false | null | undefined; + /** * A shared base interface for all Blueprint component props. */ @@ -45,7 +52,7 @@ export interface IActionProps extends IIntentProps, IProps { disabled?: boolean; /** Name of a Blueprint UI icon (or an icon element) to render before the text. */ - icon?: IconName | JSX.Element; + icon?: IconName | MaybeElement; /** Click event handler. */ onClick?: (event: React.MouseEvent) => void; diff --git a/packages/core/src/components/alert/alert.tsx b/packages/core/src/components/alert/alert.tsx index 5c1b4566cd..a0e94c7a86 100644 --- a/packages/core/src/components/alert/alert.tsx +++ b/packages/core/src/components/alert/alert.tsx @@ -7,7 +7,7 @@ import classNames from "classnames"; import * as React from "react"; -import { AbstractPureComponent, Classes, DISPLAYNAME_PREFIX, Intent, IProps } from "../../common"; +import { AbstractPureComponent, Classes, DISPLAYNAME_PREFIX, Intent, IProps, MaybeElement } from "../../common"; import { ALERT_WARN_CANCEL_ESCAPE_KEY, ALERT_WARN_CANCEL_OUTSIDE_CLICK, @@ -48,7 +48,7 @@ export interface IAlertProps extends IOverlayLifecycleProps, IProps { confirmButtonText?: string; /** Name of a Blueprint UI icon (or an icon element) to display on the left side. */ - icon?: IconName | JSX.Element; + icon?: IconName | MaybeElement; /** * The intent to be applied to the confirm (right-most) button. @@ -75,6 +75,13 @@ export interface IAlertProps extends IOverlayLifecycleProps, IProps { */ transitionDuration?: number; + /** + * 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; + /** * Handler invoked when the alert is canceled. Alerts can be **canceled** in the following ways: * - clicking the cancel button (if `cancelButtonText` is defined) @@ -130,6 +137,7 @@ export class Alert extends AbstractPureComponent { canEscapeKeyClose={canEscapeKeyCancel} canOutsideClickClose={canOutsideClickCancel} onClose={this.handleCancel} + portalContainer={this.props.portalContainer} >
diff --git a/packages/core/src/components/breadcrumbs/_breadcrumbs.scss b/packages/core/src/components/breadcrumbs/_breadcrumbs.scss index fd43dd8ef6..6731532a85 100644 --- a/packages/core/src/components/breadcrumbs/_breadcrumbs.scss +++ b/packages/core/src/components/breadcrumbs/_breadcrumbs.scss @@ -91,6 +91,7 @@ Styleguide breadcrumbs background: $light-gray1; cursor: pointer; padding: 1px ($pt-grid-size / 2); + vertical-align: text-bottom; &::before { display: block; diff --git a/packages/core/src/components/breadcrumbs/breadcrumb.tsx b/packages/core/src/components/breadcrumbs/breadcrumb.tsx index 8cdde97f3f..47aa844819 100644 --- a/packages/core/src/components/breadcrumbs/breadcrumb.tsx +++ b/packages/core/src/components/breadcrumbs/breadcrumb.tsx @@ -10,16 +10,28 @@ import * as React from "react"; import * as Classes from "../../common/classes"; import { IActionProps, ILinkProps } from "../../common/props"; -export interface IBreadcrumbProps extends IActionProps, ILinkProps {} +export interface IBreadcrumbProps extends IActionProps, ILinkProps { + /** Whether this breadcrumb is the current breadcrumb. */ + current?: boolean; +} export const Breadcrumb: React.SFC = breadcrumbProps => { const classes = classNames( Classes.BREADCRUMB, { + [Classes.BREADCRUMB_CURRENT]: breadcrumbProps.current, [Classes.DISABLED]: breadcrumbProps.disabled, }, breadcrumbProps.className, ); + if (breadcrumbProps.href == null && breadcrumbProps.onClick == null) { + return ( + + {breadcrumbProps.text} + {breadcrumbProps.children} + + ); + } return ( = breadcrumbProps => { target={breadcrumbProps.target} > {breadcrumbProps.text} + {breadcrumbProps.children} ); }; diff --git a/packages/core/src/components/breadcrumbs/breadcrumbs.md b/packages/core/src/components/breadcrumbs/breadcrumbs.md index 8e640ef69b..61b103a1dc 100644 --- a/packages/core/src/components/breadcrumbs/breadcrumbs.md +++ b/packages/core/src/components/breadcrumbs/breadcrumbs.md @@ -1,14 +1,51 @@ @# Breadcrumbs -Breadcrumbs identify the current resource in an application. +Breadcrumbs identify the path to the current resource in an application. -@css breadcrumbs +@reactExample BreadcrumbsExample @## Props -The component renders an `a.@ns-breadcrumb`. You are responsible for constructing -the `ul.@ns-breadcrumbs` list. [`CollapsibleList`](#core/components/collapsible-list) -works nicely with this component because its props are a subset of `IMenuItemProps`. +@### Breadcrumbs + +The `Breadcrumbs` component requires an `items` array of +[breadcrumb props](#core/components/breadcrumbs.breadcrumb) and renders them in +an [`OverflowList`](#core/components/overflow-list) to automatically collapse +breadcrumbs that do not fit in the available space. + +```tsx +const { Breadcrumbs, IBreadcrumbProps, Icon } = "@blueprintjs/core"; + +const BREADCRUMBS: IBreadcrumbProps[] = [ + { href: "/users", icon: "folder-close", text: "Users" }, + { href: "/users/janet", icon: "folder-close", text: "Janet" }, + { icon: "document", text: "image.jpg" }, +]; + +export class BreadcrumbsExample extends React.Component { + public render() { + return ( + + ); + } + private renderCurrentBreadcrumb = ({ text, ...restProps }: IBreadcrumbProps) => { + // customize rendering of last breadcrumb + return {text} ; + }; +} +``` + +@interface IBreadcrumbsProps + +@### Breadcrumb + +The `Breadcrumb` component renders an `a.@ns-breadcrumb` if given an `href` or +`onClick` and a `span.@ns-breadcrumb` otherwise. Typically you will supply an +array of `IBreadcrumbProps` to the `` prop and only render +this component directly when defining a custom `breadcrumbRenderer`. @interface IBreadcrumbProps @@ -27,3 +64,5 @@ user to that resource. containing breadcrumbs that are collapsed due to layout constraints. * When adding another element (such as a [tooltip](#core/components/tooltip) or [popover](#core/components/popover)) to a breadcrumb, wrap it around the contents of the `li`. + +@css breadcrumbs diff --git a/packages/core/src/components/breadcrumbs/breadcrumbs.tsx b/packages/core/src/components/breadcrumbs/breadcrumbs.tsx new file mode 100644 index 0000000000..3e611e4cd3 --- /dev/null +++ b/packages/core/src/components/breadcrumbs/breadcrumbs.tsx @@ -0,0 +1,130 @@ +/* + * 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 { Boundary } from "../../common/boundary"; +import * as Classes from "../../common/classes"; +import { Position } from "../../common/position"; +import { IProps } from "../../common/props"; +import { Menu } from "../menu/menu"; +import { MenuItem } from "../menu/menuItem"; +import { IOverflowListProps, OverflowList } from "../overflow-list/overflowList"; +import { IPopoverProps, Popover } from "../popover/popover"; +import { Breadcrumb, IBreadcrumbProps } from "./breadcrumb"; + +export interface IBreadcrumbsProps extends IProps { + /** + * Callback invoked to render visible breadcrumbs. Best practice is to + * render a `` element. If `currentBreadcrumbRenderer` is also + * supplied, that callback will be used for the current breadcrumb instead. + * @default Breadcrumb + */ + breadcrumbRenderer?: (props: IBreadcrumbProps) => JSX.Element; + + /** + * Which direction the breadcrumbs should collapse from: start or end. + * @default Boundary.START + */ + collapseFrom?: Boundary; + + /** + * Callback invoked to render the current breadcrumb, which is the last + * element in the `items` array. + * + * If this prop is omitted, `breadcrumbRenderer` will be invoked for the + * current breadcrumb instead. + */ + currentBreadcrumbRenderer?: (props: IBreadcrumbProps) => JSX.Element; + + /** + * All breadcrumbs to display. Breadcrumbs that do not fit in the container + * will be rendered in an overflow menu instead. + */ + items: IBreadcrumbProps[]; + + /** + * The minimum number of visible breadcrumbs that should never collapse into + * the overflow menu, regardless of DOM dimensions. + * @default 0 + */ + minVisibleItems?: number; + + /** + * Props to spread to `OverflowList`. Note that `items`, + * `overflowRenderer`, and `visibleItemRenderer` cannot be changed. + */ + overflowListProps?: Partial>; + + /** + * Props to spread to the `Popover` showing the overflow menu. + */ + popoverProps?: IPopoverProps; +} + +export class Breadcrumbs extends React.PureComponent { + public static defaultProps: Partial = { + collapseFrom: Boundary.START, + }; + + public render() { + const { className, collapseFrom, items, minVisibleItems, overflowListProps = {} } = this.props; + return ( + + ); + } + + private renderOverflow = (items: IBreadcrumbProps[]) => { + const { collapseFrom } = this.props; + const position = collapseFrom === Boundary.END ? Position.BOTTOM_RIGHT : Position.BOTTOM_LEFT; + let orderedItems = items; + if (collapseFrom === Boundary.START) { + // If we're collapsing from the start, the menu should be read from the bottom to the + // top, continuing with the breadcrumbs to the right. Since this means the first + // breadcrumb in the props must be the last in the menu, we need to reverse the overlow + // order. + orderedItems = items.slice().reverse(); + } + return ( +
  • + + + {orderedItems.map(this.renderOverflowBreadcrumb)} + +
  • + ); + }; + + private renderOverflowBreadcrumb = (props: IBreadcrumbProps, index: number) => { + const isClickable = props.href != null || props.onClick != null; + return ; + }; + + private renderBreadcrumbWrapper = (props: IBreadcrumbProps, index: number) => { + const isCurrent = this.props.items[this.props.items.length - 1] === props; + return
  • {this.renderBreadcrumb(props, isCurrent)}
  • ; + }; + + private renderBreadcrumb(props: IBreadcrumbProps, isCurrent: boolean) { + if (isCurrent && this.props.currentBreadcrumbRenderer != null) { + return this.props.currentBreadcrumbRenderer(props); + } else if (this.props.breadcrumbRenderer != null) { + return this.props.breadcrumbRenderer(props); + } else { + return ; + } + } +} diff --git a/packages/core/src/components/button/abstractButton.tsx b/packages/core/src/components/button/abstractButton.tsx index a14eabf550..088353d02f 100644 --- a/packages/core/src/components/button/abstractButton.tsx +++ b/packages/core/src/components/button/abstractButton.tsx @@ -10,7 +10,7 @@ import * as React from "react"; import { Alignment } from "../../common/alignment"; import * as Classes from "../../common/classes"; import * as Keys from "../../common/keys"; -import { IActionProps } from "../../common/props"; +import { IActionProps, MaybeElement } from "../../common/props"; import { isReactNodeEmpty, safeInvoke } from "../../common/utils"; import { Icon, IconName } from "../icon/icon"; import { Spinner } from "../spinner/spinner"; @@ -52,7 +52,7 @@ export interface IButtonProps extends IActionProps { minimal?: boolean; /** Name of a Blueprint UI icon (or an icon element) to render after the text. */ - rightIcon?: IconName | JSX.Element; + rightIcon?: IconName | MaybeElement; /** Whether this button should use small styles. */ small?: boolean; diff --git a/packages/core/src/components/callout/_callout.scss b/packages/core/src/components/callout/_callout.scss index 03eb844f66..ccaefc9f9f 100644 --- a/packages/core/src/components/callout/_callout.scss +++ b/packages/core/src/components/callout/_callout.scss @@ -57,6 +57,10 @@ Styleguide callout margin-top: 0; margin-bottom: $pt-grid-size / 2; line-height: $pt-icon-size-large; + + &:last-child { + margin-bottom: 0; + } } .#{$ns}-dark & { diff --git a/packages/core/src/components/callout/callout.tsx b/packages/core/src/components/callout/callout.tsx index 216293d766..37e021c66f 100644 --- a/packages/core/src/components/callout/callout.tsx +++ b/packages/core/src/components/callout/callout.tsx @@ -7,7 +7,7 @@ import classNames from "classnames"; import * as React from "react"; -import { Classes, DISPLAYNAME_PREFIX, HTMLDivProps, IIntentProps, Intent, IProps } from "../../common"; +import { Classes, DISPLAYNAME_PREFIX, HTMLDivProps, IIntentProps, Intent, IProps, MaybeElement } from "../../common"; import { Icon } from "../../index"; import { H4 } from "../html/html"; import { IconName } from "../icon/icon"; @@ -20,7 +20,7 @@ export interface ICalloutProps extends IIntentProps, IProps, HTMLDivProps { * If this prop is omitted or `undefined`, the `intent` prop will determine a default icon. * If this prop is explicitly `null`, no icon will be displayed (regardless of `intent`). */ - icon?: IconName | JSX.Element | null; + icon?: IconName | MaybeElement; /** * Visual intent color to apply to background, title, and icon. @@ -62,7 +62,7 @@ export class Callout extends React.PureComponent { ); } - private getIconName(icon?: ICalloutProps["icon"], intent?: Intent): JSX.Element | IconName | undefined { + private getIconName(icon?: ICalloutProps["icon"], intent?: Intent): IconName | MaybeElement { // 1. no icon if (icon === null) { return undefined; diff --git a/packages/core/src/components/dialog/dialog.tsx b/packages/core/src/components/dialog/dialog.tsx index 6f67db1301..1d1811baf3 100644 --- a/packages/core/src/components/dialog/dialog.tsx +++ b/packages/core/src/components/dialog/dialog.tsx @@ -10,7 +10,7 @@ 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 { DISPLAYNAME_PREFIX, IProps, MaybeElement } from "../../common/props"; import { Button } from "../button/buttons"; import { H4 } from "../html/html"; import { Icon, IconName } from "../icon/icon"; @@ -34,7 +34,7 @@ export interface IDialogProps extends IOverlayableProps, IBackdropProps, IProps * dialog's header. Note that the header will only be rendered if `title` is * provided. */ - icon?: IconName | JSX.Element; + icon?: IconName | MaybeElement; /** * Whether to show the close button in the dialog's header. diff --git a/packages/core/src/components/forms/_control-group.scss b/packages/core/src/components/forms/_control-group.scss index b92ed4e63e..d1b511d373 100644 --- a/packages/core/src/components/forms/_control-group.scss +++ b/packages/core/src/components/forms/_control-group.scss @@ -156,7 +156,10 @@ Styleguide control-group // keep the select-menu carets on top of everything always (particularly when // .#{$ns}-selects are focused). - .#{$ns}-select::after { + .#{$ns}-select::after, + .#{$ns}-html-select::after, + .#{$ns}-select > .#{$ns}-icon, + .#{$ns}-html-select > .#{$ns}-icon { z-index: index($control-group-stack, "select-caret"); } diff --git a/packages/core/src/components/forms/_controls.scss b/packages/core/src/components/forms/_controls.scss index 8ae6516624..0751a4d563 100644 --- a/packages/core/src/components/forms/_controls.scss +++ b/packages/core/src/components/forms/_controls.scss @@ -133,7 +133,7 @@ $control-indicator-spacing: $pt-grid-size !default; user-select: none; &::before { - display: inline-block; + display: block; width: 1em; height: 1em; content: ""; diff --git a/packages/core/src/components/forms/formGroup.tsx b/packages/core/src/components/forms/formGroup.tsx index ab0c752a28..9e02ea4398 100644 --- a/packages/core/src/components/forms/formGroup.tsx +++ b/packages/core/src/components/forms/formGroup.tsx @@ -10,6 +10,12 @@ import * as Classes from "../../common/classes"; import { DISPLAYNAME_PREFIX, IIntentProps, IProps } from "../../common/props"; export interface IFormGroupProps extends IIntentProps, IProps { + /** + * A space-delimited list of class names to pass along to the + * `Classes.FORM_CONTENT` element that contains `children`. + */ + contentClassName?: string; + /** * Whether form group should appear as non-interactive. * Remember that `input` elements must be disabled separately. @@ -39,21 +45,24 @@ export interface IFormGroupProps extends IIntentProps, IProps { * Optional secondary text that appears after the label. */ labelInfo?: React.ReactNode; + + /** CSS properties to apply to the root element. */ + style?: React.CSSProperties; } export class FormGroup extends React.PureComponent { public static displayName = `${DISPLAYNAME_PREFIX}.FormGroup`; public render() { - const { children, helperText, label, labelFor, labelInfo } = this.props; + const { children, contentClassName, helperText, label, labelFor, labelInfo, style } = this.props; return ( -
    +
    {label && ( )} -
    +
    {children} {helperText &&
    {helperText}
    }
    diff --git a/packages/core/src/components/forms/inputGroup.tsx b/packages/core/src/components/forms/inputGroup.tsx index 2c34bcc3d8..15074688bd 100644 --- a/packages/core/src/components/forms/inputGroup.tsx +++ b/packages/core/src/components/forms/inputGroup.tsx @@ -14,6 +14,7 @@ import { IControlledProps, IIntentProps, IProps, + MaybeElement, removeNonHTMLProps, } from "../../common/props"; import { Icon, IconName } from "../icon/icon"; @@ -37,7 +38,7 @@ export interface IInputGroupProps extends IControlledProps, IIntentProps, IProps * Name of a Blueprint UI icon (or an icon element) to render on the left side of the input group, * before the user's cursor. */ - leftIcon?: IconName | JSX.Element; + leftIcon?: IconName | MaybeElement; /** Whether this input should use large styles. */ large?: boolean; diff --git a/packages/core/src/components/forms/label.md b/packages/core/src/components/forms/label.md index 6095e730a5..c2d55f5c93 100644 --- a/packages/core/src/components/forms/label.md +++ b/packages/core/src/components/forms/label.md @@ -19,12 +19,13 @@ below, clicking a label focuses its ``. This component supports the full range of HTML props. ```tsx -