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);
}
.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 {
padding: var(--Spacing-x4) var(--Spacing-x2);
display: grid;
@@ -21,3 +33,9 @@
width: 100%;
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 Hero from "@/components/Hero"
import Intro from "@/components/Intro"
import Sidebar from "@/components/Sidebar"
import LinkChips from "@/components/TempDesignSystem/LinkChips"
import Preamble from "@/components/TempDesignSystem/Text/Preamble"
import Title from "@/components/TempDesignSystem/Text/Title"
import TrackingSDK from "@/components/TrackingSDK"
@@ -18,31 +18,34 @@ export default async function ContentPage() {
}
const { tracking, contentPage } = contentPageRes
const heroImage = contentPage.hero_image
const { blocks, hero_image, header, sidebar } = contentPage
return (
<>
<section className={styles.contentPage}>
{contentPage.sidebar?.length ? (
<Sidebar blocks={contentPage.sidebar} />
) : null}
{sidebar?.length ? <Sidebar blocks={sidebar} /> : null}
<header className={styles.header}>
<Intro>
<Title as="h2">{contentPage.header.heading}</Title>
<Preamble>{contentPage.header.preamble}</Preamble>
</Intro>
<div className={styles.headerContent}>
<div className={styles.headerIntro}>
<Title as="h2">{header.heading}</Title>
<Preamble>{header.preamble}</Preamble>
</div>
{header.navigation_links ? (
<LinkChips chips={header.navigation_links} />
) : null}
</div>
</header>
<main className={styles.content}>
<div className={styles.innerContent}>
{heroImage ? (
{hero_image ? (
<Hero
alt={heroImage.meta.alt || heroImage.meta.caption || ""}
src={heroImage.url}
alt={hero_image.meta.alt || hero_image.meta.caption || ""}
src={hero_image.url}
/>
) : null}
{contentPage.blocks ? <Blocks blocks={contentPage.blocks} /> : null}
{blocks ? <Blocks blocks={blocks} /> : null}
</div>
</main>
</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,
ChevronLeftIcon,
ChevronRightIcon,
ChevronRightSmallIcon,
CloseIcon,
CloseLarge,
CoffeeIcon,
@@ -89,6 +90,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
return ChevronLeftIcon
case IconName.ChevronRight:
return ChevronRightIcon
case IconName.ChevronRightSmall:
return ChevronRightSmallIcon
case IconName.Close:
return CloseIcon
case IconName.CloseLarge:

View File

@@ -11,6 +11,7 @@ export { default as CheckCircleIcon } from "./CheckCircle"
export { default as ChevronDownIcon } from "./ChevronDown"
export { default as ChevronLeftIcon } from "./ChevronLeft"
export { default as ChevronRightIcon } from "./ChevronRight"
export { default as ChevronRightSmallIcon } from "./ChevronRightSmall"
export { default as CloseIcon } from "./Close"
export { default as CloseLarge } from "./CloseLarge"
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/UspGrid.graphql"
#import "../../Fragments/ContentPage/NavigationLinks.graphql"
#import "../../Fragments/Sidebar/Content.graphql"
#import "../../Fragments/Sidebar/DynamicContent.graphql"
#import "../../Fragments/Sidebar/JoinLoyaltyContact.graphql"
@@ -18,6 +20,7 @@ query GetContentPage($locale: String!, $uid: String!) {
header {
heading
preamble
...NavigationLinks
}
blocks {
__typename
@@ -44,6 +47,20 @@ query GetContentPage($locale: String!, $uid: String!) {
query GetContentPageRefs($locale: String!, $uid: String!) {
content_page(locale: $locale, uid: $uid) {
header {
navigation_links {
linkConnection {
edges {
node {
__typename
...ContentPageRef
...HotelPageRef
...LoyaltyPageRef
}
}
}
}
}
blocks {
__typename
...CardsGrid_ContentPageRefs

View File

@@ -11,8 +11,8 @@ import {
contentSchema as blockContentSchema,
} from "../schemas/blocks/content"
import {
dynamicContentRefsSchema,
dynamicContentSchema as blockDynamicContentSchema,
dynamicContentRefsSchema,
} from "../schemas/blocks/dynamicContent"
import {
shortcutsRefsSchema,
@@ -32,7 +32,18 @@ import {
} from "../schemas/sidebar/joinLoyaltyContact"
import { systemSchema } from "../schemas/system"
import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks"
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
export const contentPageCards = z
@@ -106,6 +117,19 @@ export const sidebarSchema = z.discriminatedUnion("__typename", [
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
export const contentPageSchema = z.object({
content_page: z.object({
@@ -116,6 +140,7 @@ export const contentPageSchema = z.object({
header: z.object({
heading: z.string(),
preamble: z.string(),
navigation_links: navigationLinksSchema,
}),
system: systemSchema.merge(
z.object({
@@ -191,8 +216,13 @@ const contentPageSidebarRefsItem = z.discriminatedUnion("__typename", [
contentPageSidebarJoinLoyaltyContactRef,
])
const contentPageHeaderRefs = z.object({
navigation_links: z.array(linkConnectionRefs),
})
export const contentPageRefsSchema = z.object({
content_page: z.object({
header: contentPageHeaderRefs,
blocks: discriminatedUnionArray(
contentPageBlockRefsItem.options
).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",
ChevronLeft = "ChevronLeft",
ChevronRight = "ChevronRight",
ChevronRightSmall = "ChevronRightSmall",
Close = "Close",
CloseLarge = "CloseLarge",
Coffee = "Coffee",