Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 109 additions & 27 deletions src/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
? `<li><a href="styles/${LATEST_DIR}/index.html">latest</a> (${latest})</li>\n `
: '';
const versionLinks = versions
.map((version) => `<li><a href="styles/${version}/index.html">${version}</a></li>`)
.join('\n ');
return `<!doctype html>
const renderStylesheets = (styleBase?: string): string => {
if (styleBase === undefined) {
return '';
}
return `<link href="${styleBase}/fonts/open-sans/400.css" rel="stylesheet" />
<link href="${styleBase}/fonts/open-sans/600.css" rel="stylesheet" />
<link href="${styleBase}/fonts/open-sans/700.css" rel="stylesheet" />
<link href="${styleBase}/fonts/saira-extra-condensed/700.css" rel="stylesheet" />
<link href="${styleBase}/icons/ionicons.css" rel="stylesheet" />
<link href="${styleBase}/tikui.css" rel="stylesheet" />`;
};

const renderLatestCard = (latest?: string): string => {
if (latest === undefined) {
return `<section class="ippon-card -border">
<div class="ippon-v-space -gap-16 -left">
<span class="ippon-text -label">No stable release yet</span>
<nav class="ippon-h-space -gap-16">
<a class="ippon-button -outline" href="versions.json">versions.json</a>
</nav>
</div>
</section>`;
}
return `<section class="ippon-card -shadow-l2 -border">
<div class="ippon-v-space -gap-16 -left">
<span class="ippon-text -label">Latest stable release</span>
<h3 class="ippon-title-display -medium">v${latest}</h3>
<nav class="ippon-h-space -gap-16">
<a class="ippon-button" href="styles/${LATEST_DIR}/index.html">Browse the library</a>
<a class="ippon-button -outline" href="versions.json">versions.json</a>
</nav>
</div>
</section>`;
};

const renderPrereleaseBadge = (version: string): string => {
if (semver.prerelease(version) === null) {
return '';
}
return '<span class="ippon-badge -secondary -warning">pre-release</span>';
};

const renderVersionItem = (version: string): string =>
`<li><a class="ippon-button-card -border -full-width" href="styles/${version}/index.html"><span class="ippon-h-space -gap-16 -middle"><span class="ippon-h-space--slot -expand"><span class="ippon-text -body -bold">${version}</span></span>${renderPrereleaseBadge(version)}<span class="ippon-icon -size-24 -color-brand-primary ippon-ion-chevron-forward" role="presentation"></span></span></a></li>`;

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 '<p class="ippon-text -body">No versions have been published yet.</p>';
}
return `<ol class="ippon-v-space -gap-8">\n ${versions.map(renderVersionItem).join('\n ')}\n </ol>`;
};

export const renderIndexPage = (versions: string[], latest?: string): string =>
`<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Ippon UI Pattern Library</title>
<style>
body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 3rem auto;
padding: 0 1rem;
line-height: 1.6;
}
h1 {
font-size: 1.5rem;
}
</style>
<meta
name="description"
content="Every published version of the Ippon UI Pattern Library (@ippon-ui/styles)."
/>
${renderStylesheets(findStyleBase(versions, latest))}
</head>
<body>
<h1>Ippon UI Pattern Library</h1>
<p>Published versions of <code>@ippon-ui/styles</code>:</p>
<ul>
${latestLink}${versionLinks}
</ul>
<p>Versions are also listed in <a href="versions.json">versions.json</a>.</p>
<div class="ippon-layout">
<header class="ippon-layout--header">
<div class="ippon-header">
<div class="ippon-header--slot">
<span class="ippon-text -label -large">Ippon Technologies</span>
</div>
</div>
</header>
<main class="ippon-layout--body">
<div class="ippon-container">
<div class="ippon-v-space -gap-32">
<header class="ippon-v-space -gap-8 -left">
<h1 class="ippon-title-display">Ippon UI</h1>
<p class="ippon-text -large -body">The design system behind Ippon products.</p>
</header>
<section class="ippon-v-space -gap-24">
<div class="ippon-v-space -gap-8 -left">
<h2 class="ippon-title">Styles</h2>
<p class="ippon-text -body">
The Pattern Library &mdash; every published version of
<code>@ippon-ui/styles</code>, mirrored from npm.
</p>
</div>
${renderLatestCard(latest)}
<section class="ippon-v-space -gap-16">
<h3 class="ippon-title">All versions</h3>
${renderVersionList(versions)}
</section>
</section>
<hr class="ippon-separator" />
<footer>
<span class="ippon-text -small -body">
Sources: <a href="https://github.com/ippontech/ui">ippontech/ui</a> &middot;
<a href="https://github.com/ippontech/ui-website">ippontech/ui-website</a>
</span>
</footer>
</div>
</div>
</main>
</div>
</body>
</html>
`;
};

export const generateSitePages = async (
siteDir: string,
Expand Down
54 changes: 53 additions & 1 deletion test/site.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand All @@ -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('<style>');
expect(html).not.toContain('style="');
});
});
Loading