feat(SW-368): added link chips component
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
40
components/Icons/ChevronRightSmall.tsx
Normal file
40
components/Icons/ChevronRightSmall.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
14
components/TempDesignSystem/LinkChips/Chip/chip.module.css
Normal file
14
components/TempDesignSystem/LinkChips/Chip/chip.module.css
Normal 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);
|
||||
}
|
||||
4
components/TempDesignSystem/LinkChips/Chip/chip.ts
Normal file
4
components/TempDesignSystem/LinkChips/Chip/chip.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface LinkChipProps {
|
||||
url: string
|
||||
title: string
|
||||
}
|
||||
19
components/TempDesignSystem/LinkChips/Chip/index.tsx
Normal file
19
components/TempDesignSystem/LinkChips/Chip/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
20
components/TempDesignSystem/LinkChips/index.tsx
Normal file
20
components/TempDesignSystem/LinkChips/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.linkChips {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
5
components/TempDesignSystem/LinkChips/linkChips.ts
Normal file
5
components/TempDesignSystem/LinkChips/linkChips.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { LinkChipProps } from "./Chip/chip"
|
||||
|
||||
export interface LinkChipsProps {
|
||||
chips: LinkChipProps[]
|
||||
}
|
||||
19
lib/graphql/Fragments/ContentPage/NavigationLinks.graphql
Normal file
19
lib/graphql/Fragments/ContentPage/NavigationLinks.graphql
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
74
server/routers/contentstack/schemas/linkConnection.ts
Normal file
74
server/routers/contentstack/schemas/linkConnection.ts
Normal 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 }
|
||||
})
|
||||
@@ -21,6 +21,7 @@ export enum IconName {
|
||||
ChevronDown = "ChevronDown",
|
||||
ChevronLeft = "ChevronLeft",
|
||||
ChevronRight = "ChevronRight",
|
||||
ChevronRightSmall = "ChevronRightSmall",
|
||||
Close = "Close",
|
||||
CloseLarge = "CloseLarge",
|
||||
Coffee = "Coffee",
|
||||
|
||||
Reference in New Issue
Block a user