All files / packages/design-system-2-site/src/app/(frontend)/style-preview/page-layouts page.tsx

100% Statements 28/28
75% Branches 3/4
100% Functions 10/10
100% Lines 23/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439                  1x       1x   1x   1x   1x       1x   1x                                             1x                                                                                                                                                                                                                                                                       1x           6x               1x 3x 3x               21x                 2x   1x 15x 15x 6x 1x   1x                                                                                                                                                                                                                             45x                                       6x                                                                                                                                                                            
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { HeroSplit } from '@/components/HeroSplit';
import { STYLE_PREVIEW_ENABLED } from '@/lib/dev-tools';
import { getLinkIcon, linkIconNames } from '@/lib/link-icons';
import { renderMarkdownString } from '@/lib/markdown';
import doDontExample from '@/assets/do-dont-example.svg';
import { PreviewLabel, PreviewLayout, PreviewSection, type PreviewTocItem } from '../preview-ui';
 
export const metadata: Metadata = {
  title: 'Style Preview — Page Layouts',
};
 
const IMG_HERO = 'https://res.cloudinary.com/hl955tqfo/image/upload/slds2-site/content/ce85ba5490e3c902.jpg';
const IMG_CARD_1 =
  'https://res.cloudinary.com/hl955tqfo/image/upload/slds2-site/content/0d4e5b9b2be33178.jpg';
const IMG_CARD_2 =
  'https://res.cloudinary.com/hl955tqfo/image/upload/slds2-site/content/086294c77570ee00.jpg';
const IMG_CARD_3 =
  'https://res.cloudinary.com/hl955tqfo/image/upload/slds2-site/content/3474219b263e9b07.jpg';
const DO_DONT_IMAGE = doDontExample.src;
// A real story from content/styling-api/text.md, so the chrome shows its theme toggle and open link.
const STORYBOOK_URL =
  'https://slds2-metadata-stories-54d63ecb6c29.herokuapp.com/iframe.html?id=utilities-with-code-text--body-size-default';
 
const TOC: PreviewTocItem[] = [
  { id: 'content-blocks', text: 'Content blocks' },
  { id: 'blockquote', text: 'Blockquote' },
  { id: 'lists', text: 'Lists' },
  { id: 'code', text: 'Code' },
  { id: 'rule', text: 'Rule' },
  { id: 'image', text: 'Image' },
  { id: 'do-dont', text: "Do / Don't" },
  { id: 'callouts', text: 'Callouts' },
  { id: 'cards', text: 'Cards' },
  { id: 'link-tiles', text: 'Link tiles' },
  { id: 'data-tables', text: 'Data tables' },
  { id: 'accordion', text: 'Accordion' },
  { id: 'video-embed', text: 'Video embed' },
  { id: 'hero-banner', text: 'Hero banner' },
  { id: 'hero-split', text: 'Hero split' },
  { id: 'storybook', text: 'Storybook embed chrome' },
];
 
/*
 * Each sample is real markdown rendered through the content pipeline, so the
 * preview shows exactly what a directive emits in page content.
 */
