Merged develop into feat/hotel-pages-intro-section

This commit is contained in:
Chuma Mcphoy (We Ahead)
2024-07-08 09:35:05 +00:00
30 changed files with 966 additions and 72 deletions

View File

@@ -4,6 +4,7 @@
padding-left: var(--Spacing-x0);
padding-right: var(--Spacing-x0);
position: relative;
justify-content: center;
}
.blocks {

View File

@@ -3,6 +3,7 @@ import { useRouter } from "next/navigation"
import { Button } from "@scandic-hotels/design-system/current"
import { renderOptions as currentRenderOptions } from "@/components/Current/currentRenderOptions"
import Image from "@/components/Image"
import JsonToHtml from "@/components/JsonToHtml"
@@ -44,7 +45,7 @@ export default function Puff({
<JsonToHtml
embeds={[]}
nodes={text.json.children}
renderOptions={renderOptions}
renderOptions={{ ...currentRenderOptions, ...renderOptions }}
/>
<div>
<Button onPress={onClick}>{link.title || title}</Button>
@@ -73,7 +74,7 @@ export default function Puff({
<JsonToHtml
embeds={[]}
nodes={text.json.children}
renderOptions={renderOptions}
renderOptions={{ ...currentRenderOptions, ...renderOptions }}
/>
</section>
</article>

View File

@@ -1,5 +1,7 @@
import JsonToHtml from "@/components/JsonToHtml"
import { renderOptions } from "./../currentRenderOptions"
import type { TextProps } from "@/types/components/current/blocks/text"
export default function Text({ text }: TextProps) {
@@ -7,6 +9,7 @@ export default function Text({ text }: TextProps) {
<JsonToHtml
embeds={text.content.embedded_itemsConnection.edges}
nodes={text.content.json.children}
renderOptions={renderOptions}
/>
)
}

View File

@@ -1,5 +1,6 @@
import JsonToHtml from "@/components/JsonToHtml"
import { renderOptions as currentRenderOptions } from "./../currentRenderOptions"
import Breadcrumbs from "./Breadcrumbs"
import { renderOptions } from "./renderOptions"
@@ -27,7 +28,7 @@ export default function Preamble({
<JsonToHtml
embeds={preamble.text.embedded_itemsConnection.edges}
nodes={preamble.text.json.children}
renderOptions={renderOptions}
renderOptions={{ ...currentRenderOptions, ...renderOptions }}
/>
) : null}
</section>

View File

@@ -0,0 +1,6 @@
.image {
height: auto;
margin-bottom: var(--Spacing-x2);
max-width: 100%;
object-fit: cover;
}

View File

@@ -0,0 +1,474 @@
import Image from "@/components/Image"
import Link from "@/components/TempDesignSystem/Link"
import styles from "./currentRenderOptions.module.css"
import type { EmbedByUid } from "@/types/components/jsontohtml"
import { EmbedEnum } from "@/types/requests/utils/embeds"
import type { Attributes } from "@/types/rte/attrs"
import { RTEItemTypeEnum, RTETypeEnum } from "@/types/rte/enums"
import type {
RTEDefaultNode,
RTENext,
RTENode,
RTERegularNode,
} from "@/types/rte/node"
import { RTEMarkType } from "@/types/rte/node"
import type { RenderOptions } from "@/types/rte/option"
function extractPossibleAttributes(attrs: Attributes | undefined) {
if (!attrs) return {}
const props: Record<string, any> = {}
if (attrs.id) {
props.id = attrs.id
}
if (attrs.class) {
props.className = attrs.class
} else if (attrs["class-name"]) {
props.className = attrs["class-name"]
} else if (attrs.classname) {
props.className = attrs.classname
} else if (attrs?.style?.["text-align"]) {
props.style = {
textAlign: attrs?.style?.["text-align"],
}
}
return props
}
export const renderOptions: RenderOptions = {
[RTETypeEnum.a]: (
node: RTERegularNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
if (node.attrs.url) {
const props = extractPossibleAttributes(node.attrs)
return (
<a
{...props}
href={node.attrs.url}
target={node.attrs.target ?? "_blank"}
key={node.uid}
>
{next(node.children, embeds, fullRenderOptions)}
</a>
)
}
return null
},
[RTETypeEnum.blockquote]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<blockquote key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</blockquote>
)
},
[RTETypeEnum.code]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<code key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</code>
)
},
[RTETypeEnum.embed]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
if (node.attrs.src) {
props.src = node.attrs.src
}
if (node.attrs.url) {
props.src = node.attrs.url
}
if (!props.src) {
return null
}
return (
<iframe key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</iframe>
)
},
[RTETypeEnum.h1]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<h1 key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</h1>
)
},
[RTETypeEnum.h2]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<h2 key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</h2>
)
},
[RTETypeEnum.h3]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<h3 key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</h3>
)
},
[RTETypeEnum.h4]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<h4 key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</h4>
)
},
[RTETypeEnum.h5]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<h5 key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</h5>
)
},
[RTETypeEnum.h6]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<h6 key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</h6>
)
},
[RTETypeEnum.hr]: () => {
return <hr />
},
[RTETypeEnum.li]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<li key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</li>
)
},
[RTETypeEnum.ol]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<ol key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</ol>
)
},
[RTETypeEnum.p]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<p {...props} key={node.uid}>
{next(node.children, embeds, fullRenderOptions)}
</p>
)
},
[RTETypeEnum.reference]: (
node: RTENode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
if ("attrs" in node) {
const type = node.attrs.type
if (type === RTEItemTypeEnum.asset) {
const image = embeds?.[node?.attrs?.["asset-uid"]]
if (image?.node.__typename === EmbedEnum.SysAsset) {
const alt = image?.node?.title ?? node.attrs.alt
const alignment = node.attrs?.style?.["text-align"]
? {
alignSelf: node.attrs?.style?.["text-align"],
}
: {}
return (
<Image
key={node.uid}
alt={alt}
className={styles.image}
height={image.node.dimension.height}
src={image?.node?.url}
width={image.node.dimension.width}
style={alignment}
/>
)
}
} else {
const props = extractPossibleAttributes(node.attrs)
const href = node.attrs?.locale
? `/${node.attrs.locale}${node.attrs.href}`
: node.attrs.href
return (
<Link {...props} href={href} key={node.uid}>
{next(node.children, embeds, fullRenderOptions)}
</Link>
)
}
}
return null
},
[RTETypeEnum.table]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<table key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</table>
)
},
[RTETypeEnum.thead]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<thead key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</thead>
)
},
[RTETypeEnum.tbody]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<tbody key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</tbody>
)
},
[RTETypeEnum.tfoot]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<tfoot key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</tfoot>
)
},
[RTETypeEnum.tr]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<tr key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</tr>
)
},
[RTETypeEnum.th]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<th key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</th>
)
},
[RTETypeEnum.td]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<td key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</td>
)
},
[RTETypeEnum.ul]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<ul key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</ul>
)
},
/** TextNode wrappers */
[RTEMarkType.bold]: (children: React.ReactNode) => {
return <strong>{children}</strong>
},
[RTEMarkType.italic]: (children: React.ReactNode) => {
return <em>{children}</em>
},
[RTEMarkType.underline]: (children: React.ReactNode) => {
return <u>{children}</u>
},
[RTEMarkType.strikethrough]: (children: React.ReactNode) => {
return <s>{children}</s>
},
[RTEMarkType.inlineCode]: (children: React.ReactNode) => {
return <span>{children}</span>
},
[RTEMarkType.subscript]: (children: React.ReactNode) => {
return <sub>{children}</sub>
},
[RTEMarkType.superscript]: (children: React.ReactNode) => {
return <sup>{children}</sup>
},
[RTEMarkType.break]: (children: React.ReactNode) => {
return (
<>
<br />
{children}
</>
)
},
[RTEMarkType.classnameOrId]: (
children: React.ReactNode,
className?: string,
id?: string
) => {
let props = {
className,
id,
}
if (!className) {
delete props.className
}
if (!id) {
delete props.id
}
return (
<span key={id} {...props}>
{children}
</span>
)
},
/**
* Contentstack can return something called `default` as seen here in their
* own SDK (https://github.com/contentstack/contentstack-utils-javascript/blob/master/src/options/default-node-options.ts#L89)
*/
default: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
return next(node.children, embeds, fullRenderOptions)
},
}

