1 | /*
|
2 | * Copyright 2017 Palantir Technologies, Inc. All rights reserved.
|
3 | *
|
4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | * you may not use this file except in compliance with the License.
|
6 | * You may obtain a copy of the License at
|
7 | *
|
8 | * http://www.apache.org/licenses/LICENSE-2.0
|
9 | *
|
10 | * Unless required by applicable law or agreed to in writing, software
|
11 | * distributed under the License is distributed on an "AS IS" BASIS,
|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13 | * See the License for the specific language governing permissions and
|
14 | * limitations under the License.
|
15 | */
|
16 |
|
17 | import classNames from "classnames";
|
18 | import * as React from "react";
|
19 | import { polyfill } from "react-lifecycles-compat";
|
20 |
|
21 | import { IconName, IconSvgPaths16, IconSvgPaths20 } from "@blueprintjs/icons";
|
22 |
|
23 | import { AbstractPureComponent2, Classes, DISPLAYNAME_PREFIX, IntentProps, Props, MaybeElement } from "../../common";
|
24 |
|
25 | export { IconName };
|
26 |
|
27 | export enum IconSize {
|
28 | STANDARD = 16,
|
29 | LARGE = 20,
|
30 | }
|
31 |
|
32 | // eslint-disable-next-line deprecation/deprecation
|
33 | export type IconProps = IIconProps;
|
34 | /** @deprecated use IconProps */
|
35 | export interface IIconProps extends IntentProps, Props {
|
36 | /** This component does not support custom children. Use the `icon` prop. */
|
37 | children?: never;
|
38 |
|
39 | /**
|
40 | * Color of icon. This is used as the `fill` attribute on the `<svg>` image
|
41 | * so it will override any CSS `color` property, including that set by
|
42 | * `intent`. If this prop is omitted, icon color is inherited from
|
43 | * surrounding text.
|
44 | */
|
45 | color?: string;
|
46 |
|
47 | /**
|
48 | * String for the `title` attribute on the rendered element, which will appear
|
49 | * on hover as a native browser tooltip.
|
50 | */
|
51 | htmlTitle?: string;
|
52 |
|
53 | /**
|
54 | * Name of a Blueprint UI icon, or an icon element, to render. This prop is
|
55 | * required because it determines the content of the component, but it can
|
56 | * be explicitly set to falsy values to render nothing.
|
57 | *
|
58 | * - If `null` or `undefined` or `false`, this component will render nothing.
|
59 | * - If given an `IconName` (a string literal union of all icon names), that
|
60 | * icon will be rendered as an `<svg>` with `<path>` tags. Unknown strings
|
61 | * will render a blank icon to occupy space.
|
62 | * - If given a `JSX.Element`, that element will be rendered and _all other
|
63 | * props on this component are ignored._ This type is supported to
|
64 | * simplify icon support in other Blueprint components. As a consumer, you
|
65 | * should avoid using `<Icon icon={<Element />}` directly; simply render
|
66 | * `<Element />` instead.
|
67 | */
|
68 | icon: IconName | MaybeElement;
|
69 |
|
70 | /**
|
71 | * @deprecated use size prop instead
|
72 | */
|
73 | iconSize?: number;
|
74 |
|
75 | /**
|
76 | * Size of the icon, in pixels. Blueprint contains 16px and 20px SVG icon
|
77 | * images, and chooses the appropriate resolution based on this prop.
|
78 | *
|
79 | * @default IconSize.STANDARD = 16
|
80 | */
|
81 | size?: number;
|
82 |
|
83 | /** CSS style properties. */
|
84 | style?: React.CSSProperties;
|
85 |
|
86 | /**
|
87 | * HTML tag to use for the rendered element.
|
88 | *
|
89 | * @default "span"
|
90 | */
|
91 | tagName?: keyof JSX.IntrinsicElements;
|
92 |
|
93 | /**
|
94 | * Description string. This string does not appear in normal browsers, but
|
95 | * it increases accessibility. For instance, screen readers will use it for
|
96 | * aural feedback.
|
97 | *
|
98 | * If this value is nullish, `false`, or an empty string, the component will assume
|
99 | * that the icon is decorative and `aria-hidden="true"` will be applied.
|
100 | *
|
101 | * @see https://www.w3.org/WAI/tutorials/images/decorative/
|
102 | */
|
103 | title?: string | false | null;
|
104 | }
|
105 |
|
106 |
|
107 | export class Icon extends AbstractPureComponent2<IconProps & Omit<React.HTMLAttributes<HTMLElement>, "title">> {
|
108 | public static displayName = `${DISPLAYNAME_PREFIX}.Icon`;
|
109 |
|
110 | /** @deprecated use IconSize.STANDARD */
|
111 | public static readonly SIZE_STANDARD = IconSize.STANDARD;
|
112 |
|
113 | /** @deprecated use IconSize.LARGE */
|
114 | public static readonly SIZE_LARGE = IconSize.LARGE;
|
115 |
|
116 | public render(): JSX.Element | null {
|
117 | const { icon } = this.props;
|
118 | if (icon == null || typeof icon === "boolean") {
|
119 | return null;
|
120 | } else if (typeof icon !== "string") {
|
121 | return icon;
|
122 | }
|
123 |
|
124 | const {
|
125 | className,
|
126 | color,
|
127 | htmlTitle,
|
128 | // eslint-disable-next-line deprecation/deprecation
|
129 | iconSize,
|
130 | intent,
|
131 | size = iconSize ?? IconSize.STANDARD,
|
132 | title,
|
133 | tagName = "span",
|
134 | ...htmlprops
|
135 | } = this.props;
|
136 |
|
137 | // choose which pixel grid is most appropriate for given icon size
|
138 | const pixelGridSize = size >= IconSize.LARGE ? IconSize.LARGE : IconSize.STANDARD;
|
139 | // render path elements, or nothing if icon name is unknown.
|
140 | const paths = this.renderSvgPaths(pixelGridSize, icon);
|
141 |
|
142 | // eslint-disable-next-line deprecation/deprecation
|
143 | const classes = classNames(Classes.ICON, Classes.iconClass(icon), Classes.intentClass(intent), className);
|
144 | const viewBox = `0 0 ${pixelGridSize} ${pixelGridSize}`;
|
145 |
|
146 | return React.createElement(
|
147 | tagName,
|
148 | {
|
149 | ...htmlprops,
|
150 | "aria-hidden": title ? undefined : true,
|
151 | className: classes,
|
152 | title: htmlTitle,
|
153 | },
|
154 | <svg fill={color} data-icon={icon} width={size} height={size} viewBox={viewBox}>
|
155 | {title && <desc>{title}</desc>}
|
156 | {paths}
|
157 | </svg>,
|
158 | );
|
159 | }
|
160 |
|
161 | /** Render `<path>` elements for the given icon name. Returns `null` if name is unknown. */
|
162 | private renderSvgPaths(pathsSize: number, iconName: IconName): JSX.Element[] | null {
|
163 | const svgPathsRecord = pathsSize === IconSize.STANDARD ? IconSvgPaths16 : IconSvgPaths20;
|
164 | const pathStrings = svgPathsRecord[iconName];
|
165 | if (pathStrings == null) {
|
166 | return null;
|
167 | }
|
168 | return pathStrings.map((d, i) => <path key={i} d={d} fillRule="evenodd" />);
|
169 | }
|
170 | }
|