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.

starter-remix/app/ui/layout.tsx

74 lines · 2.0 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
}

/**
 * Tooltips (`data-tip`): tip only controls that are NOT obvious from visible
 * label/text. Labeled nav and action buttons do not get tips. Icon-only /
 * ambiguous controls (logo package id, header install) do.
 */
export function AppShell(handle: Handle<AppShellProps>) {
	return () => {
		const { page, heading, lede, children } = handle.props
		return (
			<div class="pak-wrap pak-stack">
				<header class="pak-header">
					<div class="pak-brand">
						<img
							class="pak-logo"
							src={routes.icon192.href()}
							width="28"
							height="28"
							alt=""
							decoding="async"
							tabindex="0"
							data-tip="__PACKAGE_NAME__"
							aria-label="__PACKAGE_NAME__"
						/>
						<h1 class="pak-title">{heading}</h1>
						{/* Reserved install slot — right of title; icon tip is intentional */}
						<InstallCta appName="__APP_TITLE__" />
					</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>
		)
	}
}