diff --git a/package.json b/package.json index 277acfcda1..6d260d3477 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "copy-landing-app": "cp -rf packages/landing-app/dist/* docs/.", "deploy": "gh-pages -d docs -b master", "dev:all": "lerna run dev --parallel --scope '!@blueprintjs/{landing-app,table-dev-app}'", - "dev:core": "lerna run dev --parallel --scope '@blueprintjs/{core,docs-app}'", + "dev:core": "lerna run dev --parallel --scope '@blueprintjs/{core,icons,docs-app}'", "dev:docs": "lerna run dev --parallel --scope '@blueprintjs/{docs-app,docs-theme}'", "dev:datetime": "lerna run dev --parallel --scope '@blueprintjs/{core,datetime,docs-app}'", "dev:labs": "lerna run dev --parallel --scope '@blueprintjs/{core,labs,select,docs-app}'", diff --git a/packages/core/src/common/classes.ts b/packages/core/src/common/classes.ts index 201118b69b..2f8a15c0a2 100644 --- a/packages/core/src/common/classes.ts +++ b/packages/core/src/common/classes.ts @@ -44,6 +44,7 @@ export const BUTTON = "pt-button"; export const BUTTON_GROUP = "pt-button-group"; export const CALLOUT = "pt-callout"; +export const CALLOUT_ICON = "pt-callout-icon"; export const CARD = "pt-card"; diff --git a/packages/core/src/components/alert/alert.tsx b/packages/core/src/components/alert/alert.tsx index 28f479aa4c..1bca3f9ee5 100644 --- a/packages/core/src/components/alert/alert.tsx +++ b/packages/core/src/components/alert/alert.tsx @@ -69,7 +69,7 @@ export class Alert extends AbstractPureComponent { return (
- +
{children}
diff --git a/packages/core/src/components/button/_button.scss b/packages/core/src/components/button/_button.scss index b3bfead49b..41c185afed 100644 --- a/packages/core/src/components/button/_button.scss +++ b/packages/core/src/components/button/_button.scss @@ -87,6 +87,14 @@ Styleguide pt-button color: $pt-icon-color; } + // icon-only button + .pt-icon:first-child:last-child { + // center icon horizontally. this works for large buttons too. + $icon-margin-offset: -($pt-button-height - $pt-icon-size-standard) / 2; + margin-right: $icon-margin-offset; + margin-left: $icon-margin-offset; + } + /* Advanced icon usage diff --git a/packages/core/src/components/button/abstractButton.tsx b/packages/core/src/components/button/abstractButton.tsx index f0262c38b1..2fcc71ce38 100644 --- a/packages/core/src/components/button/abstractButton.tsx +++ b/packages/core/src/components/button/abstractButton.tsx @@ -74,7 +74,6 @@ export abstract class AbstractButton extends React.Component extends React.Component { if (child === "") { @@ -127,12 +126,15 @@ export abstract class AbstractButton extends React.Component : undefined, - text != null ? {text} : undefined, - ...children, - , - ]; + return ( + <> + + {loading && } + {text != null && {text}} + {children} + + + ); } } diff --git a/packages/core/src/components/callout/_callout.scss b/packages/core/src/components/callout/_callout.scss index 3cebb0021e..9715af8061 100644 --- a/packages/core/src/components/callout/_callout.scss +++ b/packages/core/src/components/callout/_callout.scss @@ -59,6 +59,7 @@ Styleguide pt-callout background-color: rgba($color, 0.15); &[class*="pt-icon-"]::before, + .pt-callout-icon, h5 { color: map-get($pt-intent-text-colors, $intent); } @@ -67,6 +68,7 @@ Styleguide pt-callout background-color: rgba($color, 0.25); &[class*="pt-icon-"]::before, + .pt-callout-icon, h5 { color: map-get($pt-dark-intent-text-colors, $intent); } @@ -78,3 +80,12 @@ Styleguide pt-callout margin: ($pt-grid-size * 2) 0; } } + +// callout icon fills vertical space at left side, pushing title and content over. +// note that CSS API `.pt-callout.pt-icon-[name]` is still supported. +.pt-callout-icon { + float: left; + margin-right: $pt-grid-size; + height: 100%; + color: $pt-icon-color; +} diff --git a/packages/core/src/components/callout/callout.md b/packages/core/src/components/callout/callout.md index bcdca177ce..b052cef5b6 100644 --- a/packages/core/src/components/callout/callout.md +++ b/packages/core/src/components/callout/callout.md @@ -2,6 +2,8 @@ Callouts visually highlight important content for the user. +@reactExample CalloutExample + @## CSS API Callouts use the same visual intent modifier classes as buttons. If you need a @@ -18,7 +20,13 @@ heading, use the `
` element. The `Callout` component is available in the __@blueprintjs/core__ package. Make sure to review the [general usage docs for JS components](#blueprint.usage). -The component is a simple wrapper around the CSS API that provides props for modifiers and optional title element. -Any additional HTML props will be spread to the rendered `
` element. +The component is a simple wrapper around the CSS API that provides props for modifiers and the optional title +element. Any additional HTML props will be spread to the rendered `
` element. It provides two additional +useful features: + +1. Providing an `intent` will set use a default icon per intent, which can be overridden by supplying +your own `iconName`. +1. The React component renders an SVG `Icon` element for the `iconName` prop, instead of the `.pt-icon-*` +CSS class. @interface ICalloutProps diff --git a/packages/core/src/components/callout/callout.tsx b/packages/core/src/components/callout/callout.tsx index 3861f389ae..1421954ced 100644 --- a/packages/core/src/components/callout/callout.tsx +++ b/packages/core/src/components/callout/callout.tsx @@ -7,13 +7,19 @@ import * as classNames from "classnames"; import * as React from "react"; -import { Classes, IIntentProps, IProps } from "../../common"; +import { Classes, IIntentProps, Intent, IProps } from "../../common"; +import { Icon } from "../../index"; import { IconName } from "../icon/icon"; /** This component also supports the full range of HTML `
` props. */ export interface ICalloutProps extends IIntentProps, IProps { - /** Name of icon to render on left-hand side. */ - iconName?: IconName; + /** + * Name of icon to render on left-hand side. + * + * 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`). + */ + iconName?: IconName | null; /** * String content of optional title element. @@ -26,18 +32,44 @@ export interface ICalloutProps extends IIntentProps, IProps { export class Callout extends React.PureComponent, {}> { public render() { - const { className, children, iconName, intent, title, ...htmlProps } = this.props; - const classes = classNames( - Classes.CALLOUT, - Classes.intentClass(intent), - Classes.iconClass(iconName), - className, - ); + const { className, children, iconName: _nospread, intent, title, ...htmlProps } = this.props; + const iconName = this.getIconName(); + const classes = classNames(Classes.CALLOUT, Classes.intentClass(intent), className); return (
+ {iconName && ( + + + + )} {title &&
{title}
} {children}
); } + + private getIconName(): IconName | undefined { + const { iconName, intent } = this.props; + // 1. no icon + if (iconName === null) { + return undefined; + } + // 2. defined iconName prop + if (iconName !== undefined) { + return iconName; + } + // 3. default intent icon + switch (intent) { + case Intent.DANGER: + return "error"; + case Intent.PRIMARY: + return "info-sign"; + case Intent.WARNING: + return "warning-sign"; + case Intent.SUCCESS: + return "tick"; + default: + return undefined; + } + } } diff --git a/packages/core/src/components/dialog/_dialog.scss b/packages/core/src/components/dialog/_dialog.scss index b04f8766d4..fe1a0940a0 100644 --- a/packages/core/src/components/dialog/_dialog.scss +++ b/packages/core/src/components/dialog/_dialog.scss @@ -100,6 +100,7 @@ $dialog-padding: $pt-grid-size * 2 !default; min-height: $pt-icon-size-large + $dialog-padding; padding-left: $dialog-padding; + > .pt-icon, .pt-icon-large { flex: 0 0 auto; margin-right: $dialog-padding / 2; @@ -143,6 +144,7 @@ $dialog-padding: $pt-grid-size * 2 !default; cursor: pointer; padding: $pt-grid-size; + padding-right: $dialog-padding; } .pt-dialog-body { diff --git a/packages/core/src/components/dialog/dialog.tsx b/packages/core/src/components/dialog/dialog.tsx index eb10a41598..a9aab78a0a 100644 --- a/packages/core/src/components/dialog/dialog.tsx +++ b/packages/core/src/components/dialog/dialog.tsx @@ -95,11 +95,14 @@ export class Dialog extends AbstractPureComponent { } private maybeRenderCloseButton() { - // for now, show close button if prop is undefined or null + // 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) { - const classes = classNames(Classes.DIALOG_CLOSE_BUTTON, Classes.iconClass("small-cross")); - return + ); } else { return undefined; } @@ -112,7 +115,7 @@ export class Dialog extends AbstractPureComponent { } return (
- +
{title}
{this.maybeRenderCloseButton()}
diff --git a/packages/core/src/components/forms/_input-group.scss b/packages/core/src/components/forms/_input-group.scss index 2f39875358..bfc7808459 100644 --- a/packages/core/src/components/forms/_input-group.scss +++ b/packages/core/src/components/forms/_input-group.scss @@ -76,6 +76,12 @@ $input-button-height-large: $pt-button-height !default; margin: ($pt-input-height - $input-button-height) / 2; padding-top: 0; padding-bottom: 0; + + .pt-icon { + $small-icon-margin: ($input-button-height - $pt-icon-size-standard) / 2; + margin-top: $small-icon-margin; + margin-bottom: $small-icon-margin; + } } .pt-icon { @@ -83,8 +89,7 @@ $input-button-height-large: $pt-button-height !default; // bump icon up so it sits above input z-index: 1; - margin: 0 ($pt-input-height - $pt-icon-size-standard) / 2; - line-height: $pt-input-height; + margin: ($pt-input-height - $pt-icon-size-standard) / 2; color: $pt-icon-color; } @@ -145,8 +150,7 @@ $input-button-height-large: $pt-button-height !default; } .pt-icon { - margin: 0 ($pt-input-height-large - $pt-icon-size-standard) / 2; - line-height: $pt-input-height-large; + margin: ($pt-input-height-large - $pt-icon-size-standard) / 2; } .pt-input { diff --git a/packages/core/src/components/forms/_label.scss b/packages/core/src/components/forms/_label.scss index 54cdd1b0bb..6296569743 100644 --- a/packages/core/src/components/forms/_label.scss +++ b/packages/core/src/components/forms/_label.scss @@ -32,7 +32,8 @@ label.pt-label { margin: 0 0 ($pt-grid-size * 1.5); .pt-input, - .pt-select { + .pt-select, + .pt-popover-wrapper { display: block; margin-top: $pt-grid-size / 2; text-transform: none; @@ -49,7 +50,8 @@ label.pt-label { .pt-input, .pt-input-group, - .pt-select { + .pt-select, + .pt-popover-wrapper { display: inline-block; margin: 0 0 0 ($pt-grid-size / 2); vertical-align: top; @@ -64,6 +66,10 @@ label.pt-label { } } + &:not(.pt-inline) .pt-popover-target { + display: block; + } + /* Disabled labels diff --git a/packages/core/src/components/forms/inputGroup.tsx b/packages/core/src/components/forms/inputGroup.tsx index b694b91ce1..f6e2e9af0d 100644 --- a/packages/core/src/components/forms/inputGroup.tsx +++ b/packages/core/src/components/forms/inputGroup.tsx @@ -71,7 +71,7 @@ export class InputGroup extends React.PureComponent - + +
+
SVG icons in 2.0
+ Blueprint 2.0 introduced SVG icon support and moved icon resources to a separate __@blueprintjs/icons__ package. + The `Icon` component now renders SVG paths and the icon classes are no longer used by any Blueprint React component. + Icon font support has been preserved but should be considered a legacy feature that will be removed in a + future major version. +
+ This section describes two ways of using the UI icon font: via CSS or via React component. Many Blueprint components provide an `iconName` prop, which supports both the full name `pt-icon-projects` and the short name `projects`. +@reactExample IconExample + +@## JavaScript API + +The `Icon` component is available in the __@blueprintjs/core__ package. +Make sure to review the [general usage docs for JS components](#blueprint.usage). + +Use the `` component to easily render __SVG icons__ in React. The `iconName` prop is typed +such that editors can offer autocomplete for known icon names. The optional `iconSize` prop ensures +you'll never forget a sizing class and clarifies the expected width and height of the icon element. +The component also accepts all valid HTML props for an `` element. + +Data files in the __@blueprintjs/icons__ package provide SVG path information for Blueprint's 300+ icons +for 16px and 20px grids. The `iconName` prop dictates which SVG is rendered and `iconSize` determines +which pixel grid is used: `iconSize >= 20` will use the 20px grid and smaller icons will use the 16px grid. + +```tsx +// string literals are supported through IconName union type + + + + +// can also use IconClasses string enum and Icon.SIZE_* constants +import { IconClasses } from "@blueprintjs/core"; + + +// can pass all valid HTML props + +``` + +@interface IIconProps + @## CSS API +
+
Icon fonts are legacy in 2.0
+ Blueprint's icon fonts should be considered a legacy feature and will be removed in a future major version. + The SVGs rendered by the React component do not suffer from the blurriness of icon fonts, and browser + support is equivalent. +
+ +The CSS-only icons API uses the __icon fonts__ from the __@blueprintjs/icons__ package. + To use Blueprint UI icons via CSS, you must apply two classes to a `` element: - a __sizing class__, either `pt-icon-standard` (16px) or `pt-icon-large` (20px) - an __icon name class__, such as `pt-icon-projects` @@ -30,26 +79,3 @@ Icon classes also support the four `.pt-intent-*` modifiers to color the image. necessary, set a `font-size` that is whole multiple of 16 or 20 with the relevant size class. You can instead use the class `pt-icon` to make the icon inherit its size from surrounding text.
- -@## JavaScript API - -Use the `` component to easily render icons in React. The required `iconName` prop is typed -such that editors can offer autocomplete for known icon names. The optional `iconSize` prop ensures -you'll never forget a sizing class and clarifies the expected width and height of the icon element. -The component also accepts all valid HTML props for a `` element. - -```tsx -// string literals are supported through IconName union type - - - - -// can also use IconClasses string enum and Icon.SIZE_* constants -import { IconClasses } from "@blueprintjs/core"; - - -// can pass all valid HTML props - -``` - -@interface IIconProps diff --git a/packages/core/src/components/icon/icon.tsx b/packages/core/src/components/icon/icon.tsx index f1096eefe0..b1621c6e44 100644 --- a/packages/core/src/components/icon/icon.tsx +++ b/packages/core/src/components/icon/icon.tsx @@ -7,60 +7,73 @@ import * as classNames from "classnames"; import * as React from "react"; -import { IconName } from "@blueprintjs/icons"; +import { IconName, IconSvgPaths16, IconSvgPaths20, LegacyIconName } from "@blueprintjs/icons"; import { Classes, IIntentProps, IProps } from "../../common"; export { IconName }; export interface IIconProps extends IIntentProps, IProps { + /** + * Color of icon. Equivalent to setting CSS `fill` property. + */ + color?: string; + /** * Name of the icon (with or without `"pt-icon-"` prefix). - * If `undefined`, this component will render nothing. + * If omitted or `undefined`, this component will render nothing. */ - iconName: IconName | undefined; + iconName?: LegacyIconName; /** - * Size of the icon. - * Blueprint provides each icon in two sizes: 16px and 20px. The keyword `"inherit"` will - * render a 20px icon but inherit `font-size` from its parent. - * Constants are exposed for each of these values on the component itself: - * `Icon.SIZE_(STANDARD|LARGE|INHERIT)`, - * @default 16 + * Size of the icon, in pixels. + * Blueprint contains 16px and 20px SVG icon images, + * and chooses the appropriate resolution based on this prop. + * @default Icon.SIZE_STANDARD = 16 */ - iconSize?: 16 | 20 | "inherit"; + iconSize?: number; + + /** CSS style properties. */ + style?: React.CSSProperties; } -export class Icon extends React.PureComponent, never> { +export class Icon extends React.PureComponent> { public static displayName = "Blueprint2.Icon"; - public static readonly SIZE_STANDARD = 16 as 16; - public static readonly SIZE_LARGE = 20 as 20; - public static readonly SIZE_INHERIT = "inherit" as "inherit"; + public static readonly SIZE_STANDARD = 16; + public static readonly SIZE_LARGE = 20; public render() { if (this.props.iconName == null) { return null; } - const { className, iconName, intent, iconSize = Icon.SIZE_STANDARD, ...restProps } = this.props; + const { className, iconName, iconSize = Icon.SIZE_STANDARD, intent, ...svgProps } = this.props; + const normalizedIconName = iconName.replace("pt-icon-", "") as IconName; - const classes = classNames( - getSizeClass(iconSize), - Classes.iconClass(iconName), - Classes.intentClass(intent), - className, + // choose which pixel grid is most appropriate for given icon size + const pixelGridSize = iconSize >= Icon.SIZE_LARGE ? Icon.SIZE_LARGE : Icon.SIZE_STANDARD; + const classes = classNames(Classes.ICON, Classes.intentClass(intent), className); + const viewBox = `0 0 ${pixelGridSize} ${pixelGridSize}`; + return ( + + {normalizedIconName} + {this.renderSvgPaths(pixelGridSize, normalizedIconName)} + ); - return ; } -} -// NOTE: not using a type alias here so the full union will appear in the interface docs -function getSizeClass(size: 16 | 20 | "inherit") { - switch (size) { - case Icon.SIZE_STANDARD: - return Classes.ICON_STANDARD; - case Icon.SIZE_LARGE: - return Classes.ICON_LARGE; - default: - return Classes.ICON; + private renderSvgPaths(pathsSize: number, iconName: IconName) { + const svgPathsRecord = pathsSize === Icon.SIZE_STANDARD ? IconSvgPaths16 : IconSvgPaths20; + const pathStrings = svgPathsRecord[iconName]; + if (pathStrings == null) { + return null; + } + return pathStrings.map((d, i) => ); } } diff --git a/packages/core/src/components/menu/_common.scss b/packages/core/src/components/menu/_common.scss index 4ca1058cd5..b6df6eb726 100644 --- a/packages/core/src/components/menu/_common.scss +++ b/packages/core/src/components/menu/_common.scss @@ -63,6 +63,10 @@ $dark-menu-item-color-active: $dark-minimal-button-background-color-active !defa &.pt-intent-#{$intent} { color: $color; + .pt-icon { + color: inherit; + } + &::before, &::after, .pt-menu-item-label { diff --git a/packages/core/src/components/menu/_menu.scss b/packages/core/src/components/menu/_menu.scss index 63b056b7f6..e2a898ef62 100644 --- a/packages/core/src/components/menu/_menu.scss +++ b/packages/core/src/components/menu/_menu.scss @@ -64,6 +64,14 @@ Styleguide pt-menu color: $pt-icon-color; } + .pt-icon { + color: $pt-icon-color; + + &:first-child { + margin-right: $menu-item-padding; + } + } + .pt-menu-item-label { color: $pt-text-color-muted; } @@ -88,6 +96,7 @@ Styleguide pt-menu &::before, &::after, + .pt-icon, .pt-menu-item-label { color: $pt-icon-color-disabled !important; } @@ -161,7 +170,8 @@ Styleguide pt-menu.pt-menu-header @include menu-item-intent($pt-dark-intent-text-colors); &::before, - &::after { + &::after, + .pt-icon { color: $pt-dark-icon-color; } @@ -188,6 +198,7 @@ Styleguide pt-menu.pt-menu-header &::before, &::after, + .pt-icon, .pt-menu-item-label { color: $pt-dark-icon-color-disabled !important; } diff --git a/packages/core/src/components/menu/_submenu.scss b/packages/core/src/components/menu/_submenu.scss index 97682c1a50..c2b29b5bcd 100644 --- a/packages/core/src/components/menu/_submenu.scss +++ b/packages/core/src/components/menu/_submenu.scss @@ -55,5 +55,13 @@ > .pt-popover-content { box-shadow: $pt-popover-box-shadow; } + + .pt-dark & { + box-shadow: none; + + > .pt-popover-content { + box-shadow: $pt-dark-popover-box-shadow; + } + } } } diff --git a/packages/core/src/components/menu/menuItem.tsx b/packages/core/src/components/menu/menuItem.tsx index 375af53842..4262c55187 100644 --- a/packages/core/src/components/menu/menuItem.tsx +++ b/packages/core/src/components/menu/menuItem.tsx @@ -12,6 +12,7 @@ import * as Classes from "../../common/classes"; import * as Errors from "../../common/errors"; import { Position } from "../../common/position"; import { IActionProps, ILinkProps } from "../../common/props"; +import { Icon } from "../icon/icon"; import { IPopoverProps, Popover, PopoverInteractionKind } from "../popover/popover"; import { Menu } from "./menu"; @@ -55,7 +56,6 @@ export class MenuItem extends AbstractPureComponent { const submenuChildren = this.renderSubmenuChildren(); const hasSubmenu = submenuChildren != null; - const liClasses = classNames({ [Classes.MENU_SUBMENU]: hasSubmenu }); const anchorClasses = classNames( Classes.MENU_ITEM, Classes.intentClass(this.props.intent), @@ -64,7 +64,6 @@ export class MenuItem extends AbstractPureComponent { // prevent popover from closing when clicking on submenu trigger or disabled item [Classes.POPOVER_DISMISS]: this.props.shouldDismissPopover && !disabled && !hasSubmenu, }, - Classes.iconClass(this.props.iconName), this.props.className, ); @@ -76,11 +75,13 @@ export class MenuItem extends AbstractPureComponent { tabIndex={disabled ? undefined : 0} target={this.props.target} > + {label && {label}} {this.props.text} ); + const liClasses = classNames({ [Classes.MENU_SUBMENU]: hasSubmenu }); return
  • {this.maybeRenderPopover(target, submenuChildren)}
  • ; } diff --git a/packages/core/src/components/non-ideal-state/nonIdealState.tsx b/packages/core/src/components/non-ideal-state/nonIdealState.tsx index 3e48534ce2..eac21a3d81 100644 --- a/packages/core/src/components/non-ideal-state/nonIdealState.tsx +++ b/packages/core/src/components/non-ideal-state/nonIdealState.tsx @@ -76,7 +76,7 @@ export class NonIdealState extends React.PureComponent } else if (typeof visual === "string") { return (
    - +
    ); } else { diff --git a/packages/core/src/components/tag-input/tagInput.tsx b/packages/core/src/components/tag-input/tagInput.tsx index 6d209b6804..a500830a7b 100644 --- a/packages/core/src/components/tag-input/tagInput.tsx +++ b/packages/core/src/components/tag-input/tagInput.tsx @@ -201,7 +201,11 @@ export class TagInput extends AbstractPureComponent - + {values.map(this.maybeRenderTag)} { buttonTestSuite(Button, "button"); @@ -26,9 +26,10 @@ function buttonTestSuite(component: React.ComponentClass, tagName: string) assert.isTrue(wrapper.hasClass("foo")); }); - it('iconName="style" gets icon class', () => { + it('iconName="style" renders Icon as first child', () => { const wrapper = button({ iconName: "style" }); - assert.isTrue(wrapper.hasClass(Classes.iconClass("style"))); + const firstChild = wrapper.children().childAt(0); + assert.isTrue(firstChild.is(Icon)); }); it("renders the button text prop", () => { diff --git a/packages/core/test/callout/calloutTests.tsx b/packages/core/test/callout/calloutTests.tsx index ebb6cea569..7551fd6596 100644 --- a/packages/core/test/callout/calloutTests.tsx +++ b/packages/core/test/callout/calloutTests.tsx @@ -8,7 +8,7 @@ import { assert } from "chai"; import { shallow } from "enzyme"; import * as React from "react"; -import { Callout, Classes, Intent } from "../../src/index"; +import { Callout, Classes, Icon, Intent } from "../../src/index"; describe("", () => { it("supports className", () => { @@ -20,7 +20,7 @@ describe("", () => { it("supports icon", () => { const wrapper = shallow(); - assert.isTrue(wrapper.hasClass(Classes.iconClass("graph"))); + assert.isTrue(wrapper.find(Icon).exists()); }); it("supports intent", () => { @@ -28,6 +28,16 @@ describe("", () => { assert.isTrue(wrapper.hasClass(Classes.INTENT_DANGER)); }); + it("intent renders default icon", () => { + const wrapper = shallow(); + assert.isTrue(wrapper.find(Icon).exists()); + }); + + it("iconName=null removes intent icon", () => { + const wrapper = shallow(); + assert.isFalse(wrapper.find(Icon).exists()); + }); + it("renders optional title element", () => { const title = "I am the title"; const wrapper = shallow(); diff --git a/packages/core/test/controls/numericInputTests.tsx b/packages/core/test/controls/numericInputTests.tsx index 682dd33616..fbc250beb5 100644 --- a/packages/core/test/controls/numericInputTests.tsx +++ b/packages/core/test/controls/numericInputTests.tsx @@ -15,6 +15,7 @@ import { Button, Classes, HTMLInputProps, + Icon, InputGroup, INumericInputProps, Keys, @@ -860,14 +861,10 @@ describe("", () => { }); it("shows a left icon if provided", () => { - const component = mount(); - - const icon = component - .find(InputGroup) - .children() - .childAt(0) // Icon - .childAt(0); // span - expect(icon.hasClass("pt-icon-variable")).to.be.true; + const leftIcon = mount() + .find(Icon) + .first(); + expect(leftIcon.text()).to.equal("variable"); }); it("shows placeholder text if provided", () => { diff --git a/packages/core/test/dialog/dialogTests.tsx b/packages/core/test/dialog/dialogTests.tsx index dc1b5ba1a4..c1907216d1 100644 --- a/packages/core/test/dialog/dialogTests.tsx +++ b/packages/core/test/dialog/dialogTests.tsx @@ -82,7 +82,7 @@ describe("", () => { dialog body , ); - assert.strictEqual(dialog.find(`.${Classes.DIALOG_HEADER}`).text(), "Hello!"); + assert.match(dialog.find(`.${Classes.DIALOG_HEADER}`).text(), /^Hello!/); }); it(`renders close button if isCloseButtonShown={true}`, () => { diff --git a/packages/core/test/icon/iconTests.tsx b/packages/core/test/icon/iconTests.tsx index 24167cfa17..514196a6d5 100644 --- a/packages/core/test/icon/iconTests.tsx +++ b/packages/core/test/icon/iconTests.tsx @@ -8,31 +8,23 @@ import { assert } from "chai"; import { shallow } from "enzyme"; import * as React from "react"; -import { IconClasses } from "@blueprintjs/icons"; +import { IconClasses, IconName } from "@blueprintjs/icons"; import { Classes, Icon, IIconProps, Intent } from "../../src/index"; describe("", () => { it("iconSize=16 renders standard size", () => - assertIconClass( - , - Classes.ICON_STANDARD, - )); + assertIconSize(, Icon.SIZE_STANDARD)); it("iconSize=20 renders large size", () => - assertIconClass(, Classes.ICON_LARGE)); - - it("iconSize=inherit renders auto-size", () => - assertIconClass(, Classes.ICON)); + assertIconSize(, Icon.SIZE_LARGE)); it("renders intent class", () => - assertIconClass(, Classes.INTENT_DANGER)); + assert.isTrue(shallow().hasClass(Classes.INTENT_DANGER))); - it("renders iconName class", () => - assertIconClass(, IconClasses.VERTICAL_DISTRIBUTION)); + it("renders iconName class", () => assertIcon(, "calendar")); - it("supports prefixed iconName", () => - assertIconClass(, IconClasses.AIRPLANE)); + it("supports prefixed iconName", () => assertIcon(, "airplane")); it("iconName=undefined renders nothing", () => { const icon = shallow(); @@ -40,9 +32,14 @@ describe("", () => { }); /** Asserts that rendered icon has given className. */ - function assertIconClass(icon: React.ReactElement, className: string) { + function assertIcon(icon: React.ReactElement, iconName: IconName) { + assert.strictEqual(shallow(icon).text(), iconName); + } + + /** Asserts that rendered icon has width/height equal to size. */ + function assertIconSize(icon: React.ReactElement, size: number) { const wrapper = shallow(icon); - assert.isTrue(wrapper.hasClass(className)); - return wrapper; + assert.strictEqual(wrapper.prop("width"), size); + assert.strictEqual(wrapper.prop("height"), size); } }); diff --git a/packages/core/test/menu/menuTests.tsx b/packages/core/test/menu/menuTests.tsx index d3cf6a2b5e..74728255dd 100644 --- a/packages/core/test/menu/menuTests.tsx +++ b/packages/core/test/menu/menuTests.tsx @@ -12,6 +12,7 @@ import { spy, stub } from "sinon"; import { MENU_WARN_CHILDREN_SUBMENU_MUTEX } from "../../src/common/errors"; import { Classes, + Icon, IMenuItemProps, IMenuProps, IPopoverProps, @@ -25,8 +26,8 @@ import { describe("MenuItem", () => { it("React renders MenuItem", () => { const wrapper = shallow(); - assert.lengthOf(wrapper.find(".pt-icon-graph"), 1); - assert.strictEqual(wrapper.text(), "Graph"); + assert.lengthOf(wrapper.find(Icon), 1); + assert.match(wrapper.text(), /Graph$/); }); it("children appear in submenu", () => { diff --git a/packages/core/test/tag-input/tagInputTests.tsx b/packages/core/test/tag-input/tagInputTests.tsx index f28a86d75f..c5161ea560 100644 --- a/packages/core/test/tag-input/tagInputTests.tsx +++ b/packages/core/test/tag-input/tagInputTests.tsx @@ -53,9 +53,7 @@ describe("", () => { assert.isTrue(hasClass, errorMessage); }; - assertLeftIconHasClass(Classes.ICON_STANDARD, "standard icon"); - wrapper.setProps({ className: Classes.LARGE }); - assertLeftIconHasClass(Classes.ICON_LARGE, "large icon"); + assertLeftIconHasClass(Classes.ICON, "icon"); }); it("rightElement appears as last child", () => { diff --git a/packages/core/test/tree/treeTests.tsx b/packages/core/test/tree/treeTests.tsx index d4ba906b43..018610aa0e 100644 --- a/packages/core/test/tree/treeTests.tsx +++ b/packages/core/test/tree/treeTests.tsx @@ -135,13 +135,13 @@ describe("", () => { it("icons are rendered correctly if present", () => { const contents = createDefaultContents(); contents[1].iconName = "document"; - contents[2].iconName = "pt-icon-document"; + contents[2].iconName = "document"; const tree = renderTree({ contents }); - const iconSelector = `span.${Classes.TREE_NODE_ICON}.pt-icon-document`; - assert.lengthOf(tree.find(`.c0 > .${Classes.TREE_NODE_CONTENT} span.${Classes.TREE_NODE_ICON}`), 0, "c0"); - assert.lengthOf(tree.find(`.c1 > .${Classes.TREE_NODE_CONTENT} ${iconSelector}`), 1, "c1"); - assert.lengthOf(tree.find(`.c2 > .${Classes.TREE_NODE_CONTENT} ${iconSelector}`), 1, "c2"); + const iconSelector = `.${Classes.TREE_NODE_CONTENT} .${Classes.TREE_NODE_ICON}`; + assert.lengthOf(tree.find(`.c0 > ${iconSelector}`).hostNodes(), 0, "c0"); + assert.lengthOf(tree.find(`.c1 > ${iconSelector}`).hostNodes(), 1, "c1"); + assert.lengthOf(tree.find(`.c2 > ${iconSelector}`).hostNodes(), 1, "c2"); }); it("isExpanded controls node expansion", () => { diff --git a/packages/docs-app/src/examples/core-examples/alertExample.tsx b/packages/docs-app/src/examples/core-examples/alertExample.tsx index 3e17c2fa43..f07dbb4298 100644 --- a/packages/docs-app/src/examples/core-examples/alertExample.tsx +++ b/packages/docs-app/src/examples/core-examples/alertExample.tsx @@ -49,6 +49,7 @@ export class AlertExample extends BaseExample<{}> {