diff --git a/packages/design-system/lib/components/Button/button.module.css b/packages/design-system/lib/components/Button/button.module.css index cd63d66e4..a3343e648 100644 --- a/packages/design-system/lib/components/Button/button.module.css +++ b/packages/design-system/lib/components/Button/button.module.css @@ -78,3 +78,19 @@ color: var(--Scandic-Red-100); padding: var(--Space-x15) 0; } + +.variant-icon { + background-color: transparent; + border-color: transparent; + color: inherit; + padding: 0; + margin: 0; + + display: flex; + align-items: center; + justify-content: center; +} + +.color-icon-default { + color: var(--Icon-Default); +} diff --git a/packages/design-system/lib/components/Button/variants.ts b/packages/design-system/lib/components/Button/variants.ts index 3a6750782..4249984b4 100644 --- a/packages/design-system/lib/components/Button/variants.ts +++ b/packages/design-system/lib/components/Button/variants.ts @@ -11,10 +11,12 @@ export const config = { Secondary: styles['variant-secondary'], Tertiary: styles['variant-tertiary'], Text: styles['variant-text'], + Icon: styles['variant-icon'], }, color: { Primary: styles['color-primary'], Inverted: styles['color-inverted'], + IconDefault: styles['color-icon-default'], }, size: { Small: styles['size-small'], diff --git a/packages/design-system/lib/components/Icons/InfoCircle.tsx b/packages/design-system/lib/components/Icons/InfoCircle.tsx new file mode 100644 index 000000000..15b074df9 --- /dev/null +++ b/packages/design-system/lib/components/Icons/InfoCircle.tsx @@ -0,0 +1,18 @@ +import { SVGProps } from 'react' + +export default function InfoCircleIcon(props: SVGProps) { + return ( + + + + ) +} diff --git a/packages/design-system/lib/components/Icons/InfoCircleFilled.tsx b/packages/design-system/lib/components/Icons/InfoCircleFilled.tsx new file mode 100644 index 000000000..eb7856289 --- /dev/null +++ b/packages/design-system/lib/components/Icons/InfoCircleFilled.tsx @@ -0,0 +1,18 @@ +import { SVGProps } from 'react' + +export default function InfoCircleFilledIcon(props: SVGProps) { + return ( + + + + ) +} diff --git a/packages/design-system/lib/components/Radio/Radio.tsx b/packages/design-system/lib/components/Radio/Radio.tsx new file mode 100644 index 000000000..b22e481b9 --- /dev/null +++ b/packages/design-system/lib/components/Radio/Radio.tsx @@ -0,0 +1,31 @@ +import { PropsWithChildren } from 'react' +import { Radio as AriaRadio } from 'react-aria-components' +import styles from './radio.module.css' +import { variants } from './variants' + +interface RadioProps extends PropsWithChildren { + value: string + id?: string + isDisabled?: boolean + color?: 'Burgundy' +} + +export function Radio({ id, value, children, color, isDisabled }: RadioProps) { + const inputId = id || `radio-${value}` + + const classNames = variants({ + color, + }) + + return ( + +
+
{children}
+ + ) +} diff --git a/packages/design-system/lib/components/Radio/index.tsx b/packages/design-system/lib/components/Radio/index.tsx new file mode 100644 index 000000000..b2d70f722 --- /dev/null +++ b/packages/design-system/lib/components/Radio/index.tsx @@ -0,0 +1 @@ +export { Radio } from './Radio' diff --git a/packages/design-system/lib/components/Radio/radio.module.css b/packages/design-system/lib/components/Radio/radio.module.css new file mode 100644 index 000000000..310a5c68e --- /dev/null +++ b/packages/design-system/lib/components/Radio/radio.module.css @@ -0,0 +1,32 @@ +.container { + display: flex; + align-items: center; + gap: var(--Space-x15); + padding: var(--Space-x1) 0; + cursor: pointer; +} + +.radio { + position: relative; + width: 24px; + height: 24px; + background-color: var(--Surface-UI-Fill-Default); + border: 2px solid var(--Scandic-Beige-50); + border-radius: 50%; + transition: all 0.2s ease-in-out; + box-sizing: border-box; +} + +.container[data-selected] .radio { + border-color: var(--Surface-UI-Fill-Active); + border-width: 8px; +} + +.disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.container[data-selected] .color-burgundy { + border-color: var(--Surface-UI-Fill-Active); +} diff --git a/packages/design-system/lib/components/Radio/variants.ts b/packages/design-system/lib/components/Radio/variants.ts new file mode 100644 index 000000000..a909b0a32 --- /dev/null +++ b/packages/design-system/lib/components/Radio/variants.ts @@ -0,0 +1,16 @@ +import { cva } from 'class-variance-authority' + +import styles from './radio.module.css' + +export const config = { + variants: { + color: { + Burgundy: styles['color-burgundy'], + }, + }, + defaultVariants: { + color: 'Burgundy', + }, +} as const + +export const variants = cva(styles.radio, config) diff --git a/packages/design-system/lib/components/RateCard/Campaign.stories.tsx b/packages/design-system/lib/components/RateCard/Campaign.stories.tsx new file mode 100644 index 000000000..f5a0c6fb7 --- /dev/null +++ b/packages/design-system/lib/components/RateCard/Campaign.stories.tsx @@ -0,0 +1,108 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { RateCard } from './RateCard' + +const meta: Meta = { + title: 'Components/RateCard/Campaign', + component: RateCard, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + title: { control: 'text' }, + }, +} + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + variant: 'Campaign', + title: 'NON-REFUNDABLE / PAY NOW', + bannerText: 'Campaign ∙ Breakfast included', + rate: { + label: "Valentine's Special", + price: '198', + unit: 'EUR/NIGHT', + }, + approximateRate: { + price: '198 EUR', + label: 'Approx.', + }, + referenceRate: { + price: '249 EUR', + label: 'Lowest past price (last 30 days)', + }, + }, +} + +export const Package: Story = { + args: { + variant: 'Campaign', + title: 'NON-REFUNDABLE / PAY NOW', + bannerText: 'WDCPHG ∙ Breakfast included', + rate: { + label: 'Luxurious wine & dine in Copenhagen', + price: '1989', + unit: 'EUR/NIGHT', + }, + approximateRate: { + price: '1989 EUR', + label: 'Approx.', + }, + }, +} + +export const CampaignLoggedIn: Story = { + args: { + variant: 'Campaign', + title: 'NON-REFUNDABLE / PAY NOW', + bannerText: 'SUM2025 ∙ Breakfast included', + rate: { + label: 'Luxurious wine & dine in Copenhagen', + price: '198', + unit: 'EUR/NIGHT', + }, + approximateRate: { + price: '198 EUR', + label: 'Approx.', + }, + comparisonRate: { + price: '249', + unit: 'EUR/NIGHT', + }, + isHighlightedRate: true, + }, +} + +export const CampaignOmnibus: Story = { + args: { + variant: 'Campaign', + title: 'NON-REFUNDABLE / PAY NOW', + bannerText: 'WDCPHG ∙ Breakfast included', + rate: { + label: 'Luxurious wine & dine in Copenhagen', + price: '198', + unit: 'EUR/NIGHT', + }, + memberRate: { + label: 'Member price', + price: '150', + unit: 'EUR/NIGHT', + }, + approximateRate: { + price: '198 EUR', + label: 'Approx.', + }, + referenceRate: { + price: '101 EUR', + label: 'Lowest past price (last 30 days)', + }, + }, +} diff --git a/packages/design-system/lib/components/RateCard/Code.stories.tsx b/packages/design-system/lib/components/RateCard/Code.stories.tsx new file mode 100644 index 000000000..a40219f90 --- /dev/null +++ b/packages/design-system/lib/components/RateCard/Code.stories.tsx @@ -0,0 +1,142 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { RateCard } from './RateCard' + +const meta: Meta = { + title: 'Components/RateCard/Code', + component: RateCard, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + title: { control: 'text' }, + }, +} + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + variant: 'Code', + title: 'FREE CANCELLATION / PAY LATER', + bannerText: 'Campaign ∙ Breakfast excluded', + rate: { + label: "Valentine's Special", + price: '1989', + unit: 'EUR/night', + }, + approximateRate: { + price: '1989 EUR', + label: 'Approx.', + }, + }, +} + +export const Voucher: Story = { + args: { + variant: 'Code', + title: 'FREE CANCELLATION / PAY LATER', + bannerText: 'VOG ∙ Breakfast included', + rate: { + label: 'Promotional name here', + price: '1', + unit: 'VOUCHER', + }, + }, +} + +export const CorporateCheck: Story = { + args: { + variant: 'Code', + title: 'FREE CANCELLATION / PAY LATER', + bannerText: 'VOG ∙ Breakfast included', + rate: { + label: 'Promotional name here', + price: '2cc + 800', + unit: 'SEK', + }, + approximateRate: { + price: '76 EUR', + label: 'Approx.', + }, + }, +} + +export const DNumberDefault: Story = { + args: { + variant: 'Code', + title: 'FREE CANCELLATION / PAY LATER', + bannerText: 'D0043148 ∙ Breakfast included', + rate: { + label: 'Helsinki Partners Oy', + price: '1989', + unit: 'EUR/NIGHT', + }, + approximateRate: { + price: '76 EUR', + label: 'Approx.', + }, + }, +} + +export const DNumberHighlightedRate: Story = { + args: { + variant: 'Code', + title: 'FREE CANCELLATION / PAY LATER', + bannerText: 'D0043148 ∙ Breakfast included', + rate: { + label: 'Helsinki Partners Oy', + price: '198', + unit: 'EUR/NIGHT', + }, + approximateRate: { + price: '76 EUR', + label: 'Approx.', + }, + isHighlightedRate: true, + }, +} + +export const LNumberDefault: Story = { + args: { + variant: 'Code', + title: 'NON-REFUNDABLE / PAY NOW', + bannerText: 'L0043148 ∙ Breakfast included', + rate: { + label: 'Nordic Team Travel', + price: '198', + unit: 'EUR/NIGHT', + }, + approximateRate: { + price: '76 EUR', + label: 'Approx.', + }, + }, +} + +export const LNumberStrikethrough: Story = { + args: { + variant: 'Code', + title: 'NON-REFUNDABLE / PAY NOW', + bannerText: 'L0043148 ∙ Breakfast included', + rate: { + label: 'Nordic Team Travel', + price: '198', + unit: 'EUR/NIGHT', + }, + comparisonRate: { + price: '249', + unit: 'EUR/NIGHT', + }, + approximateRate: { + price: '230/218 EUR', + label: 'Approx.', + }, + }, +} diff --git a/packages/design-system/lib/components/RateCard/Points.stories.tsx b/packages/design-system/lib/components/RateCard/Points.stories.tsx new file mode 100644 index 000000000..df2813e60 --- /dev/null +++ b/packages/design-system/lib/components/RateCard/Points.stories.tsx @@ -0,0 +1,122 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { RateCard } from './RateCard' + +const meta: Meta = { + title: 'Components/RateCard/Points', + component: RateCard, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + title: { control: 'text' }, + }, +} + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + variant: 'Points', + title: 'FREE CANCELLATION / PAY LATER', + bannerText: 'Reward night ∙ Breakfast included', + rates: [ + { + points: '20000', + currency: 'PTS', + }, + { + points: '15000', + currency: 'PTS', + additionalCurrency: { + price: '250', + currency: 'EUR', + }, + }, + { + points: '10000', + currency: 'PTS', + additionalCurrency: { + price: '500', + currency: 'EUR', + }, + }, + ], + selectedRate: undefined, + onRateSelect: (value) => console.log(value), + }, +} + +export const WithDisabledRates: Story = { + args: { + variant: 'Points', + title: 'FREE CANCELLATION / PAY LATER', + bannerText: 'Reward night ∙ Breakfast included', + rates: [ + { + points: '20000', + currency: 'PTS', + isDisabled: true, + }, + { + points: '15000', + currency: 'PTS', + isDisabled: true, + additionalCurrency: { + price: '250', + currency: 'EUR', + }, + }, + { + points: '10000', + currency: 'PTS', + additionalCurrency: { + price: '500', + currency: 'EUR', + }, + }, + ], + selectedRate: '2', + onRateSelect: (value) => console.log(value), + }, +} + +export const NotEnoughPoints: Story = { + args: { + variant: 'Points', + title: 'FREE CANCELLATION / PAY LATER', + bannerText: 'Reward night ∙ Breakfast included', + rates: [ + { + points: '20000', + currency: 'PTS', + }, + { + points: '15000', + currency: 'PTS', + additionalCurrency: { + price: '250', + currency: 'EUR', + }, + }, + { + points: '10000', + currency: 'PTS', + additionalCurrency: { + price: '500', + currency: 'EUR', + }, + }, + ], + selectedRate: undefined, + isNotEnoughPoints: true, + notEnoughPointsText: 'Not enough points', + onRateSelect: (value) => console.log(value), + }, +} diff --git a/packages/design-system/lib/components/RateCard/RateCard.tsx b/packages/design-system/lib/components/RateCard/RateCard.tsx new file mode 100644 index 000000000..6c72b1dff --- /dev/null +++ b/packages/design-system/lib/components/RateCard/RateCard.tsx @@ -0,0 +1,416 @@ +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) { + const classNames = variants({ + variant: props.variant, + }) + + switch (props.variant) { + case 'Campaign': + return + case 'Code': + return + case 'Points': + return + default: + return + } +} + +interface RegularRateCardProps { + variant: 'Regular' + title: string + rate: Rate + memberRate?: Rate + approximateRate: Omit + className?: string + handleTermsClick?: () => void +} + +function RegularRateCard({ + title, + className, + approximateRate, + rate, + handleTermsClick, +}: RegularRateCardProps) { + const [mainTitle, subTitle] = title.split('/').map((part) => part.trim()) + + return ( +
+
+
+ +

+ + {mainTitle} + {subTitle && ( + {` / ${subTitle}`} + )} +

+
+
+
+
+ +

{rate.label}

+
+ +

+ {rate.price}{' '} + + {rate.unit} + +

+
+
+
+ +

{approximateRate.label}

+
+ +

{approximateRate.price}

+
+
+
+
+
+ ) +} + +// RED CARD + +interface CampaignRateCardProps extends Omit { + variant: 'Campaign' + bannerText: string + comparisonRate?: Omit + referenceRate: Omit + 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 ( +
+ +

