-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathIcon.jsx
237 lines (220 loc) · 6.75 KB
/
Icon.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/**
* TEAM: frontend_infra
* WATCHERS: notandrewkaye
* @flow strict
*/
/* eslint-disable react/prefer-stateless-function */
import * as React from "react";
import {css, StyleSheet} from "aphrodite";
import classnames from "classnames";
import invariant from "./tools/invariant";
import {type IconNames, iconData} from "./tools/icons";
import colors, {type Color} from "./colors";
import deprecatedColors, {
type DeprecatedColor,
} from "./styles/deprecatedColorConstants";
import transmissionDeprecatedColors from "./dispatch/styles/colors";
import iconSizes from "./iconSizes";
import ThemeNameContext, {
type Theme,
TRANSMISSION,
} from "./context/ThemeNameContext";
export type {IconNames};
export type StandardIconSizes =
| "xxxs"
| "xxs"
| "xs"
| "s"
| "m"
| "l"
| "xl"
| "xxl";
type Props = {|
/** The size of the icon represents an enum that defines the width and height of the square icon. */
+size?: StandardIconSizes,
/** A specific number that overrides `size` and sets a specific width and height (commonly used for irregularly large icons). */
+customSize?: number,
/** The name of the icon you intend to use. */
+iconName: IconNames,
/** Please do not pass classnames to components via a prop like this. */
+className?: string,
/** Icons inherit text color by default but can be any one of our approved colors. */
+color?: Color | "inherit",
+deprecatedColor?: DeprecatedColor,
/* eslint-enable react/require-default-props */
/**
* `baseline` is the default alignment prop since most icons are displayed
* inline with text at the same size at the text. Sometimes, icons need to be
* larger or smaller than the text; in these scenarios `center` alignment
* should be used.
*/
+alignment: "baseline" | "center",
/** Whether the ARIA accessible title should be included. */
+isARIAAccessible: boolean,
/** Pre-css-in-js world icons could willy nilly inherit color from many sources. This is bad and we want to avoid at all costs. This prop defaults to retaining the old behavior. */
+deprecatedAllowColorInheritance: boolean,
|};
/**
* @short Inline SVG iconography
* @brandStatus V2
* @status In Review
* @category General
*
* Our current icon list can be accessed from our [Icon Guidelines](/design/guidelines/iconography).
* @extends React.Component */
class Icon extends React.PureComponent<Props> {
static contextType = ThemeNameContext;
static defaultProps = {
alignment: "baseline",
isARIAAccessible: false,
deprecatedAllowColorInheritance: true,
};
render() {
const {
iconName,
className,
color,
deprecatedColor,
alignment,
isARIAAccessible,
deprecatedAllowColorInheritance,
} = this.props;
const icon = iconData[iconName];
// $FlowFixMe(uforic): This is here to double check.
invariant(
icon,
`IconName received an invalid value. There is no option for "${iconName}". Check tools/icons.jsx for valid options.`
);
const isElaborate = !!icon.elaboratePath;
const iconColor = getIconColor(this.context, color, deprecatedColor);
// HACK(@kaye): the "svgIcon" classname is needed because of OOCSS color inheritance
const classes = classnames(css(styles.wrapper), className, {
svgIcon: deprecatedAllowColorInheritance,
});
const viewBox = "0 0 64 64";
const size = this.props.size || null;
const customSize =
this.props.customSize != null ? this.props.customSize : null;
// proportions are set on the svg if we know the specific size intended.
// Pass undefined if no size is defined to omit the attributes.
// Browsers such as chrome throw errors if width/height have
// empty values
const proportion = size ? iconSizes[size] : undefined;
return (
<span
className={classes}
style={{
...getIconSize(size, customSize),
...{fill: iconColor},
}}
>
<svg
className={css(
alignment === "center" ? styles.svgCenter : styles.svgBaseline
)}
color={iconColor}
viewBox={viewBox}
width={proportion}
height={proportion}
role="img"
aria-hidden="true"
aria-labelledby="title desc"
style={{
width: getDimensions(proportion, customSize),
height: getDimensions(proportion, customSize),
}}
>
<title id="title">{isARIAAccessible ? icon.name : ""}</title>
<desc id="desc">{icon.description}</desc>
<g fillRule="nonzero">
{isElaborate
? icon.elaboratePath
: icon.path.map((value, index) => (
<path
// eslint-disable-next-line react/no-array-index-key
key={index}
d={value}
role="presentation"
fillRule="evenodd"
/>
))}
</g>
</svg>
</span>
);
}
}
export default Icon;
/**
* Passing a `customSize` will override the icon's `1em` sizing
* (which is the default) and will normally allow the svg to inherit it's
* size from the text-size.
*/
const getIconSize = (size: ?StandardIconSizes, customSize: ?number) => {
if (customSize != null && !size) {
return {
width: customSize,
height: customSize,
};
} else if (!size) {
return {
width: "1em",
height: "1em",
};
}
const width = iconSizes[size];
const height = iconSizes[size];
return {
width,
height,
};
};
const getDimensions = (proportion: ?number, customSize: ?number) => {
if (proportion != null) {
// When `size` is defined, the width and height *styles* should be reset to "unset".
return "unset";
} else if (customSize != null) {
// When `customSize` is defined, the width and height *styles* should be defined on the SVG.
return customSize;
}
// When no `size` or `customSize` is specified, the icon should inherit it's size.
return "1em";
};
const getIconColor = (
theme: Theme,
color?: Color | "inherit",
deprecatedColor?: DeprecatedColor
) => {
if (color == null) {
// fall back to using deprecated colors
if (deprecatedColor) {
if (theme === TRANSMISSION) {
return transmissionDeprecatedColors[deprecatedColor];
}
return deprecatedColors[deprecatedColor];
}
return "unset";
}
if (color === "inherit") {
return "inherit";
}
return colors[color];
};
export const styles = StyleSheet.create({
wrapper: {
display: "inline-block",
position: "relative",
},
svgCenter: {
position: "absolute",
left: 0,
top: 0,
bottom: 0,
},
svgBaseline: {
position: "absolute",
bottom: "-0.125em",
left: 0,
},
});
export const _test = {Icon};