/** * 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, /** * 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'; * * ``` * * @example Default button * * @example Primary button * * @example Success button * * @example Warning button * * @example Danger button * * @example Default solid button * * @example Primary solid button * * @example Success solid button * * @example Warning solid button * * @example Danger solid button * * @example Compact button * * @example Large button * * @example Disabled 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) => { 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 = ( ); } return ( {iconComponent} {children} ); } } const ConnectedButton = connect(({application: {windowIsFocused}}) => ({ windowIsFocused, }))(Button); // $FlowFixMe export default (ConnectedButton: StyledComponent);