QA pass: layout, create-flow, and About books #68
@@ -153,21 +153,17 @@ export default function LegalDocumentPage({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="relative min-h-screen overflow-x-hidden bg-transparent">
|
<div className="relative min-h-screen overflow-x-hidden bg-transparent">
|
||||||
<ContentBanner post={post} variant="guide" />
|
<ContentBanner
|
||||||
|
post={post}
|
||||||
|
variant="guide"
|
||||||
|
updatedLabel={page.updated}
|
||||||
|
/>
|
||||||
|
|
||||||
<article
|
<article
|
||||||
className="relative z-10 p-[var(--spacing-scale-024)] sm:py-[var(--spacing-scale-032)]"
|
className="relative z-10 flex w-full justify-center p-[var(--spacing-scale-024)] sm:px-0 sm:py-[var(--spacing-scale-032)]"
|
||||||
data-node-id="19003:23574"
|
data-node-id="19003:23574"
|
||||||
>
|
>
|
||||||
<div className={`${ARTICLE_COLUMN_CLASS} -mt-[var(--spacing-scale-048)]`}>
|
<div className={ARTICLE_COLUMN_CLASS}>
|
||||||
<div
|
|
||||||
className="flex flex-wrap items-end gap-[var(--measures-spacing-008,8px)] text-xx-small-paragraph text-[var(--color-content-default-secondary)] md:text-x-small-paragraph lg:text-small-paragraph xl:text-large-paragraph"
|
|
||||||
data-name="Metadata Container"
|
|
||||||
>
|
|
||||||
<span>{page.banner.author}</span>
|
|
||||||
<span>{page.updated}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{page.intro.length > 0 ? (
|
{page.intro.length > 0 ? (
|
||||||
<div className="flex flex-col gap-[1em]">
|
<div className="flex flex-col gap-[1em]">
|
||||||
{page.intro.map((paragraph) => (
|
{page.intro.map((paragraph) => (
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export default function HowItWorksPage() {
|
|||||||
|
|
||||||
<article className="relative z-10 p-[var(--spacing-scale-024)] sm:py-[var(--spacing-scale-032)]">
|
<article className="relative z-10 p-[var(--spacing-scale-024)] sm:py-[var(--spacing-scale-032)]">
|
||||||
<div
|
<div
|
||||||
className="post-body -mt-[var(--spacing-scale-048)] text-[var(--color-content-default-primary)] text-medium-paragraph sm:text-large-paragraph lg:text-x-large-paragraph xl:text-xx-large-paragraph sm:mx-auto sm:max-w-[390px] md:max-w-[472px] lg:max-w-[700px] xl:max-w-[904px]"
|
className="post-body text-[var(--color-content-default-primary)] text-medium-paragraph sm:text-large-paragraph lg:text-x-large-paragraph xl:text-xx-large-paragraph sm:mx-auto sm:max-w-[390px] md:max-w-[472px] lg:max-w-[700px] xl:max-w-[904px]"
|
||||||
dangerouslySetInnerHTML={{ __html: syntheticPost.htmlContent }}
|
dangerouslySetInnerHTML={{ __html: syntheticPost.htmlContent }}
|
||||||
/>
|
/>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const ContentBannerContainer = memo<ContentBannerProps>(
|
|||||||
leadingImageAlt,
|
leadingImageAlt,
|
||||||
rulePreview,
|
rulePreview,
|
||||||
contentTone,
|
contentTone,
|
||||||
|
updatedLabel,
|
||||||
}) => {
|
}) => {
|
||||||
const variant = variantProp;
|
const variant = variantProp;
|
||||||
const tUseCase = useTranslation("pages.useCasesCompletedRule");
|
const tUseCase = useTranslation("pages.useCasesCompletedRule");
|
||||||
@@ -81,6 +82,7 @@ const ContentBannerContainer = memo<ContentBannerProps>(
|
|||||||
rulePreview={rulePreview}
|
rulePreview={rulePreview}
|
||||||
contentTone={contentTone}
|
contentTone={contentTone}
|
||||||
ruleCardLinkAriaLabel={ruleCardLinkAriaLabel}
|
ruleCardLinkAriaLabel={ruleCardLinkAriaLabel}
|
||||||
|
updatedLabel={updatedLabel}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ export interface ContentBannerProps {
|
|||||||
rulePreview?: ContentBannerRulePreview;
|
rulePreview?: ContentBannerRulePreview;
|
||||||
/** `useCase` only: ContentContainer text tokens (default `onLight`). */
|
/** `useCase` only: ContentContainer text tokens (default `onLight`). */
|
||||||
contentTone?: ContentContainerToneValue;
|
contentTone?: ContentContainerToneValue;
|
||||||
|
/** `guide` only: author + this label under the title. */
|
||||||
|
updatedLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContentBannerViewProps {
|
export interface ContentBannerViewProps {
|
||||||
@@ -43,4 +45,6 @@ export interface ContentBannerViewProps {
|
|||||||
contentTone?: ContentContainerToneValue;
|
contentTone?: ContentContainerToneValue;
|
||||||
/** `useCase` only: aria-label for linked rule preview. */
|
/** `useCase` only: aria-label for linked rule preview. */
|
||||||
ruleCardLinkAriaLabel?: string;
|
ruleCardLinkAriaLabel?: string;
|
||||||
|
/** `guide` only: attribution row under the title. */
|
||||||
|
updatedLabel?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,41 +17,49 @@ import type { ContentBannerViewProps } from "./ContentBanner.types";
|
|||||||
*/
|
*/
|
||||||
function ContentBannerGuideView({
|
function ContentBannerGuideView({
|
||||||
post,
|
post,
|
||||||
}: Pick<ContentBannerViewProps, "post">) {
|
updatedLabel,
|
||||||
const { title, description } = post.frontmatter;
|
}: Pick<ContentBannerViewProps, "post" | "updatedLabel">) {
|
||||||
|
const { title, description, author } = post.frontmatter;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className="relative w-full overflow-clip px-[var(--spacing-scale-020)] py-[var(--spacing-scale-024)] sm:px-[var(--spacing-scale-032)] sm:py-[var(--spacing-scale-032)] lg:px-[var(--spacing-scale-048)] lg:py-[var(--spacing-scale-040)]"
|
className="relative w-full overflow-clip px-[var(--spacing-scale-020)] py-[var(--spacing-scale-024)] sm:px-[var(--spacing-scale-032)] sm:py-[var(--spacing-scale-032)] lg:px-[var(--spacing-scale-048)] lg:py-[var(--spacing-scale-040)] xl:px-[var(--spacing-scale-064)] xl:py-[var(--spacing-scale-076)]"
|
||||||
aria-labelledby="content-banner-title"
|
aria-labelledby="content-banner-title"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="mx-auto flex w-full max-w-[1024px] flex-col items-start gap-[var(--spacing-scale-024)] md:flex-row md:items-center md:gap-[var(--spacing-scale-032)]"
|
className="mx-auto flex w-full max-w-[1024px] flex-col items-start gap-[var(--spacing-scale-024)] md:flex-row md:items-start md:gap-[var(--spacing-scale-024)] lg:items-center lg:gap-[var(--spacing-scale-032)] xl:max-w-[1312px] xl:gap-[var(--spacing-scale-056)]"
|
||||||
data-node-id="19189:9358"
|
data-node-id="19189:9358"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex w-full max-w-[365px] shrink-0 flex-col items-start justify-center gap-[var(--spacing-scale-024)]"
|
className="flex w-full shrink-0 flex-col items-start justify-center gap-[var(--measures-spacing-016)] md:max-w-[280px] lg:max-w-[365px] xl:max-w-[623px]"
|
||||||
data-node-id="19189:9171"
|
data-node-id="19189:9171"
|
||||||
>
|
>
|
||||||
<div className="flex w-full flex-col items-start gap-[var(--measures-spacing-016)]">
|
<div className="flex w-full flex-col items-start gap-[var(--measures-spacing-004)] text-left text-[var(--color-content-default-primary)]">
|
||||||
<div className="flex w-full flex-col items-start gap-[var(--measures-spacing-004)] text-left text-[var(--color-content-default-primary)]">
|
<h1
|
||||||
<h1
|
id="content-banner-title"
|
||||||
id="content-banner-title"
|
className="w-full font-bricolage font-medium text-[32px] leading-[110%] sm:text-[40px] lg:text-medium-display"
|
||||||
className="w-full font-bricolage font-medium text-[32px] leading-[110%] sm:text-[40px] lg:text-medium-display"
|
>
|
||||||
>
|
{title}
|
||||||
{title}
|
</h1>
|
||||||
</h1>
|
{description ? (
|
||||||
{description ? (
|
<p className="w-full font-normal text-[16px] leading-[130%] sm:text-large-paragraph">
|
||||||
<p className="w-full font-normal text-[16px] leading-[130%] sm:text-large-paragraph">
|
{description}
|
||||||
{description}
|
</p>
|
||||||
</p>
|
) : null}
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
{updatedLabel ? (
|
||||||
|
<div
|
||||||
|
className="flex w-full flex-wrap items-end gap-[var(--measures-spacing-008,8px)] text-xx-small-paragraph text-[var(--color-content-default-secondary)] md:text-x-small-paragraph lg:text-small-paragraph xl:text-large-paragraph"
|
||||||
|
data-name="Metadata Container"
|
||||||
|
>
|
||||||
|
<span>{author}</span>
|
||||||
|
<span>{updatedLabel}</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="flex w-full shrink-0 items-center justify-center md:flex-1"
|
className="flex w-full shrink-0 items-center justify-center max-sm:pb-[var(--spacing-scale-024)] md:flex-1 md:justify-end"
|
||||||
data-node-id="22078:806960"
|
data-node-id="22078:806960"
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
@@ -253,7 +261,7 @@ function ContentBannerUseCaseView({
|
|||||||
|
|
||||||
function ContentBannerView(props: ContentBannerViewProps) {
|
function ContentBannerView(props: ContentBannerViewProps) {
|
||||||
if (props.variant === "guide") {
|
if (props.variant === "guide") {
|
||||||
return <ContentBannerGuideView post={props.post} />;
|
return <ContentBannerGuideView post={props.post} updatedLabel={props.updatedLabel} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.variant === "useCase") {
|
if (props.variant === "useCase") {
|
||||||
|
|||||||
@@ -179,15 +179,44 @@ describe("ContentBanner", () => {
|
|||||||
expect(screen.getByText("Test description")).toBeInTheDocument();
|
expect(screen.getByText("Test description")).toBeInTheDocument();
|
||||||
expect(screen.queryByText("Test Author")).not.toBeInTheDocument();
|
expect(screen.queryByText("Test Author")).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
const copyColumn = container.querySelector('[data-node-id="19189:9171"]');
|
||||||
|
expect(copyColumn).toHaveClass(
|
||||||
|
"md:max-w-[280px]",
|
||||||
|
"lg:max-w-[365px]",
|
||||||
|
"xl:max-w-[623px]",
|
||||||
|
);
|
||||||
|
expect(copyColumn).not.toHaveClass("max-w-[365px]");
|
||||||
|
|
||||||
const bannerRow = container.querySelector('[data-node-id="19189:9358"]');
|
const bannerRow = container.querySelector('[data-node-id="19189:9358"]');
|
||||||
expect(bannerRow).toHaveClass("md:flex-row");
|
expect(bannerRow).toHaveClass("md:flex-row");
|
||||||
|
expect(bannerRow?.className).toMatch(/xl:max-w-\[1312px\]/);
|
||||||
|
|
||||||
const logoMark = container.querySelector(
|
const logoMarkWrap = container.querySelector(
|
||||||
'[data-node-id="22078:806960"] img',
|
'[data-node-id="22078:806960"]',
|
||||||
);
|
);
|
||||||
|
expect(logoMarkWrap?.className).toMatch(
|
||||||
|
/max-sm:pb-\[var\(--spacing-scale-024\)\]/,
|
||||||
|
);
|
||||||
|
expect(logoMarkWrap).toHaveClass("md:justify-end");
|
||||||
|
const logoMark = logoMarkWrap?.querySelector("img");
|
||||||
expect(logoMark).toHaveAttribute(
|
expect(logoMark).toHaveAttribute(
|
||||||
"src",
|
"src",
|
||||||
expect.stringContaining("guide-banner-logo-arrow.svg"),
|
expect.stringContaining("guide-banner-logo-arrow.svg"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders guide metadata under the title when updatedLabel is set", () => {
|
||||||
|
render(
|
||||||
|
<ContentBanner
|
||||||
|
post={mockPost}
|
||||||
|
variant="guide"
|
||||||
|
updatedLabel="Last updated August 15, 2026."
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText("Test Author")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText("Last updated August 15, 2026."),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,10 +7,16 @@ import CookiesPage from "../../app/(marketing)/cookies/page";
|
|||||||
import messages from "../../messages/en/index";
|
import messages from "../../messages/en/index";
|
||||||
|
|
||||||
vi.mock("../../app/components/sections/ContentBanner", () => ({
|
vi.mock("../../app/components/sections/ContentBanner", () => ({
|
||||||
default: ({ post, variant }) => (
|
default: ({ post, variant, updatedLabel }) => (
|
||||||
<section data-testid="content-banner" data-variant={variant}>
|
<section data-testid="content-banner" data-variant={variant}>
|
||||||
<h1>{post.frontmatter.title}</h1>
|
<h1>{post.frontmatter.title}</h1>
|
||||||
<p>{post.frontmatter.description}</p>
|
<p>{post.frontmatter.description}</p>
|
||||||
|
{updatedLabel ? (
|
||||||
|
<div>
|
||||||
|
<span>{post.frontmatter.author}</span>
|
||||||
|
<span>{updatedLabel}</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</section>
|
</section>
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user