Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#trivial Fix button plain & spinner & mergeRef types #1230

Merged
merged 2 commits into from
Jun 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 141 additions & 107 deletions react/components/ButtonPlain/index.tsx
Original file line number Diff line number Diff line change
@@ -1,97 +1,60 @@
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,
MouseEventHandler,
FocusEventHandler,
} 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,
}

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<HTMLElement & HTMLButtonElement>
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<typeof propTypes>
const ButtonPlain: FC<Props> = props => {
const innerRef = useRef<any>()

const ButtonPlain: FunctionComponent<Props> = props => {
const handleClick = (event: React.MouseEvent) => {
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
!props.disabled && !props.isLoading && props.onClick && props.onClick(event)
}

Expand All @@ -103,11 +66,21 @@ const ButtonPlain: FunctionComponent<Props> = 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,
Expand All @@ -126,19 +99,19 @@ const ButtonPlain: FunctionComponent<Props> = 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 }
Expand Down Expand Up @@ -169,7 +142,6 @@ const ButtonPlain: FunctionComponent<Props> = props => {
<Element
id={props.id}
data-testid={props.testId}
autoFocus={iconOnly ? undefined : props.autoFocus}
disabled={iconOnly ? undefined : props.disabled}
name={iconOnly ? undefined : props.name}
value={iconOnly ? undefined : props.value}
Expand All @@ -185,21 +157,22 @@ const ButtonPlain: FunctionComponent<Props> = props => {
onMouseDown={props.onMouseDown}
onFocus={props.onFocus}
onBlur={props.onBlur}
ref={
props.forwardedRef as RefObject<HTMLAnchorElement & HTMLButtonElement>
}
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 ? (
<Fragment>
<span className="top-0 left-0 w-100 h-100 absolute flex justify-center items-center">
<Spinner
secondary={variation === VARIATION.DANGER}
size={loaderSize[size]}
size={
{
small: 15,
regular: 20,
large: 25,
}[size]
}
/>
</span>
<span className={`${labelClasses} o-0`}>{children}</span>
Expand All @@ -212,15 +185,76 @@ const ButtonPlain: FunctionComponent<Props> = props => {
}

ButtonPlain.defaultProps = {
size: SIZE.REGULAR,
variation: VARIATION.PLAIN,
disabled: false,
autoFocus: false,
icon: false,
type: 'button',
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)
28 changes: 12 additions & 16 deletions react/components/Spinner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof propTypes>

const Spinner: FC<Props> = ({ color, size, block }) => (
const Spinner: FC<Props> = ({ color, size = 40, block = false }) => (
<svg
className={classNames(baseClassname('spinner'), {
'c-action-primary': !color,
Expand Down Expand Up @@ -60,7 +55,6 @@ const Spinner: FC<Props> = ({ color, size, block }) => (
}
`}
</style>

<circle
className="vtex-spinner_circle"
cx="50"
Expand All @@ -76,11 +70,13 @@ const Spinner: FC<Props> = ({ color, size, block }) => (
</svg>
)

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
12 changes: 5 additions & 7 deletions react/utilities/mergeRef/index.ts
Original file line number Diff line number Diff line change
@@ -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<unknown> | Function,
value: unknown = null
ref: MutableRefObject<any> | Function | undefined,
value: any = null
) {
if (!ref) return

Expand All @@ -17,14 +18,11 @@ export function setRef(
/**
* Merges/Aligns the values of two refs
*/
function mergeRef(
refA: MutableRefObject<unknown>,
refB: MutableRefObject<unknown>
) {
function mergeRef(refA?: MutableRefObject<any>, refB?: MutableRefObject<any>) {
if (refA == null && refB == null) {
return null
}
return (value: unknown) => {
return (value: any) => {
setRef(refA, value)
setRef(refB, value)
}
Expand Down