From b256bc68fa1cbcb360469549f2f93c98fbd9ed78 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Tue, 22 Sep 2020 12:01:46 -0700 Subject: [PATCH] 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 --- desktop/app/src/ui/components/Button.tsx | 183 ++++++++---------- desktop/app/src/ui/components/ButtonGroup.tsx | 25 +-- .../src/ui/components/ButtonGroupChain.tsx | 24 +-- 3 files changed, 96 insertions(+), 136 deletions(-) diff --git a/desktop/app/src/ui/components/Button.tsx b/desktop/app/src/ui/components/Button.tsx index 3b0dede8e..cca6759df 100644 --- a/desktop/app/src/ui/components/Button.tsx +++ b/desktop/app/src/ui/components/Button.tsx @@ -7,16 +7,15 @@ * @format */ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, {useContext, useState, useRef, useCallback} from 'react'; import electron, {MenuItemConstructorOptions} from 'electron'; import styled from '@emotion/styled'; import {colors} from './colors'; -import {connect} from 'react-redux'; import {findDOMNode} from 'react-dom'; import {keyframes} from 'emotion'; -import {State as Store} from '../../reducers/index'; import Glyph, {IconSize} from './Glyph'; +import {ButtonGroupContext} from './ButtonGroup'; +import {useStore} from '../../utils/useStore'; type ButtonType = 'primary' | 'success' | 'warning' | 'danger'; @@ -190,7 +189,7 @@ const Icon = styled(Glyph)<{hasText: boolean}>(({hasText}) => ({ })); Icon.displayName = 'Button:Icon'; -type OwnProps = { +type Props = { /** * onMouseUp handler. */ @@ -257,46 +256,39 @@ type OwnProps = { padded?: boolean; } & React.HTMLProps; -type State = { - 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 { - static contextTypes = { - inButtonGroup: PropTypes.bool, - }; +export default function Button(props: Props) { + const windowIsFocused = useStore( + (state) => state.application.windowIsFocused, + ); + const inButtonGroup = useContext(ButtonGroupContext); + const [active, setActive] = useState(false); + const [wasClosed, setWasClosed] = useState(false); - state = { - active: false, - wasClosed: false, - }; + const _ref = useRef>(); - _ref = React.createRef>(); + const onMouseDown = useCallback( + (e: React.MouseEvent) => { + setActive(true); + setWasClosed(false); + props.onMouseDown?.(e); + }, + [props.onMouseDown], + ); - onMouseDown = (e: React.MouseEvent) => { - this.setState({active: true, wasClosed: false}); - if (this.props.onMouseDown != null) { - this.props.onMouseDown(e); - } - }; - onMouseUp = () => { - if (this.props.disabled === true) { + const onMouseUp = useCallback(() => { + if (props.disabled === true) { return; } - if (this.props.dropdown && !this.state.wasClosed) { - const menu = electron.remote.Menu.buildFromTemplate(this.props.dropdown); + if (props.dropdown && !wasClosed) { + const menu = electron.remote.Menu.buildFromTemplate(props.dropdown); const position: { x?: number; y?: number; } = {}; - const {current} = this._ref; + const {current} = _ref; if (current) { const node = findDOMNode(current); if (node instanceof Element) { @@ -311,83 +303,68 @@ class Button extends React.Component { async: true, ...position, 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) => { - if (this.props.disabled === true) { - return; - } - if (this.props.onClick) { - this.props.onClick(e); - } - if (this.props.href != null) { - electron.shell.openExternal(this.props.href); - } - }; + const onClick = useCallback( + (e: React.MouseEvent) => { + if (props.disabled === true) { + return; + } + props.onClick?.(e); + if (props.href != null) { + electron.shell.openExternal(props.href); + } + }, + [props.disabled, props.onClick, props.href], + ); - render() { - const { - icon, - children, - selected, - iconSize, - windowIsFocused, - iconVariant, - ...props - } = this.props; - const {active} = this.state; + const {icon, children, selected, iconSize, iconVariant, ...restProps} = props; - 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 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} - + let iconComponent; + if (icon != null) { + iconComponent = ( + ); } -} -export default connect( - ({application: {windowIsFocused}}) => ({ - windowIsFocused, - }), -)(Button); + return ( + + {iconComponent} + {children} + + ); +} diff --git a/desktop/app/src/ui/components/ButtonGroup.tsx b/desktop/app/src/ui/components/ButtonGroup.tsx index 02cf35ca5..5ad07ef0f 100644 --- a/desktop/app/src/ui/components/ButtonGroup.tsx +++ b/desktop/app/src/ui/components/ButtonGroup.tsx @@ -8,8 +8,7 @@ */ import styled from '@emotion/styled'; -import React, {Component} from 'react'; -import PropTypes from 'prop-types'; +import React, {createContext} from 'react'; const ButtonGroupContainer = styled.div({ display: 'inline-flex', @@ -20,6 +19,8 @@ const ButtonGroupContainer = styled.div({ }); ButtonGroupContainer.displayName = 'ButtonGroup:ButtonGroupContainer'; +export const ButtonGroupContext = createContext(false); + /** * Group a series of buttons together. * @@ -31,18 +32,10 @@ ButtonGroupContainer.displayName = 'ButtonGroup:ButtonGroupContainer'; * * ``` */ -export default class ButtonGroup extends Component<{ - children: React.ReactNode; -}> { - static childContextTypes = { - inButtonGroup: PropTypes.bool, - }; - - getChildContext() { - return {inButtonGroup: true}; - } - - render() { - return {this.props.children}; - } +export default function ButtonGroup({children}: {children: React.ReactNode}) { + return ( + + {children} + + ); } diff --git a/desktop/app/src/ui/components/ButtonGroupChain.tsx b/desktop/app/src/ui/components/ButtonGroupChain.tsx index 71b2fefd8..6d9363f66 100644 --- a/desktop/app/src/ui/components/ButtonGroupChain.tsx +++ b/desktop/app/src/ui/components/ButtonGroupChain.tsx @@ -7,10 +7,10 @@ * @format */ -import React, {Component} from 'react'; -import PropTypes from 'prop-types'; +import React from 'react'; import styled from '@emotion/styled'; import Glyph from './Glyph'; +import {ButtonGroupContext} from './ButtonGroup'; const IconContainer = styled.div({ width: 0, @@ -68,19 +68,9 @@ type Props = { * * ``` */ -export default class ButtonGroupChain extends Component { - static childContextTypes = { - inButtonGroup: PropTypes.bool, - }; - - getChildContext() { - return {inButtonGroup: true}; - } - - render() { - const {children, iconSize, icon} = this.props; - - return ( +export default function ButtonGroupChain({children, iconSize, icon}: Props) { + return ( + {React.Children.map(children, (child, idx) => { if (idx === 0) { @@ -96,6 +86,6 @@ export default class ButtonGroupChain extends Component { ); })} - ); - } + + ); }