Skip to content

Commit

Permalink
[PAY-1720] Implements PlainButton (#3897)
Browse files Browse the repository at this point in the history
  • Loading branch information
schottra committed Aug 17, 2023
1 parent d448aa9 commit eb904af
Show file tree
Hide file tree
Showing 10 changed files with 439 additions and 111 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* ===Base Styles=== */
.button {
align-items: center;
box-sizing: border-box;
cursor: pointer;
display: inline-flex;
flex-shrink: 0;
justify-content: center;
overflow: hidden;
position: relative;
text-align: center;
user-select: none;
white-space: nowrap;
}

.button:focus {
outline: none !important;
}

/* Only add hover styles on devices which support it */
@media (hover: hover) {
.button:not(.disabled):hover {
transition: all var(--hover);
transform: scale(1.04);
}
}

.button:not(.disabled):active {
transition: all var(--press);
transform: scale(0.98);
}

.button.disabled {
pointer-events: none;
}

.icon path {
fill: currentColor;
}

.fullWidth {
width: 100%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { forwardRef } from 'react'

import cn from 'classnames'

import { useMediaQueryListener } from 'hooks/useMediaQueryListener'

import baseStyles from './BaseButton.module.css'
import { BaseButtonProps } from './types'

/**
* Base component for Harmony buttons. Not intended to be used directly. Use
* `HarmonyButton` or `HarmonyPlainButton`.
*/
export const BaseButton = forwardRef<HTMLButtonElement, BaseButtonProps>(
function BaseButton(props, ref) {
const {
text,
iconLeft: LeftIconComponent,
iconRight: RightIconComponent,
disabled,
widthToHideText,
minWidth,
className,
'aria-label': ariaLabelProp,
fullWidth,
styles,
style,
...other
} = props
const { isMatch: textIsHidden } = useMediaQueryListener(
`(max-width: ${widthToHideText}px)`
)

const isTextVisible = !!text && !textIsHidden

const getAriaLabel = () => {
if (ariaLabelProp) return ariaLabelProp
// Use the text prop as the aria-label if the text becomes hidden
// and no aria-label was provided to keep the button accessible.
else if (textIsHidden && typeof text === 'string') return text
return undefined
}

return (
<button
aria-label={getAriaLabel()}
className={cn(
baseStyles.button,
styles.button,
{
[baseStyles.disabled]: disabled,
[baseStyles.fullWidth]: fullWidth
},
className
)}
disabled={disabled}
ref={ref}
style={{
minWidth: minWidth && isTextVisible ? `${minWidth}px` : 'unset',
...style
}}
{...other}
>
{LeftIconComponent ? (
<LeftIconComponent className={cn(baseStyles.icon, styles.icon)} />
) : null}
{isTextVisible ? (
<span className={cn(baseStyles.text, styles.text)}>{text}</span>
) : null}
{RightIconComponent ? (
<RightIconComponent className={cn(baseStyles.icon, styles.icon)} />
) : null}
</button>
)
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,9 @@
--text-color: var(--static-white);
--overlay-color: transparent;
--overlay-opacity: 0;
align-items: center;
border: 1px solid var(--button-color);
border-radius: var(--unit-1);
box-sizing: border-box;
color: var(--text-color);
cursor: pointer;
display: inline-flex;
flex-shrink: 0;
justify-content: center;
overflow: hidden;
position: relative;
text-align: center;
user-select: none;
white-space: nowrap;
}

.button:focus {
outline: none !important;
}

/* Only add hover styles on devices which support it */
@media (hover: hover) {
.button:not(.disabled):hover {
transition: all var(--hover);
transform: scale(1.04);
}
}

.button:not(.disabled):active {
transition: all var(--press);
transform: scale(0.98);
}

.button.disabled {
pointer-events: none;
}

/* Overlay used for hover/press styling */
Expand All @@ -55,14 +23,6 @@
pointer-events: none;
}

.icon path {
fill: currentColor;
}

.fullWidth {
width: 100%;
}

/* === Sizes === */

/* Small */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const Template: Story<HarmonyButtonProps> = (args) => (
display: 'flex',
flexDirection: 'column',
gap: '16px',
justifyContent: 'center',
alignItems: 'flex-start'
}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { CSSProperties, forwardRef } from 'react'
import { forwardRef } from 'react'

import cn from 'classnames'

import { useMediaQueryListener } from 'hooks/useMediaQueryListener'
import { CSSCustomProperties } from 'styles/types'
import { toCSSVariableName } from 'utils/styles'

import { BaseButton } from './BaseButton'
import styles from './HarmonyButton.module.css'
import {
HarmonyButtonProps,
HarmonyButtonType,
HarmonyButtonSize
HarmonyButtonSize,
HarmonyButtonType
} from './types'

const SIZE_STYLE_MAP: { [k in HarmonyButtonSize]: [string, string, string] } = {
Expand Down Expand Up @@ -48,69 +48,36 @@ export const HarmonyButton = forwardRef<HTMLButtonElement, HarmonyButtonProps>(
function HarmonyButton(props, ref) {
const {
color,
text,
variant = HarmonyButtonType.PRIMARY,
size = HarmonyButtonSize.DEFAULT,
iconLeft: LeftIconComponent,
iconRight: RightIconComponent,
disabled,
widthToHideText,
minWidth,
className,
'aria-label': ariaLabelProp,
fullWidth,
...other
...baseProps
} = props
const { isMatch: textIsHidden } = useMediaQueryListener(
`(max-width: ${widthToHideText}px)`
)

const isTextVisible = !!text && !textIsHidden

const getAriaLabel = () => {
if (ariaLabelProp) return ariaLabelProp
// Use the text prop as the aria-label if the text becomes hidden
// and no aria-label was provided to keep the button accessible.
else if (textIsHidden && typeof text === 'string') return text
return undefined
}

const style: CSSCustomProperties = {
minWidth: minWidth && isTextVisible ? `${minWidth}px` : 'unset',
'--button-color':
!disabled && color ? `var(${toCSSVariableName(color)})` : undefined
}

const [buttonSizeClass, iconSizeClass, textSizeClass] = SIZE_STYLE_MAP[size]

return (
<button
aria-label={getAriaLabel()}
className={cn(
styles.button,
buttonSizeClass,
TYPE_STYLE_MAP[variant],
{
[styles.disabled]: disabled,
[styles.fullWidth]: fullWidth
},
className
)}
disabled={disabled}
<BaseButton
ref={ref}
style={style as CSSProperties}
{...other}
>
{LeftIconComponent ? (
<LeftIconComponent className={cn(styles.icon, iconSizeClass)} />
) : null}
{isTextVisible ? (
<span className={cn(styles.text, textSizeClass)}>{text}</span>
) : null}
{RightIconComponent ? (
<RightIconComponent className={cn(styles.icon, iconSizeClass)} />
) : null}
</button>
disabled={disabled}
styles={{
button: cn(
styles.button,
TYPE_STYLE_MAP[variant],
{ [styles.disabled]: disabled },
buttonSizeClass
),
icon: cn(styles.icon, iconSizeClass),
text: cn(styles.text, textSizeClass)
}}
style={style}
{...baseProps}
/>
)
}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* ===Base Styles=== */
.button {
--text-color: var(--text-default);
background: transparent;
border: none;
color: var(--text-color);
}

/* === Sizes === */

/* Default */
.buttonDefault {
gap: var(--unit-1);
height: var(--unit-4);
}

.iconDefault {
width: var(--unit-4);
height: var(--unit-4);
}

.textDefault {
font-size: var(--font-s);
font-weight: var(--font-bold);
line-height: var(--unit-4);
}

/* Large */
.buttonLarge {
gap: var(--unit-2);
height: var(--unit-5);
}

.iconLarge {
width: var(--unit-5);
height: var(--unit-5);
}

.textLarge {
font-size: var(--font-l);
font-weight: var(--font-bold);
line-height: calc(4.5 * var(--unit-5));
}

/* === Color Variants === */

/* Default */
.default {
--text-color: var(--text-default);
}

.default:hover {
--text-color: var(--secondary);
}

.default:active {
--text-color: var(--secondary-dark-2)
}

/* Subdued */
.subdued {
--text-color: var(--text-subdued);
}

.subdued:hover {
--text-color: var(--secondary);
}
.subdued:active {
--text-color: var(--secondary-dark-2);
}

/* Inverted */
.inverted {
--text-color: var(--static-white);
}
.inverted:hover {
opacity: 0.8;
}
.inverted:active {
opacity: 0.5;
}

/* Disabled states */
.disabled {
opacity: 0.2;
}

.subdued.disabled {
--text-color: var(--text-default);
}
Loading

0 comments on commit eb904af

Please sign in to comment.