fixes
This commit is contained in:
committed by
Simon Emanuelsson
parent
93962e4c59
commit
310a5a7a68
17
packages/design-system/lib/components/Icons/CheckCircle.tsx
Normal file
17
packages/design-system/lib/components/Icons/CheckCircle.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { SVGProps } from 'react'
|
||||
|
||||
export default function CheckCircleIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M14.6667 18.4333L11.8148 15.5814C11.5716 15.3383 11.2778 15.2167 10.9333 15.2167C10.5889 15.2167 10.2944 15.3389 10.05 15.5833C9.80556 15.8278 9.68333 16.1222 9.68333 16.4667C9.68333 16.8111 9.8036 17.1036 10.0441 17.3441L13.7833 21.0833C14.0348 21.3389 14.3283 21.4667 14.6636 21.4667C14.999 21.4667 15.2944 21.3389 15.55 21.0833L22.95 13.6833C23.1944 13.4389 23.3167 13.1444 23.3167 12.8C23.3167 12.4556 23.1944 12.1611 22.95 11.9167C22.7056 11.6722 22.4111 11.55 22.0667 11.55C21.7222 11.55 21.4292 11.6708 21.1877 11.9123L14.6667 18.4333ZM16.5 29C14.7021 29 13.0125 28.6582 11.4312 27.9746C9.84986 27.2909 8.47433 26.3632 7.3046 25.1912C6.13487 24.0193 5.20833 22.6435 4.525 21.0638C3.84167 19.484 3.5 17.7961 3.5 16C3.5 14.2021 3.84181 12.5125 4.52543 10.9312C5.20906 9.34986 6.13683 7.97433 7.30877 6.8046C8.4807 5.63487 9.85652 4.70833 11.4362 4.025C13.016 3.34167 14.7039 3 16.5 3C18.2979 3 19.9875 3.34181 21.5688 4.02543C23.1501 4.70906 24.5257 5.63683 25.6954 6.80877C26.8651 7.9807 27.7917 9.35652 28.475 10.9362C29.1583 12.516 29.5 14.2039 29.5 16C29.5 17.7979 29.1582 19.4875 28.4746 21.0688C27.7909 22.6501 26.8632 24.0257 25.6912 25.1954C24.5193 26.3651 23.1435 27.2917 21.5638 27.975C19.984 28.6583 18.2961 29 16.5 29Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import CampaignRateCard from '.'
|
||||
|
||||
import { RateCard } from './RateCard'
|
||||
|
||||
const meta: Meta<typeof RateCard> = {
|
||||
const meta: Meta<typeof CampaignRateCard> = {
|
||||
title: 'Components/RateCard/Campaign',
|
||||
component: RateCard,
|
||||
component: CampaignRateCard,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ maxWidth: '400px' }}>
|
||||
@@ -13,18 +12,25 @@ const meta: Meta<typeof RateCard> = {
|
||||
),
|
||||
],
|
||||
argTypes: {
|
||||
title: { control: 'text' },
|
||||
rateTitle: { control: 'text' },
|
||||
paymentTerm: { control: 'text' },
|
||||
bannerText: { control: 'text' },
|
||||
rate: { control: 'object' },
|
||||
memberRate: { control: 'object' },
|
||||
referenceRate: { control: 'object' },
|
||||
comparisonRate: { control: 'object' },
|
||||
approximateRate: { control: 'object' },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof RateCard>
|
||||
type Story = StoryObj<typeof CampaignRateCard>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
variant: 'Campaign',
|
||||
title: 'NON-REFUNDABLE / PAY NOW',
|
||||
rateTitle: 'NON-REFUNDABLE',
|
||||
paymentTerm: 'PAY NOW',
|
||||
bannerText: 'Campaign ∙ Breakfast included',
|
||||
rate: {
|
||||
label: "Valentine's Special",
|
||||
@@ -32,20 +38,22 @@ export const Default: Story = {
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '198 EUR',
|
||||
price: '198',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
referenceRate: {
|
||||
price: '249 EUR',
|
||||
price: '249',
|
||||
label: 'Lowest past price (last 30 days)',
|
||||
unit: 'EUR',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const Package: Story = {
|
||||
args: {
|
||||
variant: 'Campaign',
|
||||
title: 'NON-REFUNDABLE / PAY NOW',
|
||||
rateTitle: 'NON-REFUNDABLE',
|
||||
paymentTerm: 'PAY NOW',
|
||||
bannerText: 'WDCPHG ∙ Breakfast included',
|
||||
rate: {
|
||||
label: 'Luxurious wine & dine in Copenhagen',
|
||||
@@ -53,16 +61,17 @@ export const Package: Story = {
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '1989 EUR',
|
||||
price: '1989',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const CampaignLoggedIn: Story = {
|
||||
args: {
|
||||
variant: 'Campaign',
|
||||
title: 'NON-REFUNDABLE / PAY NOW',
|
||||
rateTitle: 'NON-REFUNDABLE',
|
||||
paymentTerm: 'PAY NOW',
|
||||
bannerText: 'SUM2025 ∙ Breakfast included',
|
||||
rate: {
|
||||
label: 'Luxurious wine & dine in Copenhagen',
|
||||
@@ -70,8 +79,9 @@ export const CampaignLoggedIn: Story = {
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '198 EUR',
|
||||
price: '198',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
comparisonRate: {
|
||||
price: '249',
|
||||
@@ -83,8 +93,8 @@ export const CampaignLoggedIn: Story = {
|
||||
|
||||
export const CampaignOmnibus: Story = {
|
||||
args: {
|
||||
variant: 'Campaign',
|
||||
title: 'NON-REFUNDABLE / PAY NOW',
|
||||
rateTitle: 'NON-REFUNDABLE',
|
||||
paymentTerm: 'PAY NOW',
|
||||
bannerText: 'WDCPHG ∙ Breakfast included',
|
||||
rate: {
|
||||
label: 'Luxurious wine & dine in Copenhagen',
|
||||
@@ -97,12 +107,14 @@ export const CampaignOmnibus: Story = {
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '198 EUR',
|
||||
price: '198',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
referenceRate: {
|
||||
price: '101 EUR',
|
||||
price: '101',
|
||||
label: 'Lowest past price (last 30 days)',
|
||||
unit: 'EUR',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import { Typography } from '../../Typography'
|
||||
import { Rate } from '../types'
|
||||
|
||||
import styles from '../rate-card.module.css'
|
||||
import { Button } from '../../Button'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircle'
|
||||
import { variants } from '../variants'
|
||||
|
||||
interface CampaignRateCardProps {
|
||||
name: string
|
||||
value: string
|
||||
isSelected: boolean
|
||||
rateTitle: string
|
||||
paymentTerm: string
|
||||
bannerText: string
|
||||
rate: Rate
|
||||
memberRate?: Rate
|
||||
referenceRate: Rate
|
||||
comparisonRate?: Omit<Rate, 'label'>
|
||||
isHighlightedRate?: boolean
|
||||
approximateRate: Rate
|
||||
handleChange: () => void
|
||||
handleTermsClick?: () => void
|
||||
}
|
||||
|
||||
export default function CampaignRateCard({
|
||||
name,
|
||||
value,
|
||||
isSelected,
|
||||
rateTitle,
|
||||
paymentTerm,
|
||||
rate,
|
||||
memberRate,
|
||||
approximateRate,
|
||||
comparisonRate,
|
||||
referenceRate,
|
||||
bannerText,
|
||||
isHighlightedRate,
|
||||
handleChange,
|
||||
handleTermsClick,
|
||||
}: CampaignRateCardProps) {
|
||||
const classNames = variants({
|
||||
variant: 'Campaign',
|
||||
})
|
||||
|
||||
return (
|
||||
<label>
|
||||
<input
|
||||
className={styles.radio}
|
||||
type="radio"
|
||||
name={name}
|
||||
value={value}
|
||||
checked={isSelected}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className={classNames}>
|
||||
<Typography variant="Tag/sm">
|
||||
<p className={styles.banner}>{bannerText}</p>
|
||||
</Typography>
|
||||
<div className={styles.container}>
|
||||
<header>
|
||||
<Typography variant="Tag/sm">
|
||||
<h3 className={styles.title}>
|
||||
<Button
|
||||
variant="Icon"
|
||||
color="IconDefault"
|
||||
size="Small"
|
||||
onPress={handleTermsClick}
|
||||
>
|
||||
<InfoCircleIcon height={20} width={20} />
|
||||
</Button>
|
||||
{rateTitle}
|
||||
<span className={styles.textSecondary}>
|
||||
{` / ${paymentTerm}`}
|
||||
</span>
|
||||
</h3>
|
||||
</Typography>
|
||||
</header>
|
||||
<div className={styles.content}>
|
||||
<div
|
||||
className={`${styles.rateRow} ${isHighlightedRate ? styles.highlightedRate : ''}`}
|
||||
>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<p>{rate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{rate.price}{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{rate.unit}</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
{memberRate ? (
|
||||
<div className={`${styles.rateRow} ${styles.highlightedRate}`}>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<p>{memberRate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{memberRate.price}{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{memberRate.unit}</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
{comparisonRate ? (
|
||||
<div className={`${styles.rateRow} ${styles.comparisonRate}`}>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
<span className={styles.strikethrough}>{rate.price}</span>{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span className={styles.strikethrough}>
|
||||
{comparisonRate.unit}
|
||||
</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={`${styles.rateRow} ${styles.approximateRate}`}>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>{approximateRate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>
|
||||
{approximateRate.price} {approximateRate.unit}
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
{referenceRate ? (
|
||||
<footer className={styles.footer}>
|
||||
<Typography variant="Tag/sm">
|
||||
<p>{referenceRate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Tag/sm">
|
||||
<p>
|
||||
{referenceRate.price} {referenceRate.unit}
|
||||
</p>
|
||||
</Typography>
|
||||
</footer>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import CodeRateCard from '.'
|
||||
|
||||
import { RateCard } from './RateCard'
|
||||
|
||||
const meta: Meta<typeof RateCard> = {
|
||||
const meta: Meta<typeof CodeRateCard> = {
|
||||
title: 'Components/RateCard/Code',
|
||||
component: RateCard,
|
||||
component: CodeRateCard,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ maxWidth: '400px' }}>
|
||||
@@ -13,18 +12,21 @@ const meta: Meta<typeof RateCard> = {
|
||||
),
|
||||
],
|
||||
argTypes: {
|
||||
title: { control: 'text' },
|
||||
rateTitle: { control: 'text' },
|
||||
paymentTerm: { control: 'text' },
|
||||
rate: { control: 'object' },
|
||||
approximateRate: { control: 'object' },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof RateCard>
|
||||
type Story = StoryObj<typeof CodeRateCard>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
variant: 'Code',
|
||||
title: 'FREE CANCELLATION / PAY LATER',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY LATER',
|
||||
bannerText: 'Campaign ∙ Breakfast excluded',
|
||||
rate: {
|
||||
label: "Valentine's Special",
|
||||
@@ -32,16 +34,17 @@ export const Default: Story = {
|
||||
unit: 'EUR/night',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '1989 EUR',
|
||||
price: '1989',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const Voucher: Story = {
|
||||
args: {
|
||||
variant: 'Code',
|
||||
title: 'FREE CANCELLATION / PAY LATER',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY LATER',
|
||||
bannerText: 'VOG ∙ Breakfast included',
|
||||
rate: {
|
||||
label: 'Promotional name here',
|
||||
@@ -53,8 +56,8 @@ export const Voucher: Story = {
|
||||
|
||||
export const CorporateCheck: Story = {
|
||||
args: {
|
||||
variant: 'Code',
|
||||
title: 'FREE CANCELLATION / PAY LATER',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY LATER',
|
||||
bannerText: 'VOG ∙ Breakfast included',
|
||||
rate: {
|
||||
label: 'Promotional name here',
|
||||
@@ -62,16 +65,17 @@ export const CorporateCheck: Story = {
|
||||
unit: 'SEK',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '76 EUR',
|
||||
price: '76',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const DNumberDefault: Story = {
|
||||
args: {
|
||||
variant: 'Code',
|
||||
title: 'FREE CANCELLATION / PAY LATER',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY LATER',
|
||||
bannerText: 'D0043148 ∙ Breakfast included',
|
||||
rate: {
|
||||
label: 'Helsinki Partners Oy',
|
||||
@@ -79,16 +83,17 @@ export const DNumberDefault: Story = {
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '76 EUR',
|
||||
price: '76',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const DNumberHighlightedRate: Story = {
|
||||
args: {
|
||||
variant: 'Code',
|
||||
title: 'FREE CANCELLATION / PAY LATER',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY LATER',
|
||||
bannerText: 'D0043148 ∙ Breakfast included',
|
||||
rate: {
|
||||
label: 'Helsinki Partners Oy',
|
||||
@@ -96,8 +101,9 @@ export const DNumberHighlightedRate: Story = {
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '76 EUR',
|
||||
price: '76',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
isHighlightedRate: true,
|
||||
},
|
||||
@@ -105,8 +111,8 @@ export const DNumberHighlightedRate: Story = {
|
||||
|
||||
export const LNumberDefault: Story = {
|
||||
args: {
|
||||
variant: 'Code',
|
||||
title: 'NON-REFUNDABLE / PAY NOW',
|
||||
rateTitle: 'NON-REFUNDABLE',
|
||||
paymentTerm: 'PAY NOW',
|
||||
bannerText: 'L0043148 ∙ Breakfast included',
|
||||
rate: {
|
||||
label: 'Nordic Team Travel',
|
||||
@@ -114,16 +120,17 @@ export const LNumberDefault: Story = {
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '76 EUR',
|
||||
price: '76',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const LNumberStrikethrough: Story = {
|
||||
args: {
|
||||
variant: 'Code',
|
||||
title: 'NON-REFUNDABLE / PAY NOW',
|
||||
rateTitle: 'NON-REFUNDABLE',
|
||||
paymentTerm: 'PAY NOW',
|
||||
bannerText: 'L0043148 ∙ Breakfast included',
|
||||
rate: {
|
||||
label: 'Nordic Team Travel',
|
||||
@@ -135,8 +142,9 @@ export const LNumberStrikethrough: Story = {
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '230/218 EUR',
|
||||
price: '230/218',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
},
|
||||
}
|
||||
122
packages/design-system/lib/components/RateCard/Code/index.tsx
Normal file
122
packages/design-system/lib/components/RateCard/Code/index.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { Rate } from '../types'
|
||||
|
||||
import styles from '../rate-card.module.css'
|
||||
import { Typography } from '../../Typography'
|
||||
import { Button } from '../../Button'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircle'
|
||||
import { variants } from '../variants'
|
||||
|
||||
interface CodeRateCardProps {
|
||||
name: string
|
||||
value: string
|
||||
isSelected: boolean
|
||||
rateTitle: string
|
||||
paymentTerm: string
|
||||
rate: Rate
|
||||
bannerText: string
|
||||
comparisonRate?: Omit<Rate, 'label'>
|
||||
approximateRate?: Rate
|
||||
isHighlightedRate?: boolean
|
||||
handleChange: () => void
|
||||
handleTermsClick?: () => void
|
||||
}
|
||||
|
||||
export default function CodeRateCard({
|
||||
name,
|
||||
value,
|
||||
isSelected,
|
||||
rateTitle,
|
||||
paymentTerm,
|
||||
rate,
|
||||
approximateRate,
|
||||
comparisonRate,
|
||||
bannerText,
|
||||
isHighlightedRate,
|
||||
handleChange,
|
||||
handleTermsClick,
|
||||
}: CodeRateCardProps) {
|
||||
const classNames = variants({
|
||||
variant: 'Code',
|
||||
})
|
||||
|
||||
return (
|
||||
<label>
|
||||
<input
|
||||
className={styles.radio}
|
||||
type="radio"
|
||||
name={name}
|
||||
value={value}
|
||||
checked={isSelected}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className={classNames}>
|
||||
<Typography variant="Tag/sm">
|
||||
<p className={styles.banner}>{bannerText}</p>
|
||||
</Typography>
|
||||
<div className={styles.container}>
|
||||
<header>
|
||||
<Typography variant="Tag/sm">
|
||||
<h3 className={styles.title}>
|
||||
<Button
|
||||
variant="Icon"
|
||||
color="IconDefault"
|
||||
size="Small"
|
||||
onPress={handleTermsClick}
|
||||
>
|
||||
<InfoCircleIcon height={20} width={20} />
|
||||
</Button>
|
||||
{rateTitle}
|
||||
<span className={styles.textSecondary}>
|
||||
{` / ${paymentTerm}`}
|
||||
</span>
|
||||
</h3>
|
||||
</Typography>
|
||||
</header>
|
||||
<div className={styles.content}>
|
||||
<div
|
||||
className={`${styles.rateRow} ${isHighlightedRate ? styles.highlightedRate : ''}`}
|
||||
>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<p>{rate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{rate.price}{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{rate.unit}</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
{comparisonRate ? (
|
||||
<div className={`${styles.rateRow} ${styles.comparisonRate}`}>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
<span className={styles.strikethrough}>{rate.price}</span>{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span className={styles.strikethrough}>
|
||||
{comparisonRate.unit}
|
||||
</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
{approximateRate ? (
|
||||
<div className={`${styles.rateRow} ${styles.approximateRate}`}>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>{approximateRate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>
|
||||
{approximateRate.price} {approximateRate.unit}
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import NoRateAvailableCard from '.'
|
||||
|
||||
const meta: Meta<typeof NoRateAvailableCard> = {
|
||||
title: 'Components/RateCard/NoRateAvailable',
|
||||
component: NoRateAvailableCard,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ maxWidth: '400px' }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
argTypes: {
|
||||
rateTitle: { control: 'text' },
|
||||
paymentTerm: { control: 'text' },
|
||||
noPricesAvailableText: { control: 'text' },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof NoRateAvailableCard>
|
||||
|
||||
export const NoRateAvailable: Story = {
|
||||
args: {
|
||||
variant: 'Regular',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY NOW',
|
||||
noPricesAvailableText: 'No prices available',
|
||||
},
|
||||
}
|
||||
|
||||
export const NoRateAvailableCampaign: Story = {
|
||||
args: {
|
||||
variant: 'Campaign',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY NOW',
|
||||
bannerText: 'Campaign ∙ Breakfast included',
|
||||
noPricesAvailableText: 'No prices available',
|
||||
},
|
||||
}
|
||||
|
||||
export const NoRateAvailableCode: Story = {
|
||||
args: {
|
||||
variant: 'Code',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY NOW',
|
||||
bannerText: 'WDCPHG ∙ Breakfast included',
|
||||
noPricesAvailableText: 'No prices available',
|
||||
},
|
||||
}
|
||||
|
||||
export const NoRateAvailablePoints: Story = {
|
||||
args: {
|
||||
variant: 'Points',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY NOW',
|
||||
bannerText: 'WDCPHG ∙ Breakfast included',
|
||||
noPricesAvailableText: 'No prices available',
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import styles from '../rate-card.module.css'
|
||||
import { Typography } from '../../Typography'
|
||||
import { Button } from '../../Button'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircle'
|
||||
import { variants } from '../variants'
|
||||
|
||||
interface NoRateAvailableCardProps {
|
||||
variant: 'Regular' | 'Campaign' | 'Code' | 'Points'
|
||||
rateTitle: string
|
||||
paymentTerm: string
|
||||
bannerText?: string
|
||||
noPricesAvailableText: string
|
||||
handleTermsClick?: () => void
|
||||
}
|
||||
|
||||
export default function NoRateAvailableCard({
|
||||
variant,
|
||||
rateTitle,
|
||||
paymentTerm,
|
||||
bannerText,
|
||||
noPricesAvailableText,
|
||||
handleTermsClick,
|
||||
}: NoRateAvailableCardProps) {
|
||||
const classNames = variants({
|
||||
variant,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
{bannerText && (
|
||||
<Typography variant="Tag/sm">
|
||||
<p className={styles.banner}>{bannerText}</p>
|
||||
</Typography>
|
||||
)}
|
||||
<div className={styles.container}>
|
||||
<header>
|
||||
<Typography variant="Tag/sm">
|
||||
<h3 className={`${styles.title} ${styles.textDisabled}`}>
|
||||
<Button
|
||||
variant="Icon"
|
||||
color="IconDefault"
|
||||
size="Small"
|
||||
onPress={handleTermsClick}
|
||||
>
|
||||
<InfoCircleIcon height={20} width={20} />
|
||||
</Button>
|
||||
{`${rateTitle} / ${paymentTerm}`}
|
||||
</h3>
|
||||
</Typography>
|
||||
</header>
|
||||
<div>
|
||||
<div className={styles.noPricesAvailableContainer}>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<p className={styles.noPricesAvailableText}>
|
||||
{noPricesAvailableText}
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { RateCard } from './RateCard'
|
||||
import PointsRateCard from '.'
|
||||
|
||||
const meta: Meta<typeof RateCard> = {
|
||||
const meta: Meta<typeof PointsRateCard> = {
|
||||
title: 'Components/RateCard/Points',
|
||||
component: RateCard,
|
||||
component: PointsRateCard,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ maxWidth: '400px' }}>
|
||||
@@ -13,18 +13,25 @@ const meta: Meta<typeof RateCard> = {
|
||||
),
|
||||
],
|
||||
argTypes: {
|
||||
title: { control: 'text' },
|
||||
rateTitle: { control: 'text' },
|
||||
paymentTerm: { control: 'text' },
|
||||
bannerText: { control: 'text' },
|
||||
rates: { control: 'object' },
|
||||
selectedRate: { control: 'text' },
|
||||
onRateSelect: { action: 'onRateSelect' },
|
||||
isNotEnoughPoints: { control: 'boolean' },
|
||||
notEnoughPointsText: { control: 'text' },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof RateCard>
|
||||
type Story = StoryObj<typeof PointsRateCard>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
variant: 'Points',
|
||||
title: 'FREE CANCELLATION / PAY LATER',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY LATER',
|
||||
bannerText: 'Reward night ∙ Breakfast included',
|
||||
rates: [
|
||||
{
|
||||
@@ -34,7 +41,7 @@ export const Default: Story = {
|
||||
{
|
||||
points: '15000',
|
||||
currency: 'PTS',
|
||||
additionalCurrency: {
|
||||
additionalPrice: {
|
||||
price: '250',
|
||||
currency: 'EUR',
|
||||
},
|
||||
@@ -42,7 +49,7 @@ export const Default: Story = {
|
||||
{
|
||||
points: '10000',
|
||||
currency: 'PTS',
|
||||
additionalCurrency: {
|
||||
additionalPrice: {
|
||||
price: '500',
|
||||
currency: 'EUR',
|
||||
},
|
||||
@@ -55,8 +62,8 @@ export const Default: Story = {
|
||||
|
||||
export const WithDisabledRates: Story = {
|
||||
args: {
|
||||
variant: 'Points',
|
||||
title: 'FREE CANCELLATION / PAY LATER',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY LATER',
|
||||
bannerText: 'Reward night ∙ Breakfast included',
|
||||
rates: [
|
||||
{
|
||||
@@ -68,7 +75,7 @@ export const WithDisabledRates: Story = {
|
||||
points: '15000',
|
||||
currency: 'PTS',
|
||||
isDisabled: true,
|
||||
additionalCurrency: {
|
||||
additionalPrice: {
|
||||
price: '250',
|
||||
currency: 'EUR',
|
||||
},
|
||||
@@ -76,7 +83,7 @@ export const WithDisabledRates: Story = {
|
||||
{
|
||||
points: '10000',
|
||||
currency: 'PTS',
|
||||
additionalCurrency: {
|
||||
additionalPrice: {
|
||||
price: '500',
|
||||
currency: 'EUR',
|
||||
},
|
||||
@@ -89,8 +96,8 @@ export const WithDisabledRates: Story = {
|
||||
|
||||
export const NotEnoughPoints: Story = {
|
||||
args: {
|
||||
variant: 'Points',
|
||||
title: 'FREE CANCELLATION / PAY LATER',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY LATER',
|
||||
bannerText: 'Reward night ∙ Breakfast included',
|
||||
rates: [
|
||||
{
|
||||
@@ -100,7 +107,7 @@ export const NotEnoughPoints: Story = {
|
||||
{
|
||||
points: '15000',
|
||||
currency: 'PTS',
|
||||
additionalCurrency: {
|
||||
additionalPrice: {
|
||||
price: '250',
|
||||
currency: 'EUR',
|
||||
},
|
||||
@@ -108,7 +115,7 @@ export const NotEnoughPoints: Story = {
|
||||
{
|
||||
points: '10000',
|
||||
currency: 'PTS',
|
||||
additionalCurrency: {
|
||||
additionalPrice: {
|
||||
price: '500',
|
||||
currency: 'EUR',
|
||||
},
|
||||
113
packages/design-system/lib/components/RateCard/Points/index.tsx
Normal file
113
packages/design-system/lib/components/RateCard/Points/index.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { Typography } from '../../Typography'
|
||||
import { RatePointsOption } from '../types'
|
||||
|
||||
import styles from '../rate-card.module.css'
|
||||
import { Button } from '../../Button'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircle'
|
||||
import InfoCircleFilledIcon from '../../Icons/InfoCircleFilled'
|
||||
import { Radio } from '../../Radio'
|
||||
import { RadioGroup } from 'react-aria-components'
|
||||
import { variants } from '../variants'
|
||||
|
||||
interface PointsRateCardProps {
|
||||
rateTitle: string
|
||||
paymentTerm: string
|
||||
bannerText: string
|
||||
rates: RatePointsOption[]
|
||||
selectedRate: string | undefined
|
||||
onRateSelect: (value: string) => void
|
||||
isNotEnoughPoints?: boolean
|
||||
notEnoughPointsText?: string
|
||||
handleTermsClick?: () => void
|
||||
}
|
||||
|
||||
export default function PointsRateCard({
|
||||
rateTitle,
|
||||
paymentTerm,
|
||||
bannerText,
|
||||
rates,
|
||||
selectedRate,
|
||||
isNotEnoughPoints,
|
||||
notEnoughPointsText,
|
||||
onRateSelect,
|
||||
handleTermsClick,
|
||||
}: PointsRateCardProps) {
|
||||
const classNames = variants({
|
||||
variant: 'Points',
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
<Typography variant="Tag/sm">
|
||||
<p className={styles.banner}>{bannerText}</p>
|
||||
</Typography>
|
||||
<div className={styles.container}>
|
||||
<header>
|
||||
<Typography variant="Tag/sm">
|
||||
<h3 className={styles.title}>
|
||||
<Button
|
||||
variant="Icon"
|
||||
color="IconDefault"
|
||||
size="Small"
|
||||
onPress={handleTermsClick}
|
||||
>
|
||||
<InfoCircleIcon height={20} width={20} />
|
||||
</Button>
|
||||
{rateTitle}
|
||||
<span className={styles.textSecondary}>
|
||||
{` / ${paymentTerm}`}
|
||||
</span>
|
||||
</h3>
|
||||
</Typography>
|
||||
</header>
|
||||
<div className={styles.content}>
|
||||
<RadioGroup value={selectedRate} onChange={onRateSelect}>
|
||||
{rates.map((rate, index) => (
|
||||
<div key={index} className={styles.rateRow}>
|
||||
<Radio
|
||||
value={index.toString()}
|
||||
isDisabled={rate.isDisabled || isNotEnoughPoints}
|
||||
>
|
||||
<div className={styles.pointsRow}>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{rate.points}{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>
|
||||
{rate.currency} {rate.additionalPrice && ' + '}
|
||||
</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
{rate.additionalPrice && (
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{rate.additionalPrice.price}{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{rate.currency}</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</Radio>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{isNotEnoughPoints ? (
|
||||
<footer className={styles.footer}>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<p className={styles.notEnoughPoints}>
|
||||
<div className={styles.filledIcon}>
|
||||
<InfoCircleFilledIcon height={20} width={20} />
|
||||
</div>
|
||||
{notEnoughPointsText}
|
||||
</p>
|
||||
</Typography>
|
||||
</footer>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,416 +0,0 @@
|
||||
import { PropsWithChildren } from 'react'
|
||||
import { Typography } from '../Typography'
|
||||
|
||||
import styles from './rate-card.module.css'
|
||||
import { variants } from './variants'
|
||||
import { Rate, RatePointsOption } from './types'
|
||||
import InfoCircleIcon from '../Icons/InfoCircle'
|
||||
import { Radio } from '../Radio'
|
||||
import { RadioGroup } from 'react-aria-components'
|
||||
import InfoCircleFilledIcon from '../Icons/InfoCircleFilled'
|
||||
import { Button } from '../Button'
|
||||
|
||||
type RateCardProps =
|
||||
| RegularRateCardProps
|
||||
| CampaignRateCardProps
|
||||
| CodeRateCardProps
|
||||
| PointsRateCardProps
|
||||
|
||||
export function RateCard(props: PropsWithChildren<RateCardProps>) {
|
||||
const classNames = variants({
|
||||
variant: props.variant,
|
||||
})
|
||||
|
||||
switch (props.variant) {
|
||||
case 'Campaign':
|
||||
return <CampaignRateCard {...props} className={classNames} />
|
||||
case 'Code':
|
||||
return <CodeRateCard {...props} className={classNames} />
|
||||
case 'Points':
|
||||
return <PointsRateCard {...props} className={classNames} />
|
||||
default:
|
||||
return <RegularRateCard {...props} className={classNames} />
|
||||
}
|
||||
}
|
||||
|
||||
interface RegularRateCardProps {
|
||||
variant: 'Regular'
|
||||
title: string
|
||||
rate: Rate
|
||||
memberRate?: Rate
|
||||
approximateRate: Omit<Rate, 'unit'>
|
||||
className?: string
|
||||
handleTermsClick?: () => void
|
||||
}
|
||||
|
||||
function RegularRateCard({
|
||||
title,
|
||||
className,
|
||||
approximateRate,
|
||||
rate,
|
||||
handleTermsClick,
|
||||
}: RegularRateCardProps) {
|
||||
const [mainTitle, subTitle] = title.split('/').map((part) => part.trim())
|
||||
|
||||
return (
|
||||
<div className={`${styles.rateCard} ${className}`}>
|
||||
<div className={styles.container}>
|
||||
<header>
|
||||
<Typography variant="Tag/sm">
|
||||
<h3 className={styles.title}>
|
||||
<Button
|
||||
variant="Icon"
|
||||
color="IconDefault"
|
||||
size="Small"
|
||||
onPress={handleTermsClick}
|
||||
>
|
||||
<InfoCircleIcon height={20} width={20} />
|
||||
</Button>
|
||||
{mainTitle}
|
||||
{subTitle && (
|
||||
<span className={styles.textSecondary}>{` / ${subTitle}`}</span>
|
||||
)}
|
||||
</h3>
|
||||
</Typography>
|
||||
</header>
|
||||
<main className={styles.content}>
|
||||
<div className={styles.rateRow}>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p>{rate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{rate.price}{' '}
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<span>{rate.unit}</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={`${styles.rateRow} ${styles.textSecondary}`}>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>{approximateRate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>{approximateRate.price}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// RED CARD
|
||||
|
||||
interface CampaignRateCardProps extends Omit<RegularRateCardProps, 'variant'> {
|
||||
variant: 'Campaign'
|
||||
bannerText: string
|
||||
comparisonRate?: Omit<Rate, 'label'>
|
||||
referenceRate: Omit<Rate, 'unit'>
|
||||
isHighlightedRate?: boolean
|
||||
}
|
||||
|
||||
function CampaignRateCard({
|
||||
title,
|
||||
rate,
|
||||
memberRate,
|
||||
approximateRate,
|
||||
comparisonRate,
|
||||
referenceRate,
|
||||
className,
|
||||
bannerText,
|
||||
isHighlightedRate,
|
||||
handleTermsClick,
|
||||
}: CampaignRateCardProps) {
|
||||
const [mainTitle, subTitle] = title.split('/').map((part) => part.trim())
|
||||
|
||||
return (
|
||||
<div className={`${styles.rateCard} ${className}`}>
|
||||
<Typography variant="Tag/sm">
|
||||
<p className={styles.banner}>{bannerText}</p>
|
||||
</Typography>
|
||||
<div className={styles.container}>
|
||||
<header>
|
||||
<Typography variant="Tag/sm">
|
||||
<h3 className={styles.title}>
|
||||
<Button
|
||||
variant="Icon"
|
||||
color="IconDefault"
|
||||
size="Small"
|
||||
onPress={handleTermsClick}
|
||||
>
|
||||
<InfoCircleIcon height={20} width={20} />
|
||||
</Button>
|
||||
{mainTitle}
|
||||
{subTitle && (
|
||||
<span className={styles.textSecondary}>{` / ${subTitle}`}</span>
|
||||
)}
|
||||
</h3>
|
||||
</Typography>
|
||||
</header>
|
||||
<main className={styles.content}>
|
||||
<div
|
||||
className={`${styles.rateRow} ${isHighlightedRate ? styles.highlightedRate : ''}`}
|
||||
>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<p>{rate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{rate.price}{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{rate.unit}</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
{memberRate ? (
|
||||
<div className={`${styles.rateRow} ${styles.highlightedRate}`}>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<p>{memberRate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{memberRate.price}{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{memberRate.unit}</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
{comparisonRate ? (
|
||||
<div className={`${styles.rateRow} ${styles.comparisonRate}`}>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
<span className={styles.strikethrough}>{rate.price}</span>{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span className={styles.strikethrough}>
|
||||
{comparisonRate.unit}
|
||||
</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={`${styles.rateRow} ${styles.approximateRate}`}>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>{approximateRate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>{approximateRate.price}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
</main>
|
||||
{referenceRate ? (
|
||||
<footer className={styles.footer}>
|
||||
<Typography variant="Tag/sm">
|
||||
<p>{referenceRate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Tag/sm">
|
||||
<p>{referenceRate.price}</p>
|
||||
</Typography>
|
||||
</footer>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// BLUE CARD
|
||||
|
||||
interface CodeRateCardProps
|
||||
extends Omit<RegularRateCardProps, 'variant' | 'approximateRate'> {
|
||||
variant: 'Code'
|
||||
bannerText: string
|
||||
comparisonRate?: Omit<Rate, 'label'>
|
||||
approximateRate?: Omit<Rate, 'unit'>
|
||||
isHighlightedRate?: boolean
|
||||
}
|
||||
|
||||
function CodeRateCard({
|
||||
title,
|
||||
rate,
|
||||
approximateRate,
|
||||
comparisonRate,
|
||||
bannerText,
|
||||
className,
|
||||
isHighlightedRate,
|
||||
handleTermsClick,
|
||||
}: CodeRateCardProps) {
|
||||
const [mainTitle, subTitle] = title.split('/').map((part) => part.trim())
|
||||
|
||||
return (
|
||||
<div className={`${styles.rateCard} ${className}`}>
|
||||
<Typography variant="Tag/sm">
|
||||
<p className={styles.banner}>{bannerText}</p>
|
||||
</Typography>
|
||||
<div className={styles.container}>
|
||||
<header>
|
||||
<Typography variant="Tag/sm">
|
||||
<h3 className={styles.title}>
|
||||
<Button
|
||||
variant="Icon"
|
||||
color="IconDefault"
|
||||
size="Small"
|
||||
onPress={handleTermsClick}
|
||||
>
|
||||
<InfoCircleIcon height={20} width={20} />
|
||||
</Button>
|
||||
{mainTitle}
|
||||
{subTitle && (
|
||||
<span className={styles.textSecondary}>{` / ${subTitle}`}</span>
|
||||
)}
|
||||
</h3>
|
||||
</Typography>
|
||||
</header>
|
||||
<main className={styles.content}>
|
||||
<div
|
||||
className={`${styles.rateRow} ${isHighlightedRate ? styles.highlightedRate : ''}`}
|
||||
>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<p>{rate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{rate.price}{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{rate.unit}</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
{comparisonRate ? (
|
||||
<div className={`${styles.rateRow} ${styles.comparisonRate}`}>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
<span className={styles.strikethrough}>{rate.price}</span>{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span className={styles.strikethrough}>
|
||||
{comparisonRate.unit}
|
||||
</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
{approximateRate ? (
|
||||
<div className={`${styles.rateRow} ${styles.approximateRate}`}>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>{approximateRate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>{approximateRate.price}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// POINTS CARD
|
||||
|
||||
interface PointsRateCardProps {
|
||||
variant: 'Points'
|
||||
title: string
|
||||
bannerText: string
|
||||
className?: string
|
||||
rates: RatePointsOption[]
|
||||
selectedRate: string | undefined
|
||||
onRateSelect: (value: string) => void
|
||||
isNotEnoughPoints?: boolean
|
||||
notEnoughPointsText?: string
|
||||
handleTermsClick?: () => void
|
||||
}
|
||||
|
||||
function PointsRateCard({
|
||||
title,
|
||||
bannerText,
|
||||
className,
|
||||
rates,
|
||||
selectedRate,
|
||||
isNotEnoughPoints,
|
||||
notEnoughPointsText,
|
||||
onRateSelect,
|
||||
handleTermsClick,
|
||||
}: PointsRateCardProps) {
|
||||
const [mainTitle, subTitle] = title.split('/').map((part) => part.trim())
|
||||
|
||||
return (
|
||||
<div className={`${styles.rateCard} ${className}`}>
|
||||
<Typography variant="Tag/sm">
|
||||
<p className={styles.banner}>{bannerText}</p>
|
||||
</Typography>
|
||||
<div className={styles.container}>
|
||||
<header>
|
||||
<Typography variant="Tag/sm">
|
||||
<h3 className={styles.title}>
|
||||
<Button
|
||||
variant="Icon"
|
||||
color="IconDefault"
|
||||
size="Small"
|
||||
onPress={handleTermsClick}
|
||||
>
|
||||
<InfoCircleIcon height={20} width={20} />
|
||||
</Button>
|
||||
{mainTitle}
|
||||
{subTitle && (
|
||||
<span className={styles.textSecondary}>{` / ${subTitle}`}</span>
|
||||
)}
|
||||
</h3>
|
||||
</Typography>
|
||||
</header>
|
||||
<main className={styles.content}>
|
||||
<RadioGroup value={selectedRate} onChange={onRateSelect}>
|
||||
{rates.map((rate, index) => (
|
||||
<div key={index} className={styles.rateRow}>
|
||||
<Radio
|
||||
value={index.toString()}
|
||||
isDisabled={rate.isDisabled || isNotEnoughPoints}
|
||||
>
|
||||
<div className={styles.pointsRow}>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{rate.points}{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>
|
||||
{rate.currency} {rate.additionalCurrency && ' + '}
|
||||
</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
{rate.additionalCurrency && (
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{rate.additionalCurrency.price}{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{rate.currency}</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</Radio>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</main>
|
||||
{isNotEnoughPoints ? (
|
||||
<footer className={styles.footer}>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<p className={styles.notEnoughPoints}>
|
||||
<div className={styles.filledIcon}>
|
||||
<InfoCircleFilledIcon height={20} width={20} />
|
||||
</div>
|
||||
{notEnoughPointsText}
|
||||
</p>
|
||||
</Typography>
|
||||
</footer>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { RateCard } from './RateCard'
|
||||
|
||||
const meta: Meta<typeof RateCard> = {
|
||||
title: 'Components/RateCard/Regular',
|
||||
component: RateCard,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ maxWidth: '400px' }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
argTypes: {
|
||||
title: { control: 'text' },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof RateCard>
|
||||
|
||||
export const Regular: Story = {
|
||||
args: {
|
||||
variant: 'Regular',
|
||||
title: 'FREE CANCELLATION / PAY NOW',
|
||||
rate: {
|
||||
label: 'Standard Price',
|
||||
price: '1989',
|
||||
unit: 'EUR/night',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '1989 EUR',
|
||||
label: 'Approx.',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import RegularRateCard from '.'
|
||||
|
||||
const meta: Meta<typeof RegularRateCard> = {
|
||||
title: 'Components/RateCard/Regular',
|
||||
component: RegularRateCard,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ maxWidth: '400px' }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
argTypes: {
|
||||
rateTitle: { control: 'text' },
|
||||
paymentTerm: { control: 'text' },
|
||||
rate: { control: 'object' },
|
||||
memberRate: { control: 'object' },
|
||||
approximateRate: { control: 'object' },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof RegularRateCard>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
name: 'regular',
|
||||
value: 'regular',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY NOW',
|
||||
rate: {
|
||||
label: 'Standard Price',
|
||||
price: '198',
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
memberRate: {
|
||||
label: 'Member Price',
|
||||
price: '190',
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '198',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const Selected: Story = {
|
||||
args: {
|
||||
name: 'regular',
|
||||
value: 'regular',
|
||||
isSelected: true,
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY NOW',
|
||||
rate: {
|
||||
label: 'Standard Price',
|
||||
price: '198',
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
memberRate: {
|
||||
label: 'Member Price',
|
||||
price: '190',
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '198',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const HidePublicRate: Story = {
|
||||
args: {
|
||||
name: 'regular',
|
||||
value: 'regular',
|
||||
rateTitle: 'FREE CANCELLATION',
|
||||
paymentTerm: 'PAY NOW',
|
||||
rate: {
|
||||
label: 'Standard Price',
|
||||
price: '198',
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
memberRate: {
|
||||
label: 'Member Price',
|
||||
price: '190',
|
||||
unit: 'EUR/NIGHT',
|
||||
},
|
||||
approximateRate: {
|
||||
price: '198',
|
||||
label: 'Approx.',
|
||||
unit: 'EUR',
|
||||
},
|
||||
hidePublicRate: true,
|
||||
},
|
||||
}
|
||||
117
packages/design-system/lib/components/RateCard/Regular/index.tsx
Normal file
117
packages/design-system/lib/components/RateCard/Regular/index.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { Rate } from '../types'
|
||||
|
||||
import styles from '../rate-card.module.css'
|
||||
import { Typography } from '../../Typography'
|
||||
import { Button } from '../../Button'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircle'
|
||||
import CheckCircleIcon from '../../Icons/CheckCircle'
|
||||
import { variants } from '../variants'
|
||||
|
||||
interface RegularRateCardProps {
|
||||
name: string
|
||||
value: string
|
||||
isSelected: boolean
|
||||
rateTitle: string
|
||||
paymentTerm: string
|
||||
rate?: Rate
|
||||
memberRate?: Rate
|
||||
approximateRate: Rate
|
||||
hidePublicRate?: boolean
|
||||
handleChange: () => void
|
||||
handleTermsClick?: () => void
|
||||
}
|
||||
|
||||
export default function RegularRateCard({
|
||||
name,
|
||||
value,
|
||||
isSelected,
|
||||
rateTitle,
|
||||
paymentTerm,
|
||||
approximateRate,
|
||||
rate,
|
||||
memberRate,
|
||||
hidePublicRate,
|
||||
handleChange,
|
||||
handleTermsClick,
|
||||
}: RegularRateCardProps) {
|
||||
const classNames = variants({ variant: 'Regular' })
|
||||
return (
|
||||
<label>
|
||||
<input
|
||||
className={styles.radio}
|
||||
type="radio"
|
||||
name={name}
|
||||
value={value}
|
||||
checked={isSelected}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className={classNames}>
|
||||
<div className={styles.container}>
|
||||
<header>
|
||||
<Typography variant="Tag/sm">
|
||||
<h3 className={styles.title}>
|
||||
<Button
|
||||
variant="Icon"
|
||||
color="IconDefault"
|
||||
size="Small"
|
||||
onPress={handleTermsClick}
|
||||
>
|
||||
<InfoCircleIcon height={20} width={20} />
|
||||
</Button>
|
||||
{rateTitle}
|
||||
<span className={styles.textSecondary}>
|
||||
{` / ${paymentTerm}`}
|
||||
</span>
|
||||
</h3>
|
||||
</Typography>
|
||||
<div className={styles.checkIcon}>
|
||||
<CheckCircleIcon width={24} height={24} />
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
{!hidePublicRate && rate ? (
|
||||
<div className={styles.rateRow}>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<p>{rate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{rate.price}{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{rate.unit}</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
{memberRate ? (
|
||||
<div className={`${styles.rateRow} ${styles.highlightedRate}`}>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<p>{memberRate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<p>
|
||||
{memberRate.price}{' '}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{memberRate.unit}</span>
|
||||
</Typography>
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={`${styles.rateRow} ${styles.approximateRate}`}>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>{approximateRate.label}</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>
|
||||
{approximateRate.price} {approximateRate.unit}
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { RateCard } from './RateCard'
|
||||
@@ -1,8 +1,49 @@
|
||||
.radio {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.checkIcon {
|
||||
--size: 24px;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
|
||||
background-color: var(--Surface-UI-Fill-Active-Hover);
|
||||
border-radius: 50%;
|
||||
color: var(--Scandic-Blue-70);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rateCard {
|
||||
position: relative;
|
||||
background-color: var(--Scandic-Grey-00);
|
||||
border-radius: var(--Corner-radius-md);
|
||||
}
|
||||
|
||||
.rateCard:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--Scandic-Grey-10);
|
||||
}
|
||||
|
||||
.radio:checked ~ .rateCard {
|
||||
border: 1px solid var(--Scandic-Peach-80, 'black');
|
||||
}
|
||||
|
||||
.radio:checked ~ .rateCard .checkIcon {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.banner {
|
||||
background-color: var(--Surface-Brand-Primary-1-OnSurface-Accent);
|
||||
border-top-left-radius: var(--Corner-radius-md);
|
||||
@@ -51,6 +92,10 @@
|
||||
color: var(--Text-Secondary);
|
||||
}
|
||||
|
||||
.textDisabled {
|
||||
color: var(--Text-Interactive-Disabled);
|
||||
}
|
||||
|
||||
.comparisonRate {
|
||||
color: var(--Text-Secondary);
|
||||
display: flex;
|
||||
@@ -92,20 +137,33 @@
|
||||
background-color: var(--Surface-Brand-Primary-1-Default);
|
||||
}
|
||||
|
||||
.variant-campaign:hover {
|
||||
background-color: var(--Scandic-Peach-20);
|
||||
}
|
||||
|
||||
.variant-campaign .banner {
|
||||
background-color: var(--Surface-Brand-Primary-1-OnSurface-Accent);
|
||||
}
|
||||
|
||||
.variant-code {
|
||||
background: var(--Surface-Feedback-Information);
|
||||
background-color: var(--Surface-Feedback-Information);
|
||||
}
|
||||
|
||||
.variant-code:hover {
|
||||
background-color: var(--Scandic-Blue-10);
|
||||
}
|
||||
|
||||
.variant-code .banner {
|
||||
background-color: var(--Surface-Feedback-Information-Accent);
|
||||
}
|
||||
|
||||
.variant-points:hover {
|
||||
background-color: var(--Scandic-Grey-00);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.variant-points .banner {
|
||||
background: var(--Surface-Brand-Primary-1-OnSurface-Accent-Secondary);
|
||||
background-color: var(--Surface-Brand-Primary-1-OnSurface-Accent-Secondary);
|
||||
}
|
||||
|
||||
.footer {
|
||||
@@ -118,3 +176,16 @@
|
||||
align-items: center;
|
||||
color: var(--Scandic-Blue-70);
|
||||
}
|
||||
|
||||
.noPricesAvailableContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: var(--Space-x2) 0;
|
||||
}
|
||||
|
||||
.noPricesAvailableText {
|
||||
padding: var(--Space-x15) var(--Space-x3);
|
||||
border-radius: var(--Corner-radius-rounded);
|
||||
background-color: var(--Scandic-Beige-10);
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ export type RatePointsOption = {
|
||||
points: string
|
||||
currency: string
|
||||
isDisabled?: boolean
|
||||
additionalCurrency?: AdditionalCurrency
|
||||
additionalPrice?: AdditionalPrice
|
||||
}
|
||||
|
||||
type AdditionalCurrency = {
|
||||
type AdditionalPrice = {
|
||||
price: string
|
||||
currency: string
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@
|
||||
"./Chips": "./dist/components/Chips/index.js",
|
||||
"./Icons": "./dist/components/Icons/index.js",
|
||||
"./Typography": "./dist/components/Typography/index.js",
|
||||
"./RateCard": "./dist/components/RateCard/index.js",
|
||||
"./RegularRateCard": "./dist/components/RateCard/Regular/index.js",
|
||||
"./CampaignRateCard": "./dist/components/RateCard/Campaign/index.js",
|
||||
"./CodeRateCard": "./dist/components/RateCard/Code/index.js",
|
||||
"./PointsRateCard": "./dist/components/RateCard/Points/index.js",
|
||||
"./NoRateAvailableCard": "./dist/components/RateCard/NoRateAvailable/index.js",
|
||||
"./style.css": "./dist/style.css",
|
||||
"./base.css": "./dist/base.css",
|
||||
"./globals.css": "./dist/globals.css",
|
||||
|
||||
Reference in New Issue
Block a user