Merged in fix/BOOK-293-button-variants (pull request #3371)
fix(BOOK-293): changed variants and props on IconButton component * fix(BOOK-293): changed variants and props on IconButton component * fix(BOOK-293): inherit color for icon Approved-by: Bianca Widstam Approved-by: Christel Westerberg
This commit is contained in:
committed by
Bianca Widstam
parent
2197ab2137
commit
3f632e6031
@@ -2,9 +2,8 @@ import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
|
||||
import { expect, fn } from 'storybook/test'
|
||||
|
||||
import { MaterialIcon } from '../Icons/MaterialIcon'
|
||||
import { config as typographyConfig } from '../Typography/variants'
|
||||
import { Button } from './Button'
|
||||
import { buttonIconNames } from './types'
|
||||
import { config as buttonConfig } from './variants'
|
||||
|
||||
const meta: Meta<typeof Button> = {
|
||||
@@ -13,12 +12,10 @@ const meta: Meta<typeof Button> = {
|
||||
argTypes: {
|
||||
onPress: {
|
||||
table: {
|
||||
disable: true,
|
||||
type: { summary: 'function' },
|
||||
defaultValue: { summary: 'undefined' },
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
control: 'select',
|
||||
options: Object.keys(typographyConfig.variants.variant),
|
||||
description: 'Callback function to handle button press events.',
|
||||
},
|
||||
variant: {
|
||||
control: 'select',
|
||||
@@ -58,7 +55,7 @@ const meta: Meta<typeof Button> = {
|
||||
},
|
||||
},
|
||||
wrapping: {
|
||||
control: 'radio',
|
||||
control: 'boolean',
|
||||
options: Object.keys(buttonConfig.variants.wrapping),
|
||||
type: 'boolean',
|
||||
table: {
|
||||
@@ -69,6 +66,47 @@ const meta: Meta<typeof Button> = {
|
||||
description:
|
||||
'Only applies to variant `Text`. If `false`, the button will use smaller padding.',
|
||||
},
|
||||
leadingIconName: {
|
||||
control: 'select',
|
||||
options: buttonIconNames,
|
||||
table: {
|
||||
type: { summary: buttonIconNames.join(' | ') },
|
||||
defaultValue: { summary: 'undefined' },
|
||||
},
|
||||
description: 'Name of the Material Icon to use as leading icon.',
|
||||
},
|
||||
trailingIconName: {
|
||||
control: 'select',
|
||||
options: buttonIconNames,
|
||||
table: {
|
||||
type: { summary: buttonIconNames.join(' | ') },
|
||||
defaultValue: { summary: 'undefined' },
|
||||
},
|
||||
description: 'Name of the Material Icon to use as trailing icon.',
|
||||
},
|
||||
isDisabled: {
|
||||
control: 'boolean',
|
||||
table: {
|
||||
type: { summary: 'boolean' },
|
||||
defaultValue: { summary: 'false' },
|
||||
},
|
||||
},
|
||||
isPending: {
|
||||
control: 'boolean',
|
||||
table: {
|
||||
type: { summary: 'boolean' },
|
||||
defaultValue: { summary: 'false' },
|
||||
},
|
||||
},
|
||||
fullWidth: {
|
||||
control: 'boolean',
|
||||
table: {
|
||||
type: { summary: 'boolean' },
|
||||
defaultValue: { summary: 'false' },
|
||||
},
|
||||
description:
|
||||
'By default, the button width adjusts to its content. Set to true to make the button take the full width of its container.',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -83,7 +121,6 @@ export const Default: Story = {
|
||||
args: {
|
||||
onPress: fn(),
|
||||
children: 'Button',
|
||||
typography: 'Body/Paragraph/mdBold',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -95,7 +132,7 @@ export const PrimaryLarge: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
variant: 'Primary',
|
||||
size: 'Large',
|
||||
size: 'lg',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -106,7 +143,7 @@ export const PrimaryLarge: Story = {
|
||||
export const PrimaryMedium: Story = {
|
||||
args: {
|
||||
...PrimaryLarge.args,
|
||||
size: 'Medium',
|
||||
size: 'md',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -117,8 +154,7 @@ export const PrimaryMedium: Story = {
|
||||
export const PrimarySmall: Story = {
|
||||
args: {
|
||||
...PrimaryLarge.args,
|
||||
typography: 'Body/Supporting text (caption)/smBold',
|
||||
size: 'Small',
|
||||
size: 'sm',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -154,7 +190,6 @@ export const PrimaryOnDarkBackground: Story = {
|
||||
globals: globalStoryPropsInverted,
|
||||
args: {
|
||||
...PrimaryLarge.args,
|
||||
onPress: fn(), // Fresh spy instance
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -166,7 +201,7 @@ export const PrimaryInvertedLarge: Story = {
|
||||
globals: globalStoryPropsInverted,
|
||||
args: {
|
||||
...Default.args,
|
||||
size: 'Large',
|
||||
size: 'lg',
|
||||
color: 'Inverted',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
@@ -179,7 +214,7 @@ export const PrimaryInvertedMedium: Story = {
|
||||
globals: globalStoryPropsInverted,
|
||||
args: {
|
||||
...PrimaryInvertedLarge.args,
|
||||
size: 'Medium',
|
||||
size: 'md',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -191,8 +226,7 @@ export const PrimaryInvertedSmall: Story = {
|
||||
globals: globalStoryPropsInverted,
|
||||
args: {
|
||||
...PrimaryInvertedLarge.args,
|
||||
typography: 'Body/Supporting text (caption)/smBold',
|
||||
size: 'Small',
|
||||
size: 'sm',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -230,7 +264,7 @@ export const SecondaryLarge: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
variant: 'Secondary',
|
||||
size: 'Large',
|
||||
size: 'lg',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -241,7 +275,7 @@ export const SecondaryLarge: Story = {
|
||||
export const SecondaryMedium: Story = {
|
||||
args: {
|
||||
...SecondaryLarge.args,
|
||||
size: 'Medium',
|
||||
size: 'md',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -252,8 +286,7 @@ export const SecondaryMedium: Story = {
|
||||
export const SecondarySmall: Story = {
|
||||
args: {
|
||||
...SecondaryLarge.args,
|
||||
typography: 'Body/Supporting text (caption)/smBold',
|
||||
size: 'Small',
|
||||
size: 'sm',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -291,7 +324,7 @@ export const SecondaryInvertedLarge: Story = {
|
||||
...Default.args,
|
||||
variant: 'Secondary',
|
||||
color: 'Inverted',
|
||||
size: 'Large',
|
||||
size: 'lg',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -303,7 +336,7 @@ export const SecondaryInvertedMedium: Story = {
|
||||
globals: globalStoryPropsInverted,
|
||||
args: {
|
||||
...SecondaryInvertedLarge.args,
|
||||
size: 'Medium',
|
||||
size: 'md',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -315,8 +348,7 @@ export const SecondaryInvertedSmall: Story = {
|
||||
globals: globalStoryPropsInverted,
|
||||
args: {
|
||||
...SecondaryInvertedLarge.args,
|
||||
typography: 'Body/Supporting text (caption)/smBold',
|
||||
size: 'Small',
|
||||
size: 'sm',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -354,7 +386,7 @@ export const TertiaryLarge: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
variant: 'Tertiary',
|
||||
size: 'Large',
|
||||
size: 'lg',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -365,7 +397,7 @@ export const TertiaryLarge: Story = {
|
||||
export const TertiaryMedium: Story = {
|
||||
args: {
|
||||
...TertiaryLarge.args,
|
||||
size: 'Medium',
|
||||
size: 'md',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -376,8 +408,7 @@ export const TertiaryMedium: Story = {
|
||||
export const TertiarySmall: Story = {
|
||||
args: {
|
||||
...TertiaryLarge.args,
|
||||
typography: 'Body/Supporting text (caption)/smBold',
|
||||
size: 'Small',
|
||||
size: 'sm',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -413,7 +444,7 @@ export const TextLarge: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
variant: 'Text',
|
||||
size: 'Large',
|
||||
size: 'lg',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -424,7 +455,7 @@ export const TextLarge: Story = {
|
||||
export const TextMedium: Story = {
|
||||
args: {
|
||||
...TextLarge.args,
|
||||
size: 'Medium',
|
||||
size: 'md',
|
||||
},
|
||||
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
@@ -436,8 +467,7 @@ export const TextMedium: Story = {
|
||||
export const TextSmall: Story = {
|
||||
args: {
|
||||
...TextLarge.args,
|
||||
typography: 'Body/Supporting text (caption)/smBold',
|
||||
size: 'Small',
|
||||
size: 'sm',
|
||||
},
|
||||
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
@@ -475,7 +505,7 @@ export const TextInvertedLarge: Story = {
|
||||
...Default.args,
|
||||
variant: 'Text',
|
||||
color: 'Inverted',
|
||||
size: 'Large',
|
||||
size: 'lg',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -487,7 +517,7 @@ export const TextInvertedMedium: Story = {
|
||||
globals: globalStoryPropsInverted,
|
||||
args: {
|
||||
...TextInvertedLarge.args,
|
||||
size: 'Medium',
|
||||
size: 'md',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -499,8 +529,7 @@ export const TextInvertedSmall: Story = {
|
||||
globals: globalStoryPropsInverted,
|
||||
args: {
|
||||
...TextInvertedLarge.args,
|
||||
typography: 'Body/Supporting text (caption)/smBold',
|
||||
size: 'Small',
|
||||
size: 'sm',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
@@ -524,12 +553,8 @@ export const TextInvertedDisabled: Story = {
|
||||
export const TextWithIcon: Story = {
|
||||
args: {
|
||||
...TextLarge.args,
|
||||
children: (
|
||||
<>
|
||||
Text with icon
|
||||
<MaterialIcon icon="chevron_right" size={24} color="CurrentColor" />
|
||||
</>
|
||||
),
|
||||
children: 'Text with icon',
|
||||
trailingIconName: 'chevron_right',
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(await canvas.findByRole('button'))
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Button as ButtonRAC } from 'react-aria-components'
|
||||
import { Loading, type LoadingProps } from '../Loading/Loading'
|
||||
import { Loading } from '../Loading/Loading'
|
||||
|
||||
import { MaterialIcon } from '../Icons/MaterialIcon'
|
||||
import { Typography } from '../Typography'
|
||||
import type { ButtonProps } from './types'
|
||||
import { variants } from './variants'
|
||||
|
||||
@@ -10,7 +12,8 @@ export function Button({
|
||||
size,
|
||||
wrapping,
|
||||
fullWidth,
|
||||
typography,
|
||||
leadingIconName,
|
||||
trailingIconName,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
@@ -20,32 +23,44 @@ export function Button({
|
||||
color,
|
||||
size,
|
||||
wrapping,
|
||||
typography,
|
||||
fullWidth,
|
||||
className,
|
||||
})
|
||||
|
||||
return (
|
||||
<ButtonRAC {...props} className={classNames}>
|
||||
{({ isPending, isHovered }) => {
|
||||
let loadingType: LoadingProps['type'] = 'White'
|
||||
if (variant === 'Secondary') {
|
||||
if (isHovered || color !== 'Inverted') {
|
||||
loadingType = 'Dark'
|
||||
}
|
||||
} else {
|
||||
if (color === 'Inverted') {
|
||||
loadingType = 'Dark'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
{isPending && <Loading size={20} type={loadingType} />}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</ButtonRAC>
|
||||
<Typography
|
||||
variant={
|
||||
size === 'sm'
|
||||
? 'Body/Supporting text (caption)/smBold'
|
||||
: 'Body/Paragraph/mdBold'
|
||||
}
|
||||
>
|
||||
<ButtonRAC {...props} className={classNames}>
|
||||
{({ isPending }) => {
|
||||
return (
|
||||
<>
|
||||
{leadingIconName && !isPending ? (
|
||||
<MaterialIcon
|
||||
icon={leadingIconName}
|
||||
color="CurrentColor"
|
||||
size={size === 'sm' ? 20 : 24}
|
||||
/>
|
||||
) : null}
|
||||
{children}
|
||||
{trailingIconName && !isPending ? (
|
||||
<MaterialIcon
|
||||
icon={trailingIconName}
|
||||
color="CurrentColor"
|
||||
size={size === 'sm' ? 20 : 24}
|
||||
/>
|
||||
) : null}
|
||||
{isPending ? (
|
||||
<Loading size={size === 'sm' ? 18 : 20} type="CurrentColor" />
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</ButtonRAC>
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -37,15 +37,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.size-large {
|
||||
.size-lg {
|
||||
padding: calc(var(--Space-x2) - 2px) var(--Space-x3); /* Adjust for 2px border */
|
||||
}
|
||||
|
||||
.size-medium {
|
||||
.size-md {
|
||||
padding: calc(var(--Space-x15) - 2px) var(--Space-x2); /* Adjust for 2px border */
|
||||
}
|
||||
|
||||
.size-small {
|
||||
.size-sm {
|
||||
padding: var(--Space-x1) var(--Space-x2); /* Adjust for 2px border */
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,35 @@ import { Button } from 'react-aria-components'
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import type { ComponentProps } from 'react'
|
||||
|
||||
import type { SymbolCodepoints } from '../Icons/MaterialIcon/MaterialSymbol/types'
|
||||
import type { variants } from './variants'
|
||||
|
||||
export const buttonIconNames = [
|
||||
'add_circle',
|
||||
'open_in_new',
|
||||
'keyboard_arrow_down',
|
||||
'keyboard_arrow_up',
|
||||
'edit_square',
|
||||
'location_on',
|
||||
'link',
|
||||
'mail',
|
||||
'cancel',
|
||||
'calendar_month',
|
||||
'calendar_clock',
|
||||
'edit_calendar',
|
||||
'calendar_add_on',
|
||||
'delete',
|
||||
'chevron_right',
|
||||
'chevron_left',
|
||||
] as const
|
||||
|
||||
export type ButtonIconName = Extract<
|
||||
SymbolCodepoints,
|
||||
(typeof buttonIconNames)[number]
|
||||
>
|
||||
|
||||
export interface ButtonProps
|
||||
extends ComponentProps<typeof Button>,
|
||||
VariantProps<typeof variants> {}
|
||||
extends ComponentProps<typeof Button>, VariantProps<typeof variants> {
|
||||
leadingIconName?: ButtonIconName | null
|
||||
trailingIconName?: ButtonIconName | null
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
import {
|
||||
config as typographyConfig,
|
||||
withTypography,
|
||||
} from '../Typography/variants'
|
||||
|
||||
import { deepmerge } from 'deepmerge-ts'
|
||||
import styles from './button.module.css'
|
||||
@@ -14,7 +10,6 @@ export const config = {
|
||||
Primary: styles['variant-primary'],
|
||||
Secondary: styles['variant-secondary'],
|
||||
Tertiary: styles['variant-tertiary'],
|
||||
Inverted: styles['variant-inverted'],
|
||||
Text: styles['variant-text'],
|
||||
},
|
||||
color: {
|
||||
@@ -22,9 +17,9 @@ export const config = {
|
||||
Inverted: styles['color-inverted'],
|
||||
},
|
||||
size: {
|
||||
Small: styles['size-small'],
|
||||
Medium: styles['size-medium'],
|
||||
Large: styles['size-large'],
|
||||
sm: styles['size-sm'],
|
||||
md: styles['size-md'],
|
||||
lg: styles['size-lg'],
|
||||
},
|
||||
wrapping: {
|
||||
true: undefined,
|
||||
@@ -38,24 +33,21 @@ export const config = {
|
||||
defaultVariants: {
|
||||
variant: 'Primary',
|
||||
color: 'Primary',
|
||||
size: 'Large',
|
||||
size: 'lg',
|
||||
wrapping: true,
|
||||
fullWidth: false,
|
||||
},
|
||||
} as const
|
||||
|
||||
const buttonConfig = {
|
||||
variants: {
|
||||
...config.variants,
|
||||
typography: typographyConfig.variants.variant,
|
||||
},
|
||||
defaultVariants: {
|
||||
...config.defaultVariants,
|
||||
typography: 'Body/Paragraph/mdBold',
|
||||
},
|
||||
} as const
|
||||
|
||||
export const variants = cva(styles.button, withTypography(buttonConfig))
|
||||
export const variants = cva(styles.button, buttonConfig)
|
||||
|
||||
export function withButton<T>(config: T) {
|
||||
return deepmerge(buttonConfig, config)
|
||||
|
||||
Reference in New Issue
Block a user