Learn page svgs updated
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import type { Metadata } from "next";
|
||||
import dynamic from "next/dynamic";
|
||||
import type { BlogPost } from "../../../../lib/content";
|
||||
import {
|
||||
getBlogPostBySlug,
|
||||
getAllBlogPosts as getAllPosts,
|
||||
@@ -201,7 +202,7 @@ export default async function BlogPostPage({ params }: PageProps) {
|
||||
/>
|
||||
|
||||
<div
|
||||
className="min-h-screen relative overflow-hidden"
|
||||
className="relative min-h-screen overflow-x-clip"
|
||||
style={{ backgroundColor }}
|
||||
>
|
||||
{/* Content Banner */}
|
||||
@@ -242,10 +243,16 @@ export default async function BlogPostPage({ params }: PageProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<article className="p-[var(--spacing-scale-024)] sm:py-[var(--spacing-scale-032)]">
|
||||
{/* Article Content */}
|
||||
<div className="post-body -mt-[var(--spacing-scale-048)] text-[var(--color-content-inverse-primary)] text-[16px] leading-[24px] sm:text-[18px] sm:leading-[130%] lg:text-[24px] lg:leading-[32px] xl:text-[32px] xl:leading-[40px] sm:mx-auto sm:max-w-[390px] md:max-w-[472px] lg:max-w-[700px] xl:max-w-[904px]">
|
||||
{/* Main Content — Figma Content page Template (19003:23305) article body instances */}
|
||||
<article
|
||||
data-node-id="19031:10426"
|
||||
className="
|
||||
relative z-[2] flex w-full justify-center
|
||||
p-[var(--spacing-scale-024)]
|
||||
sm:px-0 sm:py-[var(--spacing-scale-032)]
|
||||
"
|
||||
>
|
||||
<div className="post-body w-full text-[var(--color-content-inverse-primary)] text-[16px] leading-[24px] sm:text-[18px] sm:leading-[130%] lg:text-[24px] lg:leading-[32px] xl:text-[32px] xl:leading-[40px] sm:max-w-[390px] md:max-w-[472px] lg:max-w-[700px] xl:max-w-[904px]">
|
||||
<div dangerouslySetInnerHTML={{ __html: post.htmlContent }} />
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
/* Blog post body styling with semantic spacing */
|
||||
.post-body > :first-child {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
.post-body p {
|
||||
/* Scales with font size - uses logical properties for better writing mode support */
|
||||
margin-block: 1em;
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
*/
|
||||
import { memo } from "react";
|
||||
import {
|
||||
getAssetPath,
|
||||
ASSETS,
|
||||
contentBlogTagPath,
|
||||
contentCatalogSlugForFallback,
|
||||
CONTENT_CATALOG_SLUG_ORDER,
|
||||
} from "../../../../lib/assetUtils";
|
||||
import ContentContainerView from "./ContentContainer.view";
|
||||
@@ -36,26 +35,14 @@ const ContentContainerContainer = memo<ContentContainerProps>(
|
||||
: "text-[var(--color-content-inverse-brand-royal)]";
|
||||
|
||||
const getIconImage = (slug: string): string => {
|
||||
const icons = [
|
||||
getAssetPath(ASSETS.ICON_1),
|
||||
getAssetPath(ASSETS.ICON_2),
|
||||
getAssetPath(ASSETS.ICON_3),
|
||||
];
|
||||
const resolvedSlug =
|
||||
CONTENT_CATALOG_SLUG_ORDER.indexOf(
|
||||
slug as (typeof CONTENT_CATALOG_SLUG_ORDER)[number],
|
||||
) >= 0
|
||||
? slug
|
||||
: contentCatalogSlugForFallback(slug);
|
||||
|
||||
if (!slug) return icons[0];
|
||||
|
||||
const index = CONTENT_CATALOG_SLUG_ORDER.indexOf(
|
||||
slug as (typeof CONTENT_CATALOG_SLUG_ORDER)[number],
|
||||
);
|
||||
if (index >= 0) {
|
||||
return contentBlogTagPath(slug);
|
||||
}
|
||||
|
||||
const fallbackIndex =
|
||||
Math.abs(
|
||||
slug.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0),
|
||||
) % icons.length;
|
||||
return icons[fallbackIndex];
|
||||
return contentBlogTagPath(resolvedSlug);
|
||||
};
|
||||
|
||||
const iconImage = leadingImageSrc ?? getIconImage(post.slug);
|
||||
|
||||
+18
-11
@@ -1,11 +1,16 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Figma: "Components" / ContentThumnailTemplate (19614-14838, 19041-13415).
|
||||
* Vertical 260×390 (19060-15787); horizontal 320×225.5 (19041-13550).
|
||||
* Figma: "Components" / Thumbnail (19428:22574).
|
||||
* Vertical 260×390; horizontal 320×225.5; per-article backgrounds in public/content/blog/.
|
||||
*/
|
||||
import { memo } from "react";
|
||||
import { getAssetPath, ASSETS } from "../../../../lib/assetUtils";
|
||||
import {
|
||||
contentBlogHorizontalPath,
|
||||
contentBlogVerticalPath,
|
||||
contentCatalogSlugForFallback,
|
||||
CONTENT_CATALOG_SLUG_ORDER,
|
||||
} from "../../../../lib/assetUtils";
|
||||
import ContentThumbnailTemplateView from "./ContentThumbnailTemplate.view";
|
||||
import type { ContentThumbnailTemplateProps } from "./ContentThumbnailTemplate.types";
|
||||
|
||||
@@ -23,7 +28,6 @@ const ContentThumbnailTemplateContainer = memo<ContentThumbnailTemplateProps>(
|
||||
post: ContentThumbnailTemplateProps["post"],
|
||||
variant: "vertical" | "horizontal",
|
||||
): string => {
|
||||
// Check if post has thumbnail images defined in frontmatter
|
||||
if (post.frontmatter?.thumbnail) {
|
||||
const imageName =
|
||||
variant === "vertical"
|
||||
@@ -31,18 +35,21 @@ const ContentThumbnailTemplateContainer = memo<ContentThumbnailTemplateProps>(
|
||||
: post.frontmatter.thumbnail.horizontal;
|
||||
|
||||
if (imageName) {
|
||||
// Return path to image in public/content/blog directory
|
||||
return `/content/blog/${imageName}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to default images if no thumbnail specified
|
||||
const fallbackImages: Record<string, string> = {
|
||||
vertical: getAssetPath(ASSETS.VERTICAL_1),
|
||||
horizontal: getAssetPath(ASSETS.HORIZONTAL_1),
|
||||
};
|
||||
const slug = post.slug;
|
||||
const resolvedSlug =
|
||||
CONTENT_CATALOG_SLUG_ORDER.indexOf(
|
||||
slug as (typeof CONTENT_CATALOG_SLUG_ORDER)[number],
|
||||
) >= 0
|
||||
? slug
|
||||
: contentCatalogSlugForFallback(slug);
|
||||
|
||||
return fallbackImages[variant] || fallbackImages.vertical;
|
||||
return variant === "vertical"
|
||||
? contentBlogVerticalPath(resolvedSlug)
|
||||
: contentBlogHorizontalPath(resolvedSlug);
|
||||
};
|
||||
|
||||
const backgroundImage = getBackgroundImage(post, variant);
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { getAssetPath } from "../../../../lib/assetUtils";
|
||||
import {
|
||||
getAssetPath,
|
||||
contentBlogHorizontalPath,
|
||||
contentBlogSectionPath,
|
||||
CONTENT_CATALOG_SLUG_ORDER,
|
||||
} from "../../../../lib/assetUtils";
|
||||
import type { BlogPost } from "../../../../lib/content";
|
||||
import ContentBannerView from "./ContentBanner.view";
|
||||
import type { ContentBannerProps } from "./ContentBanner.types";
|
||||
|
||||
/** Figma: Section / ContentBanner — article (blog) and guide (content page template 22078:791901). */
|
||||
/** Figma: Content page Template (19003:23305) — article ContentBanner per breakpoint. */
|
||||
const ContentBannerContainer = memo<ContentBannerProps>(
|
||||
({
|
||||
post,
|
||||
@@ -18,27 +23,42 @@ const ContentBannerContainer = memo<ContentBannerProps>(
|
||||
}) => {
|
||||
const variant = variantProp;
|
||||
|
||||
const getBackgroundImage = (blogPost: BlogPost): string => {
|
||||
const resolveHorizontalImage = (blogPost: BlogPost): string => {
|
||||
if (blogPost.frontmatter?.thumbnail?.horizontal) {
|
||||
return `/content/blog/${blogPost.frontmatter.thumbnail.horizontal}`;
|
||||
}
|
||||
|
||||
if (
|
||||
CONTENT_CATALOG_SLUG_ORDER.includes(
|
||||
blogPost.slug as (typeof CONTENT_CATALOG_SLUG_ORDER)[number],
|
||||
)
|
||||
) {
|
||||
return contentBlogHorizontalPath(blogPost.slug);
|
||||
}
|
||||
|
||||
return getAssetPath("assets/Content_Banner.svg");
|
||||
};
|
||||
|
||||
const getBannerImageMd = (blogPost: BlogPost): string => {
|
||||
const resolveSectionImage = (blogPost: BlogPost): string => {
|
||||
if (blogPost.frontmatter?.banner?.horizontal) {
|
||||
return `/content/blog/${blogPost.frontmatter.banner.horizontal}`;
|
||||
}
|
||||
if (blogPost.frontmatter?.thumbnail?.horizontal) {
|
||||
return `/content/blog/${blogPost.frontmatter.thumbnail.horizontal}`;
|
||||
|
||||
if (
|
||||
CONTENT_CATALOG_SLUG_ORDER.includes(
|
||||
blogPost.slug as (typeof CONTENT_CATALOG_SLUG_ORDER)[number],
|
||||
)
|
||||
) {
|
||||
return contentBlogSectionPath(blogPost.slug);
|
||||
}
|
||||
return getAssetPath("assets/Content_Banner_2.svg");
|
||||
|
||||
return resolveHorizontalImage(blogPost);
|
||||
};
|
||||
|
||||
const backgroundImageSm =
|
||||
variant === "article" ? getBackgroundImage(post) : undefined;
|
||||
const backgroundImageMd =
|
||||
variant === "article" ? getBannerImageMd(post) : undefined;
|
||||
const backgroundImageHorizontal =
|
||||
variant === "article" ? resolveHorizontalImage(post) : undefined;
|
||||
const backgroundImageSection =
|
||||
variant === "article" ? resolveSectionImage(post) : undefined;
|
||||
|
||||
return (
|
||||
<ContentBannerView
|
||||
@@ -46,8 +66,8 @@ const ContentBannerContainer = memo<ContentBannerProps>(
|
||||
post={post}
|
||||
leadingImageSrc={leadingImageSrc}
|
||||
leadingImageAlt={leadingImageAlt}
|
||||
backgroundImageSm={backgroundImageSm}
|
||||
backgroundImageMd={backgroundImageMd}
|
||||
backgroundImageHorizontal={backgroundImageHorizontal}
|
||||
backgroundImageSection={backgroundImageSection}
|
||||
rulePreview={rulePreview}
|
||||
contentTone={contentTone}
|
||||
/>
|
||||
|
||||
@@ -35,8 +35,10 @@ export interface ContentBannerViewProps {
|
||||
post: BlogPost;
|
||||
leadingImageSrc?: string;
|
||||
leadingImageAlt?: string;
|
||||
backgroundImageSm?: string;
|
||||
backgroundImageMd?: string;
|
||||
/** Article variant: horizontal thumbnail below lg (`320×225.5`). */
|
||||
backgroundImageHorizontal?: string;
|
||||
/** Article variant: section banner at md+ (`1920×672`, Figma Section orientation). */
|
||||
backgroundImageSection?: string;
|
||||
rulePreview?: ContentBannerRulePreview;
|
||||
contentTone?: ContentContainerToneValue;
|
||||
}
|
||||
|
||||
@@ -68,44 +68,71 @@ function ContentBannerGuideView({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Figma: Content page Template (19003:23305) — ContentBanner article instances.
|
||||
* Horizontal thumbnail below md; Section SVG (1920×672) at md+.
|
||||
*/
|
||||
function ContentBannerArticleView({
|
||||
post,
|
||||
leadingImageSrc,
|
||||
leadingImageAlt,
|
||||
backgroundImageSm,
|
||||
backgroundImageMd,
|
||||
backgroundImageHorizontal,
|
||||
backgroundImageSection,
|
||||
}: ContentBannerViewProps) {
|
||||
if (!backgroundImageSm || !backgroundImageMd) {
|
||||
if (!backgroundImageHorizontal || !backgroundImageSection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative h-[275px] w-full pt-[var(--measures-spacing-016)] sm:h-[326px] sm:overflow-hidden md:h-[224px] md:pt-[var(--measures-spacing-008)] lg:h-[358.4px] lg:pt-[50px] xl:h-[504px] xl:pt-[112px]">
|
||||
<div
|
||||
data-node-id="19189:9053"
|
||||
className="
|
||||
relative z-[1] w-full overflow-visible
|
||||
min-h-[275px]
|
||||
pt-[var(--spacing-scale-016)] px-[var(--spacing-scale-016)]
|
||||
pb-[var(--spacing-scale-064)]
|
||||
sm:min-h-[326px] sm:pb-[var(--spacing-scale-048)]
|
||||
md:min-h-[224px] md:px-[var(--spacing-scale-024)] md:pb-0
|
||||
md:pt-[var(--spacing-scale-008)]
|
||||
lg:min-h-[358.4px] lg:px-[var(--spacing-scale-048)] lg:py-[var(--spacing-scale-040)]
|
||||
xl:min-h-[504px] xl:px-[var(--spacing-scale-064)] xl:py-[var(--spacing-scale-076)]
|
||||
"
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 aspect-[320/225.5] h-full w-full bg-cover bg-no-repeat"
|
||||
style={{
|
||||
backgroundImage: `url(${backgroundImageSm})`,
|
||||
backgroundPosition: "center bottom",
|
||||
}}
|
||||
/>
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-x-0 top-0 -bottom-[var(--spacing-scale-024)] sm:-bottom-[var(--spacing-scale-032)] md:hidden"
|
||||
data-name="ContentBannerBackgroundHorizontal"
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={backgroundImageHorizontal}
|
||||
alt=""
|
||||
className="absolute inset-0 size-full max-w-none object-cover object-bottom"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute inset-0 hidden aspect-[640/224] h-full w-full bg-cover bg-no-repeat md:block"
|
||||
style={{
|
||||
backgroundImage: `url(${backgroundImageMd})`,
|
||||
backgroundPosition: "center bottom",
|
||||
}}
|
||||
/>
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-x-0 top-0 -bottom-[var(--spacing-scale-032)] hidden md:block"
|
||||
data-name="ContentBannerBackgroundSection"
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={backgroundImageSection}
|
||||
alt=""
|
||||
className="absolute inset-0 size-full max-w-none object-cover object-center"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-node-id="19189:9010"
|
||||
className="
|
||||
relative z-10 flex h-full flex-col
|
||||
pl-[var(--measures-spacing-016)] pr-[96px]
|
||||
justify-start
|
||||
md:absolute md:inset-x-0 md:top-1/2 md:h-auto md:w-full md:-translate-y-1/2
|
||||
md:pl-[var(--measures-spacing-024)] md:pr-[350px]
|
||||
lg:static lg:top-auto lg:h-full lg:translate-y-0 lg:justify-start
|
||||
lg:pl-[var(--measures-spacing-064)]
|
||||
relative z-10
|
||||
max-w-[calc(100%-96px)]
|
||||
sm:max-w-[calc(100%-151px)]
|
||||
md:max-w-[280px]
|
||||
lg:max-w-[365px]
|
||||
xl:max-w-[623px]
|
||||
"
|
||||
>
|
||||
<ContentContainer
|
||||
|
||||
Reference in New Issue
Block a user