Skip to content

Commit 9bea3ad

Browse files
committed
feat(icons): make icons themeable (breaking change) (#726)
* feat(icons): make icons themeable (breaking change)
1 parent c87d581 commit 9bea3ad

File tree

21 files changed

+153
-117
lines changed

21 files changed

+153
-117
lines changed

src/components/buttons/flat-button/flat-button.js

+7
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,15 @@ import AccessibleButton from '../accessible-button';
1111
const getIconElement = props => {
1212
if (!props.icon) return null;
1313

14+
let iconColor = 'solid';
15+
if (props.isDisabled) iconColor = 'neutral60';
16+
else if (props.tone === 'primary') iconColor = 'primary';
17+
else if (props.tone === 'secondary' && props.isMouseOver)
18+
iconColor = 'warning';
19+
1420
return React.cloneElement(props.icon, {
1521
size: 'medium',
22+
color: iconColor,
1623
});
1724
};
1825

src/components/buttons/icon-button/icon-button.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,17 @@ import {
1717
// Gets the color which the icon should have based on context of button's state/cursor behavior
1818
const getIconThemeColor = props => {
1919
const isActive = props.isToggleButton && props.isToggled;
20+
2021
// if button has a theme, icon should be white when hovering/clicking
2122
if (props.theme !== 'default' && isActive) {
2223
if (props.isDisabled) {
23-
return 'grey';
24+
return 'neutral60';
2425
}
25-
return 'white';
26+
return 'surface';
2627
}
2728

28-
// if button is disabled, icon should be grey
29-
if (props.isDisabled) return 'grey';
29+
// if button is disabled, icon should be neutral60
30+
if (props.isDisabled) return 'neutral60';
3031
// if button is not disabled nor has a theme, return icon's default color
3132
return props.icon.props.theme;
3233
};
@@ -71,7 +72,7 @@ export const IconButton = props => {
7172
{props.icon &&
7273
React.cloneElement(props.icon, {
7374
size: props.size,
74-
theme: getIconThemeColor(props),
75+
color: getIconThemeColor(props),
7576
})}
7677
</AccessibleButton>
7778
);

src/components/buttons/link-button/link-button.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const LinkBody = props => (
4949
{Boolean(props.iconLeft) &&
5050
React.cloneElement(props.iconLeft, {
5151
size: 'medium',
52-
theme: props.isDisabled ? 'grey' : 'green',
52+
color: props.isDisabled ? 'neutral60' : 'primary',
5353
})}
5454
<Text.Body isInline={true}>{props.label}</Text.Body>
5555
</Spacings.Inline>

src/components/buttons/primary-button/primary-button.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const PrimaryButton = props => {
4343
`}
4444
>
4545
{React.cloneElement(props.iconLeft, {
46-
theme: props.isDisabled ? 'grey' : 'white',
46+
color: props.isDisabled ? 'neutral60' : 'surface',
4747
size: props.size === 'small' ? 'medium' : 'big',
4848
})}
4949
</span>

src/components/buttons/secondary-button/secondary-button.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ import filterDataAttributes from '../../../utils/filter-data-attributes';
1313
import { getStateStyles, getThemeStyles } from './secondary-button.styles';
1414

1515
// Gets the color which the icon should have based on context of button's state/cursor behavior
16-
export const getIconThemeColor = props => {
16+
export const getIconColor = props => {
1717
const isActive = props.isToggleButton && props.isToggled;
1818
// if button has a theme, icon should be the same color as the theme on active state
1919
if (props.theme !== 'default' && (isActive && !props.isDisabled))
20-
return props.theme; // returns the passed in theme without overwriting
20+
return 'info'; // returns the passed in theme without overwriting
2121
// if button is disabled, icon should be grey
22-
if (props.isDisabled) return 'grey';
22+
if (props.isDisabled) return 'neutral60';
2323
// if button is not disabled nor has a theme, return icon's default color
24-
return props.iconLeft.props.theme;
24+
return props.iconLeft.props.color;
2525
};
2626

2727
export const SecondaryButton = props => {
@@ -77,7 +77,7 @@ export const SecondaryButton = props => {
7777
`}
7878
>
7979
{React.cloneElement(props.iconLeft, {
80-
theme: getIconThemeColor(props),
80+
color: getIconColor(props),
8181
})}
8282
</span>
8383
)}
@@ -123,7 +123,7 @@ SecondaryButton.propTypes = {
123123
`Invalid prop \`${propName}\` supplied to \`${componentName}\`. Only toggle buttons may have a theme.`
124124
);
125125
}
126-
return PropTypes.oneOf(['default', 'blue'])(
126+
return PropTypes.oneOf(['default', 'info'])(
127127
props,
128128
propName,
129129
componentName,

src/components/dropdowns/primary-action-dropdown/primary-action-dropdown.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class DropdownHead extends React.PureComponent {
7979
>
8080
{React.cloneElement(this.props.iconLeft, {
8181
size: 'big',
82-
theme: this.props.isDisabled ? 'grey' : 'black',
82+
color: this.props.isDisabled ? 'neutral60' : 'solid',
8383
})}
8484
</span>
8585
<span
@@ -134,7 +134,7 @@ const DropdownChevron = React.forwardRef((props, ref) => (
134134
{React.cloneElement(
135135
props.isOpen && !props.isDisabled ? <CaretUpIcon /> : <CaretDownIcon />,
136136
{
137-
theme: props.isDisabled ? 'grey' : 'black',
137+
color: props.isDisabled ? 'neutral60' : 'solid',
138138
size: 'small',
139139
}
140140
)}

src/components/field-label/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { FieldLabel } from '@commercetools-frontend/ui-kit';
3030
/>
3131
```
3232

33-
The `hintIcon` also accepts a custom `theme` while defaulting to `orange` in the case above. The `hintIcon` does **not** support the `size` prop, and will always be rendered in the size `medium`.
33+
The `hintIcon` also accepts a custom `color` while defaulting to `warning` in the case above. The `hintIcon` does **not** support the `size` prop, and will always be rendered in the size `medium`.
3434

3535
```diff
3636
<FieldLabel
@@ -39,7 +39,7 @@ The `hintIcon` also accepts a custom `theme` while defaulting to `orange` in the
3939
onInfoButtonClick={() => {}} />}
4040
hint={<FormattedMessage {...messages.hint} />}
4141
- hintIcon={<WarningIcon />}
42-
+ hintIcon={<WarningIcon theme="green" />}
42+
+ hintIcon={<WarningIcon color="primary" />}
4343
description={<FormattedMessage {...messages.description} />}
4444
badge={<FlatButton tone="primary" label="show" />}
4545
htmlFor="sampleInput"

src/components/field-label/field-label.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const FieldLabel = props => {
4646
// FIXME: add proper tone when tones are refactored
4747
React.cloneElement(props.hintIcon, {
4848
size: 'medium',
49-
theme: props.hintIcon.props.theme || 'orange',
49+
color: props.hintIcon.props.color || 'warning',
5050
})}
5151
{props.hint && <Text.Detail>{props.hint}</Text.Detail>}
5252
</Spacings.Inline>

src/components/icons/README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ You can find a list of all available icons in the UIKit.
1919

2020
#### Properties
2121

22-
| Props | Type | Required | Values | Default | Description |
23-
| ------- | -------- | :------: | ------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------ |
24-
| `size` | `string` | | 'small', 'medium', 'big', 'scale' | 'big' | Specifies the icon size (if `scale` is selected, the dimensions will scale according with the parents) |
25-
| `theme` | `string` | | 'black', 'grey', 'blue', 'green', 'orange', 'red' | 'black' | Specifies the icon theme |
22+
| Props | Type | Required | Values | Default | Description |
23+
| ------- | -------- | :------: | ------------------------------------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------ |
24+
| `size` | `string` | | 'small', 'medium', 'big', 'scale' | 'big' | Specifies the icon size (if `scale` is selected, the dimensions will scale according with the parents) |
25+
| `color` | `string` | | 'solid', 'neutral60', 'info', 'primary', 'primary40', 'warning', 'error' | 'solid' | Specifies the icon color |
2626

2727
#### Where to use
2828

src/components/icons/create-styled-icon.js

+29-36
Original file line numberDiff line numberDiff line change
@@ -33,55 +33,48 @@ const getSizeStyle = size => {
3333
}
3434
};
3535

36-
const getColor = theme => {
37-
if (!theme) return 'inherit';
38-
switch (theme) {
39-
case 'black':
40-
return vars.colorSolid;
41-
case 'grey':
42-
return vars.colorNeutral60;
43-
case 'white':
44-
return vars.colorSurface;
45-
case 'blue':
46-
return vars.colorInfo;
47-
case 'green':
48-
return vars.colorPrimary;
49-
case 'green-light':
50-
return vars.colorPrimary40;
51-
case 'orange':
52-
return vars.colorWarning;
53-
case 'red':
54-
return vars.colorError;
55-
default: {
56-
invariant(
57-
theme,
58-
`ui-kit/Icon: the specified theme '${theme}' is not supported.`
59-
);
60-
return 'inherit';
61-
}
36+
const capitalize = s => s[0].toUpperCase() + s.slice(1);
37+
38+
const getColor = (color, theme) => {
39+
if (!color) return 'inherit';
40+
const overwrittenVars = {
41+
...vars,
42+
...theme,
43+
};
44+
45+
const iconColor = overwrittenVars[`color${capitalize(color)}`];
46+
47+
if (!iconColor) {
48+
invariant(
49+
color,
50+
`ui-kit/Icon: the specified color '${color}' is not supported.`
51+
);
52+
return 'inherit';
6253
}
54+
55+
return iconColor;
6356
};
6457

6558
export default function createStyledIcon(Component, displayName) {
6659
const StyledComponent = styled(Component)(
6760
props => `
6861
* {
69-
fill: ${getColor(props.theme)};
62+
fill: ${getColor(props.color, props.theme)};
7063
}
7164
${getSizeStyle(props.size)}
7265
`
7366
);
7467
StyledComponent.displayName = displayName;
7568
StyledComponent.propTypes = {
76-
theme: PropTypes.oneOf([
77-
'black',
78-
'grey',
79-
'white',
80-
'blue',
81-
'green',
82-
'green-light',
83-
'orange',
84-
'red',
69+
color: PropTypes.oneOf([
70+
'solid',
71+
'neutral60',
72+
'surface',
73+
'info',
74+
'primary',
75+
'primary40',
76+
'warning',
77+
'error',
8578
]),
8679
size: PropTypes.oneOf(['small', 'medium', 'big', 'scale']),
8780
};

src/components/icons/icon.story.js

+11-11
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,19 @@ storiesOf('Components|Icons', module)
5252
<IconContainer style={containerWidth}>
5353
<Icon
5454
size={sizeValue}
55-
theme={select(
56-
'theme',
55+
color={select(
56+
'color',
5757
[
58-
'black',
59-
'grey',
60-
'white',
61-
'blue',
62-
'green',
63-
'green-light',
64-
'orange',
65-
'red',
58+
'solid',
59+
'neutral60',
60+
'surface',
61+
'info',
62+
'primary',
63+
'primary40',
64+
'warning',
65+
'error',
6666
],
67-
'black'
67+
'solid'
6868
)}
6969
/>
7070
</IconContainer>

src/components/icons/icons.visualroute.js

+38-17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import styled from '@emotion/styled';
33
import { Switch, Route } from 'react-router-dom';
4+
import { ThemeProvider } from 'emotion-theming';
45
import * as UIKit from 'ui-kit';
56
import { Suite, Spec } from '../../../test/percy';
67

@@ -28,54 +29,74 @@ const IconContainer = styled.div`
2829
const icons = Object.keys(UIKit).filter(thing => thing.endsWith('Icon'));
2930

3031
const sizes = ['small', 'medium', 'big', 'scale'];
31-
const themes = [
32-
'black',
33-
'grey',
34-
'white',
35-
'blue',
36-
'green',
37-
'green-light',
38-
'orange',
39-
'red',
32+
33+
const colors = [
34+
'solid',
35+
'neutral60',
36+
'surface',
37+
'info',
38+
'primary',
39+
'primary40',
40+
'warning',
41+
'error',
4042
];
4143

4244
export const routePath = '/icons';
4345

44-
const renderIcon = (iconName, theme, size) => {
46+
const renderIcon = (iconName, color, size) => {
4547
const Icon = UIKit[iconName];
4648
return (
4749
<IconItem key={iconName}>
4850
<IconContainer big={size === 'scale'}>
49-
<Icon theme={theme} size={size} />
51+
<Icon color={color} size={size} />
5052
</IconContainer>
5153
<UIKit.Text.Body>{iconName}</UIKit.Text.Body>
5254
</IconItem>
5355
);
5456
};
5557

56-
export const component = () => (
58+
export const component = ({ themes }) => (
5759
<Switch>
58-
{themes.map(theme => (
60+
{colors.map(color => (
5961
<Route
60-
key={theme}
61-
path={`${routePath}/${theme}`}
62+
key={color}
63+
path={`${routePath}/${color}`}
6264
exact
6365
render={() => (
6466
<Suite>
6567
{sizes.map(size => (
6668
<Spec
6769
key={size}
68-
label={`All Icons - Theme: ${theme} / Size: ${size}`}
70+
label={`All Icons - Color: ${color} / Size: ${size}`}
6971
omitPropsList
7072
>
7173
<IconList>
72-
{icons.map(iconName => renderIcon(iconName, theme, size))}
74+
{icons.map(iconName => renderIcon(iconName, color, size))}
7375
</IconList>
7476
</Spec>
7577
))}
7678
</Suite>
7779
)}
7880
/>
7981
))}
82+
<Route
83+
exact
84+
path={`${routePath}/theme`}
85+
render={() => (
86+
<Suite>
87+
<ThemeProvider theme={themes.darkTheme}>
88+
{colors.map(color => (
89+
<Spec
90+
key={color}
91+
label={`Themed Icons - Color: ${color}`}
92+
omitPropsList
93+
>
94+
<IconList>{renderIcon('ClockIcon', color, 'big')}</IconList>
95+
</Spec>
96+
))}
97+
</ThemeProvider>
98+
</Suite>
99+
)}
100+
/>
80101
</Switch>
81102
);

0 commit comments

Comments
 (0)