feat(SW-415): Added content from API

This commit is contained in:
Pontus Dreij
2024-10-10 10:34:00 +02:00
parent 87547bf128
commit 4c06a1b98d
26 changed files with 467 additions and 120 deletions

View File

@@ -0,0 +1,44 @@
import {
Button,
Dialog,
OverlayArrow,
Popover,
type PopoverProps,
} from "react-aria-components"
import { CloseIcon } from "@/components/Icons"
import styles from "./popover.module.css"
interface PricePopoverProps extends Omit<PopoverProps, "children"> {
children: React.ReactNode
}
export default function PricePopover({
children,
...props
}: PricePopoverProps) {
return (
<Popover {...props}>
<OverlayArrow className={styles.arrow}>
<svg
width="12"
height="12"
viewBox="0 0 12 12"
style={{ display: "block", transform: "rotate(180deg)" }}
>
<path d="M0 0L6 6L12 0" fill="white" />
</svg>
</OverlayArrow>
<Dialog>
<Button
onPress={() => props.onOpenChange?.(false)}
className={styles.closeButton}
>
<CloseIcon className={styles.closeIcon} />
</Button>
{children}
</Dialog>
</Popover>
)
}

View File

@@ -0,0 +1,12 @@
.arrow {
top: -6px;
}
.closeButton {
position: absolute;
top: 5px;
right: 5px;
background: none;
border: none;
cursor: pointer;
}

View File

