feat(SW-368): added link chips component

This commit is contained in:
Erik Tiekstra
2024-09-19 15:16:58 +02:00
parent 6720370c41
commit 4352997322
18 changed files with 290 additions and 41 deletions

View File

@@ -11,6 +11,18 @@
padding: var(--Spacing-x4) var(--Spacing-x2); padding: var(--Spacing-x4) var(--Spacing-x2);
} }
.headerContent {
display: grid;
gap: var(--Spacing-x3);
max-width: var(--max-width-content);
margin: 0 auto;
}
.headerIntro {
display: grid;
max-width: var(--max-width-text-block);
gap: var(--Spacing-x3);
}
.content { .content {
padding: var(--Spacing-x4) var(--Spacing-x2); padding: var(--Spacing-x4) var(--Spacing-x2);
display: grid; display: grid;
@@ -21,3 +33,9 @@
width: 100%; width: 100%;
max-width: var(--max-width-content); max-width: var(--max-width-content);
} }
@media (min-width: 768px) {
.headerIntro {
gap: var(--Spacing-x3);
}
}

View File

@@ -2,8 +2,8 @@ import { serverClient } from "@/lib/trpc/server"
import Blocks from "@/components/Blocks" import Blocks from "@/components/Blocks"
import Hero from "@/components/Hero" import Hero from "@/components/Hero"
import Intro from "@/components/Intro"
import Sidebar from "@/components/Sidebar" import Sidebar from "@/components/Sidebar"
import LinkChips from "@/components/TempDesignSystem/LinkChips"
import Preamble from "@/components/TempDesignSystem/Text/Preamble" import Preamble from "@/components/TempDesignSystem/Text/Preamble"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
import TrackingSDK from "@/components/TrackingSDK" import TrackingSDK from "@/components/TrackingSDK"
@@ -18,31 +18,34 @@ export default async function ContentPage() {
} }
const { tracking, contentPage } = contentPageRes const { tracking, contentPage } = contentPageRes
const heroImage = contentPage.hero_image const { blocks, hero_image, header, sidebar } = contentPage
return ( return (
<> <>
<section className={styles.contentPage}> <section className={styles.contentPage}>
{contentPage.sidebar?.length ? ( {sidebar?.length ? <Sidebar blocks={sidebar} /> : null}
<Sidebar blocks={contentPage.sidebar} />
) : null}
<header className={styles.header}> <header className={styles.header}>
<Intro> <div className={styles.headerContent}>
<Title as="h2">{contentPage.header.heading}</Title> <div className={styles.headerIntro}>
<Preamble>{contentPage.header.preamble}</Preamble> <Title as="h2">{header.heading}</Title>
</Intro> <Preamble>{header.preamble}</Preamble>
</div>
{header.navigation_links ? (
<LinkChips chips={header.navigation_links} />
) : null}
</div>
</header> </header>
<main className={styles.content}> <main className={styles.content}>
<div className={styles.innerContent}> <div className={styles.innerContent}>
{heroImage ? ( {hero_image ? (
<Hero <Hero
alt={heroImage.meta.alt || heroImage.meta.caption || ""} alt={hero_image.meta.alt || hero_image.meta.caption || ""}
src={heroImage.url} src={hero_image.url}
/> />
) : null} ) : null}
{contentPage.blocks ? <Blocks blocks={contentPage.blocks} /> : null} {blocks ? <Blocks blocks={blocks} /> : null}
</div> </div>
</main> </main>
</section> </section>

View File

@@ -0,0 +1,40 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function ChevronRightSmallIcon({
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_3311"
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_3311)">
<path
d="M12.65 12L8.77495 8.12497C8.59995 7.94997 8.51245 7.73538 8.51245 7.48122C8.51245 7.22705 8.59995 7.0083 8.77495 6.82497C8.94995 6.64163 9.16662 6.54788 9.42495 6.54372C9.68328 6.53955 9.90412 6.62913 10.0875 6.81247L14.6125 11.3375C14.7041 11.4291 14.7729 11.5312 14.8187 11.6437C14.8645 11.7562 14.8875 11.875 14.8875 12C14.8875 12.125 14.8645 12.2437 14.8187 12.3562C14.7729 12.4687 14.7041 12.5708 14.6125 12.6625L10.0875 17.1875C9.90412 17.3708 9.68328 17.4604 9.42495 17.4562C9.16662 17.4521 8.94995 17.3583 8.77495 17.175C8.59995 16.9916 8.51245 16.7729 8.51245 16.5187C8.51245 16.2646 8.59995 16.05 8.77495 15.875L12.65 12Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -17,6 +17,7 @@ import {
ChevronDownIcon, ChevronDownIcon,
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
ChevronRightSmallIcon,
CloseIcon, CloseIcon,
CloseLarge, CloseLarge,
CoffeeIcon, CoffeeIcon,
@@ -89,6 +90,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
return ChevronLeftIcon return ChevronLeftIcon
case IconName.ChevronRight: case IconName.ChevronRight:
return ChevronRightIcon return ChevronRightIcon
case IconName.ChevronRightSmall:
return ChevronRightSmallIcon
case IconName.Close: case IconName.Close:
return CloseIcon return CloseIcon
case IconName.CloseLarge: case IconName.CloseLarge:

View File

@@ -11,6 +11,7 @@ export { default as CheckCircleIcon } from "./CheckCircle"
export { default as ChevronDownIcon } from "./ChevronDown" export { default as ChevronDownIcon } from "./ChevronDown"
export { default as ChevronLeftIcon } from "./ChevronLeft" export { default as ChevronLeftIcon } from "./ChevronLeft"
export { default as ChevronRightIcon } from "./ChevronRight" export { default as ChevronRightIcon } from "./ChevronRight"
export { default as ChevronRightSmallIcon } from "./ChevronRightSmall"
export { default as CloseIcon } from "./Close" export { default as CloseIcon } from "./Close"
export { default as CloseLarge } from "./CloseLarge" export { default as CloseLarge } from "./CloseLarge"
export { default as CoffeeIcon } from "./Coffee" export { default as CoffeeIcon } from "./Coffee"

View File

@@ -1,11 +0,0 @@
import { PropsWithChildren } from "react"
import styles from "./intro.module.css"
export default async function Intro({ children }: PropsWithChildren) {
return (
<div className={styles.intro}>
<div className={styles.content}>{children}</div>
</div>
)
}

View File

@@ -1,16 +0,0 @@
.intro {
max-width: var(--max-width-content);
margin: 0 auto;
}
.content {
display: grid;
max-width: var(--max-width-text-block);
gap: var(--Spacing-x2);
}
@media (min-width: 768px) {
.content {
gap: var(--Spacing-x3);
}
}

View File

@@ -0,0 +1,14 @@
.linkChip {
display: flex;
align-items: center;
gap: var(--Spacing-x-half);
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
border-radius: var(--Corner-radius-Small);
background-color: var(--Base-Button-Inverted-Fill-Normal);
transition: background-color 0.3s;
text-decoration: none;
}
.linkChip:hover {
background-color: var(--Base-Button-Inverted-Fill-Hover-alt);
}

View File

@@ -0,0 +1,4 @@
export interface LinkChipProps {
url: string
title: string
}

View File

@@ -0,0 +1,19 @@
import Link from "next/link"
import { ChevronRightSmallIcon } from "@/components/Icons"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { LinkChipProps } from "./chip"
import styles from "./chip.module.css"
export default function LinkChip({ url, title }: LinkChipProps) {
return (
<Caption textTransform="bold" color="burgundy" asChild>
<Link href={url} className={styles.linkChip}>
{title}
<ChevronRightSmallIcon color="burgundy" width={20} height={20} />
</Link>
</Caption>
)
}

View File

@@ -0,0 +1,20 @@
import LinkChip from "./Chip"
import { LinkChipsProps } from "./linkChips"
import styles from "./linkChips.module.css"
export default function LinkChips({ chips }: LinkChipsProps) {
if (!chips.length) {
return null
}
return (
<ul className={styles.linkChips}>
{chips.map(({ url, title }) => (
<li key={`link-chip-${title}`}>
<LinkChip url={url} title={title} />
</li>
))}
</ul>
)
}

View File

@@ -0,0 +1,8 @@
.linkChips {
list-style: none;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: center;
gap: var(--Spacing-x1);
}

View File

@@ -0,0 +1,5 @@
import { LinkChipProps } from "./Chip/chip"
export interface LinkChipsProps {
chips: LinkChipProps[]
}

View File

@@ -0,0 +1,19 @@
#import "../PageLink/ContentPageLink.graphql"
#import "../PageLink/HotelPageLink.graphql"
#import "../PageLink/LoyaltyPageLink.graphql"
fragment NavigationLinks on ContentPageHeader {
navigation_links {
title
linkConnection {
edges {
node {
__typename
...HotelPageLink
...ContentPageLink
...LoyaltyPageLink
}
}
}
}
}

View File

@@ -7,6 +7,8 @@
#import "../../Fragments/Blocks/TextCols.graphql" #import "../../Fragments/Blocks/TextCols.graphql"
#import "../../Fragments/Blocks/UspGrid.graphql" #import "../../Fragments/Blocks/UspGrid.graphql"
#import "../../Fragments/ContentPage/NavigationLinks.graphql"
#import "../../Fragments/Sidebar/Content.graphql" #import "../../Fragments/Sidebar/Content.graphql"
#import "../../Fragments/Sidebar/DynamicContent.graphql" #import "../../Fragments/Sidebar/DynamicContent.graphql"
#import "../../Fragments/Sidebar/JoinLoyaltyContact.graphql" #import "../../Fragments/Sidebar/JoinLoyaltyContact.graphql"
@@ -18,6 +20,7 @@ query GetContentPage($locale: String!, $uid: String!) {
header { header {
heading heading
preamble preamble
...NavigationLinks
} }
blocks { blocks {
__typename __typename
@@ -44,6 +47,20 @@ query GetContentPage($locale: String!, $uid: String!) {
query GetContentPageRefs($locale: String!, $uid: String!) { query GetContentPageRefs($locale: String!, $uid: String!) {
content_page(locale: $locale, uid: $uid) { content_page(locale: $locale, uid: $uid) {
header {
navigation_links {
linkConnection {
edges {
node {
__typename
...ContentPageRef
...HotelPageRef
...LoyaltyPageRef
}
}
}
}
}
blocks { blocks {
__typename __typename
...CardsGrid_ContentPageRefs ...CardsGrid_ContentPageRefs

View File

@@ -11,8 +11,8 @@ import {
contentSchema as blockContentSchema, contentSchema as blockContentSchema,
} from "../schemas/blocks/content" } from "../schemas/blocks/content"
import { import {
dynamicContentRefsSchema,
dynamicContentSchema as blockDynamicContentSchema, dynamicContentSchema as blockDynamicContentSchema,
dynamicContentRefsSchema,
} from "../schemas/blocks/dynamicContent" } from "../schemas/blocks/dynamicContent"
import { import {
shortcutsRefsSchema, shortcutsRefsSchema,
@@ -32,7 +32,18 @@ import {
} from "../schemas/sidebar/joinLoyaltyContact" } from "../schemas/sidebar/joinLoyaltyContact"
import { systemSchema } from "../schemas/system" import { systemSchema } from "../schemas/system"
import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks"
import { ContentPageEnum } from "@/types/enums/contentPage" import { ContentPageEnum } from "@/types/enums/contentPage"
import {
linkAndTitleSchema,
linkConnectionRefs,
} from "../schemas/linkConnection"
const linkUnionSchema = z.discriminatedUnion("__typename", [
pageLinks.contentPageSchema,
pageLinks.hotelPageSchema,
pageLinks.loyaltyPageSchema,
])
// Block schemas // Block schemas
export const contentPageCards = z export const contentPageCards = z
@@ -106,6 +117,19 @@ export const sidebarSchema = z.discriminatedUnion("__typename", [
contentPageJoinLoyaltyContact, contentPageJoinLoyaltyContact,
]) ])
const navigationLinksSchema = z
.array(linkAndTitleSchema)
.nullable()
.transform((data) => {
if (!data) {
return null
}
return data
.filter((item) => !!item.link)
.map((item) => ({ url: item.link.url, title: item.title }))
})
// Content Page Schema and types // Content Page Schema and types
export const contentPageSchema = z.object({ export const contentPageSchema = z.object({
content_page: z.object({ content_page: z.object({
@@ -116,6 +140,7 @@ export const contentPageSchema = z.object({
header: z.object({ header: z.object({
heading: z.string(), heading: z.string(),
preamble: z.string(), preamble: z.string(),
navigation_links: navigationLinksSchema,
}), }),
system: systemSchema.merge( system: systemSchema.merge(
z.object({ z.object({
@@ -191,8 +216,13 @@ const contentPageSidebarRefsItem = z.discriminatedUnion("__typename", [
contentPageSidebarJoinLoyaltyContactRef, contentPageSidebarJoinLoyaltyContactRef,
]) ])
const contentPageHeaderRefs = z.object({
navigation_links: z.array(linkConnectionRefs),
})
export const contentPageRefsSchema = z.object({ export const contentPageRefsSchema = z.object({
content_page: z.object({ content_page: z.object({
header: contentPageHeaderRefs,
blocks: discriminatedUnionArray( blocks: discriminatedUnionArray(
contentPageBlockRefsItem.options contentPageBlockRefsItem.options
).nullable(), ).nullable(),

View File

@@ -0,0 +1,74 @@
import { z } from "zod"
import { discriminatedUnion } from "@/lib/discriminatedUnion"
import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks"
const linkUnionSchema = z.discriminatedUnion("__typename", [
pageLinks.contentPageSchema,
pageLinks.hotelPageSchema,
pageLinks.loyaltyPageSchema,
])
const titleSchema = z.object({
title: z.string().optional().default(""),
})
export const linkConnectionSchema = z
.object({
linkConnection: z.object({
edges: z.array(
z.object({
node: discriminatedUnion(linkUnionSchema.options),
})
),
}),
})
.transform((data) => {
if (data.linkConnection.edges.length) {
const link = pageLinks.transform(data.linkConnection.edges[0].node)
if (link) {
return {
link,
}
}
}
return {
link: null,
}
})
export const linkAndTitleSchema = z.intersection(
linkConnectionSchema,
titleSchema
)
const linkRefsUnionSchema = z.discriminatedUnion("__typename", [
pageLinks.contentPageRefSchema,
pageLinks.hotelPageRefSchema,
pageLinks.loyaltyPageRefSchema,
])
export const linkConnectionRefs = z
.object({
linkConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
})
.transform((data) => {
if (data.linkConnection.edges.length) {
const link = pageLinks.transformRef(data.linkConnection.edges[0].node)
if (link) {
return {
link,
}
}
}
return { link: null }
})

View File

@@ -21,6 +21,7 @@ export enum IconName {
ChevronDown = "ChevronDown", ChevronDown = "ChevronDown",
ChevronLeft = "ChevronLeft", ChevronLeft = "ChevronLeft",
ChevronRight = "ChevronRight", ChevronRight = "ChevronRight",
ChevronRightSmall = "ChevronRightSmall",
Close = "Close", Close = "Close",
CloseLarge = "CloseLarge", CloseLarge = "CloseLarge",
Coffee = "Coffee", Coffee = "Coffee",