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