{bannerText}

+
+
+
+ +

+ + {mainTitle} + {subTitle && ( + {` / ${subTitle}`} + )} +

+
+
+
+
+ +

{rate.label}

+
+ +

+ {rate.price}{' '} + + {rate.unit} + +

+
+
+ {memberRate ? ( +
+ +

{memberRate.label}

+
+ +

+ {memberRate.price}{' '} + + {memberRate.unit} + +

+
+
+ ) : null} + {comparisonRate ? ( +
+ +

+ {rate.price}{' '} + + + {comparisonRate.unit} + + +

+
+
+ ) : null} +
+ +

{approximateRate.label}

+
+ +

{approximateRate.price}

+
+
+
+ {referenceRate ? ( +
+ +

{referenceRate.label}

+
+ +

{referenceRate.price}

+
+
+ ) : null} +
+
+ ) +} + +// BLUE CARD + +interface CodeRateCardProps + extends Omit { + variant: 'Code' + bannerText: string + comparisonRate?: Omit + approximateRate?: Omit + isHighlightedRate?: boolean +} + +function CodeRateCard({ + title, + rate, + approximateRate, + comparisonRate, + bannerText, + className, + isHighlightedRate, + handleTermsClick, +}: CodeRateCardProps) { + const [mainTitle, subTitle] = title.split('/').map((part) => part.trim()) + + return ( +
+ +

