Fix/SW-1563 accessibility

* fix(SW-1563): Added new IconButton component to the design system and removed Icon variant inside the Button component
* fix(SW-1563): Added buttons around clickable images and changed to design system components
* fix(SW-1563): Renamed variants to match Figma
* fix(SW-1563): Renamed AriaButton to ButtonRAC

Approved-by: Michael Zetterberg
Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-05-02 06:27:30 +00:00
parent efcbde1647
commit 8b32abbefc
32 changed files with 909 additions and 547 deletions

View File

@@ -1,7 +1,7 @@
"use client" "use client"
import { useCallback, useEffect, useRef } from "react" import { useCallback, useEffect, useRef } from "react"
import { Button as AriaButton } from "react-aria-components" import { Button as ButtonRAC } from "react-aria-components"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon" import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon"
@@ -94,12 +94,12 @@ export default function HotelListItem(data: DestinationPagesHotelData) {
<Typography variant="Body/Supporting text (caption)/smRegular"> <Typography variant="Body/Supporting text (caption)/smRegular">
<div className={styles.captions}> <div className={styles.captions}>
<Typography variant="Link/sm"> <Typography variant="Link/sm">
<AriaButton <ButtonRAC
className={styles.addressButton} className={styles.addressButton}
onPress={() => setActiveMarker(hotel.id)} onPress={() => setActiveMarker(hotel.id)}
> >
{address} {address}
</AriaButton> </ButtonRAC>
</Typography> </Typography>
<Divider variant="vertical" color="beige" /> <Divider variant="vertical" color="beige" />
<p> <p>

View File

@@ -2,7 +2,7 @@
import { useState } from "react" import { useState } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
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 { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
@@ -48,9 +48,9 @@ export default function HotelMapCard({
return ( return (
<article className={className}> <article className={className}>
<div className={styles.wrapper}> <div className={styles.wrapper}>
<Button <IconButton
size="Medium" theme="Black"
variant="Icon" style="Muted"
className={styles.closeButton} className={styles.closeButton}
onPress={handleClose} onPress={handleClose}
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
@@ -63,7 +63,7 @@ export default function HotelMapCard({
className={styles.closeIcon} className={styles.closeIcon}
color="CurrentColor" color="CurrentColor"
/> />
</Button> </IconButton>
{image ? ( {image ? (
<DialogImage <DialogImage
image={image.src} image={image.src}

View File

@@ -3,7 +3,7 @@
import { useMap } from "@vis.gl/react-google-maps" import { useMap } from "@vis.gl/react-google-maps"
import { cx } from "class-variance-authority" import { cx } from "class-variance-authority"
import { useState } from "react" import { useState } from "react"
import { Button as AriaButton } from "react-aria-components" import { Button as ButtonRAC } from "react-aria-components"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
@@ -136,12 +136,12 @@ export default function Sidebar({
}`} }`}
> >
<Typography variant="Body/Paragraph/mdBold"> <Typography variant="Body/Paragraph/mdBold">
<AriaButton <ButtonRAC
className={styles.sidebarToggle} className={styles.sidebarToggle}
onPress={toggleFullScreenSidebar} onPress={toggleFullScreenSidebar}
> >
{isFullScreenSidebar ? viewAsMapMsg : viewAsListMsg} {isFullScreenSidebar ? viewAsMapMsg : viewAsListMsg}
</AriaButton> </ButtonRAC>
</Typography> </Typography>
<div className={styles.sidebarContent}> <div className={styles.sidebarContent}>
<Typography variant="Title/sm"> <Typography variant="Title/sm">
@@ -168,7 +168,7 @@ export default function Sidebar({
{pois.map((poi) => ( {pois.map((poi) => (
<li key={poi.name} className={styles.poiItem}> <li key={poi.name} className={styles.poiItem}>
<Typography variant="Body/Paragraph/mdRegular"> <Typography variant="Body/Paragraph/mdRegular">
<AriaButton <ButtonRAC
className={cx(styles.poiButton, { className={cx(styles.poiButton, {
[styles.active]: activePoi === poi.name, [styles.active]: activePoi === poi.name,
})} })}
@@ -188,7 +188,7 @@ export default function Sidebar({
} }
)} )}
</span> </span>
</AriaButton> </ButtonRAC>
</Typography> </Typography>
</li> </li>
))} ))}

View File

@@ -1,11 +1,13 @@
"use client" "use client"
import { useState } from "react" import { useState } from "react"
import { Button as ButtonRAC } from "react-aria-components"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button"
import Image from "@/components/Image" import Image from "@/components/Image"
import Lightbox from "@/components/Lightbox/" import Lightbox from "@/components/Lightbox/"
import Button from "@/components/TempDesignSystem/Button"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery" import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
import styles from "./previewImages.module.css" import styles from "./previewImages.module.css"
@@ -17,31 +19,52 @@ export default function PreviewImages({
hotelName, hotelName,
}: PreviewImagesProps) { }: PreviewImagesProps) {
const intl = useIntl() const intl = useIntl()
const [lightboxIsOpen, setLightboxIsOpen] = useState(false) const [lightboxState, setLightboxState] = useState({
activeIndex: 0,
isOpen: false,
})
const lightboxImages = mapApiImagesToGalleryImages(images) const lightboxImages = mapApiImagesToGalleryImages(images)
return ( return (
<div className={styles.imageWrapper}> <div className={styles.imageWrapper}>
{images.slice(0, 3).map((image, index) => ( {lightboxImages.slice(0, 3).map((image, index) => (
<Image <ButtonRAC
key={index} key={image.src}
src={image.imageSizes.medium} className={styles.imageButton}
alt={image.metaData.altText} aria-label={intl.formatMessage({
title={image.metaData.title} defaultMessage: "See all photos",
width={index === 0 ? 752 : 292} })}
height={index === 0 ? 540 : 266} onPress={() =>
onClick={() => setLightboxIsOpen(true)} setLightboxState({
className={styles.image} activeIndex: index,
/> isOpen: true,
})
}
>
<Image
src={image.src}
alt={image.alt}
title={image.caption || ""}
width={index === 0 ? 752 : 292}
height={index === 0 ? 540 : 266}
className={styles.image}
/>
</ButtonRAC>
))} ))}
{images.length > 1 && ( {images.length > 1 && (
<> <>
<Button <Button
theme="base" variant="Primary"
intent="inverted" color="Inverted"
size="small" size="Small"
onClick={() => setLightboxIsOpen(true)} onPress={() =>
setLightboxState({
activeIndex: 0,
isOpen: true,
})
}
typography="Body/Supporting text (caption)/smBold"
className={styles.seeAllButton} className={styles.seeAllButton}
> >
{intl.formatMessage({ {intl.formatMessage({
@@ -56,8 +79,9 @@ export default function PreviewImages({
}, },
{ title: hotelName } { title: hotelName }
)} )}
isOpen={lightboxIsOpen} isOpen={lightboxState.isOpen}
onClose={() => setLightboxIsOpen(false)} activeIndex={lightboxState.activeIndex}
onClose={() => setLightboxState({ activeIndex: 0, isOpen: false })}
/> />
</> </>
)} )}

View File

@@ -3,18 +3,25 @@
gap: 8px; gap: 8px;
position: relative; position: relative;
width: 100%; width: 100%;
padding: 0 var(--Spacing-x2);
z-index: 0; z-index: 0;
max-width: var(--max-width-page);
margin: 0 auto;
}
.imageButton {
padding: 0;
border-width: 0;
background-color: transparent;
cursor: pointer;
border-radius: var(--Corner-radius-Small);
overflow: hidden;
} }
.image { .image {
object-fit: cover; object-fit: cover;
border-radius: var(--Corner-radius-Small);
width: 100%; width: 100%;
height: 100%; height: 100%;
max-height: 30vh; max-height: 30vh;
cursor: pointer;
max-width: var(--max-width-page);
margin: 0 auto; margin: 0 auto;
} }

View File

@@ -4,6 +4,7 @@ import { Fragment } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button" import { Button } from "@scandic-hotels/design-system/Button"
import { IconButton } from "@scandic-hotels/design-system/IconButton"
import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon" import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
@@ -115,17 +116,18 @@ export default function SummaryUI({
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
{dt(booking.toDate).locale(lang).format("ddd, D MMM")} ({nightsMsg}) {dt(booking.toDate).locale(lang).format("ddd, D MMM")} ({nightsMsg})
</Body> </Body>
<Button <IconButton
onPress={handleToggleSummary} onPress={handleToggleSummary}
className={styles.chevronButton} className={styles.chevronButton}
variant="Icon" theme="Black"
style="Muted"
> >
<MaterialIcon <MaterialIcon
icon="keyboard_arrow_down" icon="keyboard_arrow_down"
size={20} size={20}
color="CurrentColor" color="CurrentColor"
/> />
</Button> </IconButton>
</header> </header>
<Divider color="primaryLightSubtle" /> <Divider color="primaryLightSubtle" />
{rooms.map(({ room }, idx) => { {rooms.map(({ room }, idx) => {

View File

@@ -3,6 +3,7 @@ import { Fragment } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button" 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 { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
@@ -88,13 +89,13 @@ export default function Summary({
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
{dt(booking.toDate).locale(lang).format("ddd, D MMM")} ({nights}) {dt(booking.toDate).locale(lang).format("ddd, D MMM")} ({nights})
</Body> </Body>
<Button onPress={toggleSummaryOpen} variant="Icon"> <IconButton onPress={toggleSummaryOpen} theme="Black" style="Muted">
<MaterialIcon <MaterialIcon
icon="keyboard_arrow_down" icon="keyboard_arrow_down"
size={20} size={20}
color="CurrentColor" color="CurrentColor"
/> />
</Button> </IconButton>
</header> </header>
<Divider color="primaryLightSubtle" /> <Divider color="primaryLightSubtle" />
{rooms.map((room, idx) => { {rooms.map((room, idx) => {

View File

@@ -7,8 +7,8 @@ import {
} from "react-aria-components" } from "react-aria-components"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button"
import { ChipButton } from "@scandic-hotels/design-system/ChipButton" import { ChipButton } from "@scandic-hotels/design-system/ChipButton"
import { IconButton } from "@scandic-hotels/design-system/IconButton"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
@@ -39,9 +39,13 @@ export default function RoomPackageFilterModal() {
{intl.formatMessage({ defaultMessage: "Special needs" })} {intl.formatMessage({ defaultMessage: "Special needs" })}
</h3> </h3>
</Typography> </Typography>
<Button variant="Icon" onPress={() => setIsOpen(false)}> <IconButton
theme="Black"
style="Muted"
onPress={() => setIsOpen(false)}
>
<MaterialIcon icon="close" size={24} color="CurrentColor" /> <MaterialIcon icon="close" size={24} color="CurrentColor" />
</Button> </IconButton>
</div> </div>
<Form close={() => setIsOpen(false)} /> <Form close={() => setIsOpen(false)} />
</Dialog> </Dialog>

View File

@@ -1,5 +1,5 @@
"use client" "use client"
import { Button as AriaButton } from "react-aria-components" import { Button as ButtonRAC } from "react-aria-components"
import { useMediaQuery } from "usehooks-ts" import { useMediaQuery } from "usehooks-ts"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
@@ -69,12 +69,12 @@ export default function RoomPackageFilter() {
color="CurrentColor" color="CurrentColor"
/> />
{pkg.description} {pkg.description}
<AriaButton <ButtonRAC
onPress={() => deleteSelectedPackage(pkg.code)} onPress={() => deleteSelectedPackage(pkg.code)}
className={styles.removeButton} className={styles.removeButton}
> >
<MaterialIcon icon="close" size={16} color="CurrentColor" /> <MaterialIcon icon="close" size={16} color="CurrentColor" />
</AriaButton> </ButtonRAC>
</span> </span>
</Typography> </Typography>
))} ))}

View File

@@ -0,0 +1,126 @@
.fullViewContainer {
background-color: var(--UI-Text-High-contrast);
height: 100%;
padding: var(--Spacing-x3) var(--Spacing-x2);
position: relative;
align-items: center;
justify-items: center;
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-columns: 1fr;
place-content: center;
gap: var(--Spacing-x5);
}
.closeButton {
position: absolute;
top: var(--Space-x2);
right: var(--Space-x2);
z-index: 1;
}
.header {
display: flex;
justify-content: center;
width: 100%;
}
.imageCount {
background-color: var(--Overlay-90);
padding: var(--Space-x025) var(--Space-x05);
border-radius: var(--Corner-radius-Small);
color: var(--Text-Inverted);
}
.imageContainer {
position: relative;
width: 100%;
height: 100%;
max-height: 25rem;
margin-bottom: var(--Spacing-x5);
}
.imageWrapper {
position: absolute;
width: 100%;
height: 100%;
}
.image {
width: 100%;
height: 100%;
object-fit: cover;
}
.footer {
color: var(--Text-Inverted);
position: absolute;
bottom: calc(-1 * var(--Spacing-x5));
}
@media screen and (max-width: 767px) {
.navigationButton {
display: none;
}
}
@media screen and (min-width: 768px) and (max-width: 1366px) {
.fullViewContainer {
padding: var(--Spacing-x5);
}
.imageContainer {
height: 100%;
max-height: 560px;
}
}
@media screen and (min-width: 768px) {
.closeButton {
position: fixed;
top: var(--Spacing-x-one-and-half);
right: var(--Spacing-x-half);
}
.fullViewContainer {
margin-top: 0;
padding: var(--Spacing-x5);
grid-template-rows: auto 1fr auto;
width: 100%;
height: 100%;
}
.imageContainer {
width: 70%;
max-width: 1454px;
max-height: 700px;
}
.navigationButton {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: var(--Component-Button-Inverted-Fill-Default);
color: var(--Component-Button-Inverted-On-fill-Default);
border-radius: var(--Corner-radius-rounded);
padding: 10px;
cursor: pointer;
border-width: 0;
display: flex;
z-index: 1;
box-shadow: 0px 0px 8px 1px #0000001a;
&:hover {
background-color: var(--Component-Button-Inverted-Fill-Hover);
color: var(--Component-Button-Inverted-On-fill-Hover);
}
}
.fullViewNextButton {
right: var(--Spacing-x5);
}
.fullViewPrevButton {
left: var(--Spacing-x5);
}
}

View File

@@ -2,15 +2,15 @@
import { AnimatePresence, motion } from "framer-motion" import { AnimatePresence, motion } from "framer-motion"
import { useState } from "react" import { useState } from "react"
import { useIntl } from "react-intl"
import { IconButton } from "@scandic-hotels/design-system/IconButton"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import Image from "@/components/Image" import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import styles from "./Lightbox.module.css" import styles from "./fullView.module.css"
import type { FullViewProps } from "@/types/components/lightbox/lightbox" import type { FullViewProps } from "@/types/components/lightbox/lightbox"
@@ -23,6 +23,7 @@ export default function FullView({
totalImages, totalImages,
hideLabel, hideLabel,
}: FullViewProps) { }: FullViewProps) {
const intl = useIntl()
const [animateLeft, setAnimateLeft] = useState(true) const [animateLeft, setAnimateLeft] = useState(true)
function handleSwipe(offset: number) { function handleSwipe(offset: number) {
@@ -54,29 +55,26 @@ export default function FullView({
return ( return (
<div className={styles.fullViewContainer}> <div className={styles.fullViewContainer}>
<Button <IconButton
intent="text" theme="Inverted"
size="small" style="Muted"
variant="icon" className={styles.closeButton}
className={styles.fullViewCloseButton} onPress={onClose}
onClick={onClose} aria-label={intl.formatMessage({
defaultMessage: "Close",
})}
> >
<MaterialIcon <MaterialIcon icon="close" color="CurrentColor" size={24} />
icon="close" </IconButton>
size={32} <div className={styles.header}>
className={styles.fullViewCloseIcon} <Typography variant="Tag/sm">
color="Icon/Inverted" <span className={styles.imageCount}>
/>
</Button>
<div className={styles.fullViewHeader}>
<span className={styles.imagePosition}>
<Caption color="white">
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
{`${currentIndex + 1} / ${totalImages}`} {`${currentIndex + 1} / ${totalImages}`}
</Caption> </span>
</span> </Typography>
</div> </div>
<div className={styles.fullViewImageContainer}> <div className={styles.imageContainer}>
<AnimatePresence initial={false} custom={animateLeft}> <AnimatePresence initial={false} custom={animateLeft}>
<motion.div <motion.div
key={image.src} key={image.src}
@@ -86,7 +84,7 @@ export default function FullView({
animate="animate" animate="animate"
exit="exit" exit="exit"
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
className={styles.fullViewImage} className={styles.imageWrapper}
drag="x" drag="x"
onDragEnd={(_e, info) => handleSwipe(info.offset.x)} onDragEnd={(_e, info) => handleSwipe(info.offset.x)}
> >
@@ -95,14 +93,14 @@ export default function FullView({
fill fill
sizes="(min-width: 1500px) 1500px, 100vw" sizes="(min-width: 1500px) 1500px, 100vw"
src={image.src} src={image.src}
style={{ objectFit: "cover" }} className={styles.image}
/> />
<div className={styles.fullViewFooter}> {image.caption && !hideLabel ? (
{image.caption && !hideLabel && ( <Typography variant="Body/Paragraph/mdRegular">
<Body color="white">{image.caption}</Body> <p className={styles.footer}>{image.caption}</p>
)} </Typography>
</div> ) : null}
</motion.div> </motion.div>
</AnimatePresence> </AnimatePresence>
</div> </div>
@@ -112,8 +110,8 @@ export default function FullView({
onClick={handlePrev} onClick={handlePrev}
> >
<MaterialIcon <MaterialIcon
icon="arrow_forward" icon="arrow_back"
color="Icon/Interactive/Default" color="CurrentColor"
className={styles.leftTransformIcon} className={styles.leftTransformIcon}
/> />
</motion.button> </motion.button>
@@ -121,7 +119,7 @@ export default function FullView({
className={`${styles.navigationButton} ${styles.fullViewNextButton}`} className={`${styles.navigationButton} ${styles.fullViewNextButton}`}
onClick={handleNext} onClick={handleNext}
> >
<MaterialIcon icon="arrow_forward" color="Icon/Interactive/Default" /> <MaterialIcon icon="arrow_forward" color="CurrentColor" />
</motion.button> </motion.button>
</div> </div>
) )

View File

@@ -0,0 +1,160 @@
.galleryContainer {
display: grid;
gap: var(--Space-x2);
padding: var(--Space-x2);
height: 100%;
overflow-y: auto;
background-color: var(--Base-Background-Primary-Normal);
}
.mobileGallery {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--Space-x1);
padding-bottom: var(--Space-x3);
}
.thumbnailContainer {
position: relative;
height: 242px;
}
.fullWidthImage {
grid-column: 1 / -1;
height: 240px;
}
.imageButton {
position: relative;
height: 100%;
width: 100%;
padding: 0;
border-width: 0;
background-color: transparent;
cursor: pointer;
border-radius: var(--Corner-radius-Medium);
overflow: hidden;
z-index: 0;
&:focus-visible {
outline-offset: -2px; /* Adjust the outline offset as wrappers uses overflow-hidden */
}
}
.image {
transition: opacity 0.3s ease-in-out;
object-fit: cover;
z-index: -1;
}
@media screen and (max-width: 767px) {
.desktopCloseIcon,
.desktopGallery {
display: none;
}
.closeButton {
justify-self: start;
}
}
@media screen and (min-width: 768px) {
.mobileGallery,
.mobileCloseIcon {
display: none;
}
.galleryContainer {
padding: var(--Spacing-x5) var(--Spacing-x6);
}
.closeButton {
position: absolute;
top: var(--Space-x2);
right: var(--Space-x2);
z-index: 1;
}
.desktopGallery {
display: grid;
grid-template-rows: 28px 1fr 7.8125rem;
row-gap: var(--Spacing-x-one-and-half);
background-color: var(--Base-Background-Primary-Normal);
height: 100%;
position: relative;
overflow: hidden;
}
.galleryHeader {
display: flex;
align-items: center;
}
.imageCaption {
background-color: var(--Base-Surface-Subtle-Normal);
padding: var(--Spacing-x-half) var(--Spacing-x1);
border-radius: var(--Corner-radius-Small);
color: var(--Text-Secondary);
}
.mainImageWrapper {
position: relative;
width: 100%;
}
.mainImageContainer {
width: 100%;
height: 100%;
will-change: transform;
position: absolute;
}
.desktopThumbnailGrid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: var(--Spacing-x1);
max-height: 7.8125rem;
overflow: hidden;
}
.thumbnailContainer {
height: 125px;
}
.fullWidthImage {
grid-column: auto;
height: auto;
}
.thumbnailContainer img {
border-radius: var(--Corner-radius-Small);
}
.navigationButton {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: var(--Component-Button-Inverted-Fill-Default);
color: var(--Component-Button-Inverted-On-fill-Default);
border-radius: var(--Corner-radius-rounded);
padding: 10px;
cursor: pointer;
border-width: 0;
display: flex;
z-index: 1;
box-shadow: 0px 0px 8px 1px #0000001a;
&:hover {
background-color: var(--Component-Button-Inverted-Fill-Hover);
color: var(--Component-Button-Inverted-On-fill-Hover);
}
}
.galleryPrevButton {
left: var(--Spacing-x2);
}
.galleryNextButton {
right: var(--Spacing-x2);
}
}

View File

@@ -1,15 +1,16 @@
"use client" "use client"
import { AnimatePresence, motion } from "framer-motion" import { AnimatePresence, motion } from "framer-motion"
import { useState } from "react" import { useState } from "react"
import { Button as ButtonRAC } from "react-aria-components"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { IconButton } from "@scandic-hotels/design-system/IconButton"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import Image from "@/components/Image" import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import styles from "./Lightbox.module.css" import styles from "./gallery.module.css"
import type { GalleryProps } from "@/types/components/lightbox/lightbox" import type { GalleryProps } from "@/types/components/lightbox/lightbox"
@@ -61,36 +62,38 @@ export default function Gallery({
return ( return (
<div className={styles.galleryContainer}> <div className={styles.galleryContainer}>
<Button <IconButton
intent="text" theme="Black"
size="small" style="Muted"
className={styles.closeButton} className={styles.closeButton}
onClick={onClose} onPress={onClose}
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
defaultMessage: "Close", defaultMessage: "Close",
})} })}
> >
<MaterialIcon <MaterialIcon
icon="chevron_left" icon="chevron_left"
color="Icon/Intense" color="CurrentColor"
size={32} size={24}
className={styles.mobileCloseIcon} className={styles.mobileCloseIcon}
/> />
<MaterialIcon <MaterialIcon
icon="close" icon="close"
size={32} color="CurrentColor"
size={24}
className={styles.desktopCloseIcon} className={styles.desktopCloseIcon}
/> />
</Button> </IconButton>
{/* Desktop Gallery */} {/* Desktop Gallery */}
<div className={styles.desktopGallery}> <div className={styles.desktopGallery}>
<div className={styles.galleryHeader}> <Typography variant="Body/Supporting text (caption)/smRegular">
{mainImage.caption && !hideLabel && ( <p className={styles.galleryHeader}>
<div className={styles.imageCaption}> {mainImage.caption && !hideLabel && (
<Caption color="textMediumContrast">{mainImage.caption}</Caption> <span className={styles.imageCaption}>{mainImage.caption}</span>
</div> )}
)} </p>
</div> </Typography>
<div className={styles.mainImageWrapper}> <div className={styles.mainImageWrapper}>
<AnimatePresence initial={false} custom={animateLeft}> <AnimatePresence initial={false} custom={animateLeft}>
<motion.div <motion.div
@@ -103,34 +106,34 @@ export default function Gallery({
exit="exit" exit="exit"
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
> >
<Image <ButtonRAC
src={mainImage.src} onPress={onImageClick}
alt={mainImage.alt} className={styles.imageButton}
fill aria-label={intl.formatMessage({
sizes="(min-width: 1000px) 1000px, 100vw" defaultMessage: "Open image",
className={styles.image} })}
onClick={onImageClick} >
/> <Image
src={mainImage.src}
alt={mainImage.alt}
fill
sizes="(min-width: 1000px) 1000px, 100vw"
className={styles.image}
/>
</ButtonRAC>
</motion.div> </motion.div>
</AnimatePresence> </AnimatePresence>
<motion.button <motion.button
className={`${styles.navigationButton} ${styles.galleryPrevButton}`} className={`${styles.navigationButton} ${styles.galleryPrevButton}`}
onClick={handlePrev} onClick={handlePrev}
> >
<MaterialIcon <MaterialIcon icon="arrow_back" color="CurrentColor" />
icon="arrow_forward"
color="Icon/Interactive/Default"
className={styles.leftTransformIcon}
/>
</motion.button> </motion.button>
<motion.button <motion.button
className={`${styles.navigationButton} ${styles.galleryNextButton}`} className={`${styles.navigationButton} ${styles.galleryNextButton}`}
onClick={handleNext} onClick={handleNext}
> >
<MaterialIcon <MaterialIcon icon="arrow_forward" color="CurrentColor" />
icon="arrow_forward"
color="Icon/Interactive/Default"
/>
</motion.button> </motion.button>
</div> </div>
<div className={styles.desktopThumbnailGrid}> <div className={styles.desktopThumbnailGrid}>
@@ -139,19 +142,26 @@ export default function Gallery({
<motion.div <motion.div
key={image.smallSrc || image.src} key={image.smallSrc || image.src}
className={styles.thumbnailContainer} className={styles.thumbnailContainer}
onClick={() => onSelectImage(image)}
initial={{ opacity: 0, x: 50 }} initial={{ opacity: 0, x: 50 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -50 }} exit={{ opacity: 0, x: -50 }}
transition={{ duration: 0.2, delay: index * 0.05 }} transition={{ duration: 0.2, delay: index * 0.05 }}
> >
<Image <ButtonRAC
src={image.smallSrc || image.src} className={styles.imageButton}
alt={image.alt} onPress={() => onSelectImage(image)}
fill aria-label={intl.formatMessage({
sizes="200px" defaultMessage: "Open image",
className={styles.image} })}
/> >
<Image
src={image.smallSrc || image.src}
alt={image.alt}
fill
sizes="200px"
className={styles.image}
/>
</ButtonRAC>
</motion.div> </motion.div>
))} ))}
</AnimatePresence> </AnimatePresence>
@@ -160,31 +170,32 @@ export default function Gallery({
{/* Mobile Gallery */} {/* Mobile Gallery */}
<div className={styles.mobileGallery}> <div className={styles.mobileGallery}>
<div className={styles.mobileGalleryContent}> {images.map((image, index) => (
<div className={styles.thumbnailGrid}> <motion.div
{images.map((image, index) => ( key={image.smallSrc || image.src}
<motion.div className={`${styles.thumbnailContainer} ${index % 3 === 0 ? styles.fullWidthImage : ""}`}
key={image.smallSrc || image.src} initial={{ opacity: 0, y: 20 }}
className={`${styles.thumbnailContainer} ${index % 3 === 0 ? styles.fullWidthImage : ""}`} animate={{ opacity: 1, y: 0 }}
onClick={() => { transition={{ duration: 0.3, delay: index * 0.05 }}
onSelectImage(image) >
onImageClick() <ButtonRAC
}} className={styles.imageButton}
initial={{ opacity: 0, y: 20 }} aria-label={intl.formatMessage({ defaultMessage: "Open image" })}
animate={{ opacity: 1, y: 0 }} onPress={() => {
transition={{ duration: 0.3, delay: index * 0.05 }} onSelectImage(image)
> onImageClick()
<Image }}
src={image.smallSrc || image.src} >
alt={image.alt} <Image
fill src={image.smallSrc || image.src}
sizes="100vw" alt={image.alt}
className={styles.image} fill
/> sizes="100vw"
</motion.div> className={styles.image}
))} />
</div> </ButtonRAC>
</div> </motion.div>
))}
</div> </div>
</div> </div>
) )

View File

@@ -1,348 +0,0 @@
@keyframes darken-background {
from {
background-color: rgba(0, 0, 0, 0);
}
to {
background-color: rgba(0, 0, 0, 0.5);
}
}
.mobileGallery {
height: 100%;
position: relative;
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
}
.closeButton {
justify-content: flex-start;
width: fit-content;
}
.closeButton .desktopCloseIcon {
display: none;
}
.mobileGalleryContent {
display: block;
}
.fullViewCloseButton {
position: absolute;
top: var(--Spacing-x-one-and-half);
right: var(--Spacing-x-half);
z-index: 1;
}
.fullViewCloseButton:hover .fullViewCloseIcon {
background-color: var(--UI-Text-Medium-contrast);
border-radius: 50%;
}
.leftTransformIcon {
transform: scaleX(-1);
}
.content {
width: 100%;
height: 100%;
border-radius: 0;
position: fixed;
top: 50%;
left: 50%;
z-index: var(--lightbox-z-index);
}
.overlay {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: var(--lightbox-z-index);
}
.overlay[data-entering] {
animation: darken-background 0.2s;
}
.overlay[data-exiting] {
animation: darken-background 0.2s reverse;
}
.galleryContainer {
background-color: var(--Base-Background-Primary-Normal);
padding: var(--Spacing-x2);
height: 100%;
display: flex;
flex-direction: column;
position: relative;
overflow-y: auto;
}
.galleryHeader {
display: flex;
justify-content: space-between;
align-items: center;
height: 1.71875rem;
}
.desktopGallery,
.desktopThumbnailGrid,
.navigationButton {
display: none;
}
.imageCaption {
background-color: var(--Base-Surface-Subtle-Normal);
padding: var(--Spacing-x-half) var(--Spacing-x1);
border-radius: var(--Corner-radius-Small);
}
.mainImageWrapper {
position: relative;
width: 100%;
}
.mainImageContainer {
width: 100%;
height: 100%;
will-change: transform;
overflow: hidden;
position: absolute;
}
.mainImageContainer img,
.thumbnailContainer img {
border-radius: var(--Corner-radius-Small);
cursor: pointer;
transition: opacity 0.3s ease-in-out;
}
.thumbnailGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--Spacing-x1);
max-height: none;
padding: var(--Spacing-x3) 0;
}
.thumbnailContainer {
position: relative;
height: 242px;
}
.fullWidthImage {
grid-column: 1 / -1;
height: 240px;
}
.thumbnailContainer img {
border-radius: var(--Corner-radius-Medium);
}
.fullViewContainer {
background-color: var(--UI-Text-High-contrast);
height: 100%;
padding: var(--Spacing-x2);
position: relative;
align-items: center;
justify-items: center;
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-columns: 1fr;
place-content: center;
gap: var(--Spacing-x5);
}
.fullViewHeader {
display: flex;
justify-content: center;
width: 100%;
}
.fullViewImageContainer {
position: relative;
width: 100%;
height: 100%;
max-height: 25rem;
margin-bottom: var(--Spacing-x5);
}
.fullViewImage {
position: absolute;
width: 100%;
height: 100%;
border-radius: var(--Corner-radius-Medium);
}
.fullViewImageContainer img {
border-radius: var(--Corner-radius-Medium);
width: 100%;
height: 100%;
}
.fullViewFooter {
position: absolute;
bottom: calc(-1 * var(--Spacing-x5));
}
.imagePosition {
background-color: var(--UI-Grey-90);
padding: var(--Spacing-x-quarter) var(--Spacing-x-half);
border-radius: var(--Corner-radius-Small);
}
.portraitImage {
max-width: 548px;
}
.image {
object-fit: cover;
}
@media (min-width: 768px) and (max-width: 1366px) {
.fullViewContainer {
padding: var(--Spacing-x5);
}
.fullViewImageContainer {
height: 100%;
max-height: 35rem;
}
}
@media (min-width: 768px) {
.mobileGallery,
.thumbnailGrid {
display: none;
}
.content {
position: fixed;
top: 50%;
left: 50%;
overflow: hidden;
}
.content:not(.fullViewContent) {
border-radius: var(--Corner-radius-Large);
}
.galleryContent {
width: 1090px;
width: min(var(--max-width-page), 1090px);
height: min(725px, 85dvh);
}
.fullViewContent {
width: 100vw;
height: 100vh;
}
.galleryContainer {
padding: var(--Spacing-x5) var(--Spacing-x6);
}
.desktopGallery {
display: grid;
grid-template-rows: 1.71875rem 1fr 7.8125rem;
row-gap: var(--Spacing-x-one-and-half);
background-color: var(--Base-Background-Primary-Normal);
height: 100%;
position: relative;
overflow: hidden;
}
.closeButton {
display: block;
position: absolute;
top: var(--Spacing-x-one-and-half);
right: var(--Spacing-x1);
z-index: 1;
}
.closeButton .mobileCloseIcon {
display: none;
}
.closeButton .desktopCloseIcon {
display: block;
}
.closeButton:hover .desktopCloseIcon {
background-color: var(--Base-Surface-Primary-light-Hover-alt);
border-radius: 50%;
}
.desktopThumbnailGrid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: var(--Spacing-x1);
max-height: 7.8125rem;
overflow: hidden;
}
.thumbnailContainer {
height: 125px;
}
.fullViewCloseButton {
position: fixed;
top: var(--Spacing-x-one-and-half);
right: var(--Spacing-x-half);
}
.fullWidthImage {
grid-column: auto;
height: auto;
}
.thumbnailContainer img {
border-radius: var(--Corner-radius-Small);
}
.fullViewContainer {
margin-top: 0;
padding: var(--Spacing-x5);
grid-template-rows: auto 1fr auto;
width: 100%;
height: 100%;
}
.fullViewImageContainer {
width: 70%;
max-width: 90.875rem;
max-height: 43.75rem;
}
.navigationButton {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: var(--Base-Button-Inverted-Fill-Normal);
border-radius: 50%;
padding: var(--Spacing-x1);
cursor: pointer;
border: none;
display: flex;
z-index: 1;
}
.galleryPrevButton {
left: var(--Spacing-x2);
}
.galleryNextButton {
right: var(--Spacing-x2);
}
.fullViewNextButton {
right: var(--Spacing-x5);
}
.fullViewPrevButton {
left: var(--Spacing-x5);
}
.fullViewFooter {
text-align: left;
}
}

View File

@@ -6,7 +6,7 @@ import { Dialog, Modal, ModalOverlay } from "react-aria-components"
import FullView from "./FullView" import FullView from "./FullView"
import Gallery from "./Gallery" import Gallery from "./Gallery"
import styles from "./Lightbox.module.css" import styles from "./lightbox.module.css"
import type { LightboxProps } from "@/types/components/lightbox/lightbox" import type { LightboxProps } from "@/types/components/lightbox/lightbox"

View File

@@ -0,0 +1,57 @@
.overlay {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: var(--lightbox-z-index);
&[data-entering] {
animation: darken-background 0.2s;
}
&[data-exiting] {
animation: darken-background 0.2s reverse;
}
}
.content {
width: 100%;
height: 100%;
border-radius: 0;
position: fixed;
top: 50%;
left: 50%;
z-index: var(--lightbox-z-index);
}
@media screen and (min-width: 768px) {
.content {
position: fixed;
top: 50%;
left: 50%;
overflow: hidden;
&:not(.fullViewContent) {
border-radius: var(--Corner-radius-Large);
}
&.fullViewContent {
width: 100vw;
height: 100vh;
}
&.galleryContent {
width: min(var(--max-width-page), 1090px);
height: min(725px, 85dvh);
}
}
}
@keyframes darken-background {
from {
background-color: rgba(0, 0, 0, 0);
}
to {
background-color: rgba(0, 0, 0, 0.5);
}
}

View File

@@ -36,8 +36,7 @@ const meta: Meta<typeof Button> = {
control: 'select', control: 'select',
options: Object.keys(buttonConfig.variants.size), options: Object.keys(buttonConfig.variants.size),
type: 'string', type: 'string',
description: description: 'The size of the button. Defaults to `Large`.',
'The size of the button. Defaults to `Large`. This variant does not apply to the `Icon` variant.',
}, },
wrapping: { wrapping: {
control: 'radio', control: 'radio',
@@ -351,25 +350,3 @@ export const TextWithIconInverted: Story = {
color: 'Inverted', color: 'Inverted',
}, },
} }
export const Icon: Story = {
args: {
onPress: fn(),
children: <MaterialIcon icon="favorite" size={24} />,
variant: 'Icon',
},
}
export const IconWithColor: Story = {
args: {
onPress: fn(),
children: (
<MaterialIcon
icon="check_circle"
size={24}
color="Icon/Feedback/Success"
/>
),
variant: 'Icon',
},
}

View File

@@ -1,5 +1,3 @@
'use client'
import { Button as ButtonRAC } from 'react-aria-components' import { Button as ButtonRAC } from 'react-aria-components'
import { variants } from './variants' import { variants } from './variants'

View File

@@ -6,6 +6,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: var(--Space-x05);
&:disabled { &:disabled {
cursor: unset; cursor: unset;
@@ -166,11 +167,3 @@
.variant-text.color-inverted:disabled { .variant-text.color-inverted:disabled {
color: var(--Component-Button-Brand-Secondary-On-fill-Disabled); color: var(--Component-Button-Brand-Secondary-On-fill-Disabled);
} }
.variant-icon {
background-color: transparent;
border-color: transparent;
color: inherit;
padding: 0;
margin: 0;
}

View File

@@ -16,7 +16,6 @@ export const config = {
Tertiary: styles['variant-tertiary'], Tertiary: styles['variant-tertiary'],
Inverted: styles['variant-inverted'], Inverted: styles['variant-inverted'],
Text: styles['variant-text'], Text: styles['variant-text'],
Icon: styles['variant-icon'],
}, },
color: { color: {
Primary: styles['color-primary'], Primary: styles['color-primary'],

View File

@@ -0,0 +1,141 @@
import type { Meta, StoryObj } from '@storybook/react'
import { fn } from '@storybook/test'
import { MaterialIcon } from '../Icons/MaterialIcon'
import { IconButton } from './IconButton'
import { config } from './variants'
const meta: Meta<typeof IconButton> = {
title: 'Components/IconButton',
component: IconButton,
argTypes: {
onPress: {
table: {
disable: true,
},
},
theme: {
control: 'select',
options: Object.keys(config.variants.theme),
default: 'Primary',
},
style: {
control: 'select',
options: Object.keys(config.variants.style),
default: 'Normal',
type: 'string',
description: `The style variant is only applied on certain variants. The examples below shows the possible combinations of variants and style variants.`,
},
},
}
export default meta
type Story = StoryObj<typeof IconButton>
export const PrimaryDefault: Story = {
args: {
onPress: fn(),
children: <MaterialIcon icon="search" size={24} color="CurrentColor" />,
theme: 'Primary',
},
}
export const PrimaryDisabled: Story = {
args: {
...PrimaryDefault.args,
isDisabled: true,
},
}
export const InvertedDefault: Story = {
args: {
onPress: fn(),
children: (
<MaterialIcon icon="arrow_forward" size={24} color="CurrentColor" />
),
theme: 'Inverted',
},
}
export const InvertedDisabled: Story = {
args: {
...InvertedDefault.args,
isDisabled: true,
},
}
export const InvertedElevated: Story = {
args: {
...InvertedDefault.args,
style: 'Elevated',
},
}
export const InvertedElevatedDisabled: Story = {
args: {
...InvertedElevated.args,
isDisabled: true,
},
}
export const InvertedMuted: Story = {
args: {
...InvertedDefault.args,
children: <MaterialIcon icon="close" size={24} color="CurrentColor" />,
style: 'Muted',
},
}
export const InvertedMutedDisabled: Story = {
args: {
...InvertedMuted.args,
isDisabled: true,
},
}
export const InvertedFaded: Story = {
args: {
...InvertedDefault.args,
style: 'Faded',
},
}
export const InvertedFadedDisabled: Story = {
args: {
...InvertedFaded.args,
isDisabled: true,
},
}
export const TertiaryElevated: Story = {
args: {
onPress: fn(),
children: <MaterialIcon icon="arrow_back" size={24} color="CurrentColor" />,
theme: 'Tertiary',
style: 'Elevated',
},
}
export const TertiaryDisabled: Story = {
args: {
...TertiaryElevated.args,
isDisabled: true,
},
}
export const BlackMuted: Story = {
args: {
onPress: fn(),
children: <MaterialIcon icon="close" size={24} color="CurrentColor" />,
theme: 'Black',
},
}
export const BlackMutedDisabled: Story = {
args: {
...BlackMuted.args,
isDisabled: true,
},
}

View File

@@ -0,0 +1,20 @@
import { Button as ButtonRAC } from 'react-aria-components'
import { variants } from './variants'
import type { IconButtonProps } from './types'
export function IconButton({
theme,
style,
className,
...props
}: IconButtonProps) {
const classNames = variants({
theme,
style,
className,
})
return <ButtonRAC {...props} className={classNames} />
}

View File

@@ -0,0 +1,102 @@
.iconButton {
border-radius: var(--Corner-radius-rounded);
border-width: 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
&:disabled {
cursor: unset;
}
}
.theme-primary {
background-color: var(--Component-Button-Brand-Primary-Fill-Default);
color: var(--Component-Button-Brand-Primary-On-fill-Default);
&:hover:not(:disabled) {
background-color: var(--Component-Button-Brand-Primary-Fill-Hover);
color: var(--Component-Button-Brand-Primary-On-fill-Hover);
}
&:disabled {
background-color: var(--Component-Button-Brand-Primary-Fill-Disabled);
color: var(--Component-Button-Brand-Primary-On-fill-Disabled);
}
}
.theme-inverted {
background-color: var(--Component-Button-Inverted-Fill-Default);
color: var(--Component-Button-Inverted-On-fill-Default);
&:hover:not(:disabled) {
background-color: var(--Component-Button-Inverted-Fill-Hover);
color: var(--Component-Button-Inverted-On-fill-Hover);
}
&:disabled {
background-color: var(--Component-Button-Inverted-Fill-Disabled);
color: var(--Component-Button-Inverted-On-fill-Disabled);
}
&.style-muted {
color: var(--Component-Button-Muted-On-fill-Inverted);
&:hover:not(:disabled) {
color: var(--Component-Button-Muted-On-fill-Inverted);
}
&:disabled {
color: var(--Component-Button-Muted-On-fill-Disabled);
}
}
}
.theme-tertiary {
background-color: var(--Component-Button-Brand-Tertiary-Fill-Default);
color: var(--Component-Button-Brand-Tertiary-On-fill-Default);
&:hover:not(:disabled) {
background-color: var(--Component-Button-Brand-Tertiary-Fill-Hover);
color: var(--Component-Button-Brand-Tertiary-On-fill-Hover);
}
&:disabled {
background-color: var(--Component-Button-Brand-Tertiary-Fill-Disabled);
color: var(--Component-Button-Brand-Tertiary-On-fill-Disabled);
}
}
.theme-black {
color: var(--Component-Button-Muted-On-fill-Default);
&:hover:not(:disabled) {
color: var(--Component-Button-Muted-On-fill-Hover-Inverted);
}
&:disabled {
color: var(--Component-Button-Muted-On-fill-Disabled);
}
}
.style-elevated {
box-shadow: 0px 0px 8px 1px #0000001a;
}
.style-faded {
background-color: var(--Component-Button-Inverted-Fill-Faded);
}
.style-muted {
background-color: var(--Component-Button-Muted-Fill-Default);
&:hover:not(:disabled) {
background-color: var(--Component-Button-Muted-Fill-Hover-inverted);
}
&:disabled {
background-color: var(--Component-Button-Muted-Fill-Disabled-inverted);
}
}

View File

@@ -0,0 +1 @@
export { IconButton } from './IconButton'

View File

@@ -0,0 +1,10 @@
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

@@ -0,0 +1,78 @@
import { cva } from 'class-variance-authority'
import styles from './iconButton.module.css'
const variantKeys = {
theme: {
Primary: 'Primary',
Tertiary: 'Tertiary',
Inverted: 'Inverted',
Black: 'Black',
},
style: {
Normal: 'Normal',
Muted: 'Muted',
Elevated: 'Elevated',
Faded: 'Faded',
},
} 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]: '',
},
},
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

@@ -1,7 +1,7 @@
import { Typography } from '../../Typography' import { Typography } from '../../Typography'
import { Rate, RateTermDetails } from '../types' import { Rate, RateTermDetails } from '../types'
import { Button } from '../../Button' import { IconButton } from '../../IconButton'
import { MaterialIcon } from '../../Icons/MaterialIcon' import { MaterialIcon } from '../../Icons/MaterialIcon'
import Modal from '../Modal' import Modal from '../Modal'
import styles from '../rate-card.module.css' import styles from '../rate-card.module.css'
@@ -67,13 +67,13 @@ export default function CampaignRateCard({
title={rateTitle} title={rateTitle}
subtitle={paymentTerm} subtitle={paymentTerm}
trigger={ trigger={
<Button variant="Icon" size="Small"> <IconButton theme="Black" style="Muted">
<MaterialIcon <MaterialIcon
icon="info" icon="info"
size={20} size={20}
color="Icon/Default" color="Icon/Default"
/> />
</Button> </IconButton>
} }
> >
{rateTermDetails.map((termGroup) => ( {rateTermDetails.map((termGroup) => (

View File

@@ -1,6 +1,6 @@
import { Rate, RateTermDetails } from '../types' import { Rate, RateTermDetails } from '../types'
import { Button } from '../../Button' import { IconButton } from '../../IconButton'
import { MaterialIcon } from '../../Icons/MaterialIcon' import { MaterialIcon } from '../../Icons/MaterialIcon'
import { Typography } from '../../Typography' import { Typography } from '../../Typography'
import Modal from '../Modal' import Modal from '../Modal'
@@ -63,13 +63,13 @@ export default function CodeRateCard({
title={rateTitle} title={rateTitle}
subtitle={paymentTerm} subtitle={paymentTerm}
trigger={ trigger={
<Button variant="Icon" size="Small"> <IconButton theme="Black" style="Muted">
<MaterialIcon <MaterialIcon
icon="info" icon="info"
size={20} size={20}
color="Icon/Default" color="Icon/Default"
/> />
</Button> </IconButton>
} }
> >
{rateTermDetails.map((termGroup) => ( {rateTermDetails.map((termGroup) => (

View File

@@ -1,4 +1,4 @@
import { Button } from '../../Button' import { IconButton } from '../../IconButton'
import { MaterialIcon } from '../../Icons/MaterialIcon' import { MaterialIcon } from '../../Icons/MaterialIcon'
import { Typography } from '../../Typography' import { Typography } from '../../Typography'
import styles from '../rate-card.module.css' import styles from '../rate-card.module.css'
@@ -34,9 +34,9 @@ export default function NoRateAvailableCard({
<header> <header>
<Typography variant="Tag/sm"> <Typography variant="Tag/sm">
<h3 className={`${styles.title} ${styles.textDisabled}`}> <h3 className={`${styles.title} ${styles.textDisabled}`}>
<Button variant="Icon" size="Small"> <IconButton theme="Black" style="Muted">
<MaterialIcon icon="info" size={20} color="Icon/Default" /> <MaterialIcon icon="info" size={20} color="Icon/Default" />
</Button> </IconButton>
{`${rateTitle} / ${paymentTerm}`} {`${rateTitle} / ${paymentTerm}`}
</h3> </h3>
</Typography> </Typography>

View File

@@ -2,7 +2,7 @@ import { Typography } from '../../Typography'
import { RatePointsOption, RateTermDetails } from '../types' import { RatePointsOption, RateTermDetails } from '../types'
import { RadioGroup } from 'react-aria-components' import { RadioGroup } from 'react-aria-components'
import { Button } from '../../Button' import { IconButton } from '../../IconButton'
import { MaterialIcon } from '../../Icons/MaterialIcon' import { MaterialIcon } from '../../Icons/MaterialIcon'
import { Radio } from '../../Radio' import { Radio } from '../../Radio'
import Modal from '../Modal' import Modal from '../Modal'
@@ -49,9 +49,9 @@ export default function PointsRateCard({
title={rateTitle} title={rateTitle}
subtitle={paymentTerm} subtitle={paymentTerm}
trigger={ trigger={
<Button variant="Icon" size="Small"> <IconButton theme="Black" style="Muted">
<MaterialIcon icon="info" size={20} color="Icon/Default" /> <MaterialIcon icon="info" size={20} color="Icon/Default" />
</Button> </IconButton>
} }
> >
{rateTermDetails.map((termGroup) => ( {rateTermDetails.map((termGroup) => (

View File

@@ -1,6 +1,6 @@
import { Rate, RateTermDetails } from '../types' import { Rate, RateTermDetails } from '../types'
import { Button } from '../../Button' import { IconButton } from '../../IconButton'
import { MaterialIcon } from '../../Icons/MaterialIcon' import { MaterialIcon } from '../../Icons/MaterialIcon'
import { Typography } from '../../Typography' import { Typography } from '../../Typography'
import Modal from '../Modal' import Modal from '../Modal'
@@ -56,13 +56,13 @@ export default function RegularRateCard({
title={rateTitle} title={rateTitle}
subtitle={paymentTerm} subtitle={paymentTerm}
trigger={ trigger={
<Button variant="Icon" size="Small"> <IconButton theme="Black" style="Muted">
<MaterialIcon <MaterialIcon
icon="info" icon="info"
size={20} size={20}
color="Icon/Default" color="Icon/Default"
/> />
</Button> </IconButton>
} }
> >
{rateTermDetails.map((termGroup) => ( {rateTermDetails.map((termGroup) => (

View File

@@ -16,6 +16,7 @@
"./CodeRateCard": "./dist/components/RateCard/Code/index.js", "./CodeRateCard": "./dist/components/RateCard/Code/index.js",
"./PointsRateCard": "./dist/components/RateCard/Points/index.js", "./PointsRateCard": "./dist/components/RateCard/Points/index.js",
"./NoRateAvailableCard": "./dist/components/RateCard/NoRateAvailable/index.js", "./NoRateAvailableCard": "./dist/components/RateCard/NoRateAvailable/index.js",
"./IconButton": "./dist/components/IconButton/index.js",
"./Icons": "./dist/components/Icons/index.js", "./Icons": "./dist/components/Icons/index.js",
"./Icons/BathroomCabinetIcon": "./dist/components/Icons/Nucleo/Amenities_Facilities/bathroom-cabinet-2.js", "./Icons/BathroomCabinetIcon": "./dist/components/Icons/Nucleo/Amenities_Facilities/bathroom-cabinet-2.js",
"./Icons/BedHotelIcon": "./dist/components/Icons/Customised/Amenities_Facilities/BedHotel.js", "./Icons/BedHotelIcon": "./dist/components/Icons/Customised/Amenities_Facilities/BedHotel.js",