convert buttons to React hooks

Summary: Converted Buttons to hooks, to make it easier to use context in the future. No further changes.

Reviewed By: cekkaewnumchai

Differential Revision: D23812921

fbshipit-source-id: 3739ad49e734dbe4d903a23d58da7cc267f6e109
This commit is contained in:
Michel Weststrate
2020-09-22 12:01:46 -07:00
committed by Facebook GitHub Bot
parent fdd2151532
commit b256bc68fa
3 changed files with 96 additions and 136 deletions

View File

@@ -7,16 +7,15 @@
* @format * @format
*/ */
import React from 'react'; import React, {useContext, useState, useRef, useCallback} from 'react';
import PropTypes from 'prop-types';
import electron, {MenuItemConstructorOptions} from 'electron'; import electron, {MenuItemConstructorOptions} from 'electron';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import {colors} from './colors'; import {colors} from './colors';
import {connect} from 'react-redux';
import {findDOMNode} from 'react-dom'; import {findDOMNode} from 'react-dom';
import {keyframes} from 'emotion'; import {keyframes} from 'emotion';
import {State as Store} from '../../reducers/index';
import Glyph, {IconSize} from './Glyph'; import Glyph, {IconSize} from './Glyph';
import {ButtonGroupContext} from './ButtonGroup';
import {useStore} from '../../utils/useStore';
type ButtonType = 'primary' | 'success' | 'warning' | 'danger'; type ButtonType = 'primary' | 'success' | 'warning' | 'danger';
@@ -190,7 +189,7 @@ const Icon = styled(Glyph)<{hasText: boolean}>(({hasText}) => ({
})); }));
Icon.displayName = 'Button:Icon'; Icon.displayName = 'Button:Icon';
type OwnProps = { type Props = {
/** /**
* onMouseUp handler. * onMouseUp handler.
*/ */
@@ -257,46 +256,39 @@ type OwnProps = {
padded?: boolean; padded?: boolean;
} & React.HTMLProps<HTMLDivElement>; } & React.HTMLProps<HTMLDivElement>;
type State = {
active: boolean;
wasClosed: boolean;
};
type StateFromProps = {windowIsFocused: boolean};
type Props = OwnProps & StateFromProps;
/** /**
* A simple button, used in many parts of the application. * A simple button, used in many parts of the application.
*/ */
class Button extends React.Component<Props, State> { export default function Button(props: Props) {
static contextTypes = { const windowIsFocused = useStore(
inButtonGroup: PropTypes.bool, (state) => state.application.windowIsFocused,
}; );
const inButtonGroup = useContext(ButtonGroupContext);
const [active, setActive] = useState(false);
const [wasClosed, setWasClosed] = useState(false);
state = { const _ref = useRef<React.Component<typeof StyledButton>>();
active: false,
wasClosed: false,
};
_ref = React.createRef<React.Component<typeof StyledButton>>(); const onMouseDown = useCallback(
(e: React.MouseEvent) => {
setActive(true);
setWasClosed(false);
props.onMouseDown?.(e);
},
[props.onMouseDown],
);
onMouseDown = (e: React.MouseEvent) => { const onMouseUp = useCallback(() => {
this.setState({active: true, wasClosed: false}); if (props.disabled === true) {
if (this.props.onMouseDown != null) {
this.props.onMouseDown(e);
}
};
onMouseUp = () => {
if (this.props.disabled === true) {
return; return;
} }
if (this.props.dropdown && !this.state.wasClosed) { if (props.dropdown && !wasClosed) {
const menu = electron.remote.Menu.buildFromTemplate(this.props.dropdown); const menu = electron.remote.Menu.buildFromTemplate(props.dropdown);
const position: { const position: {
x?: number; x?: number;
y?: number; y?: number;
} = {}; } = {};
const {current} = this._ref; const {current} = _ref;
if (current) { if (current) {
const node = findDOMNode(current); const node = findDOMNode(current);
if (node instanceof Element) { if (node instanceof Element) {
@@ -311,36 +303,28 @@ class Button extends React.Component<Props, State> {
async: true, async: true,
...position, ...position,
callback: () => { callback: () => {
this.setState({wasClosed: true}); setWasClosed(true);
}, },
}); });
} }
this.setState({active: false, wasClosed: false}); setActive(false);
}; setWasClosed(false);
}, [props.disabled, props.dropdown, wasClosed]);
onClick = (e: React.MouseEvent) => { const onClick = useCallback(
if (this.props.disabled === true) { (e: React.MouseEvent) => {
if (props.disabled === true) {
return; return;
} }
if (this.props.onClick) { props.onClick?.(e);
this.props.onClick(e); if (props.href != null) {
electron.shell.openExternal(props.href);
} }
if (this.props.href != null) { },
electron.shell.openExternal(this.props.href); [props.disabled, props.onClick, props.href],
} );
};
render() { const {icon, children, selected, iconSize, iconVariant, ...restProps} = props;
const {
icon,
children,
selected,
iconSize,
windowIsFocused,
iconVariant,
...props
} = this.props;
const {active} = this.state;
let color = colors.macOSTitleBarIcon; let color = colors.macOSTitleBarIcon;
if (props.disabled === true) { if (props.disabled === true) {
@@ -362,7 +346,7 @@ class Button extends React.Component<Props, State> {
iconComponent = ( iconComponent = (
<Icon <Icon
name={icon} name={icon}
size={iconSize || (this.props.compact === true ? 12 : 16)} size={iconSize || (props.compact === true ? 12 : 16)}
color={color} color={color}
variant={iconVariant || 'filled'} variant={iconVariant || 'filled'}
hasText={Boolean(children)} hasText={Boolean(children)}
@@ -372,22 +356,15 @@ class Button extends React.Component<Props, State> {
return ( return (
<StyledButton <StyledButton
{...props} {...restProps}
ref={this._ref as any} ref={_ref as any}
windowIsFocused={windowIsFocused} windowIsFocused={windowIsFocused}
onClick={this.onClick} onClick={onClick}
onMouseDown={this.onMouseDown} onMouseDown={onMouseDown}
onMouseUp={this.onMouseUp} onMouseUp={onMouseUp}
inButtonGroup={this.context.inButtonGroup}> inButtonGroup={inButtonGroup}>
{iconComponent} {iconComponent}
{children} {children}
</StyledButton> </StyledButton>
); );
}
} }
export default connect<StateFromProps, {}, OwnProps, Store>(
({application: {windowIsFocused}}) => ({
windowIsFocused,
}),
)(Button);

View File

@@ -8,8 +8,7 @@
*/ */
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import React, {Component} from 'react'; import React, {createContext} from 'react';
import PropTypes from 'prop-types';
const ButtonGroupContainer = styled.div({ const ButtonGroupContainer = styled.div({
display: 'inline-flex', display: 'inline-flex',
@@ -20,6 +19,8 @@ const ButtonGroupContainer = styled.div({
}); });
ButtonGroupContainer.displayName = 'ButtonGroup:ButtonGroupContainer'; ButtonGroupContainer.displayName = 'ButtonGroup:ButtonGroupContainer';
export const ButtonGroupContext = createContext(false);
/** /**
* Group a series of buttons together. * Group a series of buttons together.
* *
@@ -31,18 +32,10 @@ ButtonGroupContainer.displayName = 'ButtonGroup:ButtonGroupContainer';
* </ButtonGroup> * </ButtonGroup>
* ``` * ```
*/ */
export default class ButtonGroup extends Component<{ export default function ButtonGroup({children}: {children: React.ReactNode}) {
children: React.ReactNode; return (
}> { <ButtonGroupContext.Provider value={true}>
static childContextTypes = { <ButtonGroupContainer>{children}</ButtonGroupContainer>
inButtonGroup: PropTypes.bool, </ButtonGroupContext.Provider>
}; );
getChildContext() {
return {inButtonGroup: true};
}
render() {
return <ButtonGroupContainer>{this.props.children}</ButtonGroupContainer>;
}
} }

View File

@@ -7,10 +7,10 @@
* @format * @format
*/ */
import React, {Component} from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import Glyph from './Glyph'; import Glyph from './Glyph';
import {ButtonGroupContext} from './ButtonGroup';
const IconContainer = styled.div({ const IconContainer = styled.div({
width: 0, width: 0,
@@ -68,19 +68,9 @@ type Props = {
* </ButtonGroupChain> * </ButtonGroupChain>
* ``` * ```
*/ */
export default class ButtonGroupChain extends Component<Props> { export default function ButtonGroupChain({children, iconSize, icon}: Props) {
static childContextTypes = {
inButtonGroup: PropTypes.bool,
};
getChildContext() {
return {inButtonGroup: true};
}
render() {
const {children, iconSize, icon} = this.props;
return ( return (
<ButtonGroupContext.Provider value={true}>
<ButtonGroupChainContainer iconSize={iconSize}> <ButtonGroupChainContainer iconSize={iconSize}>
{React.Children.map(children, (child, idx) => { {React.Children.map(children, (child, idx) => {
if (idx === 0) { if (idx === 0) {
@@ -96,6 +86,6 @@ export default class ButtonGroupChain extends Component<Props> {
); );
})} })}
</ButtonGroupChainContainer> </ButtonGroupChainContainer>
</ButtonGroupContext.Provider>
); );
}
} }