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:
@@ -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'))
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import { Button as ButtonRAC } from 'react-aria-components'
|
||||
|
||||
import { VariantProps } from 'class-variance-authority'
|
||||
import { ComponentProps } from 'react'
|
||||
import { variants } from './variants'
|
||||
|
||||
import type { IconButtonProps } from './types'
|
||||
interface IconButtonProps
|
||||
extends ComponentProps<typeof ButtonRAC>,
|
||||
VariantProps<typeof variants> {}
|
||||
|
||||
export function IconButton({
|
||||
theme,
|
||||
style,
|
||||
variant,
|
||||
emphasis,
|
||||
size,
|
||||
className,
|
||||
wrapping,
|
||||
...props
|
||||
}: IconButtonProps) {
|
||||
const classNames = variants({
|
||||
theme,
|
||||
style,
|
||||
wrapping,
|
||||
variant,
|
||||
emphasis,
|
||||
size,
|
||||
className,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
.iconButton {
|
||||
position: relative;
|
||||
border-radius: var(--Corner-radius-rounded);
|
||||
border-width: 0;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
border-width: 0;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
border-radius: var(--Corner-radius-rounded);
|
||||
|
||||
&[data-disabled] {
|
||||
cursor: unset;
|
||||
@@ -15,13 +17,47 @@
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--Border-Interactive-Focus);
|
||||
outline-offset: 2px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -2px;
|
||||
border: 2px solid var(--Border-Inverted);
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-primary {
|
||||
.size-sm {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.size-md {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.size-lg {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.size-xl {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.variant-filled {
|
||||
background-color: var(--Component-Button-Brand-Primary-Fill-Default);
|
||||
color: var(--Component-Button-Brand-Primary-On-fill-Default);
|
||||
|
||||
&[data-disabled] {
|
||||
background-color: var(--Component-Button-Brand-Primary-Fill-Disabled);
|
||||
color: var(--Component-Button-Brand-Primary-On-fill-Disabled);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover:not([data-disabled]) {
|
||||
background:
|
||||
@@ -35,26 +71,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* This theme 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 */
|
||||
&:focus-visible::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -2px;
|
||||
border: 2px solid var(--Border-Inverted);
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
}
|
||||
&.emphasis {
|
||||
background-color: var(--Component-Button-Brand-Tertiary-Fill-Default);
|
||||
color: var(--Component-Button-Brand-Tertiary-On-fill-Default);
|
||||
|
||||
&[data-disabled] {
|
||||
background-color: var(--Component-Button-Brand-Primary-Fill-Disabled);
|
||||
color: var(--Component-Button-Brand-Primary-On-fill-Disabled);
|
||||
&[data-disabled] {
|
||||
background-color: var(--Component-Button-Brand-Tertiary-Fill-Disabled);
|
||||
color: var(--Component-Button-Brand-Tertiary-On-fill-Disabled);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover:not([data-disabled]) {
|
||||
background:
|
||||
linear-gradient(
|
||||
0deg,
|
||||
var(--Component-Button-Brand-Tertiary-Fill-Hover) 0%,
|
||||
var(--Component-Button-Brand-Tertiary-Fill-Hover) 100%
|
||||
),
|
||||
var(--Component-Button-Brand-Tertiary-Fill-Default);
|
||||
color: var(--Component-Button-Brand-Tertiary-On-fill-Hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-inverted {
|
||||
.variant-outlined {
|
||||
border: 1px solid var(--Border-Default);
|
||||
background-color: var(--Component-Button-Inverted-Fill-Default);
|
||||
color: var(--Component-Button-Inverted-On-fill-Default);
|
||||
color: var(--Icon-Interactive-Default);
|
||||
|
||||
&[data-disabled] {
|
||||
border-color: var(--Border-Interactive-Disabled);
|
||||
background:
|
||||
linear-gradient(
|
||||
0deg,
|
||||
var(--Component-Button-Inverted-Fill-Disabled) 0%,
|
||||
var(--Component-Button-Inverted-Fill-Disabled) 100%
|
||||
),
|
||||
var(--Component-Button-Inverted-Fill-Faded);
|
||||
color: var(--Component-Button-Brand-Primary-On-fill-Disabled);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover:not([data-disabled]) {
|
||||
@@ -68,90 +124,125 @@
|
||||
}
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
background-color: var(--Component-Button-Inverted-Fill-Disabled);
|
||||
color: var(--Component-Button-Inverted-On-fill-Disabled);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline-offset: 0;
|
||||
|
||||
&.style-muted {
|
||||
background-color: var(--Component-Button-Muted-Fill-Default);
|
||||
color: var(--Component-Button-Muted-On-fill-Inverted);
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover:not(:disabled) {
|
||||
background-color: var(--Component-Button-Muted-Fill-Hover);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline-color: var(--Border-Inverted);
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
color: var(--Component-Button-Muted-On-fill-Disabled);
|
||||
&::before {
|
||||
inset: -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-tertiary {
|
||||
background-color: var(--Component-Button-Brand-Tertiary-Fill-Default);
|
||||
color: var(--Component-Button-Brand-Tertiary-On-fill-Default);
|
||||
.variant-elevated {
|
||||
background-color: var(--Component-Button-Inverted-Fill-Default);
|
||||
color: var(--Icon-Interactive-Default);
|
||||
box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&[data-disabled] {
|
||||
background:
|
||||
linear-gradient(
|
||||
0deg,
|
||||
var(--Component-Button-Inverted-Fill-Disabled) 0%,
|
||||
var(--Component-Button-Inverted-Fill-Disabled) 100%
|
||||
),
|
||||
var(--Component-Button-Inverted-Fill-Faded);
|
||||
box-shadow: none;
|
||||
color: var(--Component-Button-Brand-Primary-On-fill-Disabled);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover:not([data-disabled]) {
|
||||
background:
|
||||
linear-gradient(
|
||||
0deg,
|
||||
var(--Component-Button-Brand-Tertiary-Fill-Hover) 0%,
|
||||
var(--Component-Button-Brand-Tertiary-Fill-Hover) 100%
|
||||
var(--Component-Button-Inverted-Fill-Hover) 0%,
|
||||
var(--Component-Button-Inverted-Fill-Hover) 100%
|
||||
),
|
||||
var(--Component-Button-Brand-Tertiary-Fill-Default);
|
||||
color: var(--Component-Button-Brand-Tertiary-On-fill-Hover);
|
||||
var(--Component-Button-Inverted-Fill-Default);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
background-color: var(--Component-Button-Brand-Tertiary-Fill-Disabled);
|
||||
color: var(--Component-Button-Brand-Tertiary-On-fill-Disabled);
|
||||
}
|
||||
}
|
||||
&:focus-visible {
|
||||
outline-offset: 0;
|
||||
|
||||
.theme-black {
|
||||
color: var(--Component-Button-Muted-On-fill-Default);
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover:not([data-disabled]) {
|
||||
color: var(--Component-Button-Muted-On-fill-Hover-Inverted);
|
||||
&::before {
|
||||
inset: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
color: var(--Component-Button-Muted-On-fill-Disabled);
|
||||
}
|
||||
}
|
||||
|
||||
.style-elevated {
|
||||
box-shadow: 0px 0px 8px 1px #0000001a;
|
||||
}
|
||||
|
||||
.style-faded {
|
||||
.variant-faded {
|
||||
background-color: var(--Component-Button-Inverted-Fill-Faded);
|
||||
}
|
||||
color: var(--Icon-Interactive-Default);
|
||||
|
||||
.style-muted {
|
||||
background-color: var(--Component-Button-Muted-Fill-Default);
|
||||
&[data-disabled] {
|
||||
background:
|
||||
linear-gradient(
|
||||
0deg,
|
||||
var(--Component-Button-Inverted-Fill-Disabled) 0%,
|
||||
var(--Component-Button-Inverted-Fill-Disabled) 100%
|
||||
),
|
||||
var(--Component-Button-Inverted-Fill-Default);
|
||||
color: var(--Component-Button-Brand-Primary-On-fill-Disabled);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover:not([data-disabled]) {
|
||||
background-color: var(--Component-Button-Muted-Fill-Hover-inverted);
|
||||
background:
|
||||
linear-gradient(
|
||||
0deg,
|
||||
var(--Component-Button-Inverted-Fill-Hover) 0%,
|
||||
var(--Component-Button-Inverted-Fill-Hover) 100%
|
||||
),
|
||||
var(--Component-Button-Inverted-Fill-Default);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
background-color: var(--Component-Button-Muted-Fill-Disabled-inverted);
|
||||
&:focus-visible {
|
||||
outline-offset: 0;
|
||||
|
||||
&::before {
|
||||
inset: -4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-wrapping {
|
||||
padding: 0;
|
||||
.variant-muted {
|
||||
background-color: var(--Component-Button-Muted-Fill-Default);
|
||||
color: var(--Icon-Inverted);
|
||||
|
||||
&[data-disabled] {
|
||||
background-color: var(--Component-Button-Muted-Fill-Disabled);
|
||||
color: var(--Component-Button-Brand-Primary-On-fill-Disabled);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover:not([data-disabled]) {
|
||||
background-color: var(--Component-Button-Muted-Fill-Hover);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline-offset: 0;
|
||||
|
||||
&::before {
|
||||
inset: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
&.emphasis {
|
||||
color: var(--Component-Button-Muted-On-fill-Default);
|
||||
|
||||
&[data-disabled] {
|
||||
background-color: var(--Component-Button-Muted-Fill-Disabled-inverted);
|
||||
color: var(--Component-Button-Muted-On-fill-Disabled);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover:not([data-disabled]) {
|
||||
background-color: var(--Component-Button-Muted-Fill-Hover-inverted);
|
||||
color: var(--Component-Button-Muted-On-fill-Hover-Inverted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Button } from 'react-aria-components'
|
||||
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import type { ComponentProps } from 'react'
|
||||
|
||||
import type { variants } from './variants'
|
||||
|
||||
export interface IconButtonProps
|
||||
extends Omit<ComponentProps<typeof Button>, 'style'>,
|
||||
VariantProps<typeof variants> {}
|
||||
@@ -2,81 +2,31 @@ import { cva } from 'class-variance-authority'
|
||||
|
||||
import styles from './iconButton.module.css'
|
||||
|
||||
const variantKeys = {
|
||||
theme: {
|
||||
Primary: 'Primary',
|
||||
Tertiary: 'Tertiary',
|
||||
Inverted: 'Inverted',
|
||||
Black: 'Black',
|
||||
export const config = {
|
||||
variants: {
|
||||
variant: {
|
||||
Filled: styles['variant-filled'],
|
||||
Outlined: styles['variant-outlined'],
|
||||
Elevated: styles['variant-elevated'],
|
||||
Faded: styles['variant-faded'],
|
||||
Muted: styles['variant-muted'],
|
||||
},
|
||||
emphasis: {
|
||||
true: styles['emphasis'],
|
||||
false: undefined,
|
||||
},
|
||||
size: {
|
||||
xl: styles['size-xl'],
|
||||
lg: styles['size-lg'],
|
||||
md: styles['size-md'],
|
||||
sm: styles['size-sm'],
|
||||
},
|
||||
},
|
||||
style: {
|
||||
Normal: 'Normal',
|
||||
Muted: 'Muted',
|
||||
Elevated: 'Elevated',
|
||||
Faded: 'Faded',
|
||||
defaultVariants: {
|
||||
variant: 'Filled',
|
||||
size: 'lg',
|
||||
emphasis: false,
|
||||
},
|
||||
} as const
|
||||
|
||||
export const config = {
|
||||
variants: {
|
||||
theme: {
|
||||
[variantKeys.theme.Primary]: styles['theme-primary'],
|
||||
[variantKeys.theme.Tertiary]: styles['theme-tertiary'],
|
||||
[variantKeys.theme.Inverted]: styles['theme-inverted'],
|
||||
[variantKeys.theme.Black]: styles['theme-black'],
|
||||
},
|
||||
// Some variants cannot be used in combination with certain style variants.
|
||||
// The style variant will be applied using the compoundVariants.
|
||||
style: {
|
||||
[variantKeys.style.Normal]: '',
|
||||
[variantKeys.style.Muted]: '',
|
||||
[variantKeys.style.Elevated]: '',
|
||||
[variantKeys.style.Faded]: '',
|
||||
},
|
||||
wrapping: {
|
||||
true: styles['no-wrapping'],
|
||||
false: undefined,
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
// Primary should only use Normal
|
||||
{ theme: variantKeys.theme.Primary, className: styles['style-normal'] },
|
||||
|
||||
// Tertiary should only use Elevated
|
||||
{
|
||||
theme: variantKeys.theme.Tertiary,
|
||||
className: styles['style-elevated'],
|
||||
},
|
||||
|
||||
// Black should only use Muted
|
||||
{ theme: variantKeys.theme.Black, className: styles['style-muted'] },
|
||||
|
||||
// Inverted can use any style variant
|
||||
{
|
||||
theme: variantKeys.theme.Inverted,
|
||||
style: variantKeys.style.Normal,
|
||||
className: styles['style-normal'],
|
||||
},
|
||||
{
|
||||
theme: variantKeys.theme.Inverted,
|
||||
style: variantKeys.style.Muted,
|
||||
className: styles['style-muted'],
|
||||
},
|
||||
{
|
||||
theme: variantKeys.theme.Inverted,
|
||||
style: variantKeys.style.Elevated,
|
||||
className: styles['style-elevated'],
|
||||
},
|
||||
{
|
||||
theme: variantKeys.theme.Inverted,
|
||||
style: variantKeys.style.Faded,
|
||||
className: styles['style-faded'],
|
||||
},
|
||||
],
|
||||
defaultVariants: {
|
||||
theme: variantKeys.theme.Primary,
|
||||
style: variantKeys.style.Normal,
|
||||
},
|
||||
}
|
||||
|
||||
export const variants = cva(styles.iconButton, config)
|
||||
|
||||
Reference in New Issue
Block a user