feat(SW-1988): Replaced current bed component with new design
Approved-by: Chuma Mcphoy (We Ahead)
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
|
||||
width: min(600px, 100%);
|
||||
width: min(700px, 100%);
|
||||
}
|
||||
|
||||
.iconContainer {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
type ExtraBedTypeEnum,
|
||||
} from "@/constants/booking"
|
||||
|
||||
import RadioCard from "@/components/TempDesignSystem/Form/ChoiceCard/Radio"
|
||||
import RadioCard from "@/components/TempDesignSystem/Form/RadioCard"
|
||||
import { useRoomContext } from "@/contexts/Details/Room"
|
||||
|
||||
import BedTypeInfo from "./BedTypeInfo"
|
||||
@@ -114,8 +114,8 @@ function BedIconRenderer({
|
||||
|
||||
return (
|
||||
<div className={`${props.className} ${styles.iconContainer}`}>
|
||||
<MainBedIcon size={32} />
|
||||
{ExtraBedIcon && <ExtraBedIcon size={32} />}
|
||||
<MainBedIcon height={32} />
|
||||
{ExtraBedIcon && <ExtraBedIcon height={32} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import Card from "./_Card"
|
||||
|
||||
import type { RadioProps } from "./_Card/card"
|
||||
|
||||
export default function RadioCard(props: RadioProps) {
|
||||
return <Card {...props} type="radio" />
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
.label {
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
border-radius: var(--Corner-radius-Large);
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
|
||||
transition: all 200ms ease;
|
||||
width: min(100%, 600px);
|
||||
grid-column-gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.label:hover {
|
||||
background-color: var(--Base-Surface-Secondary-light-Hover);
|
||||
}
|
||||
|
||||
.label:has(:checked) {
|
||||
background-color: var(--Primary-Light-Surface-Normal);
|
||||
border-color: var(--Base-Border-Hover);
|
||||
}
|
||||
|
||||
.icon {
|
||||
align-self: center;
|
||||
grid-column: 2/3;
|
||||
grid-row: 1/3;
|
||||
justify-self: flex-end;
|
||||
transition: fill 200ms ease;
|
||||
}
|
||||
|
||||
.label:hover .icon,
|
||||
.label:hover .icon *,
|
||||
.label:has(:checked) .icon,
|
||||
.label:has(:checked) .icon * {
|
||||
fill: var(--Base-Text-Medium-contrast);
|
||||
}
|
||||
|
||||
.label[data-declined="true"]:hover .icon,
|
||||
.label[data-declined="true"]:hover .icon *,
|
||||
.label[data-declined="true"]:has(:checked) .icon,
|
||||
.label[data-declined="true"]:has(:checked) .icon * {
|
||||
fill: var(--Base-Text-Disabled);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.title {
|
||||
grid-column: 1 / 2;
|
||||
}
|
||||
|
||||
.label .text {
|
||||
margin-top: var(--Spacing-x1);
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.listItem {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: var(--Spacing-x-quarter);
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.listItem:first-of-type {
|
||||
margin-top: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.listItem:nth-of-type(n + 2) {
|
||||
margin-top: var(--Spacing-x-quarter);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--Scandic-Brand-Scandic-Red);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import type { IconProps } from "@scandic-hotels/design-system/Icons"
|
||||
|
||||
interface BaseCardProps
|
||||
extends Omit<React.LabelHTMLAttributes<HTMLLabelElement>, "title"> {
|
||||
Icon?: (props: IconProps) => JSX.Element
|
||||
declined?: boolean
|
||||
highlightSubtitle?: boolean
|
||||
iconHeight?: number
|
||||
iconWidth?: number
|
||||
name: string
|
||||
subtitle?: React.ReactNode
|
||||
title: React.ReactNode
|
||||
type: "checkbox" | "radio"
|
||||
value?: string
|
||||
}
|
||||
|
||||
interface ListCardProps extends BaseCardProps {
|
||||
list: {
|
||||
title: string
|
||||
}[]
|
||||
text?: never
|
||||
}
|
||||
|
||||
interface TextCardProps extends BaseCardProps {
|
||||
list?: never
|
||||
text: React.ReactNode
|
||||
}
|
||||
|
||||
interface CleanCardProps extends BaseCardProps {
|
||||
list?: never
|
||||
text?: never
|
||||
}
|
||||
|
||||
export type CardProps = ListCardProps | TextCardProps | CleanCardProps
|
||||
|
||||
export type CheckboxProps =
|
||||
| Omit<ListCardProps, "type">
|
||||
| Omit<TextCardProps, "type">
|
||||
export type RadioProps =
|
||||
| Omit<ListCardProps, "type">
|
||||
| Omit<TextCardProps, "type">
|
||||
| Omit<CleanCardProps, "type">
|
||||
|
||||
export interface ListProps extends Pick<ListCardProps, "declined"> {
|
||||
list?: ListCardProps["list"]
|
||||
}
|
||||
|
||||
export interface SubtitleProps
|
||||
extends Pick<BaseCardProps, "highlightSubtitle" | "subtitle"> {}
|
||||
|
||||
export interface TextProps extends Pick<TextCardProps, "text"> {}
|
||||
@@ -1,116 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useFormContext } from "react-hook-form"
|
||||
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
||||
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
|
||||
import styles from "./card.module.css"
|
||||
|
||||
import type { CardProps, ListProps, SubtitleProps, TextProps } from "./card"
|
||||
|
||||
export default function Card({
|
||||
Icon,
|
||||
iconHeight = 32,
|
||||
iconWidth = 32,
|
||||
declined = false,
|
||||
highlightSubtitle = false,
|
||||
id,
|
||||
list,
|
||||
name,
|
||||
subtitle,
|
||||
text,
|
||||
title,
|
||||
type,
|
||||
value,
|
||||
}: CardProps) {
|
||||
const { register, setValue } = useFormContext()
|
||||
|
||||
function onLabelClick(event: React.MouseEvent) {
|
||||
// Preventing click event on label elements firing twice: https://github.com/facebook/react/issues/14295
|
||||
event.preventDefault()
|
||||
setValue(name, value)
|
||||
}
|
||||
return (
|
||||
<label
|
||||
className={styles.label}
|
||||
data-declined={declined}
|
||||
onClick={onLabelClick}
|
||||
tabIndex={0}
|
||||
>
|
||||
<Caption className={styles.title} color="burgundy" type="label" uppercase>
|
||||
{title}
|
||||
</Caption>
|
||||
<Subtitle highlightSubtitle={highlightSubtitle} subtitle={subtitle} />
|
||||
{Icon ? (
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
color="Icon/Intense"
|
||||
height={iconHeight}
|
||||
width={iconWidth}
|
||||
/>
|
||||
) : null}
|
||||
<List declined={declined} list={list} />
|
||||
<Text text={text} />
|
||||
<input
|
||||
{...register(name)}
|
||||
aria-hidden
|
||||
id={id || name}
|
||||
hidden
|
||||
type={type}
|
||||
value={value}
|
||||
/>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
function List({ declined, list }: ListProps) {
|
||||
if (!list) {
|
||||
return null
|
||||
}
|
||||
|
||||
return list.map((listItem) => (
|
||||
<span key={listItem.title} className={styles.listItem}>
|
||||
{declined ? (
|
||||
<MaterialIcon icon="close" color="Icon/Feedback/Neutral" size={20} />
|
||||
) : (
|
||||
<MaterialIcon icon="check" color="Icon/Accent" size={20} />
|
||||
)}
|
||||
<Footnote color="uiTextMediumContrast">{listItem.title}</Footnote>
|
||||
</span>
|
||||
))
|
||||
}
|
||||
|
||||
function Subtitle({ highlightSubtitle, subtitle }: SubtitleProps) {
|
||||
if (!subtitle) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Caption
|
||||
className={styles.subtitle}
|
||||
color={highlightSubtitle ? "baseTextAccent" : "uiTextMediumContrast"}
|
||||
type="label"
|
||||
uppercase
|
||||
>
|
||||
{subtitle}
|
||||
</Caption>
|
||||
)
|
||||
}
|
||||
|
||||
function Text({ text }: TextProps) {
|
||||
if (!text) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<Footnote className={styles.text} color="uiTextMediumContrast">
|
||||
{text}
|
||||
</Footnote>
|
||||
)
|
||||
}
|
||||
|
||||
export function Highlight({ children }: React.PropsWithChildren) {
|
||||
return <span className={styles.highlight}>{children}</span>
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
"use client"
|
||||
|
||||
import { cx } from "class-variance-authority"
|
||||
import { useFormContext } from "react-hook-form"
|
||||
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import styles from "./radioCard.module.css"
|
||||
|
||||
import type { RadioCardProps } from "./types"
|
||||
|
||||
export default function RadioCard({
|
||||
Icon,
|
||||
iconHeight = 32,
|
||||
id,
|
||||
name,
|
||||
subtitle,
|
||||
title,
|
||||
value,
|
||||
disabled = false,
|
||||
}: RadioCardProps) {
|
||||
const { register, setValue } = useFormContext()
|
||||
|
||||
function onLabelClick(event: React.MouseEvent) {
|
||||
// Preventing click event on label elements firing twice: https://github.com/facebook/react/issues/14295
|
||||
event.preventDefault()
|
||||
if (!disabled) {
|
||||
setValue(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<label
|
||||
className={cx(styles.label, { [styles.disabled]: disabled })}
|
||||
onClick={onLabelClick}
|
||||
tabIndex={0}
|
||||
>
|
||||
<MaterialIcon
|
||||
icon="check"
|
||||
className={styles.selectedIcon}
|
||||
size={22}
|
||||
color="Icon/Inverted"
|
||||
/>
|
||||
{Icon ? (
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
color="CurrentColor"
|
||||
height={iconHeight}
|
||||
/>
|
||||
) : null}
|
||||
<Typography variant="Body/Paragraph/mdBold" className={styles.title}>
|
||||
<p>{title}</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Paragraph/mdBold" className={styles.subtitle}>
|
||||
<p>{subtitle}</p>
|
||||
</Typography>
|
||||
<input
|
||||
{...register(name)}
|
||||
aria-hidden
|
||||
id={id || name}
|
||||
hidden
|
||||
type="radio"
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
/>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
.label {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-areas: "icon icon" "title subtitle";
|
||||
border-radius: var(--Corner-radius-md);
|
||||
border: 1px solid var(--Border-Intense);
|
||||
background: var(--Surface-Primary-Default);
|
||||
padding: var(--Space-x2) var(--Space-x3);
|
||||
gap: var(--Space-x1);
|
||||
}
|
||||
|
||||
.label.disabled {
|
||||
background: var(--Surface-Primary-Disabled);
|
||||
filter: grayscale(1);
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.label:has(:checked) {
|
||||
border: 2px solid var(--Border-Interactive-Selected);
|
||||
}
|
||||
|
||||
.label:not(:has(:checked)) .selectedIcon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.selectedIcon {
|
||||
position: absolute;
|
||||
top: calc(-1 * var(--Space-x15));
|
||||
right: calc(-1 * var(--Space-x15));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 2px solid var(--Base-Border-Inverted);
|
||||
border-radius: var(--Corner-radius-Rounded);
|
||||
background-color: var(--Surface-Feedback-Succes-Accent);
|
||||
}
|
||||
|
||||
.icon {
|
||||
grid-area: icon;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
grid-area: subtitle;
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
|
||||
.title {
|
||||
grid-area: title;
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { IconProps } from "@scandic-hotels/design-system/Icons"
|
||||
|
||||
export interface RadioCardProps
|
||||
extends Omit<React.LabelHTMLAttributes<HTMLLabelElement>, "title"> {
|
||||
Icon?: (props: IconProps) => JSX.Element
|
||||
iconHeight?: number
|
||||
name: string
|
||||
subtitle?: React.ReactNode
|
||||
title: React.ReactNode
|
||||
value?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
import {
|
||||
BedBunkExtraIcon,
|
||||
BedGenericIcon,
|
||||
BedKingIcon,
|
||||
BedPullOutExtraIcon,
|
||||
BedQueenIcon,
|
||||
BedSingleIcon,
|
||||
BedSofaExtraIcon,
|
||||
BedTwinIcon,
|
||||
BedWallExtraIcon,
|
||||
type IconProps,
|
||||
MaterialIcon,
|
||||
MdiBunkBedIcon,
|
||||
MovingBedsIcon,
|
||||
WardIcon,
|
||||
} from "@scandic-hotels/design-system/Icons"
|
||||
|
||||
export enum BookingStatusEnum {
|
||||
@@ -142,15 +147,16 @@ export const BED_TYPE_ICONS: Record<
|
||||
BedTypes,
|
||||
(props: IconProps) => JSX.Element
|
||||
> = {
|
||||
King: () => <MaterialIcon icon="king_bed" />,
|
||||
Queen: () => <MaterialIcon icon="bed" />,
|
||||
Single: () => <MaterialIcon icon="single_bed" />,
|
||||
Twin: () => <MaterialIcon icon="bed" />,
|
||||
SofaBed: () => <MaterialIcon icon="chair" />,
|
||||
WallBed: WardIcon,
|
||||
BunkBed: MdiBunkBedIcon,
|
||||
PullOutBed: MovingBedsIcon,
|
||||
Other: () => <MaterialIcon icon="single_bed" />,
|
||||
King: BedKingIcon,
|
||||
Queen: BedQueenIcon,
|
||||
Single: BedSingleIcon,
|
||||
Twin: BedTwinIcon,
|
||||
|
||||
SofaBed: BedSofaExtraIcon,
|
||||
WallBed: BedWallExtraIcon,
|
||||
BunkBed: BedBunkExtraIcon,
|
||||
PullOutBed: BedPullOutExtraIcon,
|
||||
Other: BedGenericIcon,
|
||||
}
|
||||
|
||||
export enum CancellationRuleEnum {
|
||||
|
||||
Reference in New Issue
Block a user