From 59b4ec01f2607967582c50fcdd178cfc5a9ae9a3 Mon Sep 17 00:00:00 2001 From: matheusps Date: Wed, 3 Jun 2020 22:34:07 -0300 Subject: [PATCH 1/2] Fix button plain & spinner & mergeRef types --- react/components/ButtonPlain/index.tsx | 247 ++++++++++++++----------- react/components/Spinner/index.tsx | 28 ++- react/utilities/mergeRef/index.ts | 12 +- 3 files changed, 158 insertions(+), 129 deletions(-) diff --git a/react/components/ButtonPlain/index.tsx b/react/components/ButtonPlain/index.tsx index 18763c98c..ba4818573 100644 --- a/react/components/ButtonPlain/index.tsx +++ b/react/components/ButtonPlain/index.tsx @@ -1,97 +1,61 @@ -import React, { FunctionComponent, Fragment, useEffect, RefObject } from 'react' -import PropTypes, { InferProps } from 'prop-types' +/* eslint-disable @typescript-eslint/no-explicit-any */ +import React, { + Fragment, + useEffect, + MutableRefObject, + ReactNode, + FC, + useRef, +} from 'react' +import PropTypes from 'prop-types' import classNames from 'classnames' import Spinner from '../Spinner' -import { withForwardedRef, refShape } from '../../modules/withForwardedRef' +import { withForwardedRef } from '../../modules/withForwardedRef' +import mergeRef from '../../utilities/mergeRef' -const SIZE = { - SMALL: 'small', - REGULAR: 'regular', - LARGE: 'large', -} - -const VARIATION = { - PLAIN: 'plain', - INVERTED: 'inverted', - DANGER: 'danger', -} - -const loaderSize = { - [SIZE.SMALL]: 15, - [SIZE.REGULAR]: 20, - [SIZE.LARGE]: 25, -} +type MouseEventHandler = (e: React.MouseEvent) => void +type FocusEventHandler = (e: React.FocusEvent) => void -const propTypes = { - /** Button size */ - size: PropTypes.oneOf(Object.values(SIZE)), - /** Button prominence variation */ - variation: PropTypes.oneOf(Object.values(VARIATION)), - /** [DEPRECATED] If you are using just an Icon component inside, use this as true */ - icon: PropTypes.bool, - /** Loading state */ - isLoading: PropTypes.bool, - /** @ignore For internal use - * Sets reduced paddings in order to keep the button squareish if it - * only has an icon */ - iconOnly: PropTypes.bool, - /** (Button spec attribute) */ - id: PropTypes.string, - /** Data attribute */ - testId: PropTypes.string, - /** (Button spec attribute) */ - autoFocus: PropTypes.bool, - /** (Button spec attribute) */ - autoComplete: PropTypes.string, - /** (Button spec attribute) */ - disabled: PropTypes.bool, - /** @ignore Forwarded Ref */ - forwardedRef: refShape, - /** (Button spec attribute) */ - name: PropTypes.string, - /** (Button spec attribute) */ - type: PropTypes.string, - /** (Button spec attribute) */ - value: PropTypes.string, - /** Label of the Button */ - children: PropTypes.node.isRequired, - /** onClick event. */ - onClick: PropTypes.func, +interface Props { + children: ReactNode + size?: 'small' | 'regular' | 'large' + variation?: 'plain' | 'inverted' | 'danger' + icon?: boolean + isLoading?: boolean + iconOnly?: boolean + id?: string + testId?: string + autoFocus?: boolean + autoComplete?: string + disabled?: boolean + forwardedRef?: MutableRefObject + name?: string + value?: string + type?: 'button' | 'submit' | 'reset' + /* events */ + onClick?: MouseEventHandler + onMouseEnter?: MouseEventHandler + onMouseLeave?: MouseEventHandler + onMouseOver?: MouseEventHandler + onMouseOut?: MouseEventHandler + onMouseUp?: MouseEventHandler + onMouseDown?: MouseEventHandler + onFocus?: FocusEventHandler + onBlur?: FocusEventHandler /** URL for link mode. Converts the button internally to a link. */ - href: PropTypes.string, - /** onMouseEnter event */ - onMouseEnter: PropTypes.func, - /** onMouseLeave event */ - onMouseLeave: PropTypes.func, - /** onMouseOver event */ - onMouseOver: PropTypes.func, - /** onMouseOut event */ - onMouseOut: PropTypes.func, - /** onMouseUp event */ - onMouseUp: PropTypes.func, - /** onMouseDown event */ - onMouseDown: PropTypes.func, - /** onFocus event */ - onFocus: PropTypes.func, - /** onBlur event */ - onBlur: PropTypes.func, - /** Link spec */ - target: PropTypes.string, - /** Link spec */ - rel: PropTypes.string, - /** Link spec */ - referrerPolicy: PropTypes.string, - /** Link spec */ - download: PropTypes.string, - /** Disables label wrapping */ - noWrap: PropTypes.bool, + href?: string + target?: string + rel?: string + referrerPolicy?: string + download?: string + noWrap?: boolean } -type Props = InferProps +const ButtonPlain: FC = props => { + const innerRef = useRef() -const ButtonPlain: FunctionComponent = props => { - const handleClick = (event: React.MouseEvent) => { + const handleClick = (event: React.MouseEvent) => { !props.disabled && !props.isLoading && props.onClick && props.onClick(event) } @@ -103,11 +67,21 @@ const ButtonPlain: FunctionComponent = props => { } }, [props.icon]) + useEffect( + function handleAutofocus() { + if (props.autoFocus && innerRef.current && !props.iconOnly) { + innerRef?.current.setFocus() + } + }, + [props.iconOnly, props.autoFocus] + ) + const { - size, - variation, + size = 'regular', + variation = 'plain', + type = 'button', children, - icon, + icon = false, isLoading, href, target, @@ -126,19 +100,19 @@ const ButtonPlain: FunctionComponent = props => { 'vtex-button bw1 ba fw5 v-mid relative pa0 br2 bn', horizontalCompensation, { - 't-action--small': size === SIZE.SMALL, - 't-action': size === SIZE.REGULAR, - 't-action--large': size === SIZE.LARGE, + 't-action--small': size === 'small', + 't-action': size === 'regular', + 't-action--large': size === 'large', }, { 'icon-button dib': iconOnly }, { 'bg-transparent b--transparent c-disabled': disabled, 'bg-transparent b--transparent c-action-primary hover-b--transparent hover-bg-action-secondary hover-b--action-secondary': - !disabled && variation === VARIATION.PLAIN, + !disabled && variation === 'plain', 'bg-transparent b--transparent c-on-base--inverted': - !disabled && variation === VARIATION.INVERTED, + !disabled && variation === 'inverted', 'bg-transparent b--transparent c-danger hover-bg-danger--faded hover-b--danger--faded': - !disabled && variation === VARIATION.DANGER, + !disabled && variation === 'danger', }, { pointer: !disabled }, { 'inline-flex items-center no-underline': href } @@ -169,7 +143,6 @@ const ButtonPlain: FunctionComponent = props => { = props => { onMouseDown={props.onMouseDown} onFocus={props.onFocus} onBlur={props.onBlur} - ref={ - props.forwardedRef as RefObject - } + ref={mergeRef(innerRef, props.forwardedRef)} style={style} - // Button-mode exclusive props - type={href ? undefined : (props.type as 'button' | 'submit' | 'reset')} - // Link-mode exclusive props + type={href ? undefined : type} {...(href && linkModeProps)} > {isLoading ? ( {children} @@ -212,8 +186,6 @@ const ButtonPlain: FunctionComponent = props => { } ButtonPlain.defaultProps = { - size: SIZE.REGULAR, - variation: VARIATION.PLAIN, disabled: false, autoFocus: false, icon: false, @@ -221,6 +193,69 @@ ButtonPlain.defaultProps = { isLoading: false, } -ButtonPlain.propTypes = propTypes +ButtonPlain.propTypes = { + /** Button size */ + size: PropTypes.oneOf(['small', 'regular', 'large']), + /** Button prominence variation */ + variation: PropTypes.oneOf(['plain', 'inverted', 'danger']), + /** [DEPRECATED] If you are using just an Icon component inside, use this as true */ + icon: PropTypes.bool, + /** Loading state */ + isLoading: PropTypes.bool, + /** @ignore For internal use + * Sets reduced paddings in order to keep the button squareish if it + * only has an icon */ + iconOnly: PropTypes.bool, + /** (Button spec attribute) */ + id: PropTypes.string, + /** Data attribute */ + testId: PropTypes.string, + /** (Button spec attribute) */ + autoFocus: PropTypes.bool, + /** (Button spec attribute) */ + autoComplete: PropTypes.string, + /** (Button spec attribute) */ + disabled: PropTypes.bool, + /** @ignore Forwarded Ref */ + forwardedRef: PropTypes.any, + /** (Button spec attribute) */ + name: PropTypes.string, + /** (Button spec attribute) */ + type: PropTypes.oneOf(['button', 'submit', 'reset']), + /** (Button spec attribute) */ + value: PropTypes.string, + /** Label of the Button */ + children: PropTypes.node.isRequired, + /** onClick event. */ + onClick: PropTypes.func, + /** URL for link mode. Converts the button internally to a link. */ + href: PropTypes.string, + /** onMouseEnter event */ + onMouseEnter: PropTypes.func, + /** onMouseLeave event */ + onMouseLeave: PropTypes.func, + /** onMouseOver event */ + onMouseOver: PropTypes.func, + /** onMouseOut event */ + onMouseOut: PropTypes.func, + /** onMouseUp event */ + onMouseUp: PropTypes.func, + /** onMouseDown event */ + onMouseDown: PropTypes.func, + /** onFocus event */ + onFocus: PropTypes.func, + /** onBlur event */ + onBlur: PropTypes.func, + /** Link spec */ + target: PropTypes.string, + /** Link spec */ + rel: PropTypes.string, + /** Link spec */ + referrerPolicy: PropTypes.string, + /** Link spec */ + download: PropTypes.string, + /** Disables label wrapping */ + noWrap: PropTypes.bool, +} export default withForwardedRef(ButtonPlain) diff --git a/react/components/Spinner/index.tsx b/react/components/Spinner/index.tsx index 01e2eafcf..d429f2987 100644 --- a/react/components/Spinner/index.tsx +++ b/react/components/Spinner/index.tsx @@ -7,18 +7,13 @@ import { baseClassname } from '../icon/utils' const radius = 40 const circ = 2 * radius * Math.PI -const propTypes = { - /** Color of the spinner */ - color: PropTypes.string, - /** Size (diameter) of the spinner */ - size: PropTypes.number, - /** Sets the display to block */ - block: PropTypes.bool, +interface Props { + color?: string + size?: number + block?: boolean } -type Props = PropTypes.InferProps - -const Spinner: FC = ({ color, size, block }) => ( +const Spinner: FC = ({ color, size = 40, block = false }) => ( = ({ color, size, block }) => ( } `} - = ({ color, size, block }) => ( ) -Spinner.propTypes = propTypes - -Spinner.defaultProps = { - block: false, - size: 40, +Spinner.propTypes = { + /** Color of the spinner */ + color: PropTypes.string, + /** Size (diameter) of the spinner */ + size: PropTypes.number, + /** Sets the display to block */ + block: PropTypes.bool, } export default Spinner diff --git a/react/utilities/mergeRef/index.ts b/react/utilities/mergeRef/index.ts index 845797dc8..7a97f6198 100644 --- a/react/utilities/mergeRef/index.ts +++ b/react/utilities/mergeRef/index.ts @@ -1,9 +1,10 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { MutableRefObject } from 'react' import memoize from 'memoize-one' export function setRef( - ref: MutableRefObject | Function, - value: unknown = null + ref: MutableRefObject | Function | undefined, + value: any = null ) { if (!ref) return @@ -17,14 +18,11 @@ export function setRef( /** * Merges/Aligns the values of two refs */ -function mergeRef( - refA: MutableRefObject, - refB: MutableRefObject -) { +function mergeRef(refA?: MutableRefObject, refB?: MutableRefObject) { if (refA == null && refB == null) { return null } - return (value: unknown) => { + return (value: any) => { setRef(refA, value) setRef(refB, value) } From c16672055e863c0c59762778185adee059472482 Mon Sep 17 00:00:00 2001 From: matheusps Date: Fri, 19 Jun 2020 07:43:56 -0300 Subject: [PATCH 2/2] Used Focus and Mouse handlers from react --- react/components/ButtonPlain/index.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/react/components/ButtonPlain/index.tsx b/react/components/ButtonPlain/index.tsx index ba4818573..606b52cf0 100644 --- a/react/components/ButtonPlain/index.tsx +++ b/react/components/ButtonPlain/index.tsx @@ -6,6 +6,8 @@ import React, { ReactNode, FC, useRef, + MouseEventHandler, + FocusEventHandler, } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' @@ -14,9 +16,6 @@ import Spinner from '../Spinner' import { withForwardedRef } from '../../modules/withForwardedRef' import mergeRef from '../../utilities/mergeRef' -type MouseEventHandler = (e: React.MouseEvent) => void -type FocusEventHandler = (e: React.FocusEvent) => void - interface Props { children: ReactNode size?: 'small' | 'regular' | 'large'