const MD = {
  contentBlocks: `# Heading level 1
 
A paragraph of body copy with an [inline content link](#), some **strong / bold text**, and an \`inline code span\` to show the mono treatment. This is inside \`.content-blocks\`.
 
## Heading level 2
 
Body copy following an h2.
 
### Heading level 3
 
Body copy following an h3.
 
#### Heading level 4
 
Body copy following an h4.
 
##### Heading level 5
 
Body copy following an h5.
 
###### Heading level 6
 
Body copy following an h6.`,
 
  blockquote: '> “A blockquote styled with the brand-vivid left border and italic foreground text.”',
 
  lists: `### Unordered list
 
- First bullet item in the list
- Second bullet item, a little longer to show wrapping and line height
- Third bullet item
 
### Ordered list
 
1. First numbered step
2. Second numbered step
3. Third numbered step`,
 
  inlineCode:
    'Replace hard-coded values with the `--spacing-4` token, then run `npm run build` to verify the change.',
 
  fencedCode: `\`\`\`js
function greet(name) {
  return \`Hello, \${name}!\`;
}
console.log(greet("SLDS"));
\`\`\``,
 
  rule: '---',
 
  imageFull: `::image{src="${IMG_CARD_1}" alt="Example one-column image" size="1" caption="This is the caption of an image. Captions are centered under the image, use foreground-subtle at text-size-xxs, and never span the full width."}`,
 
  imageLeft: `::image{src="${IMG_CARD_2}" alt="Example two-column image, left" size="2" align="left" caption="This is the caption of an image."}
 
A two-column image is placed in one column while the body copy fills the opposite column. Because it floats, the surrounding paragraph text wraps naturally alongside it. Keep writing and the text continues to flow beside the image until it clears the bottom edge, at which point the copy returns to the full content width.
 
A second paragraph continues the flow to show how longer passages behave next to a half-width, floated image on the left.`,
 
  imageRight: `::image{src="${IMG_CARD_3}" alt="Example two-column image, right" size="2" align="right" caption="This is the caption of an image."}
 
With \`align="right"\` the image sits in the right column and the body text flows down the left. This is the mirror of the left variant and uses the same half-width sizing and radius 3xl.`,
 
  doDont: `:::do-dont
::do{image="${DO_DONT_IMAGE}" alt="Do example" text="Do open a builder in a new browser tab with a utility bar."}
::dont{image="${DO_DONT_IMAGE}" alt="Don't example" text="Don't open a builder in the same page."}
:::`,
 
  callouts: `:::callout{type="info"}
Info callout — general information the reader should notice.
:::
 
:::callout{type="success"}
Success callout — confirms something worked or is recommended.
:::
 
:::callout{type="warning"}
Warning callout — proceed with care.
:::
 
:::callout{type="error"}
Error callout — something is wrong or unsupported.
:::`,
 
  cards: `:::card-grid{columns="3"}
::card{title="Admins" description="Adopt Salesforce Cosmos to get the new design." href="#" image="${IMG_CARD_1}"}
::card{title="Designers" description="Connect to essential components and tools." href="#" image="${IMG_CARD_2}"}
::card{title="Developers" description="Access the framework for building scalable interfaces." href="#" image="${IMG_CARD_3}"}
:::
 
:::card-grid{columns="2"}
::card{title="Best Practices" description="Follow Best Practices to keep components accessible, performant, and consistent." href="#"}
::card{title="Guidelines" description="Usage guidance that keeps experiences consistent across the system." href="#"}
:::
 
:::card-grid{columns="1"}
::card{title="Single-column card with image" description="A one-per-row card grid — the media sits beside the body on wide viewports." href="#" image="${IMG_CARD_2}"}
::card{title="Single-column card, no image" description="Follow Best Practices to keep components accessible, performant, and consistent." href="#"}
:::`,
 
  linkTiles: `:::link-grid{columns="1"}
::link-card{title="Start using our Figma Kits" href="#" icon="Figma" description="Follow best practices to ensure your components are accessible, performant, and consistent with the design system."}
::link-card{title="Browse the component library" href="#" icon="Components" description="Every component, documented with usage guidance, props, and live examples."}
::link-card{title="Read the foundations" href="#" description="Tokens, color, typography, and spacing that anchor the system."}
:::
 
:::link-grid{columns="3"}
::link-card{title="Get Started" href="#" icon="Rocket" description="Set up your environment and ship your first component."}
::link-card{title="Components" href="#" icon="Components" description="Browse the full library of accessible components."}
::link-card{title="Foundations" href="#" description="Tokens, color, typography, and spacing that anchor the system."}
:::
 
:::link-grid{columns="2"}
::link-card{title="Accessibility" href="#" description="Build inclusive experiences that meet WCAG standards."}
::link-card{title="Setup" href="#" description="Configure your project to consume the design system."}
:::`,
 
  accordion: `::::accordion
:::accordion-item{title="Where can I reference content for SLDS 1?"}
The Salesforce Lightning Design System 1 (SLDS 1) website has migrated to make space for SLDS 2. You can find all SLDS 1 content at [v1.lightningdesignsystem.com](https://v1.lightningdesignsystem.com/).
:::
:::accordion-item{title="How do I adopt the new styling hooks?" open}
Replace hard-coded values with the global tokens documented on the Global Values tab, then layer component-level hooks on top where you need per-component overrides.
:::
:::accordion-item{title="Does this work in dark mode?"}
Yes — every color is a semantic token, so accordions flip automatically with the active theme.
:::
::::`,
 
  storybook: `::component-demo{storybook="${STORYBOOK_URL}" title="Utilities With Code Text - Body Size Default" height="160"}`,
};
 
const TABLE_VARIANTS = [
  { variant: 'lines', caption: '1 · Horizontal row lines (default)' },
  { variant: 'dividers', caption: '2 · Row + column dividers' },
  { variant: 'plain', caption: '3 · No lines (header row underlined)' },
];
 
const tableMarkdown = (attrs: string) => `:::table{${attrs}}
| Token | Value | Usage |
| --- | --- | --- |
| --spacing-2 | 8px | Compact gaps |
| --spacing-4 | 16px | Default block gap |
| --spacing-6 | 24px | Section spacing |
:::`;
 