{bannerText}

+
+
+
+ +

+ + {mainTitle} + {subTitle && ( + {` / ${subTitle}`} + )} +

+
+
+
+
+ +

{rate.label}

+
+ +

+ {rate.price}{' '} + + {rate.unit} + +

+
+
+ {comparisonRate ? ( +
+ +

+ {rate.price}{' '} + + + {comparisonRate.unit} + + +

+
+
+ ) : null} + {approximateRate ? ( +
+ +

{approximateRate.label}

+
+ +

{approximateRate.price}

+
+
+ ) : null} +
+
+
+ ) +} + +// 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 ( +
+ +

{bannerText}

+
+
+
+ +

+ + {mainTitle} + {subTitle && ( + {` / ${subTitle}`} + )} +

+
+
+
+ + {rates.map((rate, index) => ( +
+ +
+ +

+ {rate.points}{' '} + + + {rate.currency} {rate.additionalCurrency && ' + '} + + +

+
+ {rate.additionalCurrency && ( + +

+ {rate.additionalCurrency.price}{' '} + + {rate.currency} + +

+
+ )} +
+
+
+ ))} +
+
+ {isNotEnoughPoints ? ( +
+ +

+

+ +
+ {notEnoughPointsText} +

+
+
+ ) : null} +
+
+ ) +} diff --git a/packages/design-system/lib/components/RateCard/Regular.stories.tsx b/packages/design-system/lib/components/RateCard/Regular.stories.tsx new file mode 100644 index 000000000..a0ad364de --- /dev/null +++ b/packages/design-system/lib/components/RateCard/Regular.stories.tsx @@ -0,0 +1,38 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { RateCard } from './RateCard' + +const meta: Meta = { + title: 'Components/RateCard/Regular', + component: RateCard, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + title: { control: 'text' }, + }, +} + +export default meta + +type Story = StoryObj + +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.', + }, + }, +} diff --git a/packages/design-system/lib/components/RateCard/index.tsx b/packages/design-system/lib/components/RateCard/index.tsx new file mode 100644 index 000000000..d516a7c17 --- /dev/null +++ b/packages/design-system/lib/components/RateCard/index.tsx @@ -0,0 +1 @@ +export { RateCard } from './RateCard' diff --git a/packages/design-system/lib/components/RateCard/rate-card.module.css b/packages/design-system/lib/components/RateCard/rate-card.module.css new file mode 100644 index 000000000..023899390 --- /dev/null +++ b/packages/design-system/lib/components/RateCard/rate-card.module.css @@ -0,0 +1,120 @@ +.rateCard { + background-color: var(--Scandic-Grey-00); + border-radius: var(--Corner-radius-md); +} + +.banner { + background-color: var(--Surface-Brand-Primary-1-OnSurface-Accent); + border-top-left-radius: var(--Corner-radius-md); + border-top-right-radius: var(--Corner-radius-md); + text-align: center; + color: var(--Text-Inverted); + padding: var(--Space-x05) 0; + text-transform: none; +} + +.container { + display: flex; + flex-direction: column; + gap: var(--Space-x1); + + padding: var(--Space-x1) var(--Space-x15) var(--Space-x15); +} + +.container > * { + padding-bottom: var(--Space-x1); + border-bottom: 2px solid var(--Neutral-Opacity-Black-5); +} + +.container > :last-child { + border-bottom: none; + padding-bottom: 0; +} + +.title { + display: flex; + align-items: center; + gap: var(--Space-x05); +} + +.rateRow { + display: grid; + grid-template-columns: 1fr auto; + gap: var(--Space-x1); +} + +.highlightedRate { + color: var(--Surface-Brand-Primary-1-OnSurface-Accent); +} + +.textSecondary { + color: var(--Text-Secondary); +} + +.comparisonRate { + color: var(--Text-Secondary); + display: flex; + justify-content: flex-end; +} + +.comparisonRate > p { + font-weight: var(--Font-weight-Regular); +} + +.strikethrough { + text-decoration: line-through; +} + +.approximateRate { + color: var(--Text-Secondary); + padding-top: var(--Space-x05); +} + +.variant-regular { + background-color: var(--Scandic-Grey-00); +} + +.pointsRow { + display: flex; + align-items: center; + gap: var(--Space-x05); +} + +.notEnoughPoints { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + gap: var(--Space-x05); +} + +.variant-campaign { + background-color: var(--Surface-Brand-Primary-1-Default); +} + +.variant-campaign .banner { + background-color: var(--Surface-Brand-Primary-1-OnSurface-Accent); +} + +.variant-code { + background: var(--Surface-Feedback-Information); +} + +.variant-code .banner { + background-color: var(--Surface-Feedback-Information-Accent); +} + +.variant-points .banner { + background: var(--Surface-Brand-Primary-1-OnSurface-Accent-Secondary); +} + +.footer { + display: flex; + justify-content: space-between; +} + +.filledIcon { + display: flex; + align-items: center; + color: var(--Scandic-Blue-70); +} diff --git a/packages/design-system/lib/components/RateCard/types.ts b/packages/design-system/lib/components/RateCard/types.ts new file mode 100644 index 000000000..ca5eabed3 --- /dev/null +++ b/packages/design-system/lib/components/RateCard/types.ts @@ -0,0 +1,17 @@ +export type Rate = { + label?: string + price: string + unit: string +} + +export type RatePointsOption = { + points: string + currency: string + isDisabled?: boolean + additionalCurrency?: AdditionalCurrency +} + +type AdditionalCurrency = { + price: string + currency: string +} diff --git a/packages/design-system/lib/components/RateCard/variants.ts b/packages/design-system/lib/components/RateCard/variants.ts new file mode 100644 index 000000000..3e81f57ef --- /dev/null +++ b/packages/design-system/lib/components/RateCard/variants.ts @@ -0,0 +1,19 @@ +import { cva } from 'class-variance-authority' + +import styles from './rate-card.module.css' + +export const config = { + variants: { + variant: { + Regular: styles['variant-regular'], + Campaign: styles['variant-campaign'], + Code: styles['variant-code'], + Points: styles['variant-points'], + }, + }, + defaultVariants: { + variant: 'Regular', + }, +} as const + +export const variants = cva(styles.rateCard, config) diff --git a/packages/design-system/package.json b/packages/design-system/package.json index d95ca06ff..5c4a5cdab 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -11,6 +11,7 @@ "./Chips": "./dist/components/Chips/index.js", "./Icons": "./dist/components/Icons/index.js", "./Typography": "./dist/components/Typography/index.js", + "./RateCard": "./dist/components/RateCard/index.js", "./style.css": "./dist/style.css", "./base.css": "./dist/base.css", "./globals.css": "./dist/globals.css",