Button components

Summary: _typescript_

Reviewed By: passy, bnelo12

Differential Revision: D16830539

fbshipit-source-id: a44ad0914b2581648b06e421476e0ba31ae96992
This commit is contained in:
Daniel Büchele
2019-08-20 05:40:31 -07:00
committed by Facebook Github Bot
parent eaceddbb32
commit 5cb12c3b1f
5 changed files with 175 additions and 159 deletions

View File

@@ -5,15 +5,16 @@
* @format
*/
import * as React from 'react';
import Glyph from './Glyph.tsx';
import React from 'react';
import PropTypes from 'prop-types';
import electron from 'electron';
import styled from '../styled/index.js';
import {colors} from './colors.tsx';
import electron, {MenuItemConstructorOptions} from 'electron';
import styled from 'react-emotion';
import {colors} from './colors';
import {connect} from 'react-redux';
import {findDOMNode} from 'react-dom';
import {keyframes} from 'react-emotion';
import {State as Store} from '../../reducers/index';
import Glyph, {IconSize} from './Glyph';
const borderColor = props => {
if (!props.windowIsFocused) {
@@ -84,7 +85,16 @@ const pulse = keyframes({
},
});
const StyledButton = styled('div')(props => ({
const StyledButton = styled('div')(
(props: {
windowIsFocused?: boolean;
compact?: boolean;
inButtonGroup?: boolean;
padded?: boolean;
pulse?: boolean;
disabled?: boolean;
dropdown?: Array<MenuItemConstructorOptions>;
}) => ({
backgroundColor: !props.windowIsFocused
? colors.macOSTitleBarButtonBackgroundBlur
: colors.white,
@@ -110,7 +120,8 @@ const StyledButton = styled('div')(props => ({
props.pulse && props.windowIsFocused
? `0 0 0 ${colors.macOSTitleBarIconSelected}`
: '',
animation: props.pulse && props.windowIsFocused ? `${pulse} 1s infinite` : '',
animation:
props.pulse && props.windowIsFocused ? `${pulse} 1s infinite` : '',
'&:not(:first-child)': {
borderTopLeftRadius: props.inButtonGroup === true ? 0 : 4,
@@ -156,91 +167,92 @@ const StyledButton = styled('div')(props => ({
colors.macOSTitleBarIcon
} transparent transparent transparent`,
},
}));
}),
);
const Icon = styled(Glyph)(({hasText}) => ({
const Icon = styled(Glyph)(({hasText}: {hasText: boolean}) => ({
marginRight: hasText ? 3 : 0,
}));
type Props = {
type OwnProps = {
/**
* onMouseUp handler.
*/
onMouseDown?: (event: SyntheticMouseEvent<>) => any,
onMouseDown?: (event: React.MouseEvent) => any;
/**
* onClick handler.
*/
onClick?: (event: SyntheticMouseEvent<>) => any,
onClick?: (event: React.MouseEvent) => any;
/**
* Whether this button is disabled.
*/
disabled?: boolean,
disabled?: boolean;
/**
* Whether this button is large. Increases padding and line-height.
*/
large?: boolean,
large?: boolean;
/**
* Whether this button is compact. Decreases padding and line-height.
*/
compact?: boolean,
compact?: boolean;
/**
* Type of button.
*/
type?: 'primary' | 'success' | 'warning' | 'danger',
type?: 'primary' | 'success' | 'warning' | 'danger';
/**
* Children.
*/
children?: React$Node,
children?: React.ReactNode;
/**
* Dropdown menu template shown on click.
*/
dropdown?: Array<MenuItemConstructorOptions>,
dropdown?: Array<MenuItemConstructorOptions>;
/**
* Name of the icon dispalyed next to the text
*/
icon?: string,
icon?: string;
/**
* Size of the icon in pixels.
*/
iconSize?: number,
iconSize?: IconSize;
/**
* For toggle buttons, if the button is selected
*/
selected?: boolean,
selected?: boolean;
/**
* Button is pulsing
*/
pulse?: boolean,
pulse?: boolean;
/**
* URL to open in the browser on click
*/
href?: string,
href?: string;
/**
* Whether the button should render depressed into its socket
*/
depressed?: boolean,
depressed?: boolean;
/**
* Style of the icon. `filled` is the default
*/
iconVariant?: 'filled' | 'outline',
iconVariant?: 'filled' | 'outline';
/**
* Whether the button should have additional padding left and right.
*/
padded?: boolean,
padded?: boolean;
};
type State = {
active: boolean,
wasClosed: boolean,
active: boolean;
wasClosed: boolean;
};
type StateFromProps = {windowIsFocused: boolean};
type Props = OwnProps & StateFromProps;
/**
* A simple button, used in many parts of the application.
*/
class Button extends React.Component<
Props & {windowIsFocused: boolean},
State,
> {
class Button extends React.Component<Props, State> {
static contextTypes = {
inButtonGroup: PropTypes.bool,
};
@@ -250,9 +262,9 @@ class Button extends React.Component<
wasClosed: false,
};
_ref = React.createRef();
_ref = React.createRef<React.Component<typeof StyledButton>>();
onMouseDown = (e: SyntheticMouseEvent<>) => {
onMouseDown = (e: React.MouseEvent) => {
this.setState({active: true, wasClosed: false});
if (this.props.onMouseDown != null) {
this.props.onMouseDown(e);
@@ -264,18 +276,22 @@ class Button extends React.Component<
}
if (this.props.dropdown && !this.state.wasClosed) {
const menu = electron.remote.Menu.buildFromTemplate(this.props.dropdown);
const position = {};
const position: {
x?: number;
y?: number;
} = {};
const {current} = this._ref;
if (current) {
const node = findDOMNode(current);
if (node instanceof Element) {
const {left, bottom} = node.getBoundingClientRect();
position.x = parseInt(left, 10);
position.y = parseInt(bottom + 6, 10);
position.x = left;
position.y = bottom + 6;
}
}
menu.popup({
window: electron.remote.getCurrentWindow(),
// @ts-ignore: async is private API in electron
async: true,
...position,
callback: () => {
@@ -286,7 +302,7 @@ class Button extends React.Component<
this.setState({active: false, wasClosed: false});
};
onClick = (e: SyntheticMouseEvent<>) => {
onClick = (e: React.MouseEvent) => {
if (this.props.disabled === true) {
return;
}
@@ -341,7 +357,7 @@ class Button extends React.Component<
return (
<StyledButton
{...props}
ref={this._ref}
ref={this._ref as any}
windowIsFocused={windowIsFocused}
onClick={this.onClick}
onMouseDown={this.onMouseDown}
@@ -354,9 +370,8 @@ class Button extends React.Component<
}
}
const ConnectedButton = connect(({application: {windowIsFocused}}) => ({
export default connect<StateFromProps, {}, OwnProps, Store>(
({application: {windowIsFocused}}) => ({
windowIsFocused,
}))(Button);
// $FlowFixMe
export default (ConnectedButton: StyledComponent<Props>);
}),
)(Button);

View File

@@ -5,9 +5,8 @@
* @format
*/
import styled from '../styled/index.js';
import {Component} from 'react';
import styled from 'react-emotion';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
const ButtonGroupContainer = styled('div')({
@@ -30,8 +29,12 @@ const ButtonGroupContainer = styled('div')({
* ```
*/
export default class ButtonGroup extends Component<{
children: React$Node,
children: React.ReactNode;
}> {
static childContextTypes = {
inButtonGroup: PropTypes.bool,
};
getChildContext() {
return {inButtonGroup: true};
}
@@ -40,7 +43,3 @@ export default class ButtonGroup extends Component<{
return <ButtonGroupContainer>{this.props.children}</ButtonGroupContainer>;
}
}
ButtonGroup.childContextTypes = {
inButtonGroup: PropTypes.bool,
};

View File

@@ -6,9 +6,8 @@
*/
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import styled from '../styled/index.js';
import Glyph from './Glyph.tsx';
import styled from 'react-emotion';
import Glyph from './Glyph';
const IconContainer = styled('div')({
width: 0,
@@ -18,7 +17,8 @@ const IconContainer = styled('div')({
pointerEvents: 'none',
});
const ButtonGroupChainContainer = styled('div')(props => ({
const ButtonGroupChainContainer = styled('div')(
(props: {iconSize: number}) => ({
display: 'inline-flex',
marginLeft: 10,
'&:first-child>*:not(:first-child):nth-child(odd)': {
@@ -34,21 +34,22 @@ const ButtonGroupChainContainer = styled('div')(props => ({
'&:first-child>:last-child': {
borderRightStyle: 'solid',
},
}));
}),
);
type Props = {
/**
* Children.
*/
children: React$Node,
children: React.ReactNode;
/**
* Size of the button seperator icon in pixels.
*/
iconSize: 8 | 10 | 12 | 16 | 18 | 20 | 24 | 32,
iconSize: 8 | 10 | 12 | 16 | 18 | 20 | 24 | 32;
/**
* Name of the icon seperating the buttons. Defaults to 'chevron-right'.
*/
icon?: string,
icon?: string;
};
/**
@@ -65,6 +66,10 @@ type Props = {
* ```
*/
export default class ButtonGroupChain extends Component<Props> {
static childContextTypes = {
inButtonGroup: PropTypes.bool,
};
getChildContext() {
return {inButtonGroup: true};
}
@@ -91,7 +96,3 @@ export default class ButtonGroupChain extends Component<Props> {
);
}
}
ButtonGroupChain.childContextTypes = {
inButtonGroup: PropTypes.bool,
};

View File

@@ -5,22 +5,23 @@
* @format
*/
import ButtonGroup from './ButtonGroup.js';
import Button from './Button.js';
import ButtonGroup from './ButtonGroup';
import Button from './Button';
import React from 'react';
/**
* Button group to navigate back and forth.
*/
export default function ButtonNavigationGroup(props: {|
export default function ButtonNavigationGroup(props: {
/** Back button is enabled */
canGoBack: boolean,
canGoBack: boolean;
/** Forwards button is enabled */
canGoForward: boolean,
canGoForward: boolean;
/** Callback when back button is clicked */
onBack: () => void,
onBack: () => void;
/** Callback when forwards button is clicked */
onForward: () => void,
|}) {
onForward: () => void;
}) {
return (
<ButtonGroup>
<Button disabled={!props.canGoBack} onClick={props.onBack}>

View File

@@ -6,13 +6,13 @@
*/
export {default as styled} from 'react-emotion';
export {default as Button} from './components/Button.js';
export {default as Button} from './components/Button.tsx';
export {default as ToggleButton} from './components/ToggleSwitch.tsx';
export {
default as ButtonNavigationGroup,
} from './components/ButtonNavigationGroup.js';
export {default as ButtonGroup} from './components/ButtonGroup.js';
export {default as ButtonGroupChain} from './components/ButtonGroupChain.js';
} from './components/ButtonNavigationGroup.tsx';
export {default as ButtonGroup} from './components/ButtonGroup.tsx';
export {default as ButtonGroupChain} from './components/ButtonGroupChain.tsx';
//
export {colors, darkColors, brandColors} from './components/colors.tsx';