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

feat(Button): No container button variant #3823

Merged
merged 9 commits into from
Jun 12, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled from 'styled-components'

import { Meta, StoryFn } from '@storybook/react'

import { IconBrightnessLow } from '@royalnavy/icon-library'
import { IconWifi } from '@royalnavy/icon-library'
import { spacing } from '@royalnavy/design-tokens'

import { Button, ButtonProps } from './index'
Expand Down Expand Up @@ -33,18 +33,20 @@ const StyledButtonStrip = styled.div`
max-width: 500px;
display: flex;
justify-content: start;
gap: ${spacing('2')};
gap: ${spacing('4')};
padding: ${spacing('4')} 0;
`

type ButtonStripArgs = ButtonProps & {
title?: string
hideText?: boolean
hideNoContainer?: boolean
}

const ButtonStrip = (args: ButtonStripArgs) => {
const localArgs = { ...args }
const shouldHideButtonText = !!args.hideText
const shouldDisplayNoContainerButton = !args.hideNoContainer
delete localArgs.variant
delete localArgs.isDisabled
const disabledStates = [false, true]
Expand All @@ -59,31 +61,37 @@ const ButtonStrip = (args: ButtonStripArgs) => {
isDisabled={state}
{...localArgs}
>
{shouldHideButtonText ? '' : 'Primary'}
{shouldHideButtonText ? '' : 'Primary button'}
</Button>
&nbsp;
<Button
variant={BUTTON_VARIANT.SECONDARY}
isDisabled={state}
{...localArgs}
>
{shouldHideButtonText ? '' : 'Secondary'}
{shouldHideButtonText ? '' : 'Secondary button'}
</Button>
&nbsp;
<Button
variant={BUTTON_VARIANT.TERTIARY}
isDisabled={state}
{...localArgs}
>
{shouldHideButtonText ? '' : 'Tertiary'}
{shouldHideButtonText ? '' : 'Tertiary button'}
</Button>
&nbsp;
{shouldDisplayNoContainerButton && (
<Button
variant={BUTTON_VARIANT.NO_CONTAINER}
isDisabled={state}
{...localArgs}
>
{shouldHideButtonText ? '' : 'No container button'}
</Button>
)}
<Button
variant={BUTTON_VARIANT.DANGER}
isDisabled={state}
{...localArgs}
>
{shouldHideButtonText ? '' : 'Danger'}
{shouldHideButtonText ? '' : 'Danger button'}
</Button>
</StyledButtonStrip>
))}
Expand All @@ -96,7 +104,7 @@ const ButtonStrip = (args: ButtonStripArgs) => {
export const RegularButtons: StoryFn<typeof Button> = (args) => {
const iconLeftArgs = {
...args,
icon: <IconBrightnessLow />,
icon: <IconWifi />,
iconPosition: BUTTON_ICON_POSITION.LEFT,
}
const iconRightArgs = {
Expand All @@ -115,12 +123,11 @@ export const RegularButtons: StoryFn<typeof Button> = (args) => {

return (
<>
<p>All variants of {args.size} buttons</p>
<ButtonStrip {...args} title="Default" />
<ButtonStrip {...args} title="Default" hideNoContainer />
<ButtonStrip {...iconLeftArgs} title="With icons left" />
<ButtonStrip {...iconRightArgs} title="With icons right" />
<ButtonStrip {...iconOnlyArgs} title="Icon only" hideText />
<ButtonStrip {...loadingArgs} title="Loading" />
<ButtonStrip {...loadingArgs} title="Loading" hideNoContainer />
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const BUTTON_VARIANT = {
SECONDARY: 'secondary',
TERTIARY: 'tertiary',
DANGER: 'danger',
NO_CONTAINER: 'no-container',
} as const

const BUTTON_ICON_POSITION = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './Button'
export * from './constants'

export { getButtonStyles } from './partials/StyledButton'
export { getButtonStyles } from './partials/getButtonStyles'
Original file line number Diff line number Diff line change
@@ -1,147 +1,5 @@
import styled, { css } from 'styled-components'
import { color, fontSize, shadow, spacing } from '@royalnavy/design-tokens'
import { rgba } from 'polished'

import { BUTTON_ICON_POSITION, BUTTON_VARIANT } from '../constants'
import { ButtonIconPositionType, ButtonVariantType } from '../Button'
import { ComponentSizeType, COMPONENT_SIZE } from '../../Forms'

const DROP_SHADOW = `0 2px 6px ${rgba(0, 0, 0, 0.3)}`
const TRANSPARENT_SHADOW = shadow('0')
const DEFAULT_HOVER_BORDER_SHADOW = `0 0 0 2px ${color('action', '900')}`
const DANGER_HOVER_BORDER_SHADOW = `0 0 0 2px ${color('danger', '900')}`

const COLOR_MAP = {
[BUTTON_VARIANT.PRIMARY]: {
color: color('neutral', 'white'),
backgroundColor: color('action', '600'),
borderColor: color('action', '600'),
borderWidth: 0,
hoverBackgroundColor: color('action', '700'),
hoverBoxShadow: DEFAULT_HOVER_BORDER_SHADOW,
activeBackgroundColor: color('action', '900'),
focusBorderColor: color('action', '800'),
},
[BUTTON_VARIANT.SECONDARY]: {
color: color('action', '900'),
backgroundColor: color('action', '100'),
borderColor: color('action', '600'),
borderWidth: '1px',
hoverBackgroundColor: color('action', '200'),
hoverBoxShadow: DEFAULT_HOVER_BORDER_SHADOW,
activeBackgroundColor: color('action', '300'),
focusBorderColor: color('action', '300'),
},
[BUTTON_VARIANT.TERTIARY]: {
color: color('action', '600'),
backgroundColor: color('neutral', 'white'),
borderColor: color('action', '600'),
borderWidth: '1px',
hoverBackgroundColor: color('neutral', '000'),
hoverBoxShadow: DEFAULT_HOVER_BORDER_SHADOW,
activeBackgroundColor: color('neutral', '100'),
focusBorderColor: color('action', '800'),
},
[BUTTON_VARIANT.DANGER]: {
color: color('neutral', 'white'),
backgroundColor: color('danger', '700'),
borderColor: color('danger', '900'),
borderWidth: 0,
hoverBackgroundColor: color('danger', '800'),
hoverBoxShadow: DANGER_HOVER_BORDER_SHADOW,
activeBackgroundColor: color('danger', '900'),
focusBorderColor: color('action', '800'),
},
}

const SIZE_MAP = {
[COMPONENT_SIZE.FORMS]: {
height: '46px',
fontSize: fontSize('m'),
borderRadius: '8px',
},
[COMPONENT_SIZE.SMALL]: {
height: '33px',
fontSize: fontSize('base'),
borderRadius: '8px',
},
}

interface StyledButtonProps {
$variant: ButtonVariantType
$size: ComponentSizeType
$iconPosition?: ButtonIconPositionType
}

export function getButtonStyles({
$size,
$variant,
$iconPosition = BUTTON_ICON_POSITION.RIGHT,
}: StyledButtonProps) {
return css`
height: ${SIZE_MAP[$size].height};
display: inline-flex;
flex-direction: ${$iconPosition === BUTTON_ICON_POSITION.LEFT
? 'row-reverse'
: 'row'};
align-items: center;
justify-content: center;
border-radius: ${SIZE_MAP[$size].borderRadius};
outline: 0;
padding: 0 ${spacing('7')};
font-size: ${SIZE_MAP[$size].fontSize};
font-weight: 400;
text-decoration: none;
cursor: pointer;
user-select: none;
transition: all 75ms cubic-bezier(0, 1.19, 0.82, 0.9);
white-space: nowrap;

&:hover {
text-decoration: none;
}

&:active {
box-shadow: ${TRANSPARENT_SHADOW}, ${TRANSPARENT_SHADOW};
}

${css`
color: ${COLOR_MAP[$variant].color};
background-color: ${COLOR_MAP[$variant].backgroundColor};
border: ${COLOR_MAP[$variant].borderWidth} solid
${COLOR_MAP[$variant].borderColor};

&:focus {
border: ${COLOR_MAP[$variant].borderWidth} solid
${COLOR_MAP[$variant].focusBorderColor};
}

&:focus,
&:hover {
background-color: ${COLOR_MAP[$variant].hoverBackgroundColor};
box-shadow: ${COLOR_MAP[$variant].hoverBoxShadow}, ${DROP_SHADOW};
}

&:active {
background-color: ${COLOR_MAP[$variant].activeBackgroundColor};
}
`}

&:disabled {
&,
&:hover,
&:active,
&:focus {
color: ${color('neutral', '400')};
background-color: ${color('neutral', '000')};
border: ${COLOR_MAP[$variant].borderWidth} solid
${color('neutral', '200')};
box-shadow: none;
cursor: not-allowed;
}
}
`
}
import styled from 'styled-components'
import { getButtonStyles, StyledButtonProps } from './getButtonStyles'

export const StyledButton = styled.button<StyledButtonProps>`
position: relative;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { css } from 'styled-components'
import { spacing } from '@royalnavy/design-tokens'

import { BUTTON_ICON_POSITION } from '../constants'
import { ButtonIconPositionType, ButtonVariantType } from '../Button'
import { ComponentSizeType } from '../../Forms'
import { SIZE_MAP, STYLES_MAP } from './styles'

const DEFAULT_HORIZONTAL_PADDING = spacing('7')

export interface StyledButtonProps {
$variant: ButtonVariantType
$size: ComponentSizeType
$iconPosition?: ButtonIconPositionType
}

const commonStyles = css`
box-sizing: border-box;
font-weight: 400;
text-decoration: none;
cursor: pointer;
user-select: none;
transition: all 75ms cubic-bezier(0, 1.19, 0.82, 0.9);
white-space: nowrap;
display: inline-flex;
align-items: center;
justify-content: center;
`

export function getButtonStyles({
$size,
$variant,
$iconPosition = BUTTON_ICON_POSITION.RIGHT,
}: StyledButtonProps) {
const styles = STYLES_MAP[$variant]
const sizes = SIZE_MAP[$size]
return css`
${commonStyles};
font-size: ${sizes.fontSize};
height: ${sizes.height};

flex-direction: ${$iconPosition === BUTTON_ICON_POSITION.LEFT
? 'row-reverse'
: 'row'};

border-radius: ${sizes.borderRadius};

padding: 0 ${styles.horizontalPadding ?? DEFAULT_HORIZONTAL_PADDING};

color: ${styles.default.color};
background-color: ${styles.default.backgroundColor};

border: 0;
outline: 0;
box-shadow: ${styles.default.boxShadow};
text-decoration: ${styles.default.textDecoration};

&:hover {
color: ${styles.hover?.color ?? styles.default.color};
background-color: ${styles.hover?.backgroundColor ??
styles.default.backgroundColor};
box-shadow: ${styles.hover?.boxShadow ?? styles.default.boxShadow};

text-decoration: ${styles.hover?.textDecoration ??
styles.default.textDecoration};
}

&:focus {
color: ${styles.focus?.color ?? styles.default.color};
background-color: ${styles.focus?.backgroundColor ??
styles.default.backgroundColor};

outline: 0;
box-shadow: ${styles.focus?.boxShadow ?? styles.default.boxShadow};

text-decoration: ${styles.focus?.textDecoration ??
styles.default.textDecoration};
}

&:active {
color: ${styles.active?.color ?? styles.default.color};
background-color: ${styles.active?.backgroundColor ??
styles.default.backgroundColor};

box-shadow: ${styles.active?.boxShadow ?? styles.default.boxShadow};
}

&:disabled {
&,
&:hover,
&:active,
&:focus {
color: ${styles.disabled.color};
background-color: ${styles.disabled.backgroundColor};
text-decoration: ${styles.disabled.textDecoration};

box-shadow: none;
cursor: not-allowed;
text-decoration: none;
}
}
`
}
Loading
Loading