From 9620071c7806db9f4a29695f428f86f7e75e3c45 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Thu, 10 Oct 2024 08:13:50 +0200 Subject: [PATCH] feat(SW-391): Added sidepeek functionality to teasercard --- app/globals.css | 1 + components/Blocks/CardsGrid.tsx | 7 +- components/BookingWidget/Client.tsx | 4 +- components/DatePicker/Screen/Mobile.tsx | 4 +- components/Icons/get-icon-by-icon-name.ts | 4 +- components/Icons/index.tsx | 2 +- .../TempDesignSystem/SidePeek/index.tsx | 11 ++- .../SidePeek/sidePeek.module.css | 5 +- .../TempDesignSystem/SidePeek/sidePeek.ts | 2 +- .../TeaserCard/Sidepeek/index.tsx | 82 +++++++++++++++++++ .../TeaserCard/Sidepeek/sidepeek.module.css | 4 + .../TempDesignSystem/TeaserCard/index.tsx | 23 ++---- .../TeaserCard/teaserCard.module.css | 17 ++-- components/TempDesignSystem/Toasts/index.tsx | 4 +- .../Fragments/Blocks/TeaserCard.graphql | 58 +++++++++++++ .../Query/ContentPage/ContentPage.graphql | 25 +++--- .../routers/contentstack/contentPage/query.ts | 47 ++++++++--- .../contentstack/schemas/blocks/cardsGrid.ts | 56 +++++++++++-- .../schemas/blocks/utils/buttonLinkSchema.ts | 1 + types/components/teaserCard.ts | 11 ++- types/trpc/routers/contentstack/blocks.ts | 12 ++- 21 files changed, 311 insertions(+), 69 deletions(-) create mode 100644 components/TempDesignSystem/TeaserCard/Sidepeek/index.tsx create mode 100644 components/TempDesignSystem/TeaserCard/Sidepeek/sidepeek.module.css diff --git a/app/globals.css b/app/globals.css index 07a78808b..5fff0324b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -115,6 +115,7 @@ --header-z-index: 10; --menu-overlay-z-index: 10; --dialog-z-index: 9; + --sidepeek-z-index: 11; } * { diff --git a/components/Blocks/CardsGrid.tsx b/components/Blocks/CardsGrid.tsx index 134eeaa41..b94afb77b 100644 --- a/components/Blocks/CardsGrid.tsx +++ b/components/Blocks/CardsGrid.tsx @@ -7,11 +7,15 @@ import TeaserCard from "@/components/TempDesignSystem/TeaserCard" import type { CardsGridProps } from "@/types/components/blocks/cardsGrid" import { CardsGridEnum } from "@/types/enums/cardsGrid" +import { CardsGridLayoutEnum } from "@/types/trpc/routers/contentstack/blocks" export default function CardsGrid({ cards_grid, firstItem = false, }: CardsGridProps) { + const columns = + cards_grid.layout === CardsGridLayoutEnum.THREE_COLUMNS ? 3 : 2 + return ( - + {cards_grid.cards.map((card) => { switch (card.__typename) { case CardsGridEnum.cards.Card: @@ -43,6 +47,7 @@ export default function CardsGrid({ primaryButton={card.primaryButton} secondaryButton={card.secondaryButton} sidePeekButton={card.sidePeekButton} + sidePeekContent={card.sidePeekContent} image={card.image} /> ) diff --git a/components/BookingWidget/Client.tsx b/components/BookingWidget/Client.tsx index 48f08f323..eded233f0 100644 --- a/components/BookingWidget/Client.tsx +++ b/components/BookingWidget/Client.tsx @@ -7,7 +7,7 @@ import { dt } from "@/lib/dt" import Form from "@/components/Forms/BookingWidget" import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema" -import { CloseLarge } from "@/components/Icons" +import { CloseLargeIcon } from "@/components/Icons" import { debounce } from "@/utils/debounce" import MobileToggleButton from "./MobileToggleButton" @@ -98,7 +98,7 @@ export default function BookingWidgetClient({ onClick={closeMobileSearch} type="button" > - +
diff --git a/components/DatePicker/Screen/Mobile.tsx b/components/DatePicker/Screen/Mobile.tsx index c56a79cd6..3d367d995 100644 --- a/components/DatePicker/Screen/Mobile.tsx +++ b/components/DatePicker/Screen/Mobile.tsx @@ -6,7 +6,7 @@ import { useIntl } from "react-intl" import { Lang } from "@/constants/languages" import { dt } from "@/lib/dt" -import { CloseLarge } from "@/components/Icons" +import { CloseLargeIcon } from "@/components/Icons" import Button from "@/components/TempDesignSystem/Button" import Body from "@/components/TempDesignSystem/Text/Body" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" @@ -127,7 +127,7 @@ export default function DatePickerMobile({ ))} {children} diff --git a/components/Icons/get-icon-by-icon-name.ts b/components/Icons/get-icon-by-icon-name.ts index 9bfc4d9c7..2f89aa6db 100644 --- a/components/Icons/get-icon-by-icon-name.ts +++ b/components/Icons/get-icon-by-icon-name.ts @@ -21,7 +21,7 @@ import { ChevronRightIcon, ChevronRightSmallIcon, CloseIcon, - CloseLarge, + CloseLargeIcon, CoffeeIcon, ConciergeIcon, CrossCircle, @@ -103,7 +103,7 @@ export function getIconByIconName(icon?: IconName): FC | null { case IconName.Close: return CloseIcon case IconName.CloseLarge: - return CloseLarge + return CloseLargeIcon case IconName.Coffee: return CoffeeIcon case IconName.Concierge: diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index 31b77b1e5..ba86f0784 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -16,7 +16,7 @@ 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 CloseLargeIcon } from "./CloseLarge" export { default as CoffeeIcon } from "./Coffee" export { default as ConciergeIcon } from "./Concierge" export { default as CreditCard } from "./CreditCard" diff --git a/components/TempDesignSystem/SidePeek/index.tsx b/components/TempDesignSystem/SidePeek/index.tsx index 6df535b54..c64c72604 100644 --- a/components/TempDesignSystem/SidePeek/index.tsx +++ b/components/TempDesignSystem/SidePeek/index.tsx @@ -10,7 +10,7 @@ import { } from "react-aria-components" import { useIntl } from "react-intl" -import { CloseIcon } from "@/components/Icons" +import { CloseLargeIcon } from "@/components/Icons" import { SidePeekContext } from "@/components/SidePeekProvider" import Button from "../Button" @@ -52,13 +52,16 @@ function SidePeek({ ) } + return (
@@ -80,9 +83,9 @@ function SidePeek({ aria-label={intl.formatMessage({ id: "Close" })} className={styles.closeButton} intent="text" - onPress={onClose} + onClick={onClose} > - +
{children}
diff --git a/components/TempDesignSystem/SidePeek/sidePeek.module.css b/components/TempDesignSystem/SidePeek/sidePeek.module.css index 65ad886b1..8282166c1 100644 --- a/components/TempDesignSystem/SidePeek/sidePeek.module.css +++ b/components/TempDesignSystem/SidePeek/sidePeek.module.css @@ -22,12 +22,13 @@ } .overlay { - position: absolute; + position: fixed; top: 0; bottom: 0; left: 0; right: 0; - z-index: 99; + z-index: var(--sidepeek-z-index); + background-color: var(--UI-Opacity-Almost-Black-30); } .modal { diff --git a/components/TempDesignSystem/SidePeek/sidePeek.ts b/components/TempDesignSystem/SidePeek/sidePeek.ts index e1781f137..6caa9f0f4 100644 --- a/components/TempDesignSystem/SidePeek/sidePeek.ts +++ b/components/TempDesignSystem/SidePeek/sidePeek.ts @@ -1,5 +1,5 @@ export interface SidePeekProps { - contentKey: string + contentKey?: string title: string isOpen?: boolean handleClose?: (isOpen: boolean) => void diff --git a/components/TempDesignSystem/TeaserCard/Sidepeek/index.tsx b/components/TempDesignSystem/TeaserCard/Sidepeek/index.tsx new file mode 100644 index 000000000..833b66cb6 --- /dev/null +++ b/components/TempDesignSystem/TeaserCard/Sidepeek/index.tsx @@ -0,0 +1,82 @@ +"use client" + +import { useState } from "react" + +import { ChevronRightIcon } from "@/components/Icons" +import JsonToHtml from "@/components/JsonToHtml" +import Button from "@/components/TempDesignSystem/Button" + +import Link from "../../Link" +import SidePeek from "../../SidePeek" + +import styles from "./sidepeek.module.css" + +import { TeaserCardSidepeekProps } from "@/types/components/teaserCard" + +export default function TeaserCardSidepeek({ + button, + data, +}: TeaserCardSidepeekProps) { + const [sidePeekIsOpen, setSidePeekIsOpen] = useState(false) + const { heading, content, primary_button, secondary_button } = data + + return ( + <> + + setSidePeekIsOpen(false)} + > + +
+ {primary_button && ( + + )} + {secondary_button && ( + + )} +
+
+ + ) +} diff --git a/components/TempDesignSystem/TeaserCard/Sidepeek/sidepeek.module.css b/components/TempDesignSystem/TeaserCard/Sidepeek/sidepeek.module.css new file mode 100644 index 000000000..e40062540 --- /dev/null +++ b/components/TempDesignSystem/TeaserCard/Sidepeek/sidepeek.module.css @@ -0,0 +1,4 @@ +.ctaContainer { + display: grid; + gap: var(--Spacing-x2); +} diff --git a/components/TempDesignSystem/TeaserCard/index.tsx b/components/TempDesignSystem/TeaserCard/index.tsx index a441a3322..71c8c0b50 100644 --- a/components/TempDesignSystem/TeaserCard/index.tsx +++ b/components/TempDesignSystem/TeaserCard/index.tsx @@ -1,10 +1,10 @@ -import { ChevronRightIcon } from "@/components/Icons" import Image from "@/components/Image" import Button from "@/components/TempDesignSystem/Button" import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" import Subtitle from "../Text/Subtitle" +import TeaserCardSidepeek from "./Sidepeek" import { teaserCardVariants } from "./variants" import styles from "./teaserCard.module.css" @@ -17,6 +17,7 @@ export default function TeaserCard({ primaryButton, secondaryButton, sidePeekButton, + sidePeekContent, image, style = "default", alwaysStack = false, @@ -41,21 +42,11 @@ export default function TeaserCard({ {title} - {description} - {!!sidePeekButton ? ( - + + {description} + + {sidePeekButton && sidePeekContent ? ( + ) : (
{primaryButton && ( diff --git a/components/TempDesignSystem/TeaserCard/teaserCard.module.css b/components/TempDesignSystem/TeaserCard/teaserCard.module.css index 9a90d97c7..e16d06c22 100644 --- a/components/TempDesignSystem/TeaserCard/teaserCard.module.css +++ b/components/TempDesignSystem/TeaserCard/teaserCard.module.css @@ -54,6 +54,17 @@ width: 100%; } +.body { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + line-clamp: 3; + overflow: hidden; + text-overflow: ellipsis; + /* line-height variables are in %, so using the value in rem instead */ + max-height: calc(3 * 1.5rem); +} + @media (min-width: 1367px) { .card:not(.alwaysStack) .ctaContainer { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); @@ -63,9 +74,3 @@ grid-template-columns: 1fr; } } - -.sidePeekCTA { - /* TODO: Create ticket to remove padding on "link" buttons, - align w. design on this. */ - padding: 0 !important; -} diff --git a/components/TempDesignSystem/Toasts/index.tsx b/components/TempDesignSystem/Toasts/index.tsx index 486bc4f46..df08c6803 100644 --- a/components/TempDesignSystem/Toasts/index.tsx +++ b/components/TempDesignSystem/Toasts/index.tsx @@ -2,7 +2,7 @@ import { ExternalToast, toast as sonnerToast, Toaster } from "sonner" import { CheckCircleIcon, - CloseLarge, + CloseLargeIcon, CrossCircle, InfoCircleIcon, WarningTriangle, @@ -42,7 +42,7 @@ export function Toast({ message, onClose, variant }: ToastsProps) {
{message}
) diff --git a/lib/graphql/Fragments/Blocks/TeaserCard.graphql b/lib/graphql/Fragments/Blocks/TeaserCard.graphql index 5b333797c..07a16b366 100644 --- a/lib/graphql/Fragments/Blocks/TeaserCard.graphql +++ b/lib/graphql/Fragments/Blocks/TeaserCard.graphql @@ -28,6 +28,7 @@ fragment TeaserCardBlock on TeaserCard { ...LoyaltyPageLink ...ContentPageLink ...AccountPageLink + ...HotelPageLink } } } @@ -55,6 +56,63 @@ fragment TeaserCardBlock on TeaserCard { sidepeek_button { call_to_action_text } + sidepeek_content { + heading + content { + embedded_itemsConnection { + edges { + node { + __typename + ...AccountPageLink + ...ContentPageLink + ...HotelPageLink + ...LoyaltyPageLink + } + } + } + json + } + has_primary_button + primary_button { + is_contentstack_link + cta_text + open_in_new_tab + external_link { + title + href + } + linkConnection { + edges { + node { + __typename + ...LoyaltyPageLink + ...ContentPageLink + ...HotelPageLink + } + } + } + } + has_secondary_button + secondary_button { + is_contentstack_link + cta_text + open_in_new_tab + external_link { + title + href + } + linkConnection { + edges { + node { + __typename + ...LoyaltyPageLink + ...ContentPageLink + ...HotelPageLink + } + } + } + } + } system { ...System } diff --git a/lib/graphql/Query/ContentPage/ContentPage.graphql b/lib/graphql/Query/ContentPage/ContentPage.graphql index 2bed1d02d..686ed9329 100644 --- a/lib/graphql/Query/ContentPage/ContentPage.graphql +++ b/lib/graphql/Query/ContentPage/ContentPage.graphql @@ -23,16 +23,6 @@ query GetContentPage($locale: String!, $uid: String!) { preamble ...NavigationLinks } - blocks { - __typename - ...CardsGrid_ContentPage - ...Content_ContentPage - ...DynamicContent_ContentPage - ...Shortcuts_ContentPage - ...Table_ContentPage - ...TextCols_ContentPage - ...UspGrid_ContentPage - } sidebar { __typename ...ContentSidebar_ContentPage @@ -47,6 +37,21 @@ query GetContentPage($locale: String!, $uid: String!) { } } +query GetContentPageBlocks($locale: String!, $uid: String!) { + content_page(uid: $uid, locale: $locale) { + blocks { + __typename + ...CardsGrid_ContentPage + ...Content_ContentPage + ...DynamicContent_ContentPage + ...Shortcuts_ContentPage + ...Table_ContentPage + ...TextCols_ContentPage + ...UspGrid_ContentPage + } + } +} + query GetContentPageRefs($locale: String!, $uid: String!) { content_page(locale: $locale, uid: $uid) { header { diff --git a/server/routers/contentstack/contentPage/query.ts b/server/routers/contentstack/contentPage/query.ts index 79ef83a4e..1e6536ae2 100644 --- a/server/routers/contentstack/contentPage/query.ts +++ b/server/routers/contentstack/contentPage/query.ts @@ -1,5 +1,8 @@ import { Lang } from "@/constants/languages" -import { GetContentPage } from "@/lib/graphql/Query/ContentPage/ContentPage.graphql" +import { + GetContentPage, + GetContentPageBlocks, +} from "@/lib/graphql/Query/ContentPage/ContentPage.graphql" import { request } from "@/lib/graphql/request" import { contentstackExtendedProcedureUID, router } from "@/server/trpc" @@ -40,18 +43,38 @@ export const contentPageQueryRouter = router({ }) ) - const response = await request( - GetContentPage, - { locale: lang, uid }, - { - cache: "force-cache", - next: { - tags, - }, - } - ) + const [mainResponse, blocksResponse] = await Promise.all([ + request( + GetContentPage, + { locale: lang, uid }, + { + cache: "force-cache", + next: { + tags, + }, + } + ), + request( + GetContentPageBlocks, + { locale: lang, uid }, + { + cache: "force-cache", + next: { + tags, + }, + } + ), + ]) - const contentPage = contentPageSchema.safeParse(response.data) + const responseData = { + ...mainResponse.data, + content_page: { + ...mainResponse.data.content_page, + blocks: blocksResponse.data.content_page.blocks, + }, + } + + const contentPage = contentPageSchema.safeParse(responseData) if (!contentPage.success) { console.error( diff --git a/server/routers/contentstack/schemas/blocks/cardsGrid.ts b/server/routers/contentstack/schemas/blocks/cardsGrid.ts index 5f1b9af5c..8e070c8a3 100644 --- a/server/routers/contentstack/schemas/blocks/cardsGrid.ts +++ b/server/routers/contentstack/schemas/blocks/cardsGrid.ts @@ -1,12 +1,17 @@ import { z } from "zod" +import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks" + import { tempImageVaultAssetSchema } from "../imageVault" import { systemSchema } from "../system" import { buttonSchema } from "./utils/buttonLinkSchema" import { linkConnectionRefsSchema } from "./utils/linkConnection" +import { imageSchema } from "./image" +import { imageContainerSchema } from "./imageContainer" import { BlocksEnums } from "@/types/enums/blocks" import { CardsGridEnum } from "@/types/enums/cardsGrid" +import { CardsGridLayoutEnum } from "@/types/trpc/routers/contentstack/blocks" export const cardBlockSchema = z.object({ __typename: z.literal(CardsGridEnum.cards.Card), @@ -49,17 +54,57 @@ export const teaserCardBlockSchema = z.object({ has_primary_button: z.boolean().default(false), has_secondary_button: z.boolean().default(false), has_sidepeek_button: z.boolean().default(false), - side_peek_button: z + sidepeek_button: z .object({ - title: z.string().optional().default(""), + call_to_action_text: z.string().optional().default(""), }) .optional(), + sidepeek_content: z + .object({ + heading: z.string(), + content: z.object({ + json: z.any(), + embedded_itemsConnection: z.object({ + edges: z.array( + z.object({ + node: z + .discriminatedUnion("__typename", [ + imageContainerSchema, + imageSchema, + pageLinks.accountPageSchema, + pageLinks.contentPageSchema, + pageLinks.hotelPageSchema, + pageLinks.loyaltyPageSchema, + ]) + .transform((data) => { + const link = pageLinks.transform(data) + if (link) { + return link + } + return data + }), + }) + ), + }), + }), + has_primary_button: z.boolean().default(false), + primary_button: buttonSchema, + has_secondary_button: z.boolean().default(false), + secondary_button: buttonSchema, + }) + .optional(), + // sidepeek_content: z + // .object({ + // heading: z.string(), + // }) + // .optional(), system: systemSchema, }) export function transformTeaserCardBlock( card: typeof teaserCardBlockSchema._type ) { + console.log({ has_sidepeek_button: card.sidepeek_content }) return { __typename: card.__typename, body_text: card.body_text, @@ -68,8 +113,9 @@ export function transformTeaserCardBlock( secondaryButton: card.has_secondary_button ? card.secondary_button : undefined, - sidePeekButton: card.has_sidepeek_button - ? card.side_peek_button + sidePeekButton: card.has_sidepeek_button ? card.sidepeek_button : undefined, + sidePeekContent: card.has_sidepeek_button + ? card.sidepeek_content : undefined, image: card.image, system: card.system, @@ -105,7 +151,7 @@ export const cardsGridSchema = z.object({ }) ), }), - layout: z.enum(["twoColumnGrid", "threeColumnGrid", "twoPlusOne"]), + layout: z.nativeEnum(CardsGridLayoutEnum), preamble: z.string().optional().default(""), theme: z.enum(["one", "two", "three"]).nullable(), title: z.string().optional().default(""), diff --git a/server/routers/contentstack/schemas/blocks/utils/buttonLinkSchema.ts b/server/routers/contentstack/schemas/blocks/utils/buttonLinkSchema.ts index 9735c276f..62baefdab 100644 --- a/server/routers/contentstack/schemas/blocks/utils/buttonLinkSchema.ts +++ b/server/routers/contentstack/schemas/blocks/utils/buttonLinkSchema.ts @@ -20,6 +20,7 @@ export const buttonSchema = z pageLinks.accountPageSchema, pageLinks.contentPageSchema, pageLinks.loyaltyPageSchema, + pageLinks.hotelPageSchema, ]) .transform((data) => { const link = pageLinks.transform(data) diff --git a/types/components/teaserCard.ts b/types/components/teaserCard.ts index 4ca7f56f1..c9985746d 100644 --- a/types/components/teaserCard.ts +++ b/types/components/teaserCard.ts @@ -2,11 +2,12 @@ import { VariantProps } from "class-variance-authority" import { teaserCardVariants } from "@/components/TempDesignSystem/TeaserCard/variants" -import { ImageVaultAsset } from "@/types/components/imageVault" +import type { ImageVaultAsset } from "@/types/components/imageVault" import type { CardProps } from "@/components/TempDesignSystem/Card/card" +import type { TeaserCard } from "../trpc/routers/contentstack/blocks" interface SidePeekButton { - title: string + call_to_action_text: string } export interface TeaserCardProps @@ -16,6 +17,12 @@ export interface TeaserCardProps primaryButton?: CardProps["primaryButton"] secondaryButton?: CardProps["secondaryButton"] sidePeekButton?: SidePeekButton + sidePeekContent?: TeaserCard["sidepeek_content"] image?: ImageVaultAsset className?: string } + +export interface TeaserCardSidepeekProps { + button: SidePeekButton + data: NonNullable +} diff --git a/types/trpc/routers/contentstack/blocks.ts b/types/trpc/routers/contentstack/blocks.ts index 1c46dda93..d215f40ed 100644 --- a/types/trpc/routers/contentstack/blocks.ts +++ b/types/trpc/routers/contentstack/blocks.ts @@ -1,6 +1,9 @@ import { z } from "zod" -import { cardsGridSchema } from "@/server/routers/contentstack/schemas/blocks/cardsGrid" +import { + cardsGridSchema, + teaserCardBlockSchema, +} from "@/server/routers/contentstack/schemas/blocks/cardsGrid" import { contentSchema } from "@/server/routers/contentstack/schemas/blocks/content" import { dynamicContentSchema } from "@/server/routers/contentstack/schemas/blocks/dynamicContent" import { shortcutsSchema } from "@/server/routers/contentstack/schemas/blocks/shortcuts" @@ -8,6 +11,13 @@ import { tableSchema } from "@/server/routers/contentstack/schemas/blocks/table" import { textColsSchema } from "@/server/routers/contentstack/schemas/blocks/textCols" import { uspGridSchema } from "@/server/routers/contentstack/schemas/blocks/uspGrid" +export enum CardsGridLayoutEnum { + TWO_COLUMNS = "twoColumnGrid", + THREE_COLUMNS = "threeColumnGrid", + TWO_PLUS_ONE = "twoPlusOne", +} + +export interface TeaserCard extends z.output {} export interface CardsGrid extends z.output {} export interface Content extends z.output {} export interface DynamicContent extends z.output {}