View File

@@ -0,0 +1,24 @@
.container {
display: grid;
gap: var(--Spacing-x2);
width: 100%;
grid-template-columns: auto;
}
.image {
max-width: 100%;
width: 100%;
height: 365px;
object-fit: cover;
border-radius: var(--Corner-radius-Medium);
}
@media screen and (min-width: 768px) {
.container {
grid-template-columns: 1fr 1fr;
}
.image {
margin: var(--Spacing-x1) var(--Spacing-x0);
}
}

View File

@@ -0,0 +1,36 @@
import Image from "../Image"
import Caption from "../TempDesignSystem/Text/Caption"
import styles from "./imageContainer.module.css"
import type { ImageContainerProps } from "@/types/components/imageContainer"
export default function ImageContainer({
leftImage,
rightImage,
}: ImageContainerProps) {
return (
<section className={styles.container}>
<article>
<Image
className={styles.image}
src={leftImage.url}
height={365}
width={600}
alt={leftImage.meta.alt || leftImage.title}
/>
<Caption>{leftImage.meta.caption}</Caption>
</article>
<article>
<Image
className={styles.image}
src={rightImage.url}
height={365}
width={600}
alt={rightImage.meta.alt || rightImage.title}
/>
<Caption>{leftImage.meta.caption}</Caption>
</article>
</section>
)
}