const TABLES = [
  ...TABLE_VARIANTS.map(({ variant, caption }) => ({ caption, attrs: `variant="${variant}"` })),
  ...TABLE_VARIANTS.map(({ variant, caption }) => ({
    caption: `${caption} + outline`,
    attrs: `variant="${variant}" outline`,
  })),
];
 
/** Rendered markdown inside `.content-blocks`. Trusted, in-repo strings rendered at build time. */
function Markdown({ html, className }: { html: string; className?: string }) {
  return (
    <div
      className={['content-blocks', 'style-preview__content', className].filter(Boolean).join(' ')}
      dangerouslySetInnerHTML={{ __html: html }}
    />
  );
}
 
export default async function PageLayoutsPage() {
  if (!STYLE_PREVIEW_ENABLED) notFound();
 
  const keys = Object.keys(MD) as (keyof typeof MD)[];
  const rendered = await Promise.all(keys.map((k) => renderMarkdownString(MD[k])));
  const html = Object.fromEntries(keys.map((k, i) => [k, rendered[i]])) as Record<keyof typeof MD, string>;
  const tablesHtml = await Promise.all(TABLES.map((t) => renderMarkdownString(tableMarkdown(t.attrs))));
  const iconNames = linkIconNames();
 
  return (
    <PreviewLayout
      heading="Page Layouts"
      lead="Everything you can add from markdown page content — prose, content directives, and page-header chrome. Each section shows the markdown you write and the CSS it produces."
      current="/style-preview/page-layouts"
      toc={TOC}
    >
      <PreviewSection
        id="content-blocks"
        title="Content blocks"
        tag="# Heading / **bold** / [link]() → .content-blocks"
        note="Standard markdown headings, text, and links."
      >
        <Markdown html={html.contentBlocks} />
      </PreviewSection>
 
      <PreviewSection id="blockquote" title="Blockquote" tag="> quote → .content-blocks blockquote">
        <Markdown html={html.blockquote} />
      </PreviewSection>
 
      <PreviewSection id="lists" title="Lists" tag="- item / 1. item → .content-blocks ul / ol">
        <Markdown html={html.lists} />
      </PreviewSection>
 
      <PreviewSection
        id="code"
        title="Code"
        tag="`inline` → code chip · ```lang fenced → .content-blocks pre / code"
        note="Single backticks make an inline mono chip inside body text; triple backticks make a full code block."
      >
        <PreviewLabel>
          <code>`inline`</code> — inline code chip (single backticks, inside a sentence)
        </PreviewLabel>
        <Markdown html={html.inlineCode} />
        <PreviewLabel>
          <code>```lang</code> — fenced code block (triple backticks, own line)
        </PreviewLabel>
        <Markdown html={html.fencedCode} />
      </PreviewSection>
 
      <PreviewSection id="rule" title="Rule" tag="--- → .content-blocks hr">
        <Markdown html={html.rule} />
      </PreviewSection>
 
      <PreviewSection
        id="image"
        title="Image"
        tag={'::image{src, alt, caption, size="1|2", align="left|right"} → .content-image'}
        note="size=1 is a full-width image (radius 4xl); size=2 is a half-width image (radius 3xl) that floats so text flows in the opposite column. All support an optional centered caption. A plain markdown ![alt](src) still renders a basic .content-blocks img."
      >
        <PreviewLabel>
          <code>{'::image{size="1"}'}</code> — 1 column · full width · radius 4xl · with caption
        </PreviewLabel>
        <Markdown html={html.imageFull} />
        <PreviewLabel>
          <code>{'::image{size="2" align="left"}'}</code> — 2 column · half width · radius 3xl · text in
          opposite column
        </PreviewLabel>
        <Markdown html={html.imageLeft} className="style-preview__clear" />
        <PreviewLabel>
          <code>{'::image{size="2" align="right"}'}</code> — 2 column · floated right · text flows left
        </PreviewLabel>
        <Markdown html={html.imageRight} className="style-preview__clear" />
      </PreviewSection>
 
      <PreviewSection
        id="do-dont"
        title="Do / Don't"
        tag=":::do-dont + ::do / ::dont{image, alt, text} → .do-dont"
        note="Author supplies only the raw image + text. The site frames it (success/error tint, frame radius 3xl with the inner image at radius xl so its corners nest inside), adds the check/x badge notched over the top edge, and repeats the icon in the caption below. Rendered here from real markdown."
      >
        <Markdown html={html.doDont} />
      </PreviewSection>
 
      <PreviewSection
        id="callouts"
        title="Callouts"
        tag={':::callout{type="info|success|warning|error"} → .callout'}
      >
        <Markdown html={html.callouts} />
      </PreviewSection>
 
      <PreviewSection
        id="cards"
        title="Cards"
        tag=":::card-grid + ::card{title,href,image,description} → .card-grid / .card"
        note="Image cards and no-image cards never share a row: 3-column with images, 2-column without, and single-column (one per row) mixing both."
      >
        <Markdown html={html.cards} />
      </PreviewSection>
 
      <PreviewSection
        id="link-tiles"
        title="Link tiles"
        tag={
          ':::link-grid{columns="1|2|3"} + ::link-card{title,href,icon,description} → .link-grid / .link-card'
        }
        note="1-column tiles are full-width rows with a trailing arrow that slides on hover (like the Pager). 2- and 3-column tiles stack vertically; a missing icon still reserves its space so tiles stay aligned."
      >
        <Markdown html={html.linkTiles} />
 
        <h3 className="text-heading-sm style-preview__registry-title">
          Icon registry ({iconNames.length} icons)
        </h3>
        <p className="style-preview__registry-note">
          Files in <code>src/icons/link-tiles/</code>. Reference by name via{' '}
          <code>icon=&quot;Rocket&quot;</code> (case/spacing tolerant). Restart the dev server after adding
          new files.
        </p>
        <div className="style-preview__link-icons">
          {iconNames.map((name) => (
            <div key={name} className="style-preview__link-tile">
              {/* Trusted SVG from the in-repo registry, normalized at build time. */}
              <span
                className="style-preview__link-icon"
                dangerouslySetInnerHTML={{ __html: getLinkIcon(name) ?? '' }}
              />
              <code className="style-preview__icon-name">{name}</code>
            </div>
          ))}
        </div>
      </PreviewSection>
 
      <PreviewSection
        id="data-tables"
        title="Data tables"
        tag={':::table{variant="lines|dividers|plain" outline} → .content-table--*'}
        note="Wrap a standard markdown table in the directive to pick a line variant. Three variants; add the `outline` modifier to any of them for an edge border + radius."
      >
        <div className="style-preview__stack style-preview__stack--loose">
          {TABLES.map(({ caption, attrs }, i) => (
            <div key={attrs}>
              <PreviewLabel>
                {caption} — <code>{`:::table{${attrs}}`}</code>
              </PreviewLabel>
              <Markdown html={tablesHtml[i]} />
            </div>
          ))}
        </div>
      </PreviewSection>
 
      <PreviewSection
        id="accordion"
        title="Accordion"
        tag=":::accordion + :::accordion-item{title, open} → .accordion"
        note="Rendered here from real markdown through the same pipeline as page content. Click a header to expand; the middle item starts open via the `open` attribute. Built on native <details>, so it needs no JavaScript."
      >
        <Markdown html={html.accordion} />
      </PreviewSection>
 
      <PreviewSection
        id="video-embed"
        title="Video embed"
        tag="hero / video-embed data directive → .video-embed"
      >
        <div className="content-blocks">
          <div className="video-embed">
            <div className="video-embed__wrapper" />
          </div>
        </div>
      </PreviewSection>
 
      <PreviewSection
        id="hero-banner"
        title="Hero banner"
        tag="hero-banner data directive {title,image,version,updated,tagline} → .hero-banner (+ --compact)"
      >
        <div className="hero-banner" style={{ backgroundImage: `url(${IMG_HERO})` }}>
          <h1>Hero Banner Title</h1>
          <p className="hero-banner__version">Version 2.4.0</p>
          <p className="hero-banner__updated">Updated September 2026</p>
          <p className="hero-banner__tagline">The design system that powers Salesforce</p>
        </div>
        <div
          className="hero-banner hero-banner--compact style-preview__spaced"
          style={{ backgroundImage: `url(${IMG_HERO})` }}
        >
          <h1>Compact Banner (top-level pages)</h1>
        </div>
      </PreviewSection>
 
      <PreviewSection
        id="hero-split"
        title="Hero split"
        tag=".hero-split — React page-header layout (image / no-image variants)"
      >
        <HeroSplit
          breadcrumbs={[
            { name: 'Get Started', url: '#' },
            { name: 'Admins', url: '#' },
          ]}
          title="Split Hero Title"
          description="A subtitle sits below the title, pinned to the bottom."
          image={`url(${IMG_CARD_2})`}
        />
        <div className="style-preview__spaced">
          <HeroSplit
            breadcrumbs={[
              { name: 'Components', url: '#' },
              { name: 'Tooltip', url: '#' },
            ]}
            title="No-Image Page Title"
            description="Single column, natural height — keeps the top spacing."
          />
        </div>
      </PreviewSection>
 
      <PreviewSection
        id="storybook"
        title="Storybook embed chrome"
        tag="::component-demo{storybook, title, height} → .component-demo"
      >
        <Markdown html={html.storybook} />
      </PreviewSection>
    </PreviewLayout>
  );
}