@@ -1,7 +1,11 @@
import { useIntl } from "react-intl"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./priceTable.module.css"
import { PriceTableProps } from "@/types/components/hotelReservation/selectRate/flexibilityOption"
export default function PriceTable({
@@ -18,39 +22,81 @@ export default function PriceTable({
const showRequestedPrice = publicRequestedPrice && memberRequestedPrice
return (
<dl>
<div>
<dt>{intl.formatMessage({ id: "Standard price" })}</dt>
<dl className={styles.priceTable}>
<div className={styles.priceRow}>
<dt>
<Caption
textTransform="bold"
color={publicLocalPrice ? "uiTextHighContrast" : "disabled"}
>
{intl.formatMessage({ id: "Standard price" })}
</Caption>
</dt>
<dd>
{publicLocalPrice ? (
<>
{publicLocalPrice.pricePerNight} {publicLocalPrice.currency}/
{intl.formatMessage({ id: "night" })}
</>
<div className={styles.price}>
<Subtitle type="two" color="uiTextHighContrast">
{publicLocalPrice.pricePerNight}
</Subtitle>
<Body color="uiTextHighContrast" textTransform="bold">
{publicLocalPrice.currency}
</Body>
</div>
) : (
<Subtitle type="two">{intl.formatMessage({ id: "n/a" })}</Subtitle>
<Subtitle type="two" color="disabled">
{intl.formatMessage({ id: "n/a" })}
</Subtitle>
)}
</dd>
</div>
{memberLocalPrice && (
<div>
<dt>{intl.formatMessage({ id: "Member price" })}</dt>
<dd>
{memberLocalPrice.pricePerNight} {memberLocalPrice.currency}/
{intl.formatMessage({ id: "night" })}
</dd>
</div>
)}
{showRequestedPrice && (
<div>
<dt>{intl.formatMessage({ id: "Approx." })}</dt>
<dd>
{publicRequestedPrice.pricePerNight}/
{memberRequestedPrice.pricePerNight} {publicRequestedPrice.currency}
</dd>
</div>
)}
<div className={styles.priceRow}>
<dt>
<Caption
textTransform="bold"
color={memberLocalPrice ? "red" : "disabled"}
>
{intl.formatMessage({ id: "Member price" })}
</Caption>
</dt>
<dd>
{memberLocalPrice ? (
<div className={styles.price}>
<Subtitle type="two" color="red">
{memberLocalPrice.pricePerNight}
</Subtitle>
<Body color="red" textTransform="bold">
{memberLocalPrice.currency}
</Body>
</div>
) : (
<Body textTransform="bold" color="disabled">
- {intl.formatMessage({ id: "Currency Code" })}
</Body>
)}
</dd>
</div>
<div className={styles.priceRow}>
<dt>
<Caption
color={showRequestedPrice ? "uiTextMediumContrast" : "disabled"}
>
{intl.formatMessage({ id: "Approx." })}
</Caption>
</dt>
<dd>
{showRequestedPrice ? (
<Caption color="uiTextMediumContrast">
{publicRequestedPrice.pricePerNight}/
{memberRequestedPrice.pricePerNight}{" "}
{publicRequestedPrice.currency}
</Caption>
) : (
<Caption color="disabled">- / - EUR</Caption>
)}
</dd>
</div>
</dl>
)
}

View File

@@ -0,0 +1,14 @@
.priceRow {
display: flex;
justify-content: space-between;
padding: var(--Spacing-x-quarter) 0;
}
.priceTable {
margin: 0;
}
.price {
display: flex;
gap: var(--Spacing-x-half);
}

View File

@@ -2,7 +2,11 @@
.disabledCard {
border-radius: var(--Corner-radius-Large);
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
background-color: var(--Base-Surface-Primary-light-Hover);
background-color: var(--Base-Surface-Secondary-light-Normal);
position: relative;
display: flex;
flex-direction: column;
gap: var(--Spacing-x-half);
}
.disabledCard {
@@ -15,10 +19,21 @@
.card:hover {
cursor: pointer;
background-color: var(--Base-Surface-Primary-light-Hover-alt);
}
.checkIcon {
display: none;
}
input[type="radio"]:checked + .card {
border: 1px solid var(--Primary-Dark-On-Surface-Divider);
background-color: var(--Base-Surface-Primary-light-Hover-alt);
}
input[type="radio"]:checked + .card .checkIcon {
display: block;
position: absolute;
top: -10px;
right: -10px;
}
.header {
display: flex;
@@ -26,7 +41,40 @@ input[type="radio"]:checked + .card {
}
.header .infoIcon,
.header .infoIcon * {
stroke: var(--Base-Text-Disabled);
fill: var(--Base-Surface-Primary-light-Hover);
.header .infoIcon path {
stroke: var(--UI-Text-Medium-contrast);
fill: transparent;
}
.button {
background: none;
border: none;
cursor: pointer;
grid-area: chevron;
height: 100%;
justify-self: flex-end;
padding: 0;
}
.popover {
background-color: var(--Main-Grey-White);
border-radius: var(--Corner-radius-Medium);
left: 0px;
max-height: 400px;
padding: var(--Spacing-x2);
top: calc(55px + var(--Spacing-x1));
width: 100%;
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
}
.popover section:focus-visible {
outline: none;
}
.popover .popoverText {
margin-bottom: var(--Spacing-x-half);
}
.popover .popoverHeading {
margin-bottom: var(--Spacing-x1);
font-weight: 600; /* TODO: Remove when this is updated in Design system */
}

View File

@@ -1,9 +1,11 @@
"use client"
import { useIntl } from "react-intl"
import { useState } from "react"
import { Button, DialogTrigger } from "react-aria-components"
import { InfoCircleIcon } from "@/components/Icons"
import { CheckCircleIcon, InfoCircleIcon } from "@/components/Icons"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import PricePopover from "./Popover"
import PriceTable from "./PriceTable"
import styles from "./flexibilityOption.module.css"
@@ -14,18 +16,26 @@ export default function FlexibilityOption({
product,
name,
paymentTerm,
priceInformation,
}: FlexibilityOptionProps) {
const intl = useIntl()
const [rootDiv, setRootDiv] = useState<Element | undefined>(undefined)
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
function setRef(node: Element | null) {
if (node) {
setRootDiv(node)
}
}
if (!product) {
return (
<div className={styles.disabledCard}>
<div className={styles.header}>
<InfoCircleIcon className={styles.infoIcon} />
<Caption>{name}</Caption>
<Caption color="uiTextPlaceholder">({paymentTerm})</Caption>
<PriceTable />
<Caption color="disabled">{name}</Caption>
<Caption color="disabled">({paymentTerm})</Caption>
</div>
<PriceTable />
</div>
)
}
@@ -35,18 +45,65 @@ export default function FlexibilityOption({
return (
<label>
<input
type="radio"
name="rateCode"
value={product.productType.public?.rateCode}
/>
<input type="radio" name="rateCode" value={publicPrice?.rateCode} />
<div className={styles.card}>
<div className={styles.header}>
<InfoCircleIcon className={styles.infoIcon} />
<Caption>{name}</Caption>
<div
className={styles.header}
ref={(node: Element | null) => setRef(node)}
>
<DialogTrigger>
<Button
aria-label="Help"
className={styles.button}
onPress={() => setIsPopoverOpen(true)}
>
<InfoCircleIcon className={styles.infoIcon} />
</Button>
<PricePopover
placement="bottom"
className={styles.popover}
isNonModal
shouldFlip={false}
shouldUpdatePosition={false}
/**
* react-aria uses portals to render Popover in body
* unless otherwise specified. We need it to be contained
* by this component to both access css variables assigned
* on the container as well as to not overflow it at any time.
*/
UNSTABLE_portalContainer={rootDiv}
isOpen={isPopoverOpen}
onOpenChange={setIsPopoverOpen}
>
<Caption
color="uiTextHighContrast"
textTransform="bold"
className={styles.popoverHeading}
>
{name}
</Caption>
{priceInformation?.map((info: string) => (
<Caption
key={info}
color="uiTextHighContrast"
className={styles.popoverText}
>
{info}
</Caption>
))}
</PricePopover>
</DialogTrigger>
<Caption color="uiTextHighContrast">{name}</Caption>
<Caption color="uiTextPlaceholder">({paymentTerm})</Caption>
<PriceTable publicPrice={publicPrice} memberPrice={memberPrice} />
</div>
<PriceTable publicPrice={publicPrice} memberPrice={memberPrice} />
<CheckCircleIcon
color="blue"
className={styles.checkIcon}
width={24}
height={24}
stroke="white"
/>
</div>
</label>
)