View File

@@ -1,5 +1,7 @@
import { nodesToHtml } from "./utils"
import styles from "./jsontohtml.module.css"
import type { JsonToHtmlProps } from "@/types/components/jsontohtml"
export default function JsonToHtml({
@@ -10,5 +12,9 @@ export default function JsonToHtml({
if (!Array.isArray(nodes) || !nodes.length) {
return null
}
return <>{nodesToHtml(nodes, embeds, renderOptions).filter(Boolean)}</>
return (
<section className={styles.container}>
{nodesToHtml(nodes, embeds, renderOptions).filter(Boolean)}
</section>
)
}

View File

@@ -1,6 +1,60 @@
.image {
height: auto;
margin-bottom: var(--Spacing-x2);
max-width: 100%;
width: 100%;
height: 365px;
object-fit: cover;
border-radius: var(--Corner-radius-Medium);
margin: var(--Spacing-x1) var(--Spacing-x0);
}
.ul,
.ol {
padding: var(--Spacing-x2) var(--Spacing-x0);
display: grid;
gap: var(--Spacing-x1);
}
.ol:has(li:nth-last-child(n + 4)),
.ul:has(li:nth-last-child(n + 4)) {
grid-template-columns: 1fr 1fr;
grid-auto-flow: column;
}
.ol > li::marker {
color: var(--Primary-Light-On-Surface-Accent);
}
.ul:has(.heart),
.ul:has(.check) {
list-style: none;
}
.li:has(.heart),
.li:has(.check) {
display: flex;
}
.li:not(:has(.heart), :has(.check)) {
margin-left: var(--Spacing-x2);
}
.li:has(.heart):before {
content: url("/_static/icons/heart.svg");
position: relative;
margin-right: var(--Spacing-x1);
height: 8px;
top: 3px;
}
.li:has(.check)::before {
content: url("/_static/icons/check-ring.svg");
position: relative;
margin-right: var(--Spacing-x1);
height: 8px;
top: 3px;
}
.container {
display: "grid";
gap: var(--Spacing-x3);
max-width: 1197px;
}

View File

@@ -1,17 +1,34 @@
import Image from "@/components/Image"
import Link from "@/components/TempDesignSystem/Link"
import { insertResponseToImageVaultAsset } from "@/utils/imageVault"
import ImageContainer from "../ImageContainer"
import Divider from "../TempDesignSystem/Divider"
import BiroScript from "../TempDesignSystem/Text/BiroScript"
import Body from "../TempDesignSystem/Text/Body"
import Caption from "../TempDesignSystem/Text/Caption"
import Footnote from "../TempDesignSystem/Text/Footnote"
import Subtitle from "../TempDesignSystem/Text/Subtitle"
import Title from "../TempDesignSystem/Text/Title"
import { hasAvailableParagraphFormat, hasAvailableULFormat } from "./utils"
import styles from "./jsontohtml.module.css"
import type { EmbedByUid } from "@/types/components/jsontohtml"
import { EmbedEnum } from "@/types/requests/utils/embeds"
import type { Attributes } from "@/types/rte/attrs"
import { RTEItemTypeEnum, RTETypeEnum } from "@/types/rte/enums"
import type { Attributes, RTEImageVaultAttrs } from "@/types/rte/attrs"
import {
AvailableParagraphFormatEnum,
RTEItemTypeEnum,
RTETypeEnum,
} from "@/types/rte/enums"
import type {
RTEDefaultNode,
RTEImageNode,
RTENext,
RTENode,
RTERegularNode,
RTETextNode,
} from "@/types/rte/node"
import { RTEMarkType } from "@/types/rte/node"
import type { RenderOptions } from "@/types/rte/option"
@@ -48,14 +65,16 @@ export const renderOptions: RenderOptions = {
if (node.attrs.url) {
const props = extractPossibleAttributes(node.attrs)
return (
<a
<Link
{...props}
href={node.attrs.url}
target={node.attrs.target ?? "_blank"}
key={node.uid}
target={node.attrs.target ?? "_blank"}
variant="underscored"
color="burgundy"
>
{next(node.children, embeds, fullRenderOptions)}
</a>
</Link>
)
}
return null
@@ -69,9 +88,9 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<blockquote key={node.uid} {...props}>
<BiroScript key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</blockquote>
</BiroScript>
)
},
@@ -120,9 +139,9 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<h1 key={node.uid} {...props}>
<Title key={node.uid} {...props} level="h1">
{next(node.children, embeds, fullRenderOptions)}
</h1>
</Title>
)
},
@@ -134,9 +153,9 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<h2 key={node.uid} {...props}>
<Title key={node.uid} {...props} level="h2">
{next(node.children, embeds, fullRenderOptions)}
</h2>
</Title>
)
},
@@ -148,9 +167,9 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<h3 key={node.uid} {...props}>
<Title key={node.uid} {...props} level="h3">
{next(node.children, embeds, fullRenderOptions)}
</h3>
</Title>
)
},
@@ -162,9 +181,9 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<h4 key={node.uid} {...props}>
<Title key={node.uid} {...props} level="h4">
{next(node.children, embeds, fullRenderOptions)}
</h4>
</Title>
)
},
@@ -176,28 +195,14 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<h5 key={node.uid} {...props}>
<Title key={node.uid} {...props} level="h5">
{next(node.children, embeds, fullRenderOptions)}
</h5>
)
},
[RTETypeEnum.h6]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<h6 key={node.uid} {...props}>
{next(node.children, embeds, fullRenderOptions)}
</h6>
</Title>
)
},
[RTETypeEnum.hr]: () => {
return <hr />
return <Divider />
},
[RTETypeEnum.li]: (
@@ -208,7 +213,7 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
<li key={node.uid} {...props}>
<li key={node.uid} {...props} className={styles.li}>
{next(node.children, embeds, fullRenderOptions)}
</li>
)
@@ -221,8 +226,26 @@ export const renderOptions: RenderOptions = {
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
// Set the number of rows dynamically to create even rows for each column. We want the li:s
// to flow with the column, so therefore this is needed.
let numberOfRows: number | undefined
if (node.children.length > 4) {
const half = node.children.length / 2
numberOfRows = Math.ceil(half)
}
return (
<ol key={node.uid} {...props}>
<ol
key={node.uid}
{...props}
className={styles.ol}
style={
numberOfRows
? { gridTemplateRows: `repeat(${numberOfRows}, auto)` }
: {}
}
>
{next(node.children, embeds, fullRenderOptions)}
</ol>
)
@@ -235,10 +258,21 @@ export const renderOptions: RenderOptions = {
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
const hasFormat = node.children.some((item) =>
hasAvailableParagraphFormat((item as RTETextNode)?.classname)
)
// If a child node has an available format as className, we wrap it in a
// span and render the children with the correct component
if (hasFormat) {
return next(node.children, embeds, fullRenderOptions)
}
return (
<p {...props} key={node.uid}>
<Body {...props} key={node.uid}>
{next(node.children, embeds, fullRenderOptions)}
</p>
</Body>
)
},
@@ -254,32 +288,78 @@ export const renderOptions: RenderOptions = {
const image = embeds?.[node?.attrs?.["asset-uid"]]
if (image?.node.__typename === EmbedEnum.SysAsset) {
const alt = image?.node?.title ?? node.attrs.alt
const alignment = node.attrs?.style?.["text-align"]
? {
alignSelf: node.attrs?.style?.["text-align"],
}
: {}
const props = extractPossibleAttributes(node.attrs)
props.className = styles.image
return (
<Image
key={node.uid}
alt={alt}
className={styles.image}
height={image.node.dimension.height}
src={image?.node?.url}
width={image.node.dimension.width}
style={alignment}
{...props}
/>
)
}
} else {
const props = extractPossibleAttributes(node.attrs)
const href = node.attrs?.locale
? `/${node.attrs.locale}${node.attrs.href}`
: node.attrs.href
} else if (type === RTEItemTypeEnum.entry) {
const entry = embeds?.[node?.attrs?.["entry-uid"]]
if (entry?.node.__typename === EmbedEnum.ImageContainer) {
const leftImage = insertResponseToImageVaultAsset(
entry.node.image_left
)
const rightImage = insertResponseToImageVaultAsset(
entry.node.image_right
)
return (
<ImageContainer leftImage={leftImage} rightImage={rightImage} />
)
} else {
// If entry is not an ImageContainer, it is a page and we return it as a link
const props = extractPossibleAttributes(node.attrs)
const href = node.attrs?.locale
? `/${node.attrs.locale}${node.attrs.href}`
: node.attrs.href
return (
<Link
{...props}
href={href}
key={node.uid}
variant="underscored"
color="burgundy"
>
{next(node.children, embeds, fullRenderOptions)}
</Link>
)
}
}
}
return null
},
[RTETypeEnum.ImageVault]: (node: RTEImageNode) => {
if ("attrs" in node) {
const type = node.type
if (type === RTETypeEnum.ImageVault) {
const attrs = node.attrs as RTEImageVaultAttrs
const image = insertResponseToImageVaultAsset(attrs)
const alt = image.meta.alt ?? image.title
const width = parseInt(attrs.width.replaceAll("px", ""))
const props = extractPossibleAttributes(attrs)
return (
<Link {...props} href={href} key={node.uid}>
{next(node.children, embeds, fullRenderOptions)}
</Link>
<section key={node.uid}>
<Image
alt={alt}
className={styles.image}
height={365}
src={image.url}
width={width}
{...props}
/>
<Caption>{image.meta.caption}</Caption>
</section>
)
}
}
@@ -343,6 +423,15 @@ export const renderOptions: RenderOptions = {
)
},
[RTETypeEnum.fragment]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
next: RTENext,
fullRenderOptions: RenderOptions
) => {
return <>{next(node.children, embeds, fullRenderOptions)}</>
},
[RTETypeEnum.tr]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
@@ -392,8 +481,26 @@ export const renderOptions: RenderOptions = {
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
// Set the number of rows dynamically to create even rows for each column. We want the li:s
// to flow with the column, so therefore this is needed.
let numberOfRows: number | undefined
if (node.children.length > 4) {
const half = node.children.length / 2
numberOfRows = Math.ceil(half)
}
return (
<ul key={node.uid} {...props}>
<ul
key={node.uid}
{...props}
className={styles.ul}
style={
numberOfRows
? { gridTemplateRows: `repeat(${numberOfRows}, auto)` }
: {}
}
>
{next(node.children, embeds, fullRenderOptions)}
</ul>
)
@@ -452,6 +559,62 @@ export const renderOptions: RenderOptions = {
if (!id) {
delete props.id
}
if (className) {
if (hasAvailableULFormat(className)) {
// @ts-ignore: We want to set css modules classNames even if it does not correspond
// to an existing class in the module style sheet. Due to our css modules plugin for
// typescript, we cannot do this without the ts-ignore
props.className = styles[className]
}
}
if (className === AvailableParagraphFormatEnum.footnote) {
return (
<Footnote key={id} {...props}>
{children}
</Footnote>
)
}
if (className === AvailableParagraphFormatEnum.caption) {
return (
<Caption key={id} {...props}>
{children}
</Caption>
)
}
if (className === AvailableParagraphFormatEnum["script-1"]) {
return (
<BiroScript key={id} type="one" {...props}>
{children}
</BiroScript>
)
}
if (className === AvailableParagraphFormatEnum["script-2"]) {
return (
<BiroScript key={id} type="two" {...props}>
{children}
</BiroScript>
)
}
if (className === AvailableParagraphFormatEnum["subtitle-1"]) {
return (
<Subtitle key={id} {...props}>
{children}
</Subtitle>
)
}
if (className === AvailableParagraphFormatEnum["subtitle-2"]) {
return (
<Subtitle key={id} {...props}>
{children}
</Subtitle>
)
}
return (
<span key={id} {...props}>
{children}

View File

@@ -3,7 +3,11 @@ import { renderOptions } from "./renderOptions"
import type { EmbedByUid } from "@/types/components/jsontohtml"
import type { Embeds } from "@/types/requests/embeds"
import type { Node } from "@/types/requests/utils/edges"
import { RTETypeEnum } from "@/types/rte/enums"
import {
AvailableParagraphFormatEnum,
AvailableULFormatEnum,
RTETypeEnum,
} from "@/types/rte/enums"
import type {
RTENode,
RTERenderOptionComponent,
@@ -74,7 +78,6 @@ export function textNodeToHtml(
if (node.bold) {
text = (fullRenderOptions[RTEMarkType.bold] as RTERenderMark)(text)
}
return text
}
@@ -86,6 +89,20 @@ function next(
return nodeChildrenToHtml(nodes, embeds, fullRenderOptions)
}
export function hasAvailableParagraphFormat(className?: string) {
if (!className) {
return false
}
return Object.keys(AvailableParagraphFormatEnum).includes(className)
}
export function hasAvailableULFormat(className?: string) {
if (!className) {
return false
}
return Object.keys(AvailableULFormatEnum).includes(className)
}
export function nodeToHtml(
node: RTENode,
embeds: EmbedByUid,

View File

@@ -44,7 +44,7 @@ export default async function ContactRow({ contact }: ContactRowProps) {
<Link
className={styles.link}
href={openableLink}
variant="myPage"
variant="underscored"
color="burgundy"
size="small"
>

View File

@@ -18,7 +18,7 @@ export default function SectionLink({ link, variant }: SectionLinkProps) {
className={classNames}
color="burgundy"
href={link.href}
variant="myPage"
variant="underscored"
>
<ArrowRight color="burgundy" className={styles.icon} />
{link.text}

View File

@@ -20,7 +20,7 @@
gap: var(--Spacing-x-half);
}
.myPage {
.underscored {
font-family: var(--typography-Body-Underlined-fontFamily);
font-size: var(--typography-Body-Underlined-fontSize);
font-weight: var(--typography-Body-Underlined-fontWeight);

View File

@@ -23,7 +23,7 @@ export const linkVariants = cva(styles.link, {
breadcrumb: styles.breadcrumb,
default: styles.default,
icon: styles.icon,
myPage: styles.myPage,
underscored: styles.underscored,
myPageMobileDropdown: styles.myPageMobileDropdown,
shortcut: styles.shortcut,
sidebar: styles.sidebar,

View File

@@ -49,7 +49,7 @@ export default function LoyaltyCard({
color="burgundy"
href={link.href}
target={link.openInNewTab ? "_blank" : undefined}
variant="myPage"
variant="underscored"
>
<ArrowRight
color="burgundy"

View File

@@ -32,7 +32,6 @@ const config = {
defaultVariants: {
color: "burgundy",
textAlign: "left",
textTransform: "uppercase",
type: "h1",
},
} as const

View File

@@ -70,6 +70,15 @@ query GetLoyaltyPage($locale: String!, $uid: String!) {
__typename
...LoyaltyPageLink
...ContentPageLink
...Image
... on ImageContainer {
title
image_left
image_right
system {
uid
}
}
}
}
totalCount
@@ -223,6 +232,12 @@ query GetLoyaltyPageRefs($locale: String!, $uid: String!) {
...System
}
}
... on ImageContainer {
__typename
system {
...System
}
}
}
}
}

View File

@@ -0,0 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_5625_32337" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
<rect width="16" height="16" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_5625_32337)">
<path d="M7.08333 9.21667L5.65738 7.79072C5.53579 7.66913 5.38889 7.60833 5.21667 7.60833C5.04444 7.60833 4.89722 7.66944 4.775 7.79167C4.65278 7.91389 4.59167 8.06111 4.59167 8.23333C4.59167 8.40556 4.6518 8.5518 4.77207 8.67207L6.64167 10.5417C6.76742 10.6694 6.91414 10.7333 7.08182 10.7333C7.2495 10.7333 7.39722 10.6694 7.525 10.5417L11.225 6.84167C11.3472 6.71944 11.4083 6.57222 11.4083 6.4C11.4083 6.22778 11.3472 6.08056 11.225 5.95833C11.1028 5.83611 10.9556 5.775 10.7833 5.775C10.6111 5.775 10.4646 5.83538 10.3439 5.95613L7.08333 9.21667ZM8 14.5C7.10103 14.5 6.25623 14.3291 5.46558 13.9873C4.67493 13.6455 3.98717 13.1816 3.4023 12.5956C2.81743 12.0097 2.35417 11.3217 2.0125 10.5319C1.67083 9.74202 1.5 8.89806 1.5 8C1.5 7.10103 1.67091 6.25623 2.01272 5.46558C2.35453 4.67493 2.81842 3.98717 3.40438 3.4023C3.99035 2.81743 4.67826 2.35417 5.46812 2.0125C6.25798 1.67083 7.10194 1.5 8 1.5C8.89897 1.5 9.74377 1.67091 10.5344 2.01272C11.3251 2.35453 12.0128 2.81842 12.5977 3.40438C13.1826 3.99035 13.6458 4.67826 13.9875 5.46812C14.3292 6.25798 14.5 7.10194 14.5 8C14.5 8.89897 14.3291 9.74377 13.9873 10.5344C13.6455 11.3251 13.1816 12.0128 12.5956 12.5977C12.0097 13.1826 11.3217 13.6458 10.5319 13.9875C9.74202 14.3292 8.89806 14.5 8 14.5ZM8 13.25C9.46111 13.25 10.7014 12.7403 11.7208 11.7208C12.7403 10.7014 13.25 9.46111 13.25 8C13.25 6.53889 12.7403 5.29861 11.7208 4.27917C10.7014 3.25972 9.46111 2.75 8 2.75C6.53889 2.75 5.29861 3.25972 4.27917 4.27917C3.25972 5.29861 2.75 6.53889 2.75 8C2.75 9.46111 3.25972 10.7014 4.27917 11.7208C5.29861 12.7403 6.53889 13.25 8 13.25Z" fill="#B05B65"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_5625_32301" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
<rect width="16" height="16" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_5625_32301)">
<path d="M7.99994 13.4171L7.23327 12.7255C6.14142 11.7524 5.24068 10.9189 4.53105 10.2249C3.82142 9.53081 3.26225 8.91489 2.85354 8.37709C2.44481 7.8393 2.16148 7.34774 2.00353 6.90241C1.84558 6.45708 1.7666 6.00088 1.7666 5.53379C1.7666 4.57813 2.09435 3.77255 2.74985 3.11704C3.40536 2.46154 4.21095 2.13379 5.1666 2.13379C5.70314 2.13379 6.22338 2.25046 6.72734 2.48379C7.23129 2.71712 7.65549 3.05046 7.99994 3.48379C8.36105 3.05046 8.78882 2.71712 9.28327 2.48379C9.77771 2.25046 10.2944 2.13379 10.8333 2.13379C11.7889 2.13379 12.5945 2.46154 13.25 3.11704C13.9055 3.77255 14.2333 4.57813 14.2333 5.53379C14.2333 6.00088 14.1571 6.45153 14.0047 6.88574C13.8523 7.31996 13.5717 7.80319 13.163 8.33542C12.7543 8.86767 12.1923 9.48637 11.4772 10.1915C10.762 10.8967 9.84734 11.7524 8.73327 12.7588L7.99994 13.4171Z" fill="#B05B65"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -13,6 +13,7 @@ import {
} from "@/types/components/loyalty/enums"
import { Embeds } from "@/types/requests/embeds"
import { PageLinkEnum } from "@/types/requests/pageLinks"
import { RTEEmbedsEnum } from "@/types/requests/rte"
import { EdgesWithTotalCount } from "@/types/requests/utils/edges"
import { RTEDocument } from "@/types/rte/node"
@@ -272,6 +273,20 @@ const pageConnectionRefs = z.object({
),
})
const rteConnectionRefs = z.object({
edges: z.array(
z.object({
node: z.object({
__typename: z.nativeEnum(RTEEmbedsEnum),
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
}),
})
),
})
const cardBlockRefs = z.object({
__typename: z.literal(LoyaltyCardsGridEnum.Card),
primary_button: z
@@ -349,7 +364,7 @@ const loyaltyPageBlockTextContentRefs = z.object({
__typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent),
content: z.object({
content: z.object({
embedded_itemsConnection: pageConnectionRefs,
embedded_itemsConnection: rteConnectionRefs,
}),
}),
})
@@ -365,7 +380,7 @@ const loyaltyPageSidebarTextContentRef = z.object({
__typename: z.literal(SidebarTypenameEnum.LoyaltyPageSidebarContent),
content: z.object({
content: z.object({
embedded_itemsConnection: pageConnectionRefs,
embedded_itemsConnection: rteConnectionRefs,
}),
}),
})

View File

@@ -0,0 +1,6 @@
import type { ImageVaultAsset } from "./imageVaultImage"
export type ImageContainerProps = {
leftImage: ImageVaultAsset
rightImage: ImageVaultAsset
}

View File

@@ -1,3 +1,4 @@
import type { ImageContainer } from "./imageContainer"
import type { SysAsset } from "./utils/asset"
export type Embeds = SysAsset
export type Embeds = SysAsset | ImageContainer

View File

@@ -0,0 +1,14 @@
import { InsertResponse } from "../components/imageVaultImage"
import { EmbedEnum } from "./utils/embeds"
import { Typename } from "./utils/typename"
export type ImageContainer = Typename<
{
image_left: InsertResponse
image_right: InsertResponse
system: {
uid: string
}
},
EmbedEnum.ImageContainer
>

6
types/requests/rte.ts Normal file
View File

@@ -0,0 +1,6 @@
export enum RTEEmbedsEnum {
AccountPage = "AccountPage",
ContentPage = "ContentPage",
LoyaltyPage = "LoyaltyPage",
ImageContainer = "ImageContainer",
}

View File

@@ -1,6 +1,10 @@
export enum EmbedEnum {
CurrentBlocksPage = "CurrentBlocksPage",
SysAsset = "SysAsset",
ImageContainer = "ImageContainer",
LoyaltyPage = "LoyaltyPage",
AccountPage = "AccountPage",
ContentPage = "ContentPage",
}
export type Embed = keyof typeof EmbedEnum

View File

@@ -1,6 +1,8 @@
import { InsertResponse } from "../components/imageVaultImage"
import { RTEItemTypeEnum } from "./enums"
import type { EmbedTypesEnum, RTEItemType } from "./enums"
import type { Lang } from "@/constants/languages"
import type { EmbedTypesEnum, RTEItemType } from "./enums"
export interface Attributes {
[key: string]: any
@@ -36,3 +38,9 @@ export interface RTELinkAttrs extends Attributes {
target: HTMLAnchorElement["target"]
type: RTEItemTypeEnum.entry
}
export interface RTEImageVaultAttrs extends Attributes, InsertResponse {
height: string
width: string
style: string[]
}

View File

@@ -36,6 +36,8 @@ export enum RTETypeEnum {
thead = "thead",
tr = "tr",
ul = "ul",
ImageVault = "ImageVault",
fragment = "fragment",
}
export type RTEType = keyof typeof RTETypeEnum
@@ -46,3 +48,17 @@ export enum RTEItemTypeEnum {
}
export type RTEItemType = keyof typeof RTEItemTypeEnum
export enum AvailableParagraphFormatEnum {
"script-1" = "script-1",
"script-2" = "script-2",
"footnote" = "footnote",
"caption" = "caption",
"subtitle-1" = "subtitle-1",
"subtitle-2" = "subtitle-2",
}
export enum AvailableULFormatEnum {
"heart" = "heart",
"check" = "check",
}

View File

@@ -5,6 +5,7 @@ import type {
Attributes,
RTEAnchorAttrs,
RTEAssetAttrs,
RTEImageVaultAttrs,
RTELinkAttrs,
} from "./attrs"
import type { RenderOptions } from "./option"
@@ -36,6 +37,11 @@ export interface RTEReferenceLinkNode extends RTEDefaultNode {
attrs: RTELinkAttrs
}
export interface RTEImageVaultNode extends RTEDefaultNode {
attrs: RTEImageVaultAttrs
type: RTETypeEnum.ImageVault
}
export enum RTEMarkType {
bold = "bold",
break = "break",
@@ -58,9 +64,11 @@ export type RTETextNode = RTETextNodeOptionalKeys & {
text: string
}
export type RTERegularNode = RTEDefaultNode | RTEAnchorNode
export type RTERegularNode = RTEDefaultNode | RTEAnchorNode | RTEImageVaultNode
export type RTEReferenceNode = RTEDefaultNode | RTEAnchorNode
export type RTEImageNode = RTEDefaultNode | RTEImageVaultNode
export type RTEReferenceNode = RTEAnchorNode
export type RTENode = RTERegularNode | RTEReferenceNode | RTETextNode