feat(BOOK-113): Synced hover/focus states for buttons and added better examples to storybook

* fix(BOOK-113): Updated hover colors after blend/mix has been removed

Approved-by: Christel Westerberg
This commit is contained in:
Erik Tiekstra
2025-12-03 10:45:34 +00:00
parent 60f4b8d878
commit 6730575f7a
24 changed files with 1143 additions and 528 deletions

View File

@@ -0,0 +1,100 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import { expect } from 'storybook/test'
import { BackToTopButton } from '.'
import { config as backToTopButtonConfig } from './variants'
const meta: Meta<typeof BackToTopButton> = {
title: 'Components/BackToTopButton',
component: BackToTopButton,
argTypes: {
onPress: {
table: {
disable: true,
},
},
position: {
control: 'select',
options: Object.keys(backToTopButtonConfig.variants.position),
table: {
type: {
summary: 'string',
detail: Object.keys(backToTopButtonConfig.variants.position).join(
' | '
),
},
defaultValue: {
summary: backToTopButtonConfig.defaultVariants.position,
},
},
},
label: {
control: 'text',
},
},
}
export default meta
type Story = StoryObj<typeof BackToTopButton>
const globalStoryPropsInverted = {
backgrounds: { value: 'scandicPrimaryDark' },
}
export const Default: Story = {
args: {
onPress: () => alert('Back to top button pressed!'),
label: 'Back to top',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(await canvas.findByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(1)
},
}
export const PositionLeft: Story = {
args: {
...Default.args,
position: 'left',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(await canvas.findByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(1)
},
}
export const PositionCenter: Story = {
args: {
...Default.args,
position: 'center',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(await canvas.findByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(1)
},
}
export const PositionRight: Story = {
args: {
...Default.args,
position: 'right',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(await canvas.findByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(1)
},
}
export const OnDarkBackground: Story = {
globals: globalStoryPropsInverted,
args: {
onPress: () => alert('Back to top button pressed!'),
label: 'Back to top',
},
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(await canvas.findByRole('button'))
expect(args.onPress).toHaveBeenCalledTimes(1)
},
}

View File

@@ -1,24 +1,43 @@
.backToTopButton {
display: inline-flex;
padding: var(--Space-x1);
justify-content: center;
align-items: center;
gap: var(--Space-x05);
width: max-content;
color: var(--Component-Button-Brand-Secondary-On-fill-Default);
background-color: var(--Component-Button-Brand-Secondary-Fill-Inverted);
border: 2px solid var(--Component-Button-Brand-Secondary-Border-Default);
border-radius: var(--Corner-radius-Rounded);
box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1);
border-radius: var(--Corner-radius-rounded);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--Space-x05);
padding: var(--Space-x1);
width: max-content;
background-color: var(--Component-Button-Brand-Secondary-Fill-Inverted);
color: var(--Component-Button-Brand-Secondary-On-fill-Default);
border: 2px solid var(--Component-Button-Brand-Secondary-Border-Default);
box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1);
position: sticky;
bottom: var(--Space-x2);
&:hover {
color: var(--Component-Button-Brand-Secondary-On-fill-Inverted);
background-color: var(
--Component-Button-Brand-Secondary-Fill-Hover-Inverted
);
@media (hover: hover) {
&:hover {
background-color: var(
--Component-Button-Brand-Secondary-Fill-Hover-Inverted
);
color: var(--Component-Button-Brand-Secondary-On-fill-Inverted);
}
}
&:focus-visible {
outline: 2px solid var(--Border-Interactive-Focus);
outline-offset: 2px;
/* This button is able to be on top of dark background colors,
so we need to create an illusion that it also has an inverted border on focus */
&::before {
content: '';
position: absolute;
inset: -4px;
border: 2px solid var(--Border-Inverted);
border-radius: inherit;
pointer-events: none;
}
}
}

View File

@@ -1,11 +1,11 @@
'use client'
import { type Button, Button as ButtonRAC } from 'react-aria-components'
import { Button as ButtonRAC } from 'react-aria-components'
import { MaterialIcon } from '../Icons/MaterialIcon'
import { Typography } from '../Typography'
import { backToTopButtonVariants } from './variants'
import { variants } from './variants'
import styles from './backToTopButton.module.css'
@@ -13,8 +13,8 @@ import type { VariantProps } from 'class-variance-authority'
import type { ComponentProps } from 'react'
interface BackToTopButtonProps
extends ComponentProps<typeof Button>,
VariantProps<typeof backToTopButtonVariants> {
extends ComponentProps<typeof ButtonRAC>,
VariantProps<typeof variants> {
label: string
}
@@ -23,13 +23,11 @@ export function BackToTopButton({
label,
...props
}: BackToTopButtonProps) {
const classNames = variants({ position })
return (
<Typography variant="Body/Supporting text (caption)/smBold">
<ButtonRAC
className={backToTopButtonVariants({ position })}
aria-label={label}
{...props}
>
<ButtonRAC className={classNames} aria-label={label} {...props}>
<MaterialIcon icon="arrow_upward" color="CurrentColor" size={20} />
<span className={styles.text}>{label}</span>
</ButtonRAC>

View File

@@ -2,7 +2,7 @@ import { cva } from 'class-variance-authority'
import styles from './backToTopButton.module.css'
export const backToTopButtonVariants = cva(styles.backToTopButton, {
export const config = {
variants: {
position: {
left: styles.left,
@@ -13,4 +13,6 @@ export const backToTopButtonVariants = cva(styles.backToTopButton, {
defaultVariants: {
position: 'right',
},
})
} as const
export const variants = cva(styles.backToTopButton, config)