← Public packages
@kentcdodds/package-app-kit
Design tokens, PWA install/update, About/version, cache helpers, and optional realtime notes sync for Kody package apps.
app/controllers/home.tsx
285 lines · 9.6 KB · TypeScript/** @jsxRuntime automatic */
/** @jsxImportSource remix/ui */
import type { BuildAction } from 'remix/router'
import { listNotes } from '../data/notes.ts'
import { render } from '../ui/render.tsx'
import { AppShell } from '../ui/layout.tsx'
import { Counter } from '../ui/counter.tsx'
import { BusyDemo } from '../ui/busy-demo.tsx'
import { ErrorBanner } from '../ui/error-banner.tsx'
import { DoubleCheckDemo } from '../ui/double-check-demo.tsx'
import { NotesDemo } from '../ui/notes-demo.tsx'
import { IconDownload } from '../icons/download.tsx'
import { routes } from '../routes.ts'
export default {
async handler(context) {
const notes = await listNotes(context)
const heroSrc = routes.logoFull.href()
return render(
context,
<AppShell page="home" heading="Package App Kit">
<section class="pak-hero" aria-label="Brand">
<img
class="pak-hero-logo"
src={heroSrc}
width="220"
height="412"
alt="Kody the koala as a race driver"
decoding="async"
/>
<p class="pak-muted">Kody: Ready to race to your next app.</p>
</section>
<section class="pak-card pak-stack" aria-label="Colors">
<p class="pak-eyebrow">Colors</p>
<p class="pak-muted">Accent, ink, surface, good — plus racing stripe tokens.</p>
<div class="pak-swatches">
<span class="pak-swatch-label">
<span class="pak-swatch" style="background:var(--accent)" aria-hidden="true" />
Accent
</span>
<span class="pak-swatch-label">
<span class="pak-swatch" style="background:var(--ink)" aria-hidden="true" />
Ink
</span>
<span class="pak-swatch-label">
<span class="pak-swatch" style="background:var(--surface)" aria-hidden="true" />
Surface
</span>
<span class="pak-swatch-label">
<span class="pak-swatch" style="background:var(--good)" aria-hidden="true" />
Good
</span>
</div>
<div class="pak-swatches pak-swatches-stripes" aria-label="Racing stripe tokens">
<span class="pak-swatch-label">
<span class="pak-swatch" style="background:var(--pak-stripe-cyan)" aria-hidden="true" />
Cyan
</span>
<span class="pak-swatch-label">
<span class="pak-swatch" style="background:var(--pak-stripe-green)" aria-hidden="true" />
Green
</span>
<span class="pak-swatch-label">
<span class="pak-swatch" style="background:var(--pak-stripe-yellow)" aria-hidden="true" />
Yellow
</span>
<span class="pak-swatch-label">
<span class="pak-swatch" style="background:var(--pak-stripe-magenta)" aria-hidden="true" />
Magenta
</span>
<span class="pak-swatch-label">
<span class="pak-swatch" style="background:var(--pak-stripe-red)" aria-hidden="true" />
Red
</span>
</div>
</section>
<section class="pak-card pak-stack" aria-label="Spacing">
<p class="pak-eyebrow">Spacing</p>
<p class="pak-muted">
Token scale <code>--pak-space-1</code>…<code>8</code> and layout primitives.
</p>
<div class="pak-space-rulers" aria-label="Space token rulers">
<div class="pak-space-ruler">
<span class="pak-space-ruler-label">space-1</span>
<span
class="pak-space-ruler-bar"
style="width:var(--pak-space-1)"
aria-hidden="true"
/>
</div>
<div class="pak-space-ruler">
<span class="pak-space-ruler-label">space-2</span>
<span
class="pak-space-ruler-bar"
style="width:var(--pak-space-2)"
aria-hidden="true"
/>
</div>
<div class="pak-space-ruler">
<span class="pak-space-ruler-label">space-3</span>
<span
class="pak-space-ruler-bar"
style="width:var(--pak-space-3)"
aria-hidden="true"
/>
</div>
<div class="pak-space-ruler">
<span class="pak-space-ruler-label">space-4</span>
<span
class="pak-space-ruler-bar"
style="width:var(--pak-space-4)"
aria-hidden="true"
/>
</div>
<div class="pak-space-ruler">
<span class="pak-space-ruler-label">space-5</span>
<span
class="pak-space-ruler-bar"
style="width:var(--pak-space-5)"
aria-hidden="true"
/>
</div>
<div class="pak-space-ruler">
<span class="pak-space-ruler-label">space-6</span>
<span
class="pak-space-ruler-bar"
style="width:var(--pak-space-6)"
aria-hidden="true"
/>
</div>
<div class="pak-space-ruler">
<span class="pak-space-ruler-label">space-7</span>
<span
class="pak-space-ruler-bar"
style="width:var(--pak-space-7)"
aria-hidden="true"
/>
</div>
<div class="pak-space-ruler">
<span class="pak-space-ruler-label">space-8</span>
<span
class="pak-space-ruler-bar"
style="width:var(--pak-space-8)"
aria-hidden="true"
/>
</div>
</div>
<div class="pak-cluster" aria-label="Layout primitives">
<div class="pak-demo-box pak-stack-sm">
<span class="pak-eyebrow">pak-stack</span>
<span class="pak-demo-chip">A</span>
<span class="pak-demo-chip">B</span>
</div>
<div class="pak-demo-box">
<span class="pak-eyebrow">pak-cluster</span>
<div class="pak-cluster pak-mt-2">
<span class="pak-demo-chip">1</span>
<span class="pak-demo-chip">2</span>
<span class="pak-demo-chip">3</span>
</div>
</div>
<div class="pak-demo-box pak-inset" style="background:var(--surface-2)">
<span class="pak-eyebrow">pak-inset</span>
<p class="pak-muted pak-mt-1" style="margin:0">
Padded box
</p>
</div>
</div>
</section>
<section class="pak-card pak-stack" aria-label="Buttons and chrome">
<p class="pak-eyebrow">Buttons & chrome</p>
<p class="pak-muted">Default, accent, danger — plus pill and eyebrow samples.</p>
<div class="pak-cluster">
<button type="button" class="pak-btn">
Default
</button>
<button type="button" class="pak-btn pak-btn-accent">
Accent
</button>
<button type="button" class="pak-btn pak-btn-danger">
Danger
</button>
<span class="pak-btn pak-btn-accent" style="pointer-events:none">
Notes
<span class="pak-pill" aria-hidden="true">
{notes.length}
</span>
</span>
</div>
<p class="pak-eyebrow">Eyebrow</p>
<p class="pak-muted">Muted supporting copy stays calm beside louder chrome.</p>
</section>
<section class="pak-card pak-stack" aria-label="Error banner">
<p class="pak-eyebrow">ErrorBanner</p>
<p class="pak-muted">
Use the alert pattern when replacing expected content; keep ephemeral failures in
toasts.
</p>
<ErrorBanner
title="Sample alert"
message="This is a static ErrorBanner demo so the pattern stays visible on home."
hint="Retry with a clearer action, or open Notes to try a real form error."
/>
</section>
<section class="pak-card pak-stack" aria-label="Double-check">
<p class="pak-eyebrow">Double-check</p>
<p class="pak-muted">
First press arms; second press confirms. Blur resets. Tip-free because the control is
labeled.
</p>
<DoubleCheckDemo />
</section>
<section class="pak-card pak-stack" aria-label="Busy spin-delay">
<p class="pak-eyebrow">Busy / spin-delay</p>
<p class="pak-muted">
Press feedback is immediate; the Loading label waits ~200ms and stays ≥400ms once
shown.
</p>
<div class="pak-cluster">
<BusyDemo label="Run demo job" />
</div>
</section>
<section class="pak-card pak-stack" aria-label="Tooltip">
<p class="pak-eyebrow">Tooltip</p>
<p class="pak-muted">
<code>data-tip</code> on icon-only controls — labeled Home/Notes/About stay tip-free.
</p>
<div class="pak-cluster">
<button
type="button"
class="pak-btn pak-btn-icon"
data-tip="Download sample (demo)"
aria-label="Download sample (demo)"
>
<IconDownload size={20} />
</button>
</div>
</section>
<section class="pak-card pak-stack" aria-label="Hydrated island">
<p class="pak-eyebrow">Hydrated island</p>
<p class="pak-muted">
Hydrated <code>clientEntry</code> Counter — copy this shape for interactive bits.
</p>
<div class="pak-cluster">
<Counter initialCount={0} label="Taps" />
</div>
</section>
<section class="pak-stack" aria-label="Notes live">
<div class="pak-card pak-stack-sm" style="padding-bottom:var(--space-3)">
<p class="pak-eyebrow">Notes (live)</p>
<p class="pak-muted">
Optimistic add/delete with package realtime (<code>notes.changed</code>);
packageStorage stays source of truth. Live status sits above the form.
</p>
<a class="pak-btn" href={routes.notes.index.href()} style="align-self:flex-start">
Open full Notes page
</a>
</div>
<NotesDemo notes={notes} actionHref={routes.notes.action.href()} />
</section>
<section class="pak-card pak-stack" aria-label="About and version">
<p class="pak-eyebrow">About / version</p>
<p class="pak-muted">
About shows local times and last update check. The header Install icon demos the A2HS
CTA — use it there rather than duplicating mid-page.
</p>
<a class="pak-btn pak-btn-accent" href={routes.about.href()} style="align-self:flex-start">
Open About
</a>
</section>
</AppShell>,
{ page: 'home', title: 'Package App Kit' },
)
},
} satisfies BuildAction<'ANY', typeof routes.home>