Feat/BOOK-293 button adjustments

* feat(BOOK-293): Adjusted padding of the buttons to match Figma design
* feat(BOOK-293): Updated variants for IconButton
* feat(BOOK-113): Updated focus indicators on buttons and added default focus ring color
* feat(BOOK-293): Replaced buttons inside booking widget

Approved-by: Christel Westerberg
This commit is contained in:
Erik Tiekstra
2025-12-15 07:05:31 +00:00
parent c153e0db50
commit 4ec1e85d84
59 changed files with 741 additions and 504 deletions

View File

@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import { expect, fn } from 'storybook/test'
import { MaterialIcon } from '../Icons/MaterialIcon'
import { MaterialIcon, MaterialIconProps } from '../Icons/MaterialIcon'
import { IconButton } from './IconButton'
import { config } from './variants'
@@ -20,37 +20,81 @@ const meta: Meta<typeof IconButton> = {
disable: true,
},
},
theme: {
variant: {
control: 'select',
options: Object.keys(config.variants.theme),
options: Object.keys(config.variants.variant),
table: {
defaultValue: {
summary: config.defaultVariants.theme,
summary: config.defaultVariants.variant,
},
type: {
summary: 'string',
detail: Object.keys(config.variants.theme).join(' | '),
summary: Object.keys(config.variants.variant).join(' | '),
},
},
},
style: {
size: {
control: 'select',
options: Object.keys(config.variants.style),
options: Object.keys(config.variants.size),
table: {
defaultValue: {
summary: config.defaultVariants.style,
summary: config.defaultVariants.size,
},
type: {
summary: 'string',
detail: Object.keys(config.variants.style).join(' | '),
summary: Object.keys(config.variants.size).join(' | '),
},
},
description:
'The style variant is only applied on certain variants. The examples below shows the possible combinations of variants and style variants.',
'The size of the `IconButton`. Please note that you control the size of the icon inside the button separately. Please check the examples below for recommended icon sizes for each button size.',
},
emphasis: {
control: 'boolean',
options: Object.keys(config.variants.emphasis),
table: {
defaultValue: {
summary: config.defaultVariants.emphasis.toString(),
},
type: {
summary: 'boolean',
},
},
},
},
}
const buttonAndIconSizesMap = Object.keys(config.variants.size).map<{
size: keyof typeof config.variants.size
iconSize: number
}>((key) => {
const typedKey = key as keyof typeof config.variants.size
switch (typedKey) {
case 'sm':
return {
size: typedKey,
iconSize: 16,
}
case 'md':
return {
size: typedKey,
iconSize: 20,
}
case 'lg':
return {
size: typedKey,
iconSize: 24,
}
case 'xl':
return {
size: typedKey,
iconSize: 28,
}
default:
return {
size: typedKey,
iconSize: 24,
}
}
})
const globalStoryPropsInverted = {
backgrounds: { value: 'scandicPrimaryDark' },
}
@@ -58,6 +102,36 @@ export default meta
type Story = StoryObj<typeof IconButton>
function renderAllSizesFn(
args: Story['args'],
iconName: MaterialIconProps['icon'] = 'search'
) {
return (
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
{buttonAndIconSizesMap.map(({ size, iconSize }) => (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '8px',
}}
key={size}
>
<IconButton {...args} size={size} key={size}>
<MaterialIcon
icon={iconName}
size={iconSize}
color="CurrentColor"
/>
</IconButton>
<span>{size}</span>
</div>
))}
</div>
)
}
export const Default: Story = {
args: {
onPress: fn(),
@@ -69,11 +143,150 @@ export const Default: Story = {
},
}
export const Primary: Story = {
export const Examples: Story = {
render: () => {
return (
<div style={{ display: 'grid', gap: '16px', justifyContent: 'center' }}>
<div
style={{
padding: '16px',
borderRadius: '8px',
border: '1px solid #4D001B',
}}
>
<h3 style={{ marginBottom: '8px' }}>Filled</h3>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{renderAllSizesFn({ ...Default.args, variant: 'Filled' })}
</div>
</div>
<div
style={{
padding: '16px',
borderRadius: '8px',
border: '1px solid #4D001B',
}}
>
<h3 style={{ marginBottom: '8px' }}>Filled with emphasis</h3>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{renderAllSizesFn(
{
...Default.args,
variant: 'Filled',
emphasis: true,
},
'arrow_forward'
)}
</div>
</div>
<div
style={{
padding: '16px',
borderRadius: '8px',
border: '1px solid #4D001B',
}}
>
<h3 style={{ marginBottom: '8px' }}>Outlined</h3>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{renderAllSizesFn(
{
...Default.args,
variant: 'Outlined',
},
'arrow_forward'
)}
</div>
</div>
<div
style={{
padding: '16px',
borderRadius: '8px',
border: '1px solid #4D001B',
}}
>
<h3 style={{ marginBottom: '8px' }}>Elevated</h3>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{renderAllSizesFn(
{
...Default.args,
variant: 'Elevated',
},
'arrow_forward'
)}
</div>
</div>
<div
style={{
backgroundColor: '#4D001B',
color: 'white',
padding: '16px',
borderRadius: '8px',
border: '1px solid #4D001B',
}}
>
<h3 style={{ marginBottom: '8px' }}>Faded</h3>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{renderAllSizesFn(
{
...Default.args,
variant: 'Faded',
},
'arrow_forward'
)}
</div>
</div>
<div
style={{
backgroundColor: '#4D001B',
color: 'white',
padding: '16px',
borderRadius: '8px',
border: '1px solid #4D001B',
}}
>
<h3 style={{ marginBottom: '8px' }}>Muted</h3>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
gap: '8px',
}}
>
{renderAllSizesFn(
{
...Default.args,
variant: 'Muted',
},
'arrow_forward'
)}
</div>
</div>
<div
style={{
padding: '16px',
borderRadius: '8px',
border: '1px solid #4D001B',
}}
>
<h3 style={{ marginBottom: '8px' }}>Muted with emphasis</h3>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{renderAllSizesFn(
{
...Default.args,
variant: 'Muted',
emphasis: true,
},
'arrow_forward'
)}
</div>
</div>
</div>
)
},
}
export const Filled: Story = {
args: {
...Default.args,
theme: 'Primary',
onPress: fn(), // Fresh spy instance
variant: 'Filled',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
@@ -81,11 +294,10 @@ export const Primary: Story = {
},
}
export const PrimaryDisabled: Story = {
export const FilledDisabled: Story = {
args: {
...Primary.args,
...Filled.args,
isDisabled: true,
onPress: fn(), // Fresh spy instance for disabled test
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
@@ -93,13 +305,49 @@ export const PrimaryDisabled: Story = {
},
}
export const Inverted: Story = {
export const FilledOnDarkBackground: Story = {
globals: globalStoryPropsInverted,
args: {
...Filled.args,
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(1)
},
}
export const FilledWithEmphasis: Story = {
args: {
...Filled.args,
children: (
<MaterialIcon icon="arrow_forward" size={24} color="CurrentColor" />
),
emphasis: true,
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(1)
},
}
export const FilledWithEmphasisDisabled: Story = {
args: {
...FilledWithEmphasis.args,
isDisabled: true,
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(0)
},
}
export const Outlined: Story = {
args: {
...Default.args,
children: (
<MaterialIcon icon="arrow_forward" size={24} color="CurrentColor" />
),
theme: 'Inverted',
variant: 'Outlined',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
@@ -107,11 +355,10 @@ export const Inverted: Story = {
},
}
export const InvertedDisabled: Story = {
export const OutlinedDisabled: Story = {
args: {
...Inverted.args,
...Outlined.args,
isDisabled: true,
onPress: fn(), // Fresh spy instance for disabled test
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
@@ -119,86 +366,13 @@ export const InvertedDisabled: Story = {
},
}
export const InvertedElevated: Story = {
args: {
...Inverted.args,
style: 'Elevated',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(1)
},
}
export const InvertedElevatedDisabled: Story = {
args: {
...InvertedElevated.args,
isDisabled: true,
onPress: fn(), // Fresh spy instance for disabled test
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(0)
},
}
export const InvertedMuted: Story = {
globals: globalStoryPropsInverted,
args: {
...Inverted.args,
children: <MaterialIcon icon="close" size={24} color="CurrentColor" />,
style: 'Muted',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(1)
},
}
export const InvertedMutedDisabled: Story = {
globals: globalStoryPropsInverted,
args: {
...InvertedMuted.args,
isDisabled: true,
onPress: fn(), // Fresh spy instance for disabled test
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(0)
},
}
export const InvertedFaded: Story = {
args: {
...Inverted.args,
style: 'Faded',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(1)
},
}
export const InvertedFadedDisabled: Story = {
args: {
...InvertedFaded.args,
isDisabled: true,
onPress: fn(), // Fresh spy instance for disabled test
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(0)
},
}
export const TertiaryElevated: Story = {
export const Elevated: Story = {
args: {
...Default.args,
children: <MaterialIcon icon="arrow_back" size={24} color="CurrentColor" />,
theme: 'Tertiary',
style: 'Elevated',
children: (
<MaterialIcon icon="arrow_forward" size={24} color="CurrentColor" />
),
variant: 'Elevated',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
@@ -206,11 +380,10 @@ export const TertiaryElevated: Story = {
},
}
export const TertiaryDisabled: Story = {
export const ElevatedDisabled: Story = {
args: {
...TertiaryElevated.args,
...Elevated.args,
isDisabled: true,
onPress: fn(), // Fresh spy instance for disabled test
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
@@ -218,11 +391,14 @@ export const TertiaryDisabled: Story = {
},
}
export const BlackMuted: Story = {
export const Faded: Story = {
globals: globalStoryPropsInverted,
args: {
...Default.args,
children: <MaterialIcon icon="close" size={24} color="CurrentColor" />,
theme: 'Black',
children: (
<MaterialIcon icon="arrow_forward" size={24} color="CurrentColor" />
),
variant: 'Faded',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
@@ -230,11 +406,60 @@ export const BlackMuted: Story = {
},
}
export const BlackMutedDisabled: Story = {
export const FadedDisabled: Story = {
globals: globalStoryPropsInverted,
args: {
...BlackMuted.args,
...Faded.args,
isDisabled: true,
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(0)
},
}
export const Muted: Story = {
globals: globalStoryPropsInverted,
args: {
...Default.args,
children: (
<MaterialIcon icon="arrow_forward" size={24} color="CurrentColor" />
),
variant: 'Muted',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(1)
},
}
export const MutedDisabled: Story = {
globals: globalStoryPropsInverted,
args: {
...Muted.args,
isDisabled: true,
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(0)
},
}
export const MutedWithEmphasis: Story = {
args: {
...Muted.args,
emphasis: true,
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(1)
},
}
export const MutedWithEmphasisDisabled: Story = {
args: {
...MutedWithEmphasis.args,
isDisabled: true,
onPress: fn(), // Fresh spy instance for disabled test
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole('button'))