Skip to content

Kody is live

Watch the launch video — what Kody is, and why it exists.

← 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>
		)
	}
}