From de770edb1bcd19ef8ed573a367af665a4c37d9df Mon Sep 17 00:00:00 2001 From: Vivien MALEZE Date: Tue, 7 Jul 2026 15:39:20 +0200 Subject: [PATCH] feat: redesign the landing page The generated index.html was a bare list of links. Replace it with a designed landing page built only with Ippon UI styles: layout template with a header, featured card linking to the latest stable release, button-card version list with pre-release badges, and an empty state when nothing is published yet. Export renderIndexPage and cover it with unit tests. Closes #1 Co-authored-by: Anthony Rey Co-authored-by: Claude Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_015a2QJQQ1q32XB1tpsM43hw --- src/site.ts | 136 +++++++++++++++++++++++++++++++++++++--------- test/site.test.ts | 54 +++++++++++++++++- 2 files changed, 162 insertions(+), 28 deletions(-) diff --git a/src/site.ts b/src/site.ts index 231903c..7f43513 100644 --- a/src/site.ts +++ b/src/site.ts @@ -21,43 +21,125 @@ export const updateLatest = async (versionsDir: string, latestVersion: string): await cp(join(versionsDir, latestVersion), latestDir, { recursive: true }); }; -const renderIndexPage = (versions: string[], latest?: string): string => { - const latestLink = latest - ? `
  • latest (${latest})
  • \n ` - : ''; - const versionLinks = versions - .map((version) => `
  • ${version}
  • `) - .join('\n '); - return ` +const renderStylesheets = (styleBase?: string): string => { + if (styleBase === undefined) { + return ''; + } + return ` + + + + + `; +}; + +const renderLatestCard = (latest?: string): string => { + if (latest === undefined) { + return `
    +
    + No stable release yet + +
    +
    `; + } + return `
    +
    + Latest stable release +

    v${latest}

    + +
    +
    `; +}; + +const renderPrereleaseBadge = (version: string): string => { + if (semver.prerelease(version) === null) { + return ''; + } + return 'pre-release'; +}; + +const renderVersionItem = (version: string): string => + `
  • ${version}${renderPrereleaseBadge(version)}
  • `; + +const findStyleBase = (versions: string[], latest?: string): string | undefined => { + if (latest !== undefined) { + return `styles/${LATEST_DIR}`; + } + if (versions.length > 0) { + return `styles/${versions[0]}`; + } + return undefined; +}; + +const renderVersionList = (versions: string[]): string => { + if (versions.length === 0) { + return '

    No versions have been published yet.

    '; + } + return `
      \n ${versions.map(renderVersionItem).join('\n ')}\n
    `; +}; + +export const renderIndexPage = (versions: string[], latest?: string): string => + ` Ippon UI Pattern Library - + + ${renderStylesheets(findStyleBase(versions, latest))} -

    Ippon UI Pattern Library

    -

    Published versions of @ippon-ui/styles:

    -
      - ${latestLink}${versionLinks} -
    -

    Versions are also listed in versions.json.

    +
    +
    +
    +
    + Ippon Technologies +
    +
    +
    +
    +
    +
    +
    +

    Ippon UI

    +

    The design system behind Ippon products.

    +
    +
    +
    +

    Styles

    +

    + The Pattern Library — every published version of + @ippon-ui/styles, mirrored from npm. +

    +
    + ${renderLatestCard(latest)} +
    +

    All versions

    + ${renderVersionList(versions)} +
    +
    +
    + +
    +
    +
    +
    `; -}; export const generateSitePages = async ( siteDir: string, diff --git a/test/site.test.ts b/test/site.test.ts index ce86b43..7d1730f 100644 --- a/test/site.test.ts +++ b/test/site.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { findLatestStable } from '../src/site.ts'; +import { findLatestStable, renderIndexPage } from '../src/site.ts'; describe('findLatestStable', () => { it('should return the highest version', () => { @@ -15,3 +15,55 @@ describe('findLatestStable', () => { expect(findLatestStable([])).toBeUndefined(); }); }); + +describe('renderIndexPage', () => { + it('should link every version', () => { + const html = renderIndexPage(['0.0.2', '0.0.1'], '0.0.2'); + expect(html).toContain('href="styles/0.0.2/index.html"'); + expect(html).toContain('href="styles/0.0.1/index.html"'); + expect(html).toContain('href="versions.json"'); + }); + + it('should feature the latest stable version', () => { + const html = renderIndexPage(['0.0.2'], '0.0.2'); + expect(html).toContain('href="styles/latest/index.html"'); + expect(html).toContain('v0.0.2'); + }); + + it('should style the page with the pattern library of the latest stable version', () => { + const html = renderIndexPage(['0.0.2'], '0.0.2'); + expect(html).toContain('href="styles/latest/tikui.css"'); + }); + + it('should not link latest when there is no stable version', () => { + const html = renderIndexPage(['0.1.0-beta.1']); + expect(html).not.toContain('href="styles/latest/index.html"'); + expect(html).toContain('No stable release yet'); + }); + + it('should fall back to the highest version stylesheet when there is no stable version', () => { + const html = renderIndexPage(['0.1.0-beta.1']); + expect(html).toContain('href="styles/0.1.0-beta.1/tikui.css"'); + }); + + it('should render without a stylesheet when no version is published', () => { + const html = renderIndexPage([]); + expect(html).not.toContain('tikui.css'); + }); + + it('should tag prereleases', () => { + const html = renderIndexPage(['0.1.0-beta.1', '0.0.1'], '0.0.1'); + expect(html.match(/pre-release/g)).toHaveLength(1); + }); + + it('should render an empty state when no version is published', () => { + const html = renderIndexPage([]); + expect(html).toContain('No versions have been published yet.'); + }); + + it('should only use pattern library styles', () => { + const html = renderIndexPage(['0.0.2'], '0.0.2'); + expect(html).not.toContain('