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

@@ -22,6 +22,6 @@
}
.link:focus-visible {
outline: 2px auto -webkit-focus-ring-color;
outline: 2px auto var(--Border-Interactive-Focus);
outline-offset: -4px;
}

View File

@@ -53,7 +53,6 @@
}
.closeButton {
flex-shrink: 0;
z-index: 1;
}

View File

@@ -119,8 +119,7 @@ export default function CampaignBanner() {
</InnerContent>
<IconButton
className={styles.closeButton}
theme="Inverted"
style="Muted"
variant="Muted"
onPress={handleClose}
aria-label={intl.formatMessage({
id: "campaignBanner.dismissBanner",

View File

@@ -22,8 +22,7 @@ export function CarouselPrevious({ className }: CarouselButtonProps) {
return (
<span className={cx(styles.buttonWrapper, styles.previous, className)}>
<IconButton
theme="Inverted"
style="Elevated"
variant="Elevated"
onPress={scrollPrev}
aria-label={intl.formatMessage({
id: "carousel.previousSlide",
@@ -46,8 +45,7 @@ export function CarouselNext({ className }: CarouselButtonProps) {
return (
<span className={cx(styles.buttonWrapper, styles.next, className)}>
<IconButton
theme="Inverted"
style="Elevated"
variant="Elevated"
onPress={scrollNext}
aria-label={intl.formatMessage({
id: "carousel.nextSlide",

View File

@@ -18,7 +18,7 @@
}
.item:focus-visible {
outline: 2px auto -webkit-focus-ring-color;
outline: 2px auto var(--Border-Interactive-Focus);
outline-offset: 1px;
}
.buttonWrapper {

View File

@@ -46,8 +46,8 @@ export default function CityMapCard({
return (
<article className={cx(styles.cityMapCard, className)}>
<IconButton
theme="Black"
style="Muted"
variant="Muted"
emphasis
className={styles.closeButton}
onPress={handleClose}
aria-label={intl.formatMessage({

View File

@@ -54,8 +54,8 @@ export function DestinationSearch() {
<>
<IconButton
onPress={close}
theme="Black"
style="Muted"
variant="Muted"
emphasis
className={styles.close}
>
<MaterialIcon

View File

@@ -49,8 +49,8 @@ export default function HotelMapCard({
<article className={className}>
<div className={styles.wrapper}>
<IconButton
theme="Black"
style="Muted"
variant="Muted"
emphasis
className={styles.closeButton}
onPress={handleClose}
aria-label={intl.formatMessage({

View File

@@ -63,7 +63,6 @@
.seeAsListButton {
display: flex !important;
pointer-events: initial;
box-shadow: var(--button-box-shadow);
}
/* Overriding Google maps infoWindow styles */

View File

@@ -146,10 +146,9 @@ export default function DynamicMap({
)}
<div className={styles.zoomButtons}>
<IconButton
theme="Inverted"
style="Elevated"
variant="Elevated"
className={styles.zoomButton}
onClick={zoomIn}
onPress={zoomIn}
aria-label={intl.formatMessage({
id: "map.zoomIn",
defaultMessage: "Zoom in",
@@ -159,10 +158,9 @@ export default function DynamicMap({
<MaterialIcon icon="add" color="CurrentColor" size={24} />
</IconButton>
<IconButton
theme="Inverted"
style="Elevated"
variant="Elevated"
className={styles.zoomButton}
onClick={zoomOut}
onPress={zoomOut}
aria-label={intl.formatMessage({
id: "map.zoomOut",
defaultMessage: "Zoom out",

View File

@@ -10,6 +10,5 @@
}
.closeButton {
box-shadow: var(--button-box-shadow);
pointer-events: initial;
}

View File

@@ -11,7 +11,7 @@
padding: var(--Space-x05) 0;
&:focus-visible {
outline: 2px auto -webkit-focus-ring-color;
outline: 2px auto var(--Border-Interactive-Focus);
outline-offset: 1px;
}

View File

@@ -13,7 +13,7 @@
}
&:focus-visible {
outline: 2px auto -webkit-focus-ring-color;
outline: 2px auto var(--Border-Interactive-Focus);
outline-offset: 1px;
}

View File

@@ -129,7 +129,7 @@ export default function HotelFilterAndSort() {
})}
</h3>
</Typography>
<IconButton onPress={close} theme="Black">
<IconButton onPress={close} variant="Muted" emphasis>
<MaterialIcon icon="close" color="CurrentColor" />
</IconButton>
</header>

View File

@@ -7,5 +7,5 @@
}
.download:focus-visible {
outline: 2px solid -webkit-focus-ring-color;
outline: 2px solid var(--Border-Interactive-Focus);
}

View File

@@ -265,8 +265,8 @@ export default function Room({ booking, roomNr, user }: RoomProps) {
subtitle={rateTerm.paymentTerm}
trigger={
<IconButton
theme="Black"
style="Muted"
variant="Muted"
emphasis
className={styles.termsInfoIcon}
>
<MaterialIcon

View File

@@ -50,7 +50,12 @@ export default function Terms() {
title={rateTerm.title}
subtitle={rateTerm.paymentTerm}
trigger={
<IconButton theme="Black" style="Muted" className={styles.button}>
<IconButton
variant="Muted"
emphasis
size="sm"
className={styles.button}
>
<MaterialIcon icon="info" color="Icon/Default" size={20} />
</IconButton>
}

View File

@@ -89,8 +89,8 @@ export default function MeetingPackageWidget(props: MeetingPackageWidgetProps) {
<>
<div className={styles.closeButtonWrapper}>
<IconButton
theme="Black"
style="Muted"
variant="Muted"
emphasis
onPress={close}
className={styles.closeButton}
aria-label={intl.formatMessage({

View File

@@ -41,7 +41,7 @@ export default function LevelProgressModal({
<Modal
className={styles.dialog}
trigger={
<IconButton theme="Black">
<IconButton variant="Muted" emphasis>
<MaterialIcon
className={styles.infoButton}
icon="info"

View File

@@ -71,7 +71,8 @@ export default function DeleteCreditCardConfirmation({
})}
trigger={
<IconButton
theme="Black"
variant="Muted"
emphasis
aria-label={intl.formatMessage({
id: "profile.creditCard.deleteCard",
defaultMessage: "Delete card",

View File

@@ -98,8 +98,9 @@ export default function PasswordInput({
/>
{visibilityToggleable ? (
<IconButton
theme="Black"
onClick={() => setIsPasswordVisible((value) => !value)}
variant="Muted"
emphasis
onPress={() => setIsPasswordVisible((value) => !value)}
aria-label={
isPasswordVisible
? intl.formatMessage({

View File

@@ -134,13 +134,7 @@ export default function BookingCodeFilter() {
})}
</h3>
</Typography>
<IconButton
theme="Black"
style="Muted"
onPress={() => {
close()
}}
>
<IconButton variant="Muted" emphasis onPress={close}>
<MaterialIcon
icon="close"
size={24}

View File

@@ -230,7 +230,7 @@ function CodeRulesModal() {
return (
<Modal
trigger={
<IconButton theme="Black" wrapping>
<IconButton variant="Muted" size="sm" emphasis>
<MaterialIcon
icon="info"
color="Icon/Interactive/Placeholder"

View File

@@ -101,7 +101,7 @@ export default function RewardNight() {
</Typography>
<Modal
trigger={
<IconButton theme="Black" wrapping>
<IconButton variant="Muted" emphasis size="sm">
<MaterialIcon
icon="info"
size={20}

View File

@@ -7,9 +7,6 @@
width: 24px;
height: 24px;
}
.icon {
display: none;
}
.where,
.rooms,
@@ -26,6 +23,10 @@
display: none;
}
.submitButton {
min-width: 118px;
}
.label {
color: var(--Text-Accent-Primary);
}
@@ -58,16 +59,11 @@
padding: var(--Space-x1) var(--Space-x15);
}
.button {
align-self: flex-end;
justify-content: center;
width: 100%;
}
.rooms {
height: 60px;
}
}
.voucherContainer {
height: fit-content;
}
@@ -125,11 +121,6 @@
position: relative;
}
.button {
justify-content: center;
width: 118px;
}
.showOnMobile {
display: none;
}
@@ -160,12 +151,6 @@
width: 48px;
height: 48px;
}
.buttonText {
display: none;
}
.icon {
display: flex;
}
.voucherRow {
display: flex;

View File

@@ -9,6 +9,7 @@ import { useIntl } from "react-intl"
import { hotelreservation } from "@scandic-hotels/common/constants/routes/hotelReservation"
import { dt } from "@scandic-hotels/common/dt"
import { Button } from "@scandic-hotels/design-system/Button"
import { IconButton } from "@scandic-hotels/design-system/IconButton"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
import { Typography } from "@scandic-hotels/design-system/Typography"
@@ -112,11 +113,15 @@ export default function FormContent({
</div>
</div>
<div className={cx(styles.buttonContainer, styles.showOnTablet)}>
<Button className={styles.button} form={formId} type="submit">
<span className={styles.icon}>
<MaterialIcon icon="search" color="Icon/Inverted" size={28} />
</span>
</Button>
<IconButton
size="xl"
variant="Filled"
form={formId}
type="submit"
isDisabled={isSearching}
>
<MaterialIcon icon="search" color="CurrentColor" size={28} />
</IconButton>
</div>
<div
className={cx(
@@ -139,32 +144,23 @@ export default function FormContent({
</div>
) : null}
<Button
className={styles.button}
className={styles.submitButton}
form={formId}
variant="Primary"
size="Medium"
type="submit"
isDisabled={isSearching}
typography="Body/Supporting text (caption)/smBold"
>
<Typography
variant="Body/Supporting text (caption)/smBold"
className={styles.buttonText}
>
<span>
{isDirty && isBookingFlow
? intl.formatMessage({
id: "bookingWidget.button.update",
defaultMessage: "Update",
})
: intl.formatMessage({
id: "bookingWidget.button.search",
defaultMessage: "Search",
})}
</span>
</Typography>
<span className={styles.icon}>
<MaterialIcon icon="search" color="Icon/Inverted" size={28} />
</span>
{isDirty && isBookingFlow
? intl.formatMessage({
id: "bookingWidget.button.update",
defaultMessage: "Update",
})
: intl.formatMessage({
id: "bookingWidget.button.search",
defaultMessage: "Search",
})}
</Button>
</div>
</div>
@@ -214,38 +210,26 @@ export function BookingWidgetFormContentSkeleton() {
</div>
</div>
<div className={cx(styles.buttonContainer, styles.showOnTablet)}>
<Button className={styles.button} type="submit" isDisabled>
<span className={styles.icon}>
<MaterialIcon icon="search" color="Icon/Inverted" size={28} />
</span>
</Button>
<IconButton variant="Filled" size="xl" type="submit" isDisabled>
<MaterialIcon icon="search" color="CurrentColor" size={28} />
</IconButton>
</div>
<div className={cx(styles.voucherContainer, styles.voucherRow)}>
<VoucherSkeleton />
</div>
<div className={cx(styles.buttonContainer, styles.hideOnTablet)}>
<Button
className={styles.button}
className={styles.submitButton}
variant="Primary"
size="Medium"
type="submit"
isDisabled
typography="Body/Supporting text (caption)/smBold"
>
<Typography
variant="Body/Supporting text (caption)/smBold"
className={styles.buttonText}
>
<span>
{intl.formatMessage({
id: "bookingWidget.button.search",
defaultMessage: "Search",
})}
</span>
</Typography>
<span className={styles.icon}>
<MaterialIcon icon="search" color="Icon/Inverted" size={28} />
</span>
{intl.formatMessage({
id: "bookingWidget.button.search",
defaultMessage: "Search",
})}
</Button>
</div>
</div>

View File

@@ -25,9 +25,8 @@ export default function Counter({
<div className={styles.counterContainer}>
<IconButton
className={styles.counterBtn}
onClick={handleOnDecrease}
theme="Inverted"
style="Elevated"
onPress={handleOnDecrease}
variant="Elevated"
isDisabled={disableDecrease}
>
<MaterialIcon icon="remove" color="CurrentColor" />
@@ -37,9 +36,8 @@ export default function Counter({
</Typography>
<IconButton
className={styles.counterBtn}
onClick={handleOnIncrease}
theme="Inverted"
style="Elevated"
onPress={handleOnIncrease}
variant="Elevated"
isDisabled={disableIncrease}
>
<MaterialIcon icon="add" color="CurrentColor" />

View File

@@ -102,7 +102,7 @@
.addRoomBtn:is(:focus, :focus-visible, :focus-within),
.footer .hideOnMobile .addRoomBtn:is(:focus, :focus-visible, :focus-within),
.roomActionsButton:is(:focus, :focus-visible, :focus-within) {
outline: -webkit-focus-ring-color auto 1px;
outline: var(--Border-Interactive-Focus) auto 1px;
text-decoration: none;
}

View File

@@ -52,10 +52,10 @@
background-color: var(--Base-Button-Primary-Fill-Normal);
border-radius: 50%;
display: flex;
height: 36px;
height: 40px;
justify-content: center;
justify-self: flex-end;
width: 36px;
width: 40px;
}
@media screen and (min-width: 768px) {

View File

@@ -67,8 +67,8 @@ export default function ListingHotelCardDialog({
return (
<div className={styles.container}>
<IconButton
theme="Black"
style="Muted"
variant="Muted"
emphasis
className={styles.closeButton}
onPress={handleClose}
aria-label={intl.formatMessage({

View File

@@ -178,8 +178,8 @@ export default function FilterAndSortModal({
</p>
</Typography>
<IconButton
theme="Black"
style="Muted"
variant="Muted"
emphasis
onPress={close}
aria-label={intl.formatMessage({
id: "common.close",

View File

@@ -1,7 +1,5 @@
.container .closeButton {
pointer-events: initial;
box-shadow: var(--button-box-shadow);
gap: var(--Space-x05);
display: none;
}

View File

@@ -89,8 +89,8 @@ export default function SummaryContent({
<IconButton
className={styles.closeButton}
onPress={toggleSummaryOpen}
theme="Black"
style="Muted"
variant="Muted"
emphasis
>
<MaterialIcon
icon="keyboard_arrow_down"

View File

@@ -61,8 +61,8 @@ export function RoomPackageFilterModal({
</h3>
</Typography>
<IconButton
theme="Black"
style="Muted"
variant="Muted"
emphasis
onPress={() => setIsOpen(false)}
>
<MaterialIcon icon="close" size={24} color="CurrentColor" />

View File

@@ -99,9 +99,7 @@ export function RoomPackageFilter({ roomIndex }: { roomIndex: number }) {
/>
{filterLabel}
<IconButton
style="Muted"
theme="Inverted"
wrapping
variant="Muted"
onPress={() => deletePackage(pkg.code)}
aria-label={intl.formatMessage(
{

View File

@@ -1,13 +1,13 @@
import { useIntl } from 'react-intl'
import IconChip from '../IconChip'
import FilledDiscountIcon from '../Icons/Nucleo/Benefits/FilledDiscount'
import { MaterialIcon } from '../Icons/MaterialIcon'
import FilledDiscountIcon from '../Icons/Nucleo/Benefits/FilledDiscount'
import { Typography } from '../Typography'
import styles from './bookingCodeChip.module.css'
import { cx } from 'class-variance-authority'
import { IconButton } from '../IconButton'
import styles from './bookingCodeChip.module.css'
type BaseBookingCodeChipProps = {
alignCenter?: boolean
@@ -102,9 +102,8 @@ export function BookingCodeChip({
</p>
{withCloseButton && (
<IconButton
style="Muted"
theme="Inverted"
wrapping
variant="Muted"
size="sm"
className={styles.removeButton}
onPress={onClose}
aria-label={intl.formatMessage({

View File

@@ -29,8 +29,7 @@ const meta: Meta<typeof Button> = {
summary: buttonConfig.defaultVariants.variant,
},
type: {
summary: 'string',
detail: Object.keys(buttonConfig.variants.variant).join(' | '),
summary: Object.keys(buttonConfig.variants.variant).join(' | '),
},
},
},
@@ -39,8 +38,7 @@ const meta: Meta<typeof Button> = {
options: Object.keys(buttonConfig.variants.color),
table: {
type: {
summary: 'string',
detail: Object.keys(buttonConfig.variants.color).join(' | '),
summary: Object.keys(buttonConfig.variants.color).join(' | '),
},
defaultValue: {
summary: buttonConfig.defaultVariants.color,
@@ -52,8 +50,7 @@ const meta: Meta<typeof Button> = {
options: Object.keys(buttonConfig.variants.size),
table: {
type: {
summary: 'string',
detail: Object.keys(buttonConfig.variants.size).join(' | '),
summary: Object.keys(buttonConfig.variants.size).join(' | '),
},
defaultValue: {
summary: buttonConfig.defaultVariants.size,

View File

@@ -21,23 +21,28 @@
&:focus-visible {
outline: 2px solid var(--Border-Interactive-Focus);
outline-offset: 2px;
&::before {
content: '';
position: absolute;
inset: -4px;
border: 2px solid var(--Border-Inverted);
border-radius: inherit;
pointer-events: none;
}
}
}
.color-inverted:focus-visible {
outline-color: var(--Border-Inverted);
}
.size-large {
padding: var(--Space-x2) var(--Space-x3);
padding: calc(var(--Space-x2) - 2px) var(--Space-x3); /* Adjust for 2px border */
}
.size-medium {
padding: var(--Space-x15) var(--Space-x2);
padding: calc(var(--Space-x15) - 2px) var(--Space-x2); /* Adjust for 2px border */
}
.size-small {
padding: 10px var(--Space-x2);
padding: var(--Space-x1) var(--Space-x2); /* Adjust for 2px border */
}
.variant-primary {
@@ -60,17 +65,6 @@
}
}
/* This variant 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 */
&:not(.color-inverted):focus-visible::before {
content: '';
position: absolute;
inset: -4px;
border: 2px solid var(--Border-Inverted);
border-radius: inherit;
pointer-events: none;
}
&[data-disabled] {
background-color: var(--Component-Button-Brand-Primary-Fill-Disabled);
border-color: var(--Component-Button-Brand-Primary-Border-Disabled);
@@ -103,6 +97,14 @@
border-color: var(--Component-Button-Inverted-Border-Disabled);
color: var(--Component-Button-Inverted-On-fill-Disabled);
}
&:focus-visible {
outline-color: var(--Border-Inverted);
&::before {
border-color: var(--Border-Interactive-Focus);
}
}
}
.variant-secondary {
@@ -150,6 +152,14 @@
border-color: var(--Component-Button-Brand-Secondary-Border-Disabled);
color: var(--Component-Button-Brand-Secondary-On-fill-Disabled);
}
&:focus-visible {
outline-color: var(--Border-Inverted);
&::before {
border-color: var(--Border-Interactive-Focus);
}
}
}
.variant-tertiary {
@@ -229,6 +239,10 @@
.variant-text.no-wrapping {
padding: var(--Space-x025) 0;
border-width: 0;
&:focus-visible {
outline-offset: 4px;
}
}
.variant-text.color-inverted {
@@ -246,6 +260,14 @@
&[data-disabled] {
color: var(--Component-Button-Brand-Secondary-On-fill-Disabled);
}
&:focus-visible {
outline-color: var(--Border-Inverted);
&::before {
border-color: var(--Border-Interactive-Focus);
}
}
}
.spinnerWrapper {

View File

@@ -76,8 +76,8 @@ export function StandaloneHotelCardDialog({
return (
<div className={styles.container}>
<IconButton
theme="Black"
style="Muted"
variant="Muted"
emphasis
className={styles.closeButton}
onPress={handleClose}
aria-label={intl.formatMessage({

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'))

View File

@@ -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,
})

View File

@@ -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);
}
}
}
}

View File

@@ -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> {}

View File

@@ -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)

View File

@@ -12,10 +12,10 @@ import { InputLabel } from '../InputLabel'
import styles from './input.module.css'
import type { InputProps } from './types'
import { Typography } from '../Typography'
import { MaterialIcon } from '../Icons/MaterialIcon'
import { IconButton } from '../IconButton'
import { MaterialIcon } from '../Icons/MaterialIcon'
import { Typography } from '../Typography'
import type { InputProps } from './types'
import { clearInput, useInputHasValue } from './utils'
const InputComponent = forwardRef(function AriaInputWithLabelComponent(
@@ -108,8 +108,9 @@ const InputComponent = forwardRef(function AriaInputWithLabelComponent(
<div className={styles.rightIconContainer}>
<IconButton
className={styles.rightIconButton}
theme="Black"
onClick={onClearContent}
variant="Muted"
emphasis
onPress={onClearContent}
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
aria-label="Clear content"
>
@@ -156,8 +157,9 @@ const InputComponent = forwardRef(function AriaInputWithLabelComponent(
<div className={styles.rightIconContainer}>
<IconButton
className={styles.rightIconButton}
theme="Black"
onClick={onClearContent}
variant="Muted"
emphasis
onPress={onClearContent}
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
aria-label="Clear content"
>

View File

@@ -87,8 +87,7 @@ export default function FullView({
return (
<div className={styles.fullView}>
<IconButton
theme="Inverted"
style="Muted"
variant="Muted"
className={styles.closeButton}
onPress={onClose}
aria-label={intl.formatMessage({
@@ -139,16 +138,16 @@ export default function FullView({
</div>
<IconButton
theme="Inverted"
variant="Muted"
className={`${styles.navigationButton} ${styles.prev}`}
onClick={handlePrev}
onPress={handlePrev}
>
<MaterialIcon icon="arrow_back" color="CurrentColor" />
</IconButton>
<IconButton
theme="Inverted"
variant="Muted"
className={`${styles.navigationButton} ${styles.next}`}
onClick={handleNext}
onPress={handleNext}
>
<MaterialIcon icon="arrow_forward" color="CurrentColor" />
</IconButton>

View File

@@ -88,8 +88,8 @@ export default function Gallery({
return (
<div className={styles.gallery}>
<IconButton
theme="Black"
style="Muted"
variant="Muted"
emphasis
className={styles.closeButton}
onPress={onClose}
aria-label={intl.formatMessage({
@@ -149,8 +149,7 @@ export default function Gallery({
</motion.div>
</AnimatePresence>
<IconButton
theme="Inverted"
style="Elevated"
variant="Elevated"
className={cx(styles.navigationButton, styles.previous)}
onPress={handlePrev}
aria-label={intl.formatMessage({
@@ -161,8 +160,7 @@ export default function Gallery({
<MaterialIcon icon="arrow_back" color="CurrentColor" />
</IconButton>
<IconButton
theme="Inverted"
style="Elevated"
variant="Elevated"
className={cx(styles.navigationButton, styles.next)}
onPress={handleNext}
aria-label={intl.formatMessage({

View File

@@ -142,8 +142,7 @@ export function InteractiveMap({
{closeButton}
<div className={styles.zoomButtons}>
<IconButton
theme="Inverted"
style="Elevated"
variant="Elevated"
className={styles.zoomButton}
onClick={zoomOut}
aria-label={intl.formatMessage({
@@ -156,8 +155,7 @@ export function InteractiveMap({
</IconButton>
<IconButton
theme="Inverted"
style="Elevated"
variant="Elevated"
className={styles.zoomButton}
onClick={zoomIn}
aria-label={intl.formatMessage({

View File

@@ -124,8 +124,8 @@ function InnerModal({
id: 'common.close',
defaultMessage: 'Close',
})}
theme="Black"
style="Muted"
variant="Muted"
emphasis
>
<MaterialIcon
icon="close"

View File

@@ -3,13 +3,13 @@ import { cx } from 'class-variance-authority'
import { Typography } from '../../Typography'
import { Rate, RateTermDetails } from '../types'
import { Button as ButtonRAC } from 'react-aria-components'
import { useIntl } from 'react-intl'
import { IconButton } from '../../IconButton'
import { MaterialIcon } from '../../Icons/MaterialIcon'
import Modal from '../Modal'
import styles from '../rate-card.module.css'
import { variants } from '../variants'
import { Button as ButtonRAC } from 'react-aria-components'
import { useIntl } from 'react-intl'
interface CampaignRateCardProps {
id: string
@@ -79,9 +79,9 @@ export default function CampaignRateCard({
subtitle={paymentTerm}
trigger={
<IconButton
theme="Black"
style="Muted"
wrapping
variant="Muted"
emphasis
size="sm"
className={styles.triggerButton}
aria-label={intl.formatMessage({
id: 'selectRate.rateCard.openReservationPolicy',

View File

@@ -1,14 +1,14 @@
import { Rate, RateTermDetails } from '../types'
import { cx } from 'class-variance-authority'
import { Button as ButtonRAC } from 'react-aria-components'
import { useIntl } from 'react-intl'
import { IconButton } from '../../IconButton'
import { MaterialIcon } from '../../Icons/MaterialIcon'
import { Typography } from '../../Typography'
import Modal from '../Modal'
import styles from '../rate-card.module.css'
import { variants } from '../variants'
import { Button as ButtonRAC } from 'react-aria-components'
import { cx } from 'class-variance-authority'
import { useIntl } from 'react-intl'
interface CodeRateCardProps {
id: string
@@ -69,9 +69,9 @@ export default function CodeRateCard({
subtitle={paymentTerm}
trigger={
<IconButton
theme="Black"
style="Muted"
wrapping
variant="Muted"
emphasis
size="sm"
className={styles.triggerButton}
aria-label={intl.formatMessage({
id: 'selectRate.rateCard.openReservationPolicy',

View File

@@ -3,9 +3,9 @@
import { AnimatePresence, motion } from 'motion/react'
import { type PropsWithChildren, useEffect, useState } from 'react'
import {
Modal as AriaModal,
Dialog,
DialogTrigger,
Modal as AriaModal,
ModalOverlay,
} from 'react-aria-components'
@@ -17,11 +17,11 @@ import {
} from './modal'
import { fade, slideInOut } from './motionVariants'
import styles from './modal.module.css'
import { Typography } from '../../Typography'
import { MaterialIcon } from '../../Icons/MaterialIcon'
import { IconButton } from '../../IconButton'
import { useIntl } from 'react-intl'
import { IconButton } from '../../IconButton'
import { MaterialIcon } from '../../Icons/MaterialIcon'
import { Typography } from '../../Typography'
import styles from './modal.module.css'
const MotionOverlay = motion.create(ModalOverlay)
const MotionModal = motion.create(AriaModal)
@@ -91,8 +91,8 @@ function InnerModal({
)}
</div>
<IconButton
theme="Black"
style="Muted"
variant="Muted"
emphasis
onPress={close}
aria-label={intl.formatMessage({
id: 'common.close',

View File

@@ -34,7 +34,7 @@ export default function NoRateAvailableCard({
<header>
<Typography variant="Tag/sm">
<h3 className={`${styles.title} ${styles.textDisabled}`}>
<IconButton theme="Black" style="Muted">
<IconButton variant="Muted" emphasis size="sm">
<MaterialIcon icon="info" size={20} color="Icon/Default" />
</IconButton>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}

View File

@@ -2,13 +2,13 @@ import { Typography } from '../../Typography'
import { RatePointsOption, RateTermDetails } from '../types'
import { RadioGroup } from 'react-aria-components'
import { useIntl } from 'react-intl'
import { IconButton } from '../../IconButton'
import { MaterialIcon } from '../../Icons/MaterialIcon'
import { Radio } from '../../Radio'
import Modal from '../Modal'
import styles from '../rate-card.module.css'
import { variants } from '../variants'
import { useIntl } from 'react-intl'
interface PointsRateCardProps {
rateTitle: string
@@ -56,9 +56,9 @@ export default function PointsRateCard({
subtitle={paymentTerm}
trigger={
<IconButton
theme="Black"
style="Muted"
wrapping
variant="Muted"
emphasis
size="sm"
aria-label={intl.formatMessage({
id: 'selectRate.rateCard.openReservationPolicy',
defaultMessage: 'Open reservation policy',

View File

@@ -1,14 +1,14 @@
import { Rate, RateTermDetails } from '../types'
import { cx } from 'class-variance-authority'
import { Button as ButtonRAC } from 'react-aria-components'
import { useIntl } from 'react-intl'
import { IconButton } from '../../IconButton'
import { MaterialIcon } from '../../Icons/MaterialIcon'
import { Typography } from '../../Typography'
import Modal from '../Modal'
import styles from '../rate-card.module.css'
import { variants } from '../variants'
import { Button as ButtonRAC } from 'react-aria-components'
import { cx } from 'class-variance-authority'
import { useIntl } from 'react-intl'
interface RegularRateCardProps {
id: string
@@ -64,9 +64,9 @@ export default function RegularRateCard({
subtitle={paymentTerm}
trigger={
<IconButton
theme="Black"
style="Muted"
wrapping
variant="Muted"
emphasis
size="sm"
className={styles.triggerButton}
aria-label={intl.formatMessage({
id: 'selectRate.rateCard.openReservationPolicy',

View File

@@ -65,8 +65,8 @@ export default function SidePeekSelfControlled({
</Typography>
) : null}
<IconButton
theme="Black"
style="Muted"
variant="Muted"
emphasis
onPress={() => handleClose(true)}
aria-label={intl.formatMessage({
id: 'common.close',

View File

@@ -100,8 +100,8 @@ export default function SidePeek({
</Typography>
) : null}
<IconButton
theme="Black"
style="Muted"
variant="Muted"
emphasis
aria-label={closeLabel}
onPress={onClose}
>

View File

@@ -1,12 +1,12 @@
import type { VariantProps } from 'class-variance-authority'
import { toastVariants } from './variants'
import { MaterialIcon, MaterialIconSetIconProps } from '../Icons/MaterialIcon'
import { toastVariants } from './variants'
import styles from './toasts.module.css'
import { Typography } from '../Typography'
import { useIntl } from 'react-intl'
import { IconButton } from '../IconButton'
import { Typography } from '../Typography'
import styles from './toasts.module.css'
export type ToastsProps = VariantProps<typeof toastVariants> & {
variant: NonNullable<VariantProps<typeof toastVariants>['variant']>
@@ -39,12 +39,13 @@ export function Toast({ children, message, onClose, variant }: ToastsProps) {
)}
{onClose ? (
<IconButton
onClick={onClose}
onPress={onClose}
aria-label={intl.formatMessage({
id: 'toast.dismissNotification',
defaultMessage: 'Dismiss notification',
})}
theme="Black"
variant="Muted"
emphasis
>
<MaterialIcon icon="close" />
</IconButton>

View File

@@ -19,3 +19,8 @@ ul {
margin-block-start: 0;
margin-block-end: 0;
}
*:focus-visible {
outline-color: var(--Border-Interactive-Focus);
outline-offset: 2px;
}