← 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/ui/layout.tsx
75 lines · 2.1 KB · TypeScript/** @jsxRuntime automatic */
/** @jsxImportSource remix/ui */
import type { Handle, RemixNode } from 'remix/ui'
import { routes } from '../routes.ts'
import { InstallCta } from './install-cta.tsx'
import { UpdateBanner } from './update-banner.tsx'
export type AppShellProps = {
page: 'home' | 'about' | 'notes'
heading: string
/** Optional muted lede under the nav. */
lede?: RemixNode
children?: RemixNode
}
/**
* Brand header (bust logo + title + reserved install slot) on every page, nav under it.
* Tips (`data-tip`): only on non-obvious controls — logo package id and the
* icon-only install button. Labeled nav already states its destination.
*/
export function AppShell(handle: Handle<AppShellProps>) {
return () => {
const { page, heading, lede, children } = handle.props
const logoSrc = routes.logoBust.href()
return (
<div class="pak-wrap pak-stack">
<header class="pak-header">
<div class="pak-brand">
<img
class="pak-logo"
src={logoSrc}
width="28"
height="28"
alt=""
decoding="async"
tabindex="0"
data-tip="@kentcdodds/package-app-kit"
aria-label="@kentcdodds/package-app-kit"
/>
<h1 class="pak-title">{heading}</h1>
{/* Reserved install slot — right of title; icon tip is intentional */}
<InstallCta appName="Package App Kit" />
</div>
<nav class="pak-nav" aria-label="App">
<a
class="pak-btn"
href={routes.home.href()}
aria-current={page === 'home' ? 'page' : undefined}
>
Home
</a>
<a
class="pak-btn"
href={routes.notes.index.href()}
aria-current={page === 'notes' ? 'page' : undefined}
>
Notes
</a>
<a
class="pak-btn"
href={routes.about.href()}
aria-current={page === 'about' ? 'page' : undefined}
>
About
</a>
</nav>
{lede ? <p class="pak-muted pak-lede">{lede}</p> : null}
</header>
{/* Fixed dismissible toast — does not affect document flow / CLS */}
<UpdateBanner />
{children}
</div>
)
}
}