From 414032ec1555b6e8c212e8c2bc3fba1928f7c911 Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Tue, 23 Jan 2018 13:54:18 -0800 Subject: [PATCH 01/45] add svgo to node-build-scripts --- packages/node-build-scripts/package.json | 3 ++- yarn.lock | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/node-build-scripts/package.json b/packages/node-build-scripts/package.json index c5fdfef938..fed150829d 100644 --- a/packages/node-build-scripts/package.json +++ b/packages/node-build-scripts/package.json @@ -15,7 +15,8 @@ "node-sass": "^4.7.2", "node-sass-chokidar": "^0.0.3", "node-sass-package-importer": "^3.0.4", - "strip-css-comments": "^3.0.0" + "strip-css-comments": "^3.0.0", + "svgo": "^0.7" }, "repository": { "type": "git", diff --git a/yarn.lock b/yarn.lock index 664a3fd6f5..3867d4a64b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8022,7 +8022,7 @@ svg-tags@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" -svgo@^0.7.0, svgo@^0.7.2: +svgo@^0.7, svgo@^0.7.0, svgo@^0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" dependencies: From dd72980d05bb1f74fe1ad584f5598792f191642b Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Tue, 23 Jan 2018 14:04:39 -0800 Subject: [PATCH 02/45] add type information and @ts-check to generate-icons-source --- .../node-build-scripts/generate-icons-source | 62 +++++++++++++++---- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/packages/node-build-scripts/generate-icons-source b/packages/node-build-scripts/generate-icons-source index 21c121541c..46cf4d5583 100755 --- a/packages/node-build-scripts/generate-icons-source +++ b/packages/node-build-scripts/generate-icons-source @@ -4,10 +4,23 @@ * @fileoverview Generates icons Sass and TypeScript source code from JSON metadata about icons. */ +// @ts-check const fs = require("fs"); const path = require("path"); +const SVGO = require("svgo"); +const svgo = new SVGO(); const { COPYRIGHT_HEADER } = require("./constants"); +/** + * @typedef {Object} IconMetadata + * @property {string} name - icon name for display + * @property {string} className - CSS class name + * @property {string} tags - comma separated list of tags describing this icon + * @property {string} group - group to which this icon belongs + * @property {string} content - unicode character for icon glyph in font + */ + +/** @type {IconMetadata[]} */ const ICONS_METADATA = require(path.resolve(process.cwd(), "./resources/icons/icons.json")); const GENERATED_SRC_DIR = path.resolve(process.cwd(), "./src/generated"); @@ -16,31 +29,45 @@ if (!fs.existsSync(GENERATED_SRC_DIR)) { } // great big map for iteration -writeLinesToFile("_icon-map.scss", [ +writeLinesToFile( + "_icon-map.scss", '@import "icon-variables";', "$icons: (", ...ICONS_METADATA.map(i => ` "${i.className.replace("pt-icon-", "")}": $${i.className},`), ");", -]); +); // simple variable definitions -writeLinesToFile("_icon-variables.scss", ICONS_METADATA.map(icon => `$${icon.className}: "${icon.content}";`)); +writeLinesToFile("_icon-variables.scss", ...ICONS_METADATA.map(icon => `$${icon.className}: "${icon.content}";`)); // map ENUM_NAME to className (cast as string constant so it can be used as IconName) -writeLinesToFile("iconClasses.ts", buildTSObject("IconClasses", icon => `${icon.className}" as "${icon.className}`)); +writeLinesToFile( + "iconClasses.ts", + ...buildTSObject("IconClasses", icon => `"${icon.className}" as "${icon.className}"`), +); // union type of all valid string names -writeLinesToFile("iconName.ts", buildUnionType()); +writeLinesToFile("iconName.ts", ...buildUnionType()); // map ENUM_NAME to unicode character -writeLinesToFile("iconStrings.ts", buildTSObject("IconContents", icon => icon.content.replace("\\", "\\u"))); +writeLinesToFile("iconStrings.ts", ...buildTSObject("IconContents", icon => `"${icon.content.replace("\\", "\\u")}"`)); -function writeLinesToFile(filename, lines) { +/** + * Writes lines to given filename in GENERATED_SRC_DIR. + * @param {string} filename + * @param {string[]} lines + */ +function writeLinesToFile(filename, ...lines) { const outputPath = path.join(GENERATED_SRC_DIR, filename); const contents = [COPYRIGHT_HEADER, ...lines, ""].join("\n"); fs.writeFileSync(outputPath, contents); } +/** + * Converts icon className to uppercase constant name. + * Example: `"pt-icon-time"` ⇒ `"TIME"` + * @param {IconMetadata} icon + */ function toEnumName(icon) { return icon.className .replace("pt-icon-", "") @@ -48,20 +75,29 @@ function toEnumName(icon) { .toUpperCase(); } +/** + * Builds `const ${objectName}`, keyed by icon enum name. Value is result of `valueGetter` function. + * @param {string} objectName + * @param {(icon: IconMetadata) => string} valueGetter + */ function buildTSObject(objectName, valueGetter) { return [ "// tslint:disable:object-literal-sort-keys", `export const ${objectName} = {`, - ...ICONS_METADATA.map(prop => ` ${toEnumName(prop)}: "${valueGetter(prop)}",`), + ...ICONS_METADATA.map(icon => ` ${toEnumName(icon)}: ${valueGetter(icon)},`), "};", ]; } +/** + * Returns union type of all icon names, including both short (`"time"`) and long (`"pt-icon-time"`) formats. + */ function buildUnionType() { - const iconNames = ICONS_METADATA.reduce((arr, { className }) => { - // support un/prefixed variants - arr.push(`"${className}"`, `"${className.replace("pt-icon-", "")}"`); - return arr; - }, []).sort(); + const iconNames = [ + // short names + ...ICONS_METADATA.map(({ className }) => `"${className.replace("pt-icon-", "")}"`), + // long names + ...ICONS_METADATA.map(({ className }) => `"${className}"`), + ]; return [`export type IconName =\n | ${iconNames.join("\n | ")};`]; } From a26afdbd3e758c1debe3f5ef916d022e19a2595b Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Tue, 23 Jan 2018 14:46:11 -0800 Subject: [PATCH 03/45] iconContents.tsx contains `export const ICON_NAME = ...` for each icon! --- .../node-build-scripts/generate-icons-source | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/node-build-scripts/generate-icons-source b/packages/node-build-scripts/generate-icons-source index 46cf4d5583..841e9c53c9 100755 --- a/packages/node-build-scripts/generate-icons-source +++ b/packages/node-build-scripts/generate-icons-source @@ -21,7 +21,9 @@ const { COPYRIGHT_HEADER } = require("./constants"); */ /** @type {IconMetadata[]} */ -const ICONS_METADATA = require(path.resolve(process.cwd(), "./resources/icons/icons.json")); +const ICONS_METADATA = require(path.resolve(process.cwd(), "./resources/icons/icons.json")).sort((a, b) => + a.className.localeCompare(b.className), +); const GENERATED_SRC_DIR = path.resolve(process.cwd(), "./src/generated"); if (!fs.existsSync(GENERATED_SRC_DIR)) { @@ -52,12 +54,22 @@ writeLinesToFile("iconName.ts", ...buildUnionType()); // map ENUM_NAME to unicode character writeLinesToFile("iconStrings.ts", ...buildTSObject("IconContents", icon => `"${icon.content.replace("\\", "\\u")}"`)); +// JSX SVG elements. IIFE to unwrap async. +(async () => { + writeLinesToFile( + "iconContents.tsx", + "// tslint:disable:prettier", + 'import * as React from "react";', + ...(await buildJSXContents()), + ); +})(); + /** * Writes lines to given filename in GENERATED_SRC_DIR. * @param {string} filename - * @param {string[]} lines + * @param {Array} lines */ -function writeLinesToFile(filename, ...lines) { +async function writeLinesToFile(filename, ...lines) { const outputPath = path.join(GENERATED_SRC_DIR, filename); const contents = [COPYRIGHT_HEADER, ...lines, ""].join("\n"); fs.writeFileSync(outputPath, contents); @@ -84,7 +96,7 @@ function buildTSObject(objectName, valueGetter) { return [ "// tslint:disable:object-literal-sort-keys", `export const ${objectName} = {`, - ...ICONS_METADATA.map(icon => ` ${toEnumName(icon)}: ${valueGetter(icon)},`), + ...ICONS_METADATA.map(icon => ` ${toEnumName(icon)}: "${valueGetter(icon)}",`), "};", ]; } @@ -101,3 +113,27 @@ function buildUnionType() { ]; return [`export type IconName =\n | ${iconNames.join("\n | ")};`]; } + +/** + * Builds array of `export const ${ICON_NAME} = ...` for each icon. + */ +async function buildJSXContents() { + return Promise.all( + ICONS_METADATA.map(icon => { + const svg = fs.readFileSync( + path.resolve(__dirname, `../../resources/icons/16px/${icon.className}.svg`), + "utf-8", + ); + /** @type {Promise} */ + const promise = new Promise(resolve => + svgo.optimize(svg, ({ data }) => { + const cleaned = data + .replace("class=", "className=") + .replace(/'); + resolve(`export const ${toEnumName(icon)} = ${cleaned};`); + }), + ); + return promise; + }), + ); +} From 85e41b394753ec92bcf832228a094d230ce968b1 Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Tue, 23 Jan 2018 15:59:10 -0800 Subject: [PATCH 04/45] generate and export IconSvgs --- packages/icons/src/index.ts | 3 +- .../node-build-scripts/generate-icons-source | 79 ++++++++++--------- 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/packages/icons/src/index.ts b/packages/icons/src/index.ts index badd56d76a..754c0e6f3d 100644 --- a/packages/icons/src/index.ts +++ b/packages/icons/src/index.ts @@ -5,5 +5,6 @@ */ export { IconClasses } from "./generated/iconClasses"; -export { IconName } from "./generated/iconName"; +export { IconName, LegacyIconName } from "./generated/iconName"; export { IconContents } from "./generated/iconStrings"; +export { IconSvgs } from "./generated/iconSvgs"; diff --git a/packages/node-build-scripts/generate-icons-source b/packages/node-build-scripts/generate-icons-source index 841e9c53c9..c0ce704697 100755 --- a/packages/node-build-scripts/generate-icons-source +++ b/packages/node-build-scripts/generate-icons-source @@ -43,24 +43,35 @@ writeLinesToFile( writeLinesToFile("_icon-variables.scss", ...ICONS_METADATA.map(icon => `$${icon.className}: "${icon.content}";`)); // map ENUM_NAME to className (cast as string constant so it can be used as IconName) -writeLinesToFile( - "iconClasses.ts", - ...buildTSObject("IconClasses", icon => `"${icon.className}" as "${icon.className}"`), -); +writeLinesToFile("iconClasses.ts", ...buildTSObject("IconClasses", icon => `${icon.className}" as "${icon.className}`)); // union type of all valid string names writeLinesToFile("iconName.ts", ...buildUnionType()); // map ENUM_NAME to unicode character -writeLinesToFile("iconStrings.ts", ...buildTSObject("IconContents", icon => `"${icon.content.replace("\\", "\\u")}"`)); +writeLinesToFile("iconStrings.ts", ...buildTSObject("IconContents", icon => icon.content.replace("\\", "\\u"))); -// JSX SVG elements. IIFE to unwrap async. (async () => { + // JSX SVG elements. IIFE to unwrap async. writeLinesToFile( - "iconContents.tsx", - "// tslint:disable:prettier", - 'import * as React from "react";', - ...(await buildJSXContents()), + "iconSvgs.tsx", + ...(await buildJSXObject("IconSvgs", icon => { + const svg = fs.readFileSync( + path.resolve(__dirname, `../../resources/icons/16px/${icon.className}.svg`), + "utf-8", + ); + /** @type {Promise} - must define variable to type it before returning */ + const promise = new Promise(resolve => + svgo.optimize(svg, (/** @type {{ data: string }} */ { data }) => { + const dataJsx = data + .replace("class=", "className=") + .replace(/'); + // TODO: tag. can we strip <svg> tag? + resolve(dataJsx); + }), + ); + return promise; + })), ); })(); @@ -105,35 +116,31 @@ function buildTSObject(objectName, valueGetter) { * Returns union type of all icon names, including both short (`"time"`) and long (`"pt-icon-time"`) formats. */ function buildUnionType() { - const iconNames = [ - // short names - ...ICONS_METADATA.map(({ className }) => `"${className.replace("pt-icon-", "")}"`), - // long names - ...ICONS_METADATA.map(({ className }) => `"${className}"`), + const shortNames = ICONS_METADATA.map(({ className }) => `"${className.replace("pt-icon-", "")}"`); + const longNames = ICONS_METADATA.map(({ className }) => `"${className}"`); + return [ + `export type IconName =\n | ${shortNames.join("\n | ")};`, + "", + `export type LegacyIconName =\n | ${longNames.join("\n | ")};`, ]; - return [`export type IconName =\n | ${iconNames.join("\n | ")};`]; } - /** - * Builds array of `export const ${ICON_NAME} = <svg>...</svg>` for each icon. + * Builds `const ${objectName}`, keyed by icon enum name. Value is result of `valueGetter` function. + * @param {string} objectName + * @param {(icon: IconMetadata) => Promise<string>} valueGetter */ -async function buildJSXContents() { - return Promise.all( - ICONS_METADATA.map(icon => { - const svg = fs.readFileSync( - path.resolve(__dirname, `../../resources/icons/16px/${icon.className}.svg`), - "utf-8", - ); - /** @type {Promise<string>} */ - const promise = new Promise(resolve => - svgo.optimize(svg, ({ data }) => { - const cleaned = data - .replace("class=", "className=") - .replace(/<style>([^<]+)<\/style>/g, '<style>{"$1"}</style>'); - resolve(`export const ${toEnumName(icon)} = ${cleaned};`); - }), - ); - return promise; - }), +async function buildJSXObject(objectName, valueGetter) { + const lines = ICONS_METADATA.map( + async icon => ` "${icon.className.replace("pt-icon-", "")}": ${await valueGetter(icon)},`, ); + return [ + 'import * as React from "react";', + 'import { IconName } from "./iconName";', + "", + "// tslint:disable:prettier", + `export const ${objectName}: Record<IconName, JSX.Element> = {`, + ...(await Promise.all(lines)), + "};", + "// tslint:enable:prettier", + ]; } From 50c460b9f5e74275082dc03b3436e5564c6788a6 Mon Sep 17 00:00:00 2001 From: Gilad Gray <ggray@palantir.com> Date: Tue, 23 Jan 2018 15:59:26 -0800 Subject: [PATCH 05/45] refactor existing Icon component to render SVGs --- packages/core/src/components/icon/_icons.scss | 5 ++ packages/core/src/components/icon/icon.tsx | 47 +++++++------------ 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/packages/core/src/components/icon/_icons.scss b/packages/core/src/components/icon/_icons.scss index 65a587a269..72fb11159c 100644 --- a/packages/core/src/components/icon/_icons.scss +++ b/packages/core/src/components/icon/_icons.scss @@ -38,3 +38,8 @@ span.pt-icon { content: $content; } } + +svg.pt-icon { + vertical-align: middle; + fill: currentColor; +} diff --git a/packages/core/src/components/icon/icon.tsx b/packages/core/src/components/icon/icon.tsx index d78d26a281..523fbb3fdb 100644 --- a/packages/core/src/components/icon/icon.tsx +++ b/packages/core/src/components/icon/icon.tsx @@ -7,7 +7,7 @@ import * as classNames from "classnames"; import * as React from "react"; -import { IconName } from "@blueprintjs/icons"; +import { IconName, IconSvgs } from "@blueprintjs/icons"; import { Classes, IIntentProps, IProps } from "../../common"; export { IconName }; @@ -20,14 +20,17 @@ export interface IIconProps extends IIntentProps, IProps { iconName: IconName | undefined; /** - * 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)`, + * Height of icon. A number value will be interpreted as pixels. Use a string value for other units. + * By default, inherits height from surrounding styles, such as `line-height`. + * @default "inherit" + */ + height?: number | string; + + /** + * Width of icon. A number value will be interpreted as pixels. Use a string value for other units. * @default 16 */ - iconSize?: 16 | 20 | "inherit"; + width?: number | string; } export class Icon extends React.PureComponent<IIconProps & React.HTMLAttributes<HTMLSpanElement>, never> { @@ -35,32 +38,16 @@ export class Icon extends React.PureComponent<IIconProps & React.HTMLAttributes< 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 render() { - if (this.props.iconName == null) { + const { className, iconName, intent, width = 16, height = "inherit" } = this.props; + if (iconName == null) { return null; } - const { className, iconName, intent, iconSize = Icon.SIZE_STANDARD, ...restProps } = this.props; - - const classes = classNames( - getSizeClass(iconSize), - Classes.iconClass(iconName), - Classes.intentClass(intent), - className, - ); - return <span className={classes} {...restProps} />; - } -} - -// 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; + return React.cloneElement(IconSvgs[iconName], { + className: classNames("pt-icon", Classes.iconClass(iconName), Classes.intentClass(intent), className), + height, + width, + }); } } From 3b2ff4b16b202eb80302b3d0b95739b0ad6c767f Mon Sep 17 00:00:00 2001 From: Gilad Gray <ggray@palantir.com> Date: Tue, 23 Jan 2018 16:19:38 -0800 Subject: [PATCH 06/45] fix usages (no more iconSize) --- packages/core/src/components/alert/alert.tsx | 2 +- packages/core/src/components/dialog/dialog.tsx | 2 +- packages/core/src/components/forms/inputGroup.tsx | 2 +- packages/core/src/components/icon/icon.tsx | 7 ++++--- .../components/non-ideal-state/nonIdealState.tsx | 2 +- .../core/src/components/tag-input/tagInput.tsx | 2 +- packages/core/test/icon/iconTests.tsx | 15 ++++++--------- packages/docs-app/src/components/docsIcon.tsx | 2 +- .../src/examples/core-examples/alertExample.tsx | 1 + .../core-examples/popoverInlineExample.tsx | 9 +-------- .../datetime-examples/dateRangePickerExample.tsx | 2 +- packages/docs-app/src/index.tsx | 2 +- 12 files changed, 20 insertions(+), 28 deletions(-) diff --git a/packages/core/src/components/alert/alert.tsx b/packages/core/src/components/alert/alert.tsx index 1b1ee1e5be..4741657de1 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<IAlertProps, {}> { return ( <Dialog className={classNames(Classes.ALERT, className)} isOpen={isOpen} style={style}> <div className={Classes.ALERT_BODY}> - <Icon iconName={iconName} iconSize="inherit" intent={Intent.DANGER} /> + <Icon iconName={iconName} width={40} intent={Intent.DANGER} /> <div className={Classes.ALERT_CONTENTS}>{children}</div> </div> <div className={Classes.ALERT_FOOTER}> diff --git a/packages/core/src/components/dialog/dialog.tsx b/packages/core/src/components/dialog/dialog.tsx index a22d8425c7..615aa92005 100644 --- a/packages/core/src/components/dialog/dialog.tsx +++ b/packages/core/src/components/dialog/dialog.tsx @@ -112,7 +112,7 @@ export class Dialog extends AbstractPureComponent<IDialogProps, {}> { } return ( <div className={Classes.DIALOG_HEADER}> - <Icon iconName={iconName} iconSize={20} /> + <Icon iconName={iconName} width={20} /> <h5>{title}</h5> {this.maybeRenderCloseButton()} </div> diff --git a/packages/core/src/components/forms/inputGroup.tsx b/packages/core/src/components/forms/inputGroup.tsx index 9cdbff29c1..0e1986a196 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<HTMLInputProps & IInputGroup return ( <div className={classes}> - <Icon iconName={leftIconName} iconSize="inherit" /> + <Icon iconName={leftIconName} height="inherit" /> <input type="text" {...removeNonHTMLProps(this.props)} diff --git a/packages/core/src/components/icon/icon.tsx b/packages/core/src/components/icon/icon.tsx index 523fbb3fdb..2f233bc5c9 100644 --- a/packages/core/src/components/icon/icon.tsx +++ b/packages/core/src/components/icon/icon.tsx @@ -7,7 +7,7 @@ import * as classNames from "classnames"; import * as React from "react"; -import { IconName, IconSvgs } from "@blueprintjs/icons"; +import { IconName, IconSvgs, LegacyIconName } from "@blueprintjs/icons"; import { Classes, IIntentProps, IProps } from "../../common"; export { IconName }; @@ -17,7 +17,7 @@ export interface IIconProps extends IIntentProps, IProps { * Name of the icon (with or without `"pt-icon-"` prefix). * If `undefined`, this component will render nothing. */ - iconName: IconName | undefined; + iconName: LegacyIconName | undefined; /** * Height of icon. A number value will be interpreted as pixels. Use a string value for other units. @@ -44,7 +44,8 @@ export class Icon extends React.PureComponent<IIconProps & React.HTMLAttributes< if (iconName == null) { return null; } - return React.cloneElement(IconSvgs[iconName], { + const shortName = iconName.replace("pt-icon-", "") as IconName; + return React.cloneElement(IconSvgs[shortName], { className: classNames("pt-icon", Classes.iconClass(iconName), Classes.intentClass(intent), className), height, width, diff --git a/packages/core/src/components/non-ideal-state/nonIdealState.tsx b/packages/core/src/components/non-ideal-state/nonIdealState.tsx index 3e48534ce2..6a83e7f3e0 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<INonIdealStateProps, {}> } else if (typeof visual === "string") { return ( <div className={classNames(Classes.NON_IDEAL_STATE_VISUAL, Classes.NON_IDEAL_STATE_ICON)}> - <Icon iconName={visual} iconSize="inherit" /> + <Icon iconName={visual} width={Icon.SIZE_LARGE * 3} /> </div> ); } else { diff --git a/packages/core/src/components/tag-input/tagInput.tsx b/packages/core/src/components/tag-input/tagInput.tsx index e3a71e040f..5dc400394b 100644 --- a/packages/core/src/components/tag-input/tagInput.tsx +++ b/packages/core/src/components/tag-input/tagInput.tsx @@ -199,7 +199,7 @@ export class TagInput extends AbstractPureComponent<ITagInputProps, ITagInputSta return ( <div className={classes} onBlur={this.handleContainerBlur} onClick={this.handleContainerClick}> - <Icon className={Classes.TAG_INPUT_ICON} iconName={leftIconName} iconSize={isLarge ? 20 : 16} /> + <Icon className={Classes.TAG_INPUT_ICON} iconName={leftIconName} width={isLarge ? 20 : 16} /> {values.map(this.maybeRenderTag)} <input value={this.state.inputValue} diff --git a/packages/core/test/icon/iconTests.tsx b/packages/core/test/icon/iconTests.tsx index 24167cfa17..8bc34f6977 100644 --- a/packages/core/test/icon/iconTests.tsx +++ b/packages/core/test/icon/iconTests.tsx @@ -13,17 +13,14 @@ import { IconClasses } from "@blueprintjs/icons"; import { Classes, Icon, IIconProps, Intent } from "../../src/index"; describe("<Icon>", () => { - it("iconSize=16 renders standard size", () => - assertIconClass( - <Icon iconName="vertical-distribution" iconSize={Icon.SIZE_STANDARD} />, - Classes.ICON_STANDARD, - )); + it("width=16 renders standard size", () => + assertIconClass(<Icon iconName="vertical-distribution" width={Icon.SIZE_STANDARD} />, Classes.ICON_STANDARD)); - it("iconSize=20 renders large size", () => - assertIconClass(<Icon iconName="vertical-distribution" iconSize={Icon.SIZE_LARGE} />, Classes.ICON_LARGE)); + it("width=20 renders large size", () => + assertIconClass(<Icon iconName="vertical-distribution" width={Icon.SIZE_LARGE} />, Classes.ICON_LARGE)); - it("iconSize=inherit renders auto-size", () => - assertIconClass(<Icon iconName="vertical-distribution" iconSize="inherit" />, Classes.ICON)); + it("width=inherit renders auto-size", () => + assertIconClass(<Icon iconName="vertical-distribution" width="inherit" />, Classes.ICON)); it("renders intent class", () => assertIconClass(<Icon iconName="add" intent={Intent.DANGER} />, Classes.INTENT_DANGER)); diff --git a/packages/docs-app/src/components/docsIcon.tsx b/packages/docs-app/src/components/docsIcon.tsx index 4421aa82f9..7ce2ab06dc 100644 --- a/packages/docs-app/src/components/docsIcon.tsx +++ b/packages/docs-app/src/components/docsIcon.tsx @@ -25,7 +25,7 @@ export class DocsIcon extends React.PureComponent<IDocsIconProps, {}> { const { className, name, tags } = this.props; return ( <ClickToCopy className="docs-icon" data-tags={tags} value={className}> - <Icon iconName={className} iconSize={Icon.SIZE_LARGE} /> + <Icon iconName={className} width={Icon.SIZE_LARGE} /> <span className="docs-icon-detail"> <div className="docs-icon-name">{name}</div> <div className="docs-icon-class-name pt-monospace-text">{className}</div> 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<{}> { <Button onClick={this.handleOpen} text="Open file deletion alert" /> <Alert className={this.props.themeName} + iconName="trash" intent={Intent.PRIMARY} isOpen={this.state.isOpen} confirmButtonText="Move to Trash" diff --git a/packages/docs-app/src/examples/core-examples/popoverInlineExample.tsx b/packages/docs-app/src/examples/core-examples/popoverInlineExample.tsx index 8dc378eaa2..3c095f2c60 100644 --- a/packages/docs-app/src/examples/core-examples/popoverInlineExample.tsx +++ b/packages/docs-app/src/examples/core-examples/popoverInlineExample.tsx @@ -80,14 +80,7 @@ export class PopoverInlineExample extends BaseExample<IPopoverInlineExampleState protected renderOptions() { return [ - [ - <Button - key="recenter" - text="Re-center" - iconName="pt-icon-alignment-vertical-center" - onClick={this.recenter} - />, - ], + [<Button key="recenter" text="Re-center" iconName="alignment-vertical-center" onClick={this.recenter} />], ]; } diff --git a/packages/docs-app/src/examples/datetime-examples/dateRangePickerExample.tsx b/packages/docs-app/src/examples/datetime-examples/dateRangePickerExample.tsx index d02da1c3ce..377c1391ef 100644 --- a/packages/docs-app/src/examples/datetime-examples/dateRangePickerExample.tsx +++ b/packages/docs-app/src/examples/datetime-examples/dateRangePickerExample.tsx @@ -96,7 +96,7 @@ export class DateRangePickerExample extends BaseExample<IDateRangePickerExampleS /> <div> <Moment date={start} /> - <Icon iconName="arrow-right" iconSize={20} /> + <Icon iconName="arrow-right" width={20} /> <Moment date={end} /> </div> </div> diff --git a/packages/docs-app/src/index.tsx b/packages/docs-app/src/index.tsx index 70902d1921..a201068c06 100644 --- a/packages/docs-app/src/index.tsx +++ b/packages/docs-app/src/index.tsx @@ -29,7 +29,7 @@ const reactDocs = new ReactDocsTagRenderer(ReactDocs as any); const reactExample = new ReactExampleTagRenderer(reactExamples); const tagRenderers = { - ...createDefaultRenderers(docs), + ...createDefaultRenderers(), reactDocs: reactDocs.render, reactExample: reactExample.render, }; From 36159e86f88599c17d75004d12a6a9e92d371a0c Mon Sep 17 00:00:00 2001 From: Gilad Gray <ggray@palantir.com> Date: Tue, 23 Jan 2018 16:20:19 -0800 Subject: [PATCH 07/45] svg.pt-icon style fixes, LegacyIconName extends IconName --- packages/core/src/components/icon/_icons.scss | 3 +++ packages/node-build-scripts/generate-icons-source | 2 ++ 2 files changed, 5 insertions(+) diff --git a/packages/core/src/components/icon/_icons.scss b/packages/core/src/components/icon/_icons.scss index 72fb11159c..cfe2381304 100644 --- a/packages/core/src/components/icon/_icons.scss +++ b/packages/core/src/components/icon/_icons.scss @@ -40,6 +40,9 @@ span.pt-icon { } svg.pt-icon { + // respect dimensions exactly + flex: 0 0 auto; vertical-align: middle; + // inherit text color by default fill: currentColor; } diff --git a/packages/node-build-scripts/generate-icons-source b/packages/node-build-scripts/generate-icons-source index c0ce704697..59c8d457bb 100755 --- a/packages/node-build-scripts/generate-icons-source +++ b/packages/node-build-scripts/generate-icons-source @@ -118,6 +118,8 @@ function buildTSObject(objectName, valueGetter) { function buildUnionType() { const shortNames = ICONS_METADATA.map(({ className }) => `"${className.replace("pt-icon-", "")}"`); const longNames = ICONS_METADATA.map(({ className }) => `"${className}"`); + // long names extend short names + longNames.unshift("IconName"); return [ `export type IconName =\n | ${shortNames.join("\n | ")};`, "", From ea5fbf8319e2e458316e910816bd2e437f113918 Mon Sep 17 00:00:00 2001 From: Gilad Gray <ggray@palantir.com> Date: Tue, 23 Jan 2018 16:25:36 -0800 Subject: [PATCH 08/45] revert documentalist usage --- packages/docs-app/src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs-app/src/index.tsx b/packages/docs-app/src/index.tsx index a201068c06..70902d1921 100644 --- a/packages/docs-app/src/index.tsx +++ b/packages/docs-app/src/index.tsx @@ -29,7 +29,7 @@ const reactDocs = new ReactDocsTagRenderer(ReactDocs as any); const reactExample = new ReactExampleTagRenderer(reactExamples); const tagRenderers = { - ...createDefaultRenderers(), + ...createDefaultRenderers(docs), reactDocs: reactDocs.render, reactExample: reactExample.render, }; From 739e64359ad7bb69cd6a762a066b290bd845cd55 Mon Sep 17 00:00:00 2001 From: Gilad Gray <ggray@palantir.com> Date: Tue, 23 Jan 2018 18:05:03 -0800 Subject: [PATCH 09/45] svgo v1 (0.7 is still used by css-loader :sad:) --- packages/landing-app/package.json | 2 +- packages/node-build-scripts/package.json | 2 +- yarn.lock | 100 +++++++++++++++++++++-- 3 files changed, 96 insertions(+), 8 deletions(-) diff --git a/packages/landing-app/package.json b/packages/landing-app/package.json index 85ff8495f9..e374711196 100644 --- a/packages/landing-app/package.json +++ b/packages/landing-app/package.json @@ -29,7 +29,7 @@ "copy-webpack-plugin": "^4.2.0", "npm-run-all": "^4.1.2", "raw-loader": "^0.5.1", - "svgo": "^0.7.2", + "svgo": "^1.0.3", "tslint": "^5.9.0", "webpack": "^3.10.0", "webpack-dev-server": "^2.10.1" diff --git a/packages/node-build-scripts/package.json b/packages/node-build-scripts/package.json index fed150829d..e4e3922116 100644 --- a/packages/node-build-scripts/package.json +++ b/packages/node-build-scripts/package.json @@ -16,7 +16,7 @@ "node-sass-chokidar": "^0.0.3", "node-sass-package-importer": "^3.0.4", "strip-css-comments": "^3.0.0", - "svgo": "^0.7" + "svgo": "^1.0.3" }, "repository": { "type": "git", diff --git a/yarn.lock b/yarn.lock index 3867d4a64b..772018fd6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -680,7 +680,7 @@ bonjour@^3.5.0: multicast-dns "^6.0.1" multicast-dns-service-types "^1.1.0" -boolbase@~1.0.0: +boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -1193,6 +1193,12 @@ coa@~1.0.1: dependencies: q "^1.1.2" +coa@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.1.tgz#f3f8b0b15073e35d70263fb1042cb2c023db38af" + dependencies: + q "^1.1.2" + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -1740,6 +1746,10 @@ css-rule-stream@^1.1.0: ldjson-stream "^1.2.1" through2 "^0.6.3" +css-select-base-adapter@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.0.tgz#0102b3d14630df86c3eb9fa9f5456270106cf990" + css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" @@ -1749,6 +1759,15 @@ css-select@~1.2.0: domutils "1.5.1" nth-check "~1.0.1" +css-select@~1.3.0-rc0: + version "1.3.0-rc0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.3.0-rc0.tgz#6f93196aaae737666ea1036a8cb14a8fcb7a9231" + dependencies: + boolbase "^1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "^1.0.1" + css-selector-tokenizer@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" @@ -1764,6 +1783,24 @@ css-tokenize@^1.0.1: inherits "^2.0.1" readable-stream "^1.0.33" +css-tree@1.0.0-alpha.27: + version "1.0.0-alpha.27" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.27.tgz#f211526909c7dc940843d83b9376ed98ddb8de47" + dependencies: + mdn-data "^1.0.0" + source-map "^0.5.3" + +css-tree@1.0.0-alpha25: + version "1.0.0-alpha25" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha25.tgz#1bbfabfbf6eeef4f01d9108ff2edd0be2fe35597" + dependencies: + mdn-data "^1.0.0" + source-map "^0.5.3" + +css-url-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/css-url-regex/-/css-url-regex-1.1.0.tgz#83834230cc9f74c457de59eebd1543feeb83b7ec" + css-what@2.1: version "2.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" @@ -1809,6 +1846,12 @@ cssnano@^3.10.0: postcss-value-parser "^3.2.3" postcss-zindex "^2.0.1" +csso@^3.3.1: + version "3.5.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-3.5.0.tgz#acdbba5719e2c87bc801eadc032764b2e4b9d4e7" + dependencies: + css-tree "1.0.0-alpha.27" + csso@~2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" @@ -2385,7 +2428,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.4.3, es-abstract@^1.6.1, es-abstract@^1.7.0: +es-abstract@^1.4.3, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: version "1.10.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" dependencies: @@ -4226,7 +4269,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@3.x, js-yaml@^3.10.0, js-yaml@^3.4.3, js-yaml@^3.7.0, js-yaml@^3.9.0: +js-yaml@3.x, js-yaml@^3.10.0, js-yaml@^3.4.3, js-yaml@^3.7.0, js-yaml@^3.9.0, js-yaml@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: @@ -4917,6 +4960,10 @@ mdast-util-compact@^1.0.0: unist-util-modify-children "^1.0.0" unist-util-visit "^1.1.0" +mdn-data@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.0.tgz#a7056319da95a2d0881267d7263075042eb061e2" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -5467,7 +5514,7 @@ npm-run-path@^2.0.0: gauge "~2.7.3" set-blocking "~2.0.0" -nth-check@~1.0.1: +nth-check@^1.0.1, nth-check@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" dependencies: @@ -5545,6 +5592,13 @@ object.entries@^1.0.4: function-bind "^1.1.0" has "^1.0.1" +object.getownpropertydescriptors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.1" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -7159,7 +7213,7 @@ sass-loader@^6.0.6: lodash.tail "^4.1.1" pify "^3.0.0" -sax@~1.2.1: +sax@~1.2.1, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -7633,6 +7687,10 @@ ssri@^5.0.0: dependencies: safe-buffer "^5.1.0" +stable@~0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.6.tgz#910f5d2aed7b520c6e777499c1f32e139fdecb10" + state-toggle@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.0.tgz#d20f9a616bb4f0c3b98b91922d25b640aa2bc425" @@ -8022,7 +8080,7 @@ svg-tags@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" -svgo@^0.7, svgo@^0.7.0, svgo@^0.7.2: +svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" dependencies: @@ -8034,6 +8092,25 @@ svgo@^0.7, svgo@^0.7.0, svgo@^0.7.2: sax "~1.2.1" whet.extend "~0.9.9" +svgo@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.0.3.tgz#c93be52d98ffa2a7273c7a0ac5bf34f470973dad" + dependencies: + coa "~2.0.0" + colors "~1.1.2" + css-select "~1.3.0-rc0" + css-select-base-adapter "~0.1.0" + css-tree "1.0.0-alpha25" + css-url-regex "^1.1.0" + csso "^3.3.1" + js-yaml "~3.10.0" + mkdirp "~0.5.1" + object.values "^1.0.4" + sax "~1.2.4" + stable "~0.1.6" + unquote "^1.1.0" + util.promisify "~1.0.0" + synesthesia@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/synesthesia/-/synesthesia-1.0.1.tgz#5ef95ea548c0d5c6e6f9bb4b0d0731dff864a777" @@ -8531,6 +8608,10 @@ unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" +unquote@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" @@ -8596,6 +8677,13 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" +util.promisify@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + util@0.10.3, util@^0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" From 87e8f09897d5efba37805debe8e15390a7ba0fb0 Mon Sep 17 00:00:00 2001 From: Gilad Gray <ggray@palantir.com> Date: Tue, 23 Jan 2018 18:10:43 -0800 Subject: [PATCH 10/45] iconSvgs => iconSvgPaths, map of icon name to array of path strings (d="string"). Icon component renders svg and title tags. --- packages/core/src/components/icon/icon.tsx | 14 +++--- packages/icons/src/index.ts | 2 +- .../node-build-scripts/generate-icons-source | 49 +++++++------------ 3 files changed, 26 insertions(+), 39 deletions(-) diff --git a/packages/core/src/components/icon/icon.tsx b/packages/core/src/components/icon/icon.tsx index 2f233bc5c9..472b9ce1a1 100644 --- a/packages/core/src/components/icon/icon.tsx +++ b/packages/core/src/components/icon/icon.tsx @@ -7,7 +7,7 @@ import * as classNames from "classnames"; import * as React from "react"; -import { IconName, IconSvgs, LegacyIconName } from "@blueprintjs/icons"; +import { IconName, IconSvgPaths, LegacyIconName } from "@blueprintjs/icons"; import { Classes, IIntentProps, IProps } from "../../common"; export { IconName }; @@ -45,10 +45,12 @@ export class Icon extends React.PureComponent<IIconProps & React.HTMLAttributes< return null; } const shortName = iconName.replace("pt-icon-", "") as IconName; - return React.cloneElement(IconSvgs[shortName], { - className: classNames("pt-icon", Classes.iconClass(iconName), Classes.intentClass(intent), className), - height, - width, - }); + const classes = classNames(Classes.ICON, Classes.iconClass(iconName), Classes.intentClass(intent), className); + return ( + <svg className={classes} width={width} height={height} viewBox="0 0 16 16"> + <title>{iconName} + {IconSvgPaths[shortName].map((d, i) => )} + + ); } } diff --git a/packages/icons/src/index.ts b/packages/icons/src/index.ts index 754c0e6f3d..e142e5cbed 100644 --- a/packages/icons/src/index.ts +++ b/packages/icons/src/index.ts @@ -7,4 +7,4 @@ export { IconClasses } from "./generated/iconClasses"; export { IconName, LegacyIconName } from "./generated/iconName"; export { IconContents } from "./generated/iconStrings"; -export { IconSvgs } from "./generated/iconSvgs"; +export { IconSvgPaths } from "./generated/iconSvgPaths"; diff --git a/packages/node-build-scripts/generate-icons-source b/packages/node-build-scripts/generate-icons-source index 59c8d457bb..c7d90b221a 100755 --- a/packages/node-build-scripts/generate-icons-source +++ b/packages/node-build-scripts/generate-icons-source @@ -8,7 +8,7 @@ const fs = require("fs"); const path = require("path"); const SVGO = require("svgo"); -const svgo = new SVGO(); +const svgo = new SVGO({ plugins: [{ convertShapeToPath: { convertArcs: true } }] }); const { COPYRIGHT_HEADER } = require("./constants"); /** @@ -52,27 +52,8 @@ writeLinesToFile("iconName.ts", ...buildUnionType()); writeLinesToFile("iconStrings.ts", ...buildTSObject("IconContents", icon => icon.content.replace("\\", "\\u"))); (async () => { - // JSX SVG elements. IIFE to unwrap async. - writeLinesToFile( - "iconSvgs.tsx", - ...(await buildJSXObject("IconSvgs", icon => { - const svg = fs.readFileSync( - path.resolve(__dirname, `../../resources/icons/16px/${icon.className}.svg`), - "utf-8", - ); - /** @type {Promise} - must define variable to type it before returning */ - const promise = new Promise(resolve => - svgo.optimize(svg, (/** @type {{ data: string }} */ { data }) => { - const dataJsx = data - .replace("class=", "className=") - .replace(/'); - // TODO: tag. can we strip <svg> tag? - resolve(dataJsx); - }), - ); - return promise; - })), - ); + // SVG path strings. IIFE to unwrap async. + writeLinesToFile("iconSvgPaths.ts", ...(await buildPathsObject("IconSvgPaths"))); })(); /** @@ -126,23 +107,27 @@ function buildUnionType() { `export type LegacyIconName =\n | ${longNames.join("\n | ")};`, ]; } + /** - * Builds `const ${objectName}`, keyed by icon enum name. Value is result of `valueGetter` function. + * Loads SVG file for each icon, extracts path strings `d="path-string"`, + * and constructs map of icon name to array of path strings. * @param {string} objectName - * @param {(icon: IconMetadata) => Promise<string>} valueGetter */ -async function buildJSXObject(objectName, valueGetter) { - const lines = ICONS_METADATA.map( - async icon => ` "${icon.className.replace("pt-icon-", "")}": ${await valueGetter(icon)},`, - ); +async function buildPathsObject(objectName) { + const lines = ICONS_METADATA.map(async icon => { + const filepath = path.resolve(__dirname, `../../resources/icons/16px/${icon.className}.svg`); + const svg = fs.readFileSync(filepath, "utf-8"); + const pathStrings = await svgo + .optimize(svg, { path: filepath }) + .then(({ data }) => data.match(/ d="[^"]+"/g) || []) + .then(paths => paths.map(s => s.slice(3))); + return ` "${icon.className.replace("pt-icon-", "")}": [${pathStrings.join(",\n")}],`; + }); return [ - 'import * as React from "react";', 'import { IconName } from "./iconName";', - "", "// tslint:disable:prettier", - `export const ${objectName}: Record<IconName, JSX.Element> = {`, + `export const ${objectName}: Record<IconName, string[]> = {`, ...(await Promise.all(lines)), "};", - "// tslint:enable:prettier", ]; } From eb79ca27e2e39b3a3cb12c601205d15200bc6295 Mon Sep 17 00:00:00 2001 From: Gilad Gray <ggray@palantir.com> Date: Tue, 23 Jan 2018 18:11:07 -0800 Subject: [PATCH 11/45] update DocsIcon --- packages/docs-app/src/components/docsIcon.tsx | 2 +- packages/docs-app/src/styles/_icons.scss | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/docs-app/src/components/docsIcon.tsx b/packages/docs-app/src/components/docsIcon.tsx index 7ce2ab06dc..1fd6adb746 100644 --- a/packages/docs-app/src/components/docsIcon.tsx +++ b/packages/docs-app/src/components/docsIcon.tsx @@ -25,7 +25,7 @@ export class DocsIcon extends React.PureComponent<IDocsIconProps, {}> { const { className, name, tags } = this.props; return ( <ClickToCopy className="docs-icon" data-tags={tags} value={className}> - <Icon iconName={className} width={Icon.SIZE_LARGE} /> + <Icon iconName={className} width={Icon.SIZE_LARGE} height={Icon.SIZE_LARGE} /> <span className="docs-icon-detail"> <div className="docs-icon-name">{name}</div> <div className="docs-icon-class-name pt-monospace-text">{className}</div> diff --git a/packages/docs-app/src/styles/_icons.scss b/packages/docs-app/src/styles/_icons.scss index f3c7e5a2ac..801afbc3d5 100644 --- a/packages/docs-app/src/styles/_icons.scss +++ b/packages/docs-app/src/styles/_icons.scss @@ -52,11 +52,13 @@ $icons-per-row: 5; background-color: $light-gray3; } + .pt-icon, .pt-icon-large, .docs-icon-svg { position: relative; } + .pt-icon, .pt-icon-large { color: $pt-icon-color; } @@ -70,6 +72,7 @@ $icons-per-row: 5; background-color: $dark-gray2; } + .pt-icon, .pt-icon-large { color: $pt-dark-icon-color; } From e242f500c3a4a33fe02fdfb4a1fbf554c87c016d Mon Sep 17 00:00:00 2001 From: Gilad Gray <ggray@palantir.com> Date: Tue, 23 Jan 2018 18:11:20 -0800 Subject: [PATCH 12/45] remove style tag from endorsed icon (the only one that had it) --- resources/icons/16px/pt-icon-endorsed.svg | 37 +++++++++++------------ resources/icons/20px/pt-icon-endorsed.svg | 37 +++++++++++------------ 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/resources/icons/16px/pt-icon-endorsed.svg b/resources/icons/16px/pt-icon-endorsed.svg index cd8e95283f..87917ad64c 100644 --- a/resources/icons/16px/pt-icon-endorsed.svg +++ b/resources/icons/16px/pt-icon-endorsed.svg @@ -1,20 +1,17 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve"> -<style type="text/css"> - .st0{fill-rule:evenodd;clip-rule:evenodd;} -</style> -<g id="endorsed_2_"> - <g> - <path class="st0" d="M15.86,7.5l-0.81-1.42V4.5c0-0.36-0.19-0.68-0.49-0.87l-1.37-0.8l-0.81-1.41c-0.19-0.31-0.51-0.49-0.86-0.49 - H9.89L8.5,0.14c-0.3-0.19-0.69-0.19-1,0l-1.39,0.8H4.52c-0.36,0-0.68,0.19-0.86,0.49L2.86,2.8L1.42,3.63 - C1.12,3.82,0.93,4.14,0.93,4.5v1.65l-0.8,1.37C0.05,7.67,0,7.84,0,8.01S0.05,8.35,0.14,8.5l0.8,1.37v1.65 - c0,0.36,0.19,0.68,0.49,0.87l1.42,0.81l0.8,1.37c0.19,0.31,0.51,0.49,0.86,0.49H6.1l1.39,0.8C7.64,15.95,7.81,16,7.97,16 - s0.34-0.05,0.49-0.14l1.39-0.8h1.63c0.36,0,0.68-0.19,0.86-0.49l0.81-1.41l1.37-0.8c0.3-0.19,0.49-0.51,0.49-0.87V9.93l0.81-1.42 - C16.05,8.2,16.05,7.82,15.86,7.5z M11.74,6.68l-4.01,4.01c-0.18,0.18-0.43,0.29-0.71,0.29s-0.53-0.11-0.71-0.29L4.31,8.69 - C4.13,8.5,4.01,8.25,4.01,7.98c0-0.55,0.45-1,1-1c0.28,0,0.53,0.11,0.71,0.29l1.3,1.3l3.3-3.3c0.18-0.18,0.43-0.29,0.71-0.29 - c0.55,0,1,0.45,1,1C12.04,6.25,11.92,6.5,11.74,6.68z"/> - </g> -</g> -</svg> +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve"> +<g id="endorsed_2_"> + <g> + <path fill-rule="evenodd" clip-rule="evenodd" d="M15.86,7.5l-0.81-1.42V4.5c0-0.36-0.19-0.68-0.49-0.87l-1.37-0.8l-0.81-1.41c-0.19-0.31-0.51-0.49-0.86-0.49 + H9.89L8.5,0.14c-0.3-0.19-0.69-0.19-1,0l-1.39,0.8H4.52c-0.36,0-0.68,0.19-0.86,0.49L2.86,2.8L1.42,3.63 + C1.12,3.82,0.93,4.14,0.93,4.5v1.65l-0.8,1.37C0.05,7.67,0,7.84,0,8.01S0.05,8.35,0.14,8.5l0.8,1.37v1.65 + c0,0.36,0.19,0.68,0.49,0.87l1.42,0.81l0.8,1.37c0.19,0.31,0.51,0.49,0.86,0.49H6.1l1.39,0.8C7.64,15.95,7.81,16,7.97,16 + s0.34-0.05,0.49-0.14l1.39-0.8h1.63c0.36,0,0.68-0.19,0.86-0.49l0.81-1.41l1.37-0.8c0.3-0.19,0.49-0.51,0.49-0.87V9.93l0.81-1.42 + C16.05,8.2,16.05,7.82,15.86,7.5z M11.74,6.68l-4.01,4.01c-0.18,0.18-0.43,0.29-0.71,0.29s-0.53-0.11-0.71-0.29L4.31,8.69 + C4.13,8.5,4.01,8.25,4.01,7.98c0-0.55,0.45-1,1-1c0.28,0,0.53,0.11,0.71,0.29l1.3,1.3l3.3-3.3c0.18-0.18,0.43-0.29,0.71-0.29 + c0.55,0,1,0.45,1,1C12.04,6.25,11.92,6.5,11.74,6.68z"/> + </g> +</g> +</svg> diff --git a/resources/icons/20px/pt-icon-endorsed.svg b/resources/icons/20px/pt-icon-endorsed.svg index b0038271f0..59b073447c 100644 --- a/resources/icons/20px/pt-icon-endorsed.svg +++ b/resources/icons/20px/pt-icon-endorsed.svg @@ -1,20 +1,17 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve"> -<style type="text/css"> - .st0{fill-rule:evenodd;clip-rule:evenodd;} -</style> -<g id="endorsed_1_"> - <g> - <path class="st0" d="M19.83,9.38L18.81,7.6V5.62c0-0.45-0.23-0.85-0.61-1.08l-1.71-1l-1.02-1.76c-0.23-0.38-0.63-0.61-1.08-0.61 - h-2.03l-1.74-1c-0.38-0.23-0.87-0.23-1.25,0l-1.74,1H5.65c-0.44,0-0.85,0.23-1.08,0.61L3.58,3.5l-1.8,1.04 - C1.4,4.78,1.16,5.18,1.16,5.62v2.06L0.17,9.4C0.06,9.59,0,9.8,0,10.01s0.06,0.42,0.17,0.61l0.99,1.72v2.06 - c0,0.45,0.23,0.85,0.61,1.08l1.78,1.02l0.99,1.72c0.23,0.38,0.63,0.61,1.08,0.61h1.99l1.74,1C9.54,19.94,9.76,20,9.97,20 - c0.21,0,0.42-0.06,0.61-0.17l1.74-1h2.03c0.44,0,0.85-0.23,1.08-0.61l1.02-1.76l1.71-1c0.38-0.23,0.61-0.64,0.61-1.08v-1.97 - l1.02-1.78C20.06,10.25,20.06,9.78,19.83,9.38z M14.75,8.67l-5.01,5.01c-0.18,0.18-0.43,0.29-0.71,0.29 - c-0.28,0-0.53-0.11-0.71-0.29l-3.01-3.01c-0.18-0.18-0.29-0.43-0.29-0.71c0-0.55,0.45-1,1-1c0.28,0,0.53,0.11,0.71,0.29l2.3,2.3 - l4.31-4.3c0.18-0.18,0.43-0.29,0.71-0.29c0.55,0,1,0.45,1,1C15.05,8.24,14.93,8.49,14.75,8.67z"/> - </g> -</g> -</svg> +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve"> +<g id="endorsed_1_"> + <g> + <path fill-rule="evenodd" clip-rule="evenodd" d="M19.83,9.38L18.81,7.6V5.62c0-0.45-0.23-0.85-0.61-1.08l-1.71-1l-1.02-1.76c-0.23-0.38-0.63-0.61-1.08-0.61 + h-2.03l-1.74-1c-0.38-0.23-0.87-0.23-1.25,0l-1.74,1H5.65c-0.44,0-0.85,0.23-1.08,0.61L3.58,3.5l-1.8,1.04 + C1.4,4.78,1.16,5.18,1.16,5.62v2.06L0.17,9.4C0.06,9.59,0,9.8,0,10.01s0.06,0.42,0.17,0.61l0.99,1.72v2.06 + c0,0.45,0.23,0.85,0.61,1.08l1.78,1.02l0.99,1.72c0.23,0.38,0.63,0.61,1.08,0.61h1.99l1.74,1C9.54,19.94,9.76,20,9.97,20 + c0.21,0,0.42-0.06,0.61-0.17l1.74-1h2.03c0.44,0,0.85-0.23,1.08-0.61l1.02-1.76l1.71-1c0.38-0.23,0.61-0.64,0.61-1.08v-1.97 + l1.02-1.78C20.06,10.25,20.06,9.78,19.83,9.38z M14.75,8.67l-5.01,5.01c-0.18,0.18-0.43,0.29-0.71,0.29 + c-0.28,0-0.53-0.11-0.71-0.29l-3.01-3.01c-0.18-0.18-0.29-0.43-0.29-0.71c0-0.55,0.45-1,1-1c0.28,0,0.53,0.11,0.71,0.29l2.3,2.3 + l4.31-4.3c0.18-0.18,0.43-0.29,0.71-0.29c0.55,0,1,0.45,1,1C15.05,8.24,14.93,8.49,14.75,8.67z"/> + </g> +</g> +</svg> From a82d0963de6ea5e5c07ebb885617a99d6c286ce9 Mon Sep 17 00:00:00 2001 From: Gilad Gray <ggray@palantir.com> Date: Tue, 23 Jan 2018 18:11:33 -0800 Subject: [PATCH 13/45] fix tree test --- packages/core/test/tree/treeTests.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/test/tree/treeTests.tsx b/packages/core/test/tree/treeTests.tsx index 1c70cdcdbf..f2a2e3b3fd 100644 --- a/packages/core/test/tree/treeTests.tsx +++ b/packages/core/test/tree/treeTests.tsx @@ -128,7 +128,7 @@ describe("<Tree>", () => { 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`; From 718ff50fac827cd542d638dc3be56dd83f75c127 Mon Sep 17 00:00:00 2001 From: Gilad Gray <ggray@palantir.com> Date: Tue, 23 Jan 2018 21:05:34 -0800 Subject: [PATCH 14/45] IconSvgPaths16 and IconSvgPaths20 objects --- packages/icons/src/index.ts | 2 +- .../node-build-scripts/generate-icons-source | 44 +++++++++++-------- ...-skew.svg => pt-icon-layout-skew-grid.svg} | 0 3 files changed, 27 insertions(+), 19 deletions(-) rename resources/icons/20px/{pt-icon-layout-skew.svg => pt-icon-layout-skew-grid.svg} (100%) diff --git a/packages/icons/src/index.ts b/packages/icons/src/index.ts index e142e5cbed..0d59d3340e 100644 --- a/packages/icons/src/index.ts +++ b/packages/icons/src/index.ts @@ -7,4 +7,4 @@ export { IconClasses } from "./generated/iconClasses"; export { IconName, LegacyIconName } from "./generated/iconName"; export { IconContents } from "./generated/iconStrings"; -export { IconSvgPaths } from "./generated/iconSvgPaths"; +export { IconSvgPaths16, IconSvgPaths20 } from "./generated/iconSvgPaths"; diff --git a/packages/node-build-scripts/generate-icons-source b/packages/node-build-scripts/generate-icons-source index c7d90b221a..fd7c431bab 100755 --- a/packages/node-build-scripts/generate-icons-source +++ b/packages/node-build-scripts/generate-icons-source @@ -53,7 +53,19 @@ writeLinesToFile("iconStrings.ts", ...buildTSObject("IconContents", icon => icon (async () => { // SVG path strings. IIFE to unwrap async. - writeLinesToFile("iconSvgPaths.ts", ...(await buildPathsObject("IconSvgPaths"))); + writeLinesToFile( + "iconSvgPaths.ts", + 'import { IconName } from "./iconName";', + "", + "// tslint:disable:prettier", + "export const IconSvgPaths16: Record<IconName, string[]> = {", + ...(await buildPathsObject("IconSvgPaths", 16)), + "};", + "", + "export const IconSvgPaths20: Record<IconName, string[]> = {", + ...(await buildPathsObject("IconSvgPaths", 20)), + "};", + ); })(); /** @@ -112,22 +124,18 @@ function buildUnionType() { * Loads SVG file for each icon, extracts path strings `d="path-string"`, * and constructs map of icon name to array of path strings. * @param {string} objectName + * @param {16 | 20} size */ -async function buildPathsObject(objectName) { - const lines = ICONS_METADATA.map(async icon => { - const filepath = path.resolve(__dirname, `../../resources/icons/16px/${icon.className}.svg`); - const svg = fs.readFileSync(filepath, "utf-8"); - const pathStrings = await svgo - .optimize(svg, { path: filepath }) - .then(({ data }) => data.match(/ d="[^"]+"/g) || []) - .then(paths => paths.map(s => s.slice(3))); - return ` "${icon.className.replace("pt-icon-", "")}": [${pathStrings.join(",\n")}],`; - }); - return [ - 'import { IconName } from "./iconName";', - "// tslint:disable:prettier", - `export const ${objectName}: Record<IconName, string[]> = {`, - ...(await Promise.all(lines)), - "};", - ]; +async function buildPathsObject(objectName, size) { + return Promise.all( + ICONS_METADATA.map(async icon => { + const filepath = path.resolve(__dirname, `../../resources/icons/${size}px/${icon.className}.svg`); + const svg = fs.readFileSync(filepath, "utf-8"); + const pathStrings = await svgo + .optimize(svg, { path: filepath }) + .then(({ data }) => data.match(/ d="[^"]+"/g) || []) + .then(paths => paths.map(s => s.slice(3))); + return ` "${icon.className.replace("pt-icon-", "")}": [${pathStrings.join(",\n")}],`; + }), + ); } diff --git a/resources/icons/20px/pt-icon-layout-skew.svg b/resources/icons/20px/pt-icon-layout-skew-grid.svg similarity index 100% rename from resources/icons/20px/pt-icon-layout-skew.svg rename to resources/icons/20px/pt-icon-layout-skew-grid.svg From 5e042e536666b62ee419c0f8ba702ede9de3f104 Mon Sep 17 00:00:00 2001 From: Gilad Gray <ggray@palantir.com> Date: Tue, 23 Jan 2018 21:06:32 -0800 Subject: [PATCH 15/45] [WIP] choose 16 or 20 based on iconSize + example really awkward experience. makes me think we want to use 20 whenever iconSize > 20. --- packages/core/src/components/icon/icon.md | 8 +-- packages/core/src/components/icon/icon.tsx | 48 ++++++++++------- packages/docs-app/src/components/docsIcon.tsx | 2 +- .../examples/core-examples/iconExample.tsx | 51 +++++++++++++++++++ .../src/examples/core-examples/index.ts | 1 + .../dateRangePickerExample.tsx | 2 +- 6 files changed, 90 insertions(+), 22 deletions(-) create mode 100644 packages/docs-app/src/examples/core-examples/iconExample.tsx diff --git a/packages/core/src/components/icon/icon.md b/packages/core/src/components/icon/icon.md index 7e4a05295e..b0db920cc2 100644 --- a/packages/core/src/components/icon/icon.md +++ b/packages/core/src/components/icon/icon.md @@ -9,6 +9,8 @@ This section describes two ways of using the UI icon font: via CSS or via React Many Blueprint components provide an `iconName` prop, which supports both the full name `pt-icon-projects` and the short name `projects`. +@reactExample IconExample + @## CSS API To use Blueprint UI icons via CSS, you must apply two classes to a `<span>` element: @@ -41,12 +43,12 @@ The component also accepts all valid HTML props for a `<span>` element. ```tsx // string literals are supported through IconName union type <Icon iconName="cross" /> -<Icon iconName="pt-icon-globe" iconSize="inherit" /> -<Icon iconName="graph" iconSize={20} intent={Intent.PRIMARY} /> +<Icon iconName="pt-icon-globe" width="inherit" /> +<Icon iconName="graph" width={20} intent={Intent.PRIMARY} /> // can also use IconClasses string enum and Icon.SIZE_* constants import { IconClasses } from "@blueprintjs/core"; -<Icon iconName={IconClasses.ALIGN_LEFT} iconSize={Icon.SIZE_LARGE} /> +<Icon iconName={IconClasses.ALIGN_LEFT} width={Icon.SIZE_LARGE} /> // can pass all valid HTML props <Icon iconName="add" onClick={this.handleAdd} onKeyDown={this.handleAddKeys}> diff --git a/packages/core/src/components/icon/icon.tsx b/packages/core/src/components/icon/icon.tsx index 472b9ce1a1..d82498b844 100644 --- a/packages/core/src/components/icon/icon.tsx +++ b/packages/core/src/components/icon/icon.tsx @@ -7,7 +7,7 @@ import * as classNames from "classnames"; import * as React from "react"; -import { IconName, IconSvgPaths, LegacyIconName } from "@blueprintjs/icons"; +import { IconName, IconSvgPaths16, IconSvgPaths20, LegacyIconName } from "@blueprintjs/icons"; import { Classes, IIntentProps, IProps } from "../../common"; export { IconName }; @@ -20,37 +20,51 @@ export interface IIconProps extends IIntentProps, IProps { iconName: LegacyIconName | undefined; /** - * Height of icon. A number value will be interpreted as pixels. Use a string value for other units. - * By default, inherits height from surrounding styles, such as `line-height`. - * @default "inherit" + * Size of the icon, in pixels. + * Blueprint contains 16px and 20px SVG icon images, + * and chooses the appropriate resolution based on this prop. */ - height?: number | string; - - /** - * Width of icon. A number value will be interpreted as pixels. Use a string value for other units. - * @default 16 - */ - width?: number | string; + iconSize?: number; } export class Icon extends React.PureComponent<IIconProps & React.HTMLAttributes<HTMLSpanElement>, never> { public static displayName = "Blueprint.Icon"; - public static readonly SIZE_STANDARD = 16 as 16; - public static readonly SIZE_LARGE = 20 as 20; + public static readonly SIZE_STANDARD = 16; + public static readonly SIZE_LARGE = 20; public render() { - const { className, iconName, intent, width = 16, height = "inherit" } = this.props; + const { className, iconName, iconSize = 16, intent } = this.props; if (iconName == null) { return null; } - const shortName = iconName.replace("pt-icon-", "") as IconName; + const pathsSize = this.determineIconDimension(); const classes = classNames(Classes.ICON, Classes.iconClass(iconName), Classes.intentClass(intent), className); return ( - <svg className={classes} width={width} height={height} viewBox="0 0 16 16"> + <svg className={classes} width={iconSize} height={iconSize} viewBox={`0 0 ${pathsSize} ${pathsSize}`}> <title>{iconName} - {IconSvgPaths[shortName].map((d, i) => )} + {this.renderSvgPaths(pathsSize === Icon.SIZE_STANDARD ? IconSvgPaths16 : IconSvgPaths20)} ); } + + private determineIconDimension() { + const { iconSize = 16 } = this.props; + if (numberDiff(Icon.SIZE_STANDARD, iconSize) < numberDiff(Icon.SIZE_LARGE, iconSize)) { + return Icon.SIZE_STANDARD; + } + return Icon.SIZE_LARGE; + } + + private renderSvgPaths(svgPaths: Record) { + const paths = svgPaths[this.props.iconName.replace("pt-icon-", "") as IconName]; + if (paths == null) { + return null; + } + return paths.map((d, i) => ); + } +} + +function numberDiff(target: number, actual: number) { + return Math.abs(Math.round(actual / target) - actual / target); } diff --git a/packages/docs-app/src/components/docsIcon.tsx b/packages/docs-app/src/components/docsIcon.tsx index 1fd6adb746..4421aa82f9 100644 --- a/packages/docs-app/src/components/docsIcon.tsx +++ b/packages/docs-app/src/components/docsIcon.tsx @@ -25,7 +25,7 @@ export class DocsIcon extends React.PureComponent { const { className, name, tags } = this.props; return ( - +
{name}
{className}
diff --git a/packages/docs-app/src/examples/core-examples/iconExample.tsx b/packages/docs-app/src/examples/core-examples/iconExample.tsx new file mode 100644 index 0000000000..48b48929f7 --- /dev/null +++ b/packages/docs-app/src/examples/core-examples/iconExample.tsx @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the terms of the LICENSE file distributed with this project. + */ + +import * as React from "react"; + +import { Classes, Icon, IconName, InputGroup, Slider } from "@blueprintjs/core"; +import { BaseExample, handleStringChange } from "@blueprintjs/docs-theme"; + +export interface IIconExampleState { + iconName: IconName; + iconSize: number; +} + +export class IconExample extends BaseExample { + public state: IIconExampleState = { + iconName: "calendar", + iconSize: Icon.SIZE_STANDARD, + }; + + protected renderExample() { + return ; + } + + protected renderOptions() { + return [ + [ + , + , + , + ], + ]; + } + + private handleIconSizeChange = (iconSize: number) => this.setState({ iconSize }); + + // tslint:disable-next-line:member-ordering + private handleIconNameChange = handleStringChange((iconName: IconName) => this.setState({ iconName })); +} diff --git a/packages/docs-app/src/examples/core-examples/index.ts b/packages/docs-app/src/examples/core-examples/index.ts index 6cc577fbba..ab7fe19db3 100644 --- a/packages/docs-app/src/examples/core-examples/index.ts +++ b/packages/docs-app/src/examples/core-examples/index.ts @@ -20,6 +20,7 @@ export * from "./editableTextExample"; export * from "./focusExample"; export * from "./hotkeyPiano"; export * from "./hotkeyTester"; +export * from "./iconExample"; export * from "./menuExample"; export * from "./navbarExample"; export * from "./numericInputBasicExample"; diff --git a/packages/docs-app/src/examples/datetime-examples/dateRangePickerExample.tsx b/packages/docs-app/src/examples/datetime-examples/dateRangePickerExample.tsx index 377c1391ef..d02da1c3ce 100644 --- a/packages/docs-app/src/examples/datetime-examples/dateRangePickerExample.tsx +++ b/packages/docs-app/src/examples/datetime-examples/dateRangePickerExample.tsx @@ -96,7 +96,7 @@ export class DateRangePickerExample extends BaseExample
- +
From a0f70da25c9571f72add6810d1781b6742910d4d Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Tue, 23 Jan 2018 21:15:06 -0800 Subject: [PATCH 16/45] back to iconSize --- packages/core/src/components/alert/alert.tsx | 2 +- packages/core/src/components/dialog/dialog.tsx | 2 +- .../components/non-ideal-state/nonIdealState.tsx | 2 +- packages/core/src/components/tag-input/tagInput.tsx | 6 +++++- packages/core/test/icon/iconTests.tsx | 13 ++++++++----- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/alert/alert.tsx b/packages/core/src/components/alert/alert.tsx index 4741657de1..09dc678a5e 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/dialog/dialog.tsx b/packages/core/src/components/dialog/dialog.tsx index 615aa92005..40db68d6e4 100644 --- a/packages/core/src/components/dialog/dialog.tsx +++ b/packages/core/src/components/dialog/dialog.tsx @@ -112,7 +112,7 @@ export class Dialog extends AbstractPureComponent { } return (
- +
{title}
{this.maybeRenderCloseButton()}
diff --git a/packages/core/src/components/non-ideal-state/nonIdealState.tsx b/packages/core/src/components/non-ideal-state/nonIdealState.tsx index 6a83e7f3e0..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 5dc400394b..f1fcc270bc 100644 --- a/packages/core/src/components/tag-input/tagInput.tsx +++ b/packages/core/src/components/tag-input/tagInput.tsx @@ -199,7 +199,11 @@ export class TagInput extends AbstractPureComponent - + {values.map(this.maybeRenderTag)} ", () => { it("width=16 renders standard size", () => - assertIconClass(, Classes.ICON_STANDARD)); + assertIconClass( + , + Classes.ICON_STANDARD, + )); - it("width=20 renders large size", () => - assertIconClass(, Classes.ICON_LARGE)); + it("iconSize=20 renders large size", () => + assertIconClass(, Classes.ICON_LARGE)); - it("width=inherit renders auto-size", () => - assertIconClass(, Classes.ICON)); + // it("iconSize=inherit renders auto-size", () => + // assertIconClass(, Classes.ICON)); it("renders intent class", () => assertIconClass(, Classes.INTENT_DANGER)); From fe13ed7c3bbeea3328ac02040269aec12e881a2c Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Tue, 23 Jan 2018 21:21:02 -0800 Subject: [PATCH 17/45] one more, InputGroup needs a css fix --- packages/core/src/components/forms/inputGroup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/forms/inputGroup.tsx b/packages/core/src/components/forms/inputGroup.tsx index 0e1986a196..297c574165 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 - + Date: Tue, 23 Jan 2018 22:53:46 -0800 Subject: [PATCH 18/45] requires, remove tslints --- packages/node-build-scripts/generate-icons-source | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/node-build-scripts/generate-icons-source b/packages/node-build-scripts/generate-icons-source index fd7c431bab..b4508ef59c 100755 --- a/packages/node-build-scripts/generate-icons-source +++ b/packages/node-build-scripts/generate-icons-source @@ -8,9 +8,10 @@ const fs = require("fs"); const path = require("path"); const SVGO = require("svgo"); -const svgo = new SVGO({ plugins: [{ convertShapeToPath: { convertArcs: true } }] }); const { COPYRIGHT_HEADER } = require("./constants"); +const svgo = new SVGO({ plugins: [{ convertShapeToPath: { convertArcs: true } }] }); + /** * @typedef {Object} IconMetadata * @property {string} name - icon name for display @@ -57,7 +58,6 @@ writeLinesToFile("iconStrings.ts", ...buildTSObject("IconContents", icon => icon "iconSvgPaths.ts", 'import { IconName } from "./iconName";', "", - "// tslint:disable:prettier", "export const IconSvgPaths16: Record = {", ...(await buildPathsObject("IconSvgPaths", 16)), "};", @@ -98,7 +98,6 @@ function toEnumName(icon) { */ function buildTSObject(objectName, valueGetter) { return [ - "// tslint:disable:object-literal-sort-keys", `export const ${objectName} = {`, ...ICONS_METADATA.map(icon => ` ${toEnumName(icon)}: "${valueGetter(icon)}",`), "};", From 2fd23d6fa6a784800a3f2e282c45ee086e6b84ee Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Tue, 23 Jan 2018 22:54:10 -0800 Subject: [PATCH 19/45] iconSize <= 16 ? use 16 : use 20 --- packages/core/src/components/icon/icon.tsx | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/core/src/components/icon/icon.tsx b/packages/core/src/components/icon/icon.tsx index d82498b844..2be5e52138 100644 --- a/packages/core/src/components/icon/icon.tsx +++ b/packages/core/src/components/icon/icon.tsx @@ -34,29 +34,27 @@ export class Icon extends React.PureComponent {iconName} - {this.renderSvgPaths(pathsSize === Icon.SIZE_STANDARD ? IconSvgPaths16 : IconSvgPaths20)} + {this.renderSvgPaths(pathsSize)} ); } - private determineIconDimension() { - const { iconSize = 16 } = this.props; - if (numberDiff(Icon.SIZE_STANDARD, iconSize) < numberDiff(Icon.SIZE_LARGE, iconSize)) { - return Icon.SIZE_STANDARD; - } - return Icon.SIZE_LARGE; + private determinePathsSize() { + const { iconSize = Icon.SIZE_STANDARD } = this.props; + return iconSize <= Icon.SIZE_STANDARD ? Icon.SIZE_STANDARD : Icon.SIZE_LARGE; } - private renderSvgPaths(svgPaths: Record) { + private renderSvgPaths(pathsSize: number) { + const svgPaths = pathsSize === Icon.SIZE_STANDARD ? IconSvgPaths16 : IconSvgPaths20; const paths = svgPaths[this.props.iconName.replace("pt-icon-", "") as IconName]; if (paths == null) { return null; @@ -64,7 +62,3 @@ export class Icon extends React.PureComponent ); } } - -function numberDiff(target: number, actual: number) { - return Math.abs(Math.round(actual / target) - actual / target); -} From f99ee7004c1b564b573b27b7cd1c6643cb3388d7 Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Tue, 23 Jan 2018 23:07:53 -0800 Subject: [PATCH 20/45] color + style props, spread all svg props --- packages/core/src/components/icon/icon.tsx | 25 +++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/core/src/components/icon/icon.tsx b/packages/core/src/components/icon/icon.tsx index 2be5e52138..d1b6356beb 100644 --- a/packages/core/src/components/icon/icon.tsx +++ b/packages/core/src/components/icon/icon.tsx @@ -13,6 +13,11 @@ 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. @@ -25,34 +30,34 @@ export interface IIconProps extends IIntentProps, IProps { * and chooses the appropriate resolution based on this prop. */ iconSize?: number; + + /** CSS style properties. */ + style?: React.CSSProperties; } -export class Icon extends React.PureComponent, never> { +export class Icon extends React.PureComponent> { public static displayName = "Blueprint.Icon"; public static readonly SIZE_STANDARD = 16; public static readonly SIZE_LARGE = 20; public render() { - const { className, iconName, iconSize = Icon.SIZE_STANDARD, intent } = this.props; + const { className, iconName, iconSize = Icon.SIZE_STANDARD, intent, ...svgProps } = this.props; if (iconName == null) { return null; } - const pathsSize = this.determinePathsSize(); + // choose which pixel grid is most appropriate for given icon size + const pixelGridSize = iconSize <= Icon.SIZE_STANDARD ? Icon.SIZE_STANDARD : Icon.SIZE_LARGE; const classes = classNames(Classes.ICON, Classes.iconClass(iconName), Classes.intentClass(intent), className); + const viewBox = `0 0 ${pixelGridSize} ${pixelGridSize}`; return ( - + {iconName} - {this.renderSvgPaths(pathsSize)} + {this.renderSvgPaths(pixelGridSize)} ); } - private determinePathsSize() { - const { iconSize = Icon.SIZE_STANDARD } = this.props; - return iconSize <= Icon.SIZE_STANDARD ? Icon.SIZE_STANDARD : Icon.SIZE_LARGE; - } - private renderSvgPaths(pathsSize: number) { const svgPaths = pathsSize === Icon.SIZE_STANDARD ? IconSvgPaths16 : IconSvgPaths20; const paths = svgPaths[this.props.iconName.replace("pt-icon-", "") as IconName]; From fc5dd1171c75c53bf536921913ca72e9beed72fc Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Wed, 24 Jan 2018 11:51:23 -0800 Subject: [PATCH 21/45] fix React prop warnings, remove invalid clipRule (not inside clipPath) --- packages/core/src/components/icon/icon.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/icon/icon.tsx b/packages/core/src/components/icon/icon.tsx index d1b6356beb..b70cda7cb3 100644 --- a/packages/core/src/components/icon/icon.tsx +++ b/packages/core/src/components/icon/icon.tsx @@ -64,6 +64,6 @@ export class Icon extends React.PureComponent ); + return paths.map((d, i) => ); } } From 3f900a784561ebafd492e895af61674966efa708 Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Wed, 24 Jan 2018 11:51:32 -0800 Subject: [PATCH 22/45] fix icons in input groups - padding only! --- packages/core/src/components/forms/_input-group.scss | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/forms/_input-group.scss b/packages/core/src/components/forms/_input-group.scss index 2f39875358..efce1f68bb 100644 --- a/packages/core/src/components/forms/_input-group.scss +++ b/packages/core/src/components/forms/_input-group.scss @@ -83,8 +83,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 +144,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 { From 99b11e967bd64e02a78f40971111ff9f44cf0068 Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Wed, 24 Jan 2018 11:52:09 -0800 Subject: [PATCH 23/45] improve IconExample with IconSuggest --- .../examples/core-examples/iconExample.tsx | 69 ++++++++++++++++--- packages/docs-app/src/styles/_examples.scss | 6 ++ 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/packages/docs-app/src/examples/core-examples/iconExample.tsx b/packages/docs-app/src/examples/core-examples/iconExample.tsx index 48b48929f7..46cf6044d9 100644 --- a/packages/docs-app/src/examples/core-examples/iconExample.tsx +++ b/packages/docs-app/src/examples/core-examples/iconExample.tsx @@ -6,46 +6,97 @@ import * as React from "react"; -import { Classes, Icon, IconName, InputGroup, Slider } from "@blueprintjs/core"; +import { Classes, Icon, MenuItem, Slider } from "@blueprintjs/core"; import { BaseExample, handleStringChange } from "@blueprintjs/docs-theme"; +import { IconClasses, IconName } from "@blueprintjs/icons"; +import { ISelectItemRendererProps, Suggest } from "@blueprintjs/select"; +import * as classNames from "classnames"; export interface IIconExampleState { iconName: IconName; iconSize: number; + query: string; } export class IconExample extends BaseExample { public state: IIconExampleState = { iconName: "calendar", iconSize: Icon.SIZE_STANDARD, + query: "calendar", }; protected renderExample() { - return ; + return ( +
+ +
+ ); } protected renderOptions() { + const { iconName, iconSize, query } = this.state; + const suggestInputProps = { + leftIconName: query === iconName ? iconName : "blank", + // control suggest value so it can have initial value from state + onChange: this.handleQueryChange, + value: query, + }; return [ [ -
+
+
SVG icons in 2.0
+ Blueprint 2.0 introduced SVG icon support throughout the ecosystem, 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 legacy, + and we plan to remove the icon fonts 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 @@ -11,8 +19,47 @@ 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 legacy, and we plan to remove them 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` @@ -32,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/icons/src/index.md b/packages/icons/src/index.md index bf7e82543d..c04a8af08b 100644 --- a/packages/icons/src/index.md +++ b/packages/icons/src/index.md @@ -4,17 +4,15 @@ reference: icons @# Icons -Blueprint provides over 300 UI icons in an icon font. They come in two sizes, 16px and 20px, and can -be used anywhere text is used. It's easy to change their color or apply effects like text shadows -via standard CSS properties. +Blueprint provides over 300 vector UI icons in two sizes (16px and 20px) and two formats (SVG and fonts). +It's easy to change their color or apply effects like text shadows via standard SVG or CSS properties. There are two ways of using Blueprint UI icons, described in more detail in the [**Icon component documentation**](#core/components/icon): -1. React component: `` -2. CSS classes: `` +1. React component renders SVG paths: `` +2. CSS classes use icon fonts: `` -Many Blueprint [components](#core/components) provide an `iconName` prop, which supports both the -full name `pt-icon-projects` and the short name `projects`. +Many Blueprint [components](#core/components) support an `iconName` prop to control a React `` component, which accepts both the full name `pt-icon-projects` and the short name `projects`. @reactDocs Icons From daa8c5666f942656e0975c68bcdf55f2b620e915 Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Wed, 31 Jan 2018 17:01:04 -0800 Subject: [PATCH 37/45] put short IconName in data-icon attribute --- packages/core/src/components/icon/icon.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/icon/icon.tsx b/packages/core/src/components/icon/icon.tsx index e85a5b8f51..9c73253139 100644 --- a/packages/core/src/components/icon/icon.tsx +++ b/packages/core/src/components/icon/icon.tsx @@ -43,10 +43,12 @@ export class Icon extends React.PureComponent= Icon.SIZE_LARGE ? Icon.SIZE_LARGE : Icon.SIZE_STANDARD; const classes = classNames(Classes.ICON, Classes.intentClass(intent), className); @@ -61,17 +63,17 @@ export class Icon extends React.PureComponent {iconName} - {this.renderSvgPaths(pixelGridSize)} + {this.renderSvgPaths(pixelGridSize, iconName)} ); } - private renderSvgPaths(pathsSize: number) { - const svgPaths = pathsSize === Icon.SIZE_STANDARD ? IconSvgPaths16 : IconSvgPaths20; - const paths = svgPaths[this.props.iconName.replace("pt-icon-", "") as IconName]; - if (paths == null) { + private renderSvgPaths(pathsSize: number, iconName: IconName) { + const svgPathsRecord = pathsSize === Icon.SIZE_STANDARD ? IconSvgPaths16 : IconSvgPaths20; + const pathStrings = svgPathsRecord[iconName]; + if (pathStrings == null) { return null; } - return paths.map((d, i) => ); + return pathStrings.map((d, i) => ); } } From de3013b1ed0518c4092b193e79b4db6e9a72b2f4 Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Wed, 31 Jan 2018 17:01:09 -0800 Subject: [PATCH 38/45] fix tests --- .../core/test/controls/numericInputTests.tsx | 13 +++++------- packages/core/test/icon/iconTests.tsx | 21 +++++++------------ 2 files changed, 13 insertions(+), 21 deletions(-) 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/icon/iconTests.tsx b/packages/core/test/icon/iconTests.tsx index c3f3b2fa0e..514196a6d5 100644 --- a/packages/core/test/icon/iconTests.tsx +++ b/packages/core/test/icon/iconTests.tsx @@ -8,25 +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", () => - assertIconSize(, Icon.SIZE_STANDARD)); + assertIconSize(, Icon.SIZE_STANDARD)); it("iconSize=20 renders large size", () => - assertIconSize(, Icon.SIZE_LARGE)); + 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(); @@ -34,10 +32,8 @@ describe("", () => { }); /** Asserts that rendered icon has given className. */ - function assertIconClass(icon: React.ReactElement, className: string) { - const wrapper = shallow(icon); - assert.isTrue(wrapper.hasClass(className)); - return wrapper; + function assertIcon(icon: React.ReactElement, iconName: IconName) { + assert.strictEqual(shallow(icon).text(), iconName); } /** Asserts that rendered icon has width/height equal to size. */ @@ -45,6 +41,5 @@ describe("", () => { const wrapper = shallow(icon); assert.strictEqual(wrapper.prop("width"), size); assert.strictEqual(wrapper.prop("height"), size); - return wrapper; } }); From ab45534fb47f6ec3338631896ff1230705f2398d Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Wed, 31 Jan 2018 18:08:31 -0800 Subject: [PATCH 39/45] fix Icon props spread --- packages/core/src/components/icon/icon.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/icon/icon.tsx b/packages/core/src/components/icon/icon.tsx index 9c73253139..b8a64324dd 100644 --- a/packages/core/src/components/icon/icon.tsx +++ b/packages/core/src/components/icon/icon.tsx @@ -46,8 +46,8 @@ export class Icon extends React.PureComponent= Icon.SIZE_LARGE ? Icon.SIZE_LARGE : Icon.SIZE_STANDARD; From a2ab9522268d980da742376d481462f1cb493430 Mon Sep 17 00:00:00 2001 From: Gilad Gray Date: Wed, 31 Jan 2018 18:09:18 -0800 Subject: [PATCH 40/45] examples/core-examples/common/IconSelect .pt-label supports .pt-popover-wrapper --- .../core/src/components/forms/_label.scss | 10 ++- .../core-examples/common/iconSelect.tsx | 74 +++++++++++++++++++ .../examples/core-examples/iconExample.tsx | 56 ++------------ 3 files changed, 89 insertions(+), 51 deletions(-) create mode 100644 packages/docs-app/src/examples/core-examples/common/iconSelect.tsx 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/docs-app/src/examples/core-examples/common/iconSelect.tsx b/packages/docs-app/src/examples/core-examples/common/iconSelect.tsx new file mode 100644 index 0000000000..4ea927734b --- /dev/null +++ b/packages/docs-app/src/examples/core-examples/common/iconSelect.tsx @@ -0,0 +1,74 @@ +/* + * Copyright 2016 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the terms of the LICENSE file distributed with this project. + */ + +import { Button, Classes, MenuItem } from "@blueprintjs/core"; +import { IconClasses, IconName } from "@blueprintjs/icons"; +import { ItemRenderer, Select } from "@blueprintjs/select"; +import * as classNames from "classnames"; +import * as React from "react"; + +export interface IIconSelectProps { + iconName?: IconName; + onChange: (iconName?: IconName) => void; +} + +const NONE = "(none)"; +type IconType = IconName | typeof NONE; +const ICON_NAMES = Object.keys(IconClasses).map( + (name: keyof typeof IconClasses) => IconClasses[name].replace("pt-icon-", "") as IconName, +); +ICON_NAMES.push(NONE); + +const TypedSelect = Select.ofType(); + +export class IconSelect extends React.PureComponent { + public render() { + const { iconName } = this.props; + return ( +