feat(BOOK-768): Added UspCard component with stories and implemented it in blocks
Approved-by: Bianca Widstam
This commit is contained in:
@@ -1,18 +1,10 @@
|
|||||||
import { IconByIconName } from "@scandic-hotels/design-system/Icons/IconByIconName"
|
import { UspCard } from "@scandic-hotels/design-system/UspCard"
|
||||||
import { JsonToHtml } from "@scandic-hotels/design-system/JsonToHtml"
|
|
||||||
|
|
||||||
import { getUspIconName } from "./utils"
|
|
||||||
|
|
||||||
import styles from "./uspgrid.module.css"
|
import styles from "./uspgrid.module.css"
|
||||||
|
|
||||||
import type { UspGridProps, UspIcon } from "@/types/components/blocks/uspGrid"
|
import type { UspGrid as UspGridType } from "@scandic-hotels/trpc/types/blocks"
|
||||||
|
|
||||||
function UspIcon({ icon }: { icon: UspIcon }) {
|
interface UspGridProps extends Pick<UspGridType, "usp_grid"> {}
|
||||||
const iconName = getUspIconName(icon)
|
|
||||||
return iconName ? (
|
|
||||||
<IconByIconName iconName={iconName} color="Icon/Interactive/Accent" />
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function UspGrid({ usp_grid }: UspGridProps) {
|
export default function UspGrid({ usp_grid }: UspGridProps) {
|
||||||
return (
|
return (
|
||||||
@@ -20,13 +12,13 @@ export default function UspGrid({ usp_grid }: UspGridProps) {
|
|||||||
{usp_grid.usp_card.map(
|
{usp_grid.usp_card.map(
|
||||||
(usp) =>
|
(usp) =>
|
||||||
usp.text.json && (
|
usp.text.json && (
|
||||||
<div key={usp.text.json.uid} className={styles.usp}>
|
<UspCard
|
||||||
<UspIcon icon={usp.icon} />
|
className={styles.uspCard}
|
||||||
<JsonToHtml
|
key={usp.text.json.uid}
|
||||||
embeds={usp.text.embedded_itemsConnection?.edges}
|
iconName={usp.icon}
|
||||||
|
embeds={usp.text.embedded_itemsConnection.edges}
|
||||||
nodes={usp.text.json.children}
|
nodes={usp.text.json.children}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,17 +3,12 @@
|
|||||||
gap: var(--Space-x3);
|
gap: var(--Space-x3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.usp {
|
|
||||||
display: grid;
|
|
||||||
gap: var(--Space-x3);
|
|
||||||
align-content: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.grid {
|
.grid {
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
.grid:has(.usp:nth-child(3)):not(:has(.usp:nth-child(4))) {
|
|
||||||
|
.grid:has(.uspCard:nth-child(3)):not(:has(.uspCard:nth-child(4))) {
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
import type { UspGrid } from "@scandic-hotels/trpc/types/blocks"
|
|
||||||
|
|
||||||
export interface UspGridProps extends Pick<UspGrid, "usp_grid"> {}
|
|
||||||
export type UspIcon = UspGrid["usp_grid"]["usp_card"][number]["icon"]
|
|
||||||
@@ -20,7 +20,7 @@ const DEFAULT_ARGS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const meta: Meta<typeof InfoCard> = {
|
const meta: Meta<typeof InfoCard> = {
|
||||||
title: "Product Components/InfoCard",
|
title: "Core Components/Cards/InfoCard",
|
||||||
component: InfoCard,
|
component: InfoCard,
|
||||||
argTypes: {
|
argTypes: {
|
||||||
topTitle: {
|
topTitle: {
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/nextjs-vite"
|
||||||
|
|
||||||
|
import { RTETypeEnum } from "../JsonToHtml/types/rte/enums"
|
||||||
|
import type { RTENode } from "../JsonToHtml/types/rte/node"
|
||||||
|
import { UspCard } from "./UspCard"
|
||||||
|
import { USP_ICON_NAMES } from "./utils"
|
||||||
|
|
||||||
|
const DEFAULT_ARGS = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
uid: "paragraph",
|
||||||
|
attrs: { type: "asset" },
|
||||||
|
type: RTETypeEnum.p,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae neque non ipsum efficitur hendrerit at ut nulla.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] satisfies RTENode[],
|
||||||
|
embeds: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta: Meta<typeof UspCard> = {
|
||||||
|
title: "Core Components/Cards/UspCard",
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"The card itself does not have a maximum width, but it will adapt to the width of its container. The card should be used together with other usp cards. It is recommended to use the UspCard inside a grid or a container with a set maximum width for best results.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
component: UspCard,
|
||||||
|
argTypes: {
|
||||||
|
iconName: {
|
||||||
|
control: "select",
|
||||||
|
options: USP_ICON_NAMES,
|
||||||
|
table: {
|
||||||
|
type: {
|
||||||
|
summary: USP_ICON_NAMES.join(" | "),
|
||||||
|
},
|
||||||
|
defaultValue: { summary: "Snowflake" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
embeds: {
|
||||||
|
control: "object",
|
||||||
|
description:
|
||||||
|
"The embeds used by the JsonToHtml component to render rich text content. This data comes from the RTE field in the CMS.",
|
||||||
|
table: {
|
||||||
|
type: { summary: "Node<Embeds>[]" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nodes: {
|
||||||
|
control: "object",
|
||||||
|
description:
|
||||||
|
"The nodes used by the JsonToHtml component to render rich text content. This data comes from the RTE field in the CMS.",
|
||||||
|
table: {
|
||||||
|
type: { summary: "RTENode[]" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: { ...DEFAULT_ARGS },
|
||||||
|
decorators: [
|
||||||
|
(Story, context) => {
|
||||||
|
const showMultipleStyles = ["multiple cards", "different icons"].some(
|
||||||
|
(substring) => context.name.toLowerCase().includes(substring)
|
||||||
|
)
|
||||||
|
if (showMultipleStyles) {
|
||||||
|
return <Story />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ maxWidth: "400px" }}>
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof UspCard>
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
...meta.args,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MultipleCards: Story = {
|
||||||
|
args: {
|
||||||
|
...meta.args,
|
||||||
|
},
|
||||||
|
render: (args) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(3, 1fr)",
|
||||||
|
gap: "16px",
|
||||||
|
width: "min(800px, 100%)",
|
||||||
|
margin: "0 auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UspCard {...args} />
|
||||||
|
<UspCard {...args} />
|
||||||
|
<UspCard {...args} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DifferentIcons: Story = {
|
||||||
|
args: {
|
||||||
|
...meta.args,
|
||||||
|
},
|
||||||
|
render: (args) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(3, 1fr)",
|
||||||
|
gap: "16px",
|
||||||
|
width: "min(800px, 100%)",
|
||||||
|
margin: "0 auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UspCard {...args} iconName={USP_ICON_NAMES[0]} />
|
||||||
|
<UspCard {...args} iconName={USP_ICON_NAMES[1]} />
|
||||||
|
<UspCard {...args} iconName={USP_ICON_NAMES[2]} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}
|
||||||
36
packages/design-system/lib/components/UspCard/UspCard.tsx
Normal file
36
packages/design-system/lib/components/UspCard/UspCard.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { cx } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { IconByIconName } from "../Icons/IconByIconName"
|
||||||
|
import { type Embeds, JsonToHtml, type Node } from "../JsonToHtml/JsonToHtml"
|
||||||
|
import type { RTENode } from "../JsonToHtml/types/rte/node"
|
||||||
|
import { getUspIconName, UspIconName } from "./utils"
|
||||||
|
|
||||||
|
import styles from "./uspCard.module.css"
|
||||||
|
|
||||||
|
interface UspCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
iconName?: UspIconName | null
|
||||||
|
embeds: Node<Embeds>[]
|
||||||
|
nodes: RTENode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UspCard({
|
||||||
|
className,
|
||||||
|
iconName,
|
||||||
|
embeds,
|
||||||
|
nodes,
|
||||||
|
...props
|
||||||
|
}: UspCardProps) {
|
||||||
|
const resolvedIconName = getUspIconName(iconName)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cx(styles.uspCard, className)} {...props}>
|
||||||
|
<IconByIconName
|
||||||
|
className={styles.icon}
|
||||||
|
iconName={resolvedIconName}
|
||||||
|
color="Icon/Interactive/Accent"
|
||||||
|
size={48}
|
||||||
|
/>
|
||||||
|
<JsonToHtml embeds={embeds} nodes={nodes} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
1
packages/design-system/lib/components/UspCard/index.tsx
Normal file
1
packages/design-system/lib/components/UspCard/index.tsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { UspCard } from "./UspCard"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.uspCard {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Space-x3);
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
@@ -1,9 +1,35 @@
|
|||||||
import { IconName } from "@scandic-hotels/design-system/Icons/iconName"
|
import { IconName } from "../Icons/iconName"
|
||||||
|
|
||||||
import type { UspIcon } from "@/types/components/blocks/uspGrid"
|
export const USP_ICON_NAMES = [
|
||||||
|
"Snowflake",
|
||||||
|
"Information",
|
||||||
|
"Heart",
|
||||||
|
"WiFi",
|
||||||
|
"Breakfast",
|
||||||
|
"Checkbox",
|
||||||
|
"Ticket",
|
||||||
|
"Hotel",
|
||||||
|
"Bed",
|
||||||
|
"Train",
|
||||||
|
"Airplane",
|
||||||
|
"Sun",
|
||||||
|
"Star",
|
||||||
|
"Sports",
|
||||||
|
"Gym",
|
||||||
|
"Hiking",
|
||||||
|
"Skiing",
|
||||||
|
"City",
|
||||||
|
"Pool",
|
||||||
|
"Spa",
|
||||||
|
"Bar",
|
||||||
|
"Restaurant",
|
||||||
|
"Child",
|
||||||
|
] as const
|
||||||
|
|
||||||
export function getUspIconName(icon?: UspIcon | null) {
|
export type UspIconName = (typeof USP_ICON_NAMES)[number]
|
||||||
switch (icon) {
|
|
||||||
|
export function getUspIconName(iconName?: UspIconName | null) {
|
||||||
|
switch (iconName) {
|
||||||
case "Snowflake":
|
case "Snowflake":
|
||||||
return IconName.Snowflake
|
return IconName.Snowflake
|
||||||
case "Information":
|
case "Information":
|
||||||
@@ -188,6 +188,7 @@
|
|||||||
"./Tooltip": "./lib/components/Tooltip/index.tsx",
|
"./Tooltip": "./lib/components/Tooltip/index.tsx",
|
||||||
"./TripAdvisorChip": "./lib/components/TripAdvisorChip/index.tsx",
|
"./TripAdvisorChip": "./lib/components/TripAdvisorChip/index.tsx",
|
||||||
"./Typography": "./lib/components/Typography/index.tsx",
|
"./Typography": "./lib/components/Typography/index.tsx",
|
||||||
|
"./UspCard": "./lib/components/UspCard/index.tsx",
|
||||||
"./VideoPlayer": "./lib/components/VideoPlayer/index.tsx",
|
"./VideoPlayer": "./lib/components/VideoPlayer/index.tsx",
|
||||||
"./VideoWithCard": "./lib/components/VideoPlayer/VideoWithCard/index.tsx",
|
"./VideoWithCard": "./lib/components/VideoPlayer/VideoWithCard/index.tsx",
|
||||||
"./design-system-new-deprecated.css": "./lib/design-system-new-deprecated.css",
|
"./design-system-new-deprecated.css": "./lib/design-system-new-deprecated.css",
|
||||||
|
|||||||
Reference in New Issue
Block a user