Files
flipper/src/ui/components/Button.js
Daniel Büchele 726966fdc0 convert to emotion
Summary:
My benchmarks have shown react-emotion to be faster than the current implementation of `styled`. For this reason, I am converting all styling to [emotion](https://emotion.sh).

Benchmark results:
{F136839093}

The syntax is very similar between the two libraries. The main difference is that emotion only allows a single function for the whole style attribute, whereas the old implementation had functions for every style-attirbute.

Before:
```
{
  color: props => props.color,
  fontSize: props => props.size,
}
```

After:
```
props => ({
  color: props.color,
  fontSize: props.size,
})
```

Reviewed By: jknoxville

Differential Revision: D9479893

fbshipit-source-id: 2c39e4618f7e52ceacb67bbec8ae26114025723f
2018-08-23 09:42:18 -07:00

357 lines
9.0 KiB
JavaScript

/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import Glyph from './Glyph.js';
import styled from '../styled/index.js';
import {findDOMNode} from 'react-dom';
import PropTypes from 'prop-types';
import {colors} from './colors.js';
import {connect} from 'react-redux';
import electron from 'electron';
import {keyframes} from 'react-emotion';
const borderColor = props => {
if (!props.windowIsFocused) {
return colors.macOSTitleBarButtonBorderBlur;
} else if (props.type === 'danger') {
return colors.red;
} else if (props.depressed) {
return colors.macOSTitleBarButtonBorderBottom;
} else {
return colors.macOSTitleBarButtonBorder;
}
};
const borderBottomColor = props => {
if (!props.windowIsFocused) {
return colors.macOSTitleBarButtonBorderBlur;
} else if (props.type === 'danger') {
return colors.red;
} else {
return colors.macOSTitleBarButtonBorderBottom;
}
};
const backgroundImage = props => {
if (props.windowIsFocused) {
if (props.depressed) {
return `linear-gradient(to bottom, ${
colors.macOSTitleBarBorderBlur
} 1px, ${colors.macOSTitleBarButtonBorderBlur} 0%, ${
colors.macOSTitleBarButtonBackgroundActive
} 100%)`;
} else {
return `linear-gradient(to bottom, transparent 0%,${
colors.macOSTitleBarButtonBackground
} 100%)`;
}
} else {
return 'none';
}
};
const color = props => {
if (props.type === 'danger' && props.windowIsFocused) {
return colors.red;
} else if (props.disabled) {
return colors.macOSTitleBarIconBlur;
} else {
return colors.light50;
}
};
const pulse = keyframes({
'0%': {
boxShadow: `0 0 4px 0 ${colors.macOSTitleBarIconSelected}`,
},
'70%': {
boxShadow: '0 0 4px 6px transparent',
},
'100%': {
boxShadow: '0 0 4px 0 transparent',
},
});
const StyledButton = styled('div')(props => ({
backgroundColor: !props.windowIsFocused
? colors.macOSTitleBarButtonBackgroundBlur
: colors.white,
backgroundImage: backgroundImage(props),
borderStyle: 'solid',
borderWidth: 1,
borderColor: borderColor(props),
borderBottomColor: borderBottomColor(props),
fontSize: props.compact === true ? 11 : '1em',
color: color(props),
borderRadius: 4,
position: 'relative',
padding: '0 6px',
height: props.compact === true ? 24 : 28,
margin: 0,
marginLeft: props.inButtonGroup === true ? 0 : 10,
minWidth: 34,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
boxShadow:
props.pulse && props.windowIsFocused
? `0 0 0 ${colors.macOSTitleBarIconSelected}`
: '',
animation: props.pulse && props.windowIsFocused ? `${pulse} 1s infinite` : '',
'&:not(:first-child)': {
borderTopLeftRadius: props.inButtonGroup === true ? 0 : 4,
borderBottomLeftRadius: props.inButtonGroup === true ? 0 : 4,
},
'&:not(:last-child)': {
borderTopRightRadius: props.inButtonGroup === true ? 0 : 4,
borderBottomRightRadius: props.inButtonGroup === true ? 0 : 4,
borderRight: props.inButtonGroup === true ? 0 : '',
},
'&:first-of-type': {
marginLeft: 0,
},
'&:active': {
borderColor: colors.macOSTitleBarButtonBorder,
borderBottomColor: colors.macOSTitleBarButtonBorderBottom,
background: `linear-gradient(to bottom, ${
colors.macOSTitleBarButtonBackgroundActiveHighlight
} 1px, ${colors.macOSTitleBarButtonBackgroundActive} 0%, ${
colors.macOSTitleBarButtonBorderBlur
} 100%)`,
},
'&:disabled': {
borderColor: borderColor(props),
borderBottomColor: borderBottomColor(props),
pointerEvents: 'none',
},
'&:hover::before': {
content: props.dropdown ? "''" : '',
position: 'absolute',
bottom: 1,
right: 2,
borderStyle: 'solid',
borderWidth: '4px 3px 0 3px',
borderColor: `${
colors.macOSTitleBarIcon
} transparent transparent transparent`,
},
}));
const Icon = styled(Glyph)(({hasText}) => ({
marginRight: hasText ? 3 : 0,
}));
type Props = {
/**
* onClick handler.
*/
onClick?: (event: SyntheticMouseEvent<>) => any,
/**
* Whether this button is disabled.
*/
disabled?: boolean,
/**
* Whether this button is large. Increases padding and line-height.
*/
large?: boolean,
/**
* Whether this button is compact. Decreases padding and line-height.
*/
compact?: boolean,
/**
* Type of button.
*/
type?: 'primary' | 'success' | 'warning' | 'danger',
/**
* Children.
*/
children?: React$Node,
/**
* Dropdown menu template shown on click.
*/
dropdown?: Array<Electron$MenuItemOptions>,
/**
* Name of the icon dispalyed next to the text
*/
icon?: string,
iconSize?: number,
/**
* For toggle buttons, if the button is selected
*/
selected?: boolean,
/**
* Button is pulsing
*/
pulse?: boolean,
/**
* URL to open in the browser on click
*/
href?: string,
/**
* Whether the button should render depressed into its socket
*/
depressed?: boolean,
};
type State = {
active: boolean,
};
/**
* Simple button.
*
* **Usage**
*
* ```jsx
* import {Button} from 'sonar';
* <Button onClick={handler}>Click me</Button>
* ```
*
* @example Default button
* <Button>Click me</Button>
* @example Primary button
* <Button type="primary">Click me</Button>
* @example Success button
* <Button type="success">Click me</Button>
* @example Warning button
* <Button type="warning">Click me</Button>
* @example Danger button
* <Button type="danger">Click me</Button>
* @example Default solid button
* <Button solid={true}>Click me</Button>
* @example Primary solid button
* <Button type="primary" solid={true}>Click me</Button>
* @example Success solid button
* <Button type="success" solid={true}>Click me</Button>
* @example Warning solid button
* <Button type="warning" solid={true}>Click me</Button>
* @example Danger solid button
* <Button type="danger" solid={true}>Click me</Button>
* @example Compact button
* <Button compact={true}>Click me</Button>
* @example Large button
* <Button large={true}>Click me</Button>
* @example Disabled button
* <Button disabled={true}>Click me</Button>
*/
class Button extends React.Component<
Props & {windowIsFocused: boolean},
State,
> {
static contextTypes = {
inButtonGroup: PropTypes.bool,
};
state = {
active: false,
};
_ref: ?Element | ?Text;
onMouseDown = () => this.setState({active: true});
onMouseUp = () => this.setState({active: false});
onClick = (e: SyntheticMouseEvent<>) => {
if (this.props.disabled === true) {
return;
}
if (this.props.dropdown) {
const menu = electron.remote.Menu.buildFromTemplate(this.props.dropdown);
const position = {};
if (this._ref != null && this._ref instanceof Element) {
const {left, bottom} = this._ref.getBoundingClientRect();
position.x = parseInt(left, 10);
position.y = parseInt(bottom + 6, 10);
}
menu.popup({
window: electron.remote.getCurrentWindow(),
async: true,
...position,
});
}
if (this.props.onClick) {
this.props.onClick(e);
}
if (this.props.href != null) {
electron.shell.openExternal(this.props.href);
}
};
setRef = (ref: ?React.ElementRef<any>) => {
this._ref = findDOMNode(ref);
};
render() {
const {
icon,
children,
selected,
iconSize,
windowIsFocused,
...props
} = this.props;
const {active} = this.state;
let color = colors.macOSTitleBarIcon;
if (props.disabled === true) {
color = colors.macOSTitleBarIconBlur;
} else if (windowIsFocused && selected === true) {
color = colors.macOSTitleBarIconSelected;
} else if (!windowIsFocused && (selected == null || selected === false)) {
color = colors.macOSTitleBarIconBlur;
} else if (!windowIsFocused && selected === true) {
color = colors.macOSTitleBarIconSelectedBlur;
} else if (selected == null && active) {
color = colors.macOSTitleBarIconActive;
} else if (props.type === 'danger') {
color = colors.red;
}
let iconComponent;
if (icon != null) {
iconComponent = (
<Icon
name={icon}
size={
iconSize != null ? iconSize : this.props.compact === true ? 12 : 16
}
color={color}
hasText={Boolean(children)}
/>
);
}
return (
<StyledButton
{...props}
ref={this.setRef}
windowIsFocused={windowIsFocused}
onClick={this.onClick}
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
inButtonGroup={this.context.inButtonGroup}>
{iconComponent}
{children}
</StyledButton>
);
}
}
const ConnectedButton = connect(({application: {windowIsFocused}}) => ({
windowIsFocused,
}))(Button);
// $FlowFixMe
export default (ConnectedButton: StyledComponent<Props>);