feat: added RoomCard and CardContainer component to HotelPage
This commit is contained in:
committed by
Chuma McPhoy
parent
6b5606fc8b
commit
68f40a144e
@@ -0,0 +1,15 @@
|
||||
.cardContainer {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x3);
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
}
|
||||
|
||||
.cardContainer.twoColumns {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
.cardContainer.threeColumns {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
.cardContainer.fourColumns {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cardContainerVariants } from "./variants"
|
||||
|
||||
export interface CardContainerProps
|
||||
extends React.PropsWithChildren<React.HTMLAttributes<HTMLElement>>,
|
||||
VariantProps<typeof cardContainerVariants> {}
|
||||
18
components/ContentType/HotelPage/CardContainer/index.tsx
Normal file
18
components/ContentType/HotelPage/CardContainer/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { CardContainerProps } from "./cardContainer"
|
||||
import { cardContainerVariants } from "./variants"
|
||||
|
||||
export function CardContainer({
|
||||
children,
|
||||
className,
|
||||
columns,
|
||||
...props
|
||||
}: CardContainerProps) {
|
||||
return (
|
||||
<section
|
||||
className={cardContainerVariants({ className, columns })}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
16
components/ContentType/HotelPage/CardContainer/variants.ts
Normal file
16
components/ContentType/HotelPage/CardContainer/variants.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
import styles from "./cardContainer.module.css"
|
||||
|
||||
export const cardContainerVariants = cva(styles.cardContainer, {
|
||||
variants: {
|
||||
columns: {
|
||||
2: styles.twoColumns,
|
||||
3: styles.threeColumns,
|
||||
4: styles.fourColumns,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
columns: 3,
|
||||
},
|
||||
})
|
||||
52
components/ContentType/HotelPage/RoomCard/index.tsx
Normal file
52
components/ContentType/HotelPage/RoomCard/index.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
"use client"
|
||||
|
||||
import { ImageIcon } from "@/components/Icons"
|
||||
import Image from "@/components/Image"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
|
||||
import { RoomCardProps } from "./roomCard"
|
||||
|
||||
import styles from "./roomCard.module.css"
|
||||
|
||||
export function RoomCard({
|
||||
badgeText,
|
||||
title,
|
||||
subtitle,
|
||||
cta,
|
||||
image,
|
||||
imageCount,
|
||||
imageClick,
|
||||
}: RoomCardProps) {
|
||||
return (
|
||||
<article className={styles.roomCard}>
|
||||
<button className={styles.imageWrapper} onClick={imageClick}>
|
||||
{badgeText && <span className={styles.badge}>{badgeText}</span>}
|
||||
{imageCount && (
|
||||
<span className={styles.imageCount}>
|
||||
<ImageIcon color="white" />
|
||||
{imageCount}
|
||||
</span>
|
||||
)}
|
||||
<Image
|
||||
className={styles.image}
|
||||
src={image.src}
|
||||
alt={image.alt}
|
||||
height={image.height}
|
||||
width={image.width}
|
||||
/>
|
||||
</button>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.innerContent}>
|
||||
<Title as="h4" level="h3" textTransform="capitalize" color="black">
|
||||
{title}
|
||||
</Title>
|
||||
<Body color="grey">{subtitle}</Body>
|
||||
</div>
|
||||
<button className={styles.cta} onClick={cta.callback}>
|
||||
{cta.text}
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
.roomCard {
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
background-color: var(--Base-Surface-Primary-Normal);
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: var(--Spacing-x1);
|
||||
left: var(--Spacing-x1);
|
||||
background-color: var(--Scandic-Blue-100);
|
||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
color: var(--Tertiary-Dark-On-Surface-Text, #fff0c2);
|
||||
text-transform: uppercase;
|
||||
font-family: var(--typography-Title-5-fontFamily);
|
||||
font-size: var(--typography-Footnote-Regular-fontSize);
|
||||
font-weight: var(--typography-Footnote-Regular-fontWeight);
|
||||
}
|
||||
|
||||
.imageCount {
|
||||
position: absolute;
|
||||
right: var(--Spacing-x1);
|
||||
bottom: var(--Spacing-x1);
|
||||
display: flex;
|
||||
gap: var(--Spacing-x-half);
|
||||
align-items: center;
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--Scandic-Beige-80) 80%,
|
||||
transparent
|
||||
);
|
||||
color: white;
|
||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
padding: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.innerContent {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.imageWrapper {
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
border-width: 0;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
border-top-left-radius: var(--Corner-radius-Medium);
|
||||
border-top-right-radius: var(--Corner-radius-Medium);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--UI-Text-Placeholder, #787472);
|
||||
}
|
||||
|
||||
.cta {
|
||||
background-color: transparent;
|
||||
border-width: 0;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x-half);
|
||||
color: var(--Base-Text-Medium-contrast, #8f4350);
|
||||
font-family: var(--typography-Body-Bold-fontFamily);
|
||||
font-size: var(--typography-Body-Bold-fontSize);
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.cta:hover {
|
||||
color: var(--Base-Text-High-contrast, #4d001b);
|
||||
}
|
||||
16
components/ContentType/HotelPage/RoomCard/roomCard.ts
Normal file
16
components/ContentType/HotelPage/RoomCard/roomCard.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ImageProps } from "next/image"
|
||||
|
||||
interface RoomCardCtaProps {
|
||||
text: string
|
||||
callback: () => void
|
||||
}
|
||||
|
||||
export interface RoomCardProps {
|
||||
image: ImageProps
|
||||
imageCount: number
|
||||
imageClick: () => void
|
||||
title: string
|
||||
subtitle: string
|
||||
cta: RoomCardCtaProps
|
||||
badgeText?: string
|
||||
}
|
||||
23
components/Icons/Camera.tsx
Normal file
23
components/Icons/Camera.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { iconVariants } from "./variants"
|
||||
|
||||
import type { IconProps } from "@/types/components/icon"
|
||||
|
||||
export default function CameraIcon({ className, color, ...props }: IconProps) {
|
||||
const classNames = iconVariants({ className, color })
|
||||
return (
|
||||
<svg
|
||||
className={classNames}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="18"
|
||||
viewBox="0 0 20 18"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M10 14.375C11.225 14.375 12.2625 13.95 13.1125 13.1C13.9625 12.25 14.3875 11.2125 14.3875 9.9875C14.3875 8.7625 13.9625 7.725 13.1125 6.875C12.2625 6.025 11.225 5.6 10 5.6C8.775 5.6 7.7375 6.025 6.8875 6.875C6.0375 7.725 5.6125 8.7625 5.6125 9.9875C5.6125 11.2125 6.0375 12.25 6.8875 13.1C7.7375 13.95 8.775 14.375 10 14.375ZM9.9949 12.5C9.28997 12.5 8.69583 12.2583 8.2125 11.775C7.72917 11.2917 7.4875 10.6975 7.4875 9.9926C7.4875 9.28767 7.72917 8.69183 8.2125 8.2051C8.69583 7.71837 9.28997 7.475 9.9949 7.475C10.6998 7.475 11.2957 7.71837 11.7824 8.2051C12.2691 8.69183 12.5125 9.28767 12.5125 9.9926C12.5125 10.6975 12.2691 11.2917 11.7824 11.775C11.2957 12.2583 10.6998 12.5 9.9949 12.5ZM2.125 17.75C1.60937 17.75 1.16796 17.5664 0.800775 17.1992C0.433592 16.832 0.25 16.3906 0.25 15.875V4.1125C0.25 3.59687 0.433592 3.15546 0.800775 2.78828C1.16796 2.42109 1.60937 2.2375 2.125 2.2375H5.275L6.5125 0.875C6.68652 0.6827 6.89613 0.530459 7.14133 0.418275C7.38651 0.306092 7.64357 0.25 7.9125 0.25H12.0875C12.3564 0.25 12.6135 0.306092 12.8587 0.418275C13.1039 0.530459 13.3135 0.6827 13.4875 0.875L14.725 2.2375H17.875C18.3906 2.2375 18.832 2.42109 19.1992 2.78828C19.5664 3.15546 19.75 3.59687 19.75 4.1125V15.875C19.75 16.3906 19.5664 16.832 19.1992 17.1992C18.832 17.5664 18.3906 17.75 17.875 17.75H2.125ZM2.125 15.875H17.875V4.1125H13.8853L12.0875 2.125H7.9162L6.125 4.1125H2.125V15.875Z"
|
||||
fill="#26201E"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
36
components/Icons/Image.tsx
Normal file
36
components/Icons/Image.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { iconVariants } from "./variants"
|
||||
|
||||
import type { IconProps } from "@/types/components/icon"
|
||||
|
||||
export default function ImageIcon({ className, color, ...props }: IconProps) {
|
||||
const classNames = iconVariants({ className, color })
|
||||
return (
|
||||
<svg
|
||||
className={classNames}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<mask
|
||||
id="mask0_69_3274"
|
||||
style={{ maskType: "alpha" }}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
>
|
||||
<rect width="24" height="24" fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_69_3274)">
|
||||
<path
|
||||
d="M5.09998 20.775C4.58434 20.775 4.14293 20.5914 3.77575 20.2242C3.40857 19.857 3.22498 19.4156 3.22498 18.9V5.09998C3.22498 4.58434 3.40857 4.14293 3.77575 3.77575C4.14293 3.40857 4.58434 3.22498 5.09998 3.22498H18.9C19.4156 3.22498 19.857 3.40857 20.2242 3.77575C20.5914 4.14293 20.775 4.58434 20.775 5.09998V18.9C20.775 19.4156 20.5914 19.857 20.2242 20.2242C19.857 20.5914 19.4156 20.775 18.9 20.775H5.09998ZM5.09998 18.9H18.9V5.09998H5.09998V18.9ZM7.13748 16.925H16.8826C17.0775 16.925 17.2208 16.8396 17.3125 16.6687C17.4041 16.4979 17.3875 16.3333 17.2625 16.175L14.575 12.6C14.4807 12.475 14.3551 12.4125 14.1981 12.4125C14.041 12.4125 13.9166 12.475 13.825 12.6L11.25 16.025L9.42498 13.6125C9.33074 13.4875 9.2051 13.425 9.04805 13.425C8.891 13.425 8.76664 13.4875 8.67498 13.6125L6.76588 16.1732C6.63861 16.3327 6.62185 16.4979 6.7156 16.6687C6.80935 16.8396 6.94998 16.925 7.13748 16.925ZM8.5007 9.93748C8.90022 9.93748 9.23956 9.79765 9.51873 9.518C9.79789 9.23833 9.93748 8.89875 9.93748 8.49925C9.93748 8.09973 9.79765 7.76039 9.518 7.48123C9.23833 7.20206 8.89875 7.06248 8.49925 7.06248C8.09973 7.06248 7.76039 7.2023 7.48123 7.48195C7.20206 7.76162 7.06248 8.1012 7.06248 8.5007C7.06248 8.90022 7.2023 9.23956 7.48195 9.51873C7.76162 9.79789 8.1012 9.93748 8.5007 9.93748Z"
|
||||
fill="#26201E"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
BarIcon,
|
||||
BikingIcon,
|
||||
CalendarIcon,
|
||||
CameraIcon,
|
||||
CellphoneIcon,
|
||||
CheckCircleIcon,
|
||||
CheckIcon,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
FitnessIcon,
|
||||
GlobeIcon,
|
||||
HouseIcon,
|
||||
ImageIcon,
|
||||
InfoCircleIcon,
|
||||
LocationIcon,
|
||||
LockIcon,
|
||||
@@ -51,6 +53,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
|
||||
return BikingIcon
|
||||
case IconName.Calendar:
|
||||
return CalendarIcon
|
||||
case IconName.Camera:
|
||||
return CameraIcon
|
||||
case IconName.Cellphone:
|
||||
return CellphoneIcon
|
||||
case IconName.Check:
|
||||
@@ -79,6 +83,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
|
||||
return GlobeIcon
|
||||
case IconName.House:
|
||||
return HouseIcon
|
||||
case IconName.Image:
|
||||
return ImageIcon
|
||||
case IconName.InfoCircle:
|
||||
return InfoCircleIcon
|
||||
case IconName.Location:
|
||||
|
||||
@@ -41,3 +41,8 @@
|
||||
.red * {
|
||||
fill: var(--Scandic-Brand-Scandic-Red);
|
||||
}
|
||||
|
||||
.white,
|
||||
.white * {
|
||||
fill: var(--Scandic-Opacity-White-100);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export { default as ArrowRightIcon } from "./ArrowRight"
|
||||
export { default as BarIcon } from "./Bar"
|
||||
export { default as BikingIcon } from "./Biking"
|
||||
export { default as CalendarIcon } from "./Calendar"
|
||||
export { default as CameraIcon } from "./Camera"
|
||||
export { default as CellphoneIcon } from "./Cellphone"
|
||||
export { default as CheckIcon } from "./Check"
|
||||
export { default as CheckCircleIcon } from "./CheckCircle"
|
||||
@@ -18,6 +19,7 @@ export { default as EmailIcon } from "./Email"
|
||||
export { default as FitnessIcon } from "./Fitness"
|
||||
export { default as GlobeIcon } from "./Globe"
|
||||
export { default as HouseIcon } from "./House"
|
||||
export { default as ImageIcon } from "./Image"
|
||||
export { default as InfoCircleIcon } from "./InfoCircle"
|
||||
export { default as LocationIcon } from "./Location"
|
||||
export { default as LockIcon } from "./Lock"
|
||||
|
||||
@@ -13,6 +13,7 @@ const config = {
|
||||
primaryLightOnSurfaceAccent: styles.plosa,
|
||||
red: styles.red,
|
||||
green: styles.green,
|
||||
white: styles.white,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@@ -50,6 +50,10 @@
|
||||
color: var(--Scandic-Brand-Burgundy);
|
||||
}
|
||||
|
||||
.grey {
|
||||
color: var(--UI-Text-Placeholder, #787472);
|
||||
}
|
||||
|
||||
.pale {
|
||||
color: var(--Scandic-Brand-Pale-Peach);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ const config = {
|
||||
color: {
|
||||
black: styles.black,
|
||||
burgundy: styles.burgundy,
|
||||
grey: styles.grey,
|
||||
pale: styles.pale,
|
||||
red: styles.red,
|
||||
textMediumContrast: styles.textMediumContrast,
|
||||
|
||||
@@ -13,6 +13,7 @@ export enum IconName {
|
||||
Bar = "Bar",
|
||||
Biking = "Biking",
|
||||
Calendar = "Calendar",
|
||||
Camera = "Camera",
|
||||
Cellphone = "Cellphone",
|
||||
Check = "Check",
|
||||
CheckCircle = "CheckCircle",
|
||||
@@ -27,6 +28,7 @@ export enum IconName {
|
||||
Fitness = "Fitness",
|
||||
Globe = "Globe",
|
||||
House = "House",
|
||||
Image = "Image",
|
||||
InfoCircle = "InfoCircle",
|
||||
Location = "Location",
|
||||
Lock = "Lock",
|
||||
|
||||
Reference in New Issue
Block a user