← Public packages
@kentcdodds/package-app-kit
Design tokens, PWA install/update, About/version, cache helpers, and optional realtime notes sync for Kody package apps.
src/icons.ts
167 lines · 5.1 KB · TypeScript/**
* PWA icon helpers. Kit PNG bytes are embedded in `./icon-png-bytes.ts`
* (mirrors `icons/icon-192.png` + `icons/icon-512.png` in this repo).
*
* Tool-only / `packageSave` agents: do **not** commit binary PNGs into the
* consumer app. Serve kit bytes from app routes via `iconPngResponse` and point
* the manifest at those routes with `kitServedManifestIcons` (alias of
* `defaultManifestIcons`). Optional UTF-8 SVG helpers are below for favicons /
* apps that only need an SVG mark (Chromium URL-bar Install still wants PNG 192+512).
*/
import { ICON_192_BYTES, ICON_512_BYTES } from './icon-png-bytes.ts'
export type PwaIconSize = 192 | 512
/** Bump when kit PWA PNGs change so SW + favicon/manifest caches refresh. */
export const ICON_ASSET_REVISION = '7'
const ICON_BYTES: Record<PwaIconSize, Uint8Array> = {
192: ICON_192_BYTES,
512: ICON_512_BYTES,
}
export type ManifestIcon = {
src: string
sizes: string
type: string
purpose: string
}
/**
* Return the branded PNG bytes for a Chromium-required PWA icon size.
* Prefer shipping `icons/icon-192.png` / `icons/icon-512.png` in the app repo
* when using the git lane; for tool-only apps, call this (or `iconPngResponse`)
* from routes — no local PNG files required.
*
* @param size - 192 or 512
* @returns PNG file bytes
*
* @example
* import { pngIconBytes } from 'kody:@kentcdodds/package-app-kit/icons'
* const png = pngIconBytes(192)
*/
export function pngIconBytes(size: PwaIconSize): Uint8Array {
return ICON_BYTES[size]
}
/**
* Build a `200 image/png` Response for a PWA icon.
* Wire to `GET /icons/icon-192.png` and `GET /icons/icon-512.png`.
* No local PNG files needed in the consumer package.
*
* @param size - 192 or 512
* @returns PNG response with long-lived cache headers
*
* @example
* import { iconPngResponse } from 'kody:@kentcdodds/package-app-kit/icons'
* // in fetch handler:
* if (path === '/icons/icon-192.png') return iconPngResponse(192)
* if (path === '/icons/icon-512.png') return iconPngResponse(512)
*/
export function iconPngResponse(size: PwaIconSize): Response {
const body = pngIconBytes(size)
return new Response(body, {
headers: {
'content-type': 'image/png',
'cache-control': 'public, max-age=86400',
'content-length': String(body.byteLength),
},
})
}
/**
* Manifest `icons` entries pointing at kit-served 192 + 512 PNG routes
* (`{appBase}/icons/icon-192.png` + `icon-512.png`). Pair with `iconPngResponse`
* handlers — no binary PNG files in the consumer package.
*
* @param appBasePath - `packageContext.appBasePath`
* @returns Manifest icon objects with correct sizes/types
*
* @example
* import { defaultManifestIcons } from 'kody:@kentcdodds/package-app-kit/icons'
* const icons = defaultManifestIcons('/packages/package-app-kit')
*/
export function defaultManifestIcons(appBasePath: string): ManifestIcon[] {
const base = appBasePath.replace(/\/$/, '')
return [
{
src: `${base}/icons/icon-192.png?v=${ICON_ASSET_REVISION}`,
sizes: '192x192',
type: 'image/png',
purpose: 'any',
},
{
src: `${base}/icons/icon-512.png?v=${ICON_ASSET_REVISION}`,
sizes: '512x512',
type: 'image/png',
purpose: 'any',
},
]
}
/**
* Same as `defaultManifestIcons` — named for tool-only / fetch-handler apps that
* serve kit PNG bytes from routes (no committed `icons/*.png` in the app).
*
* @example
* import {
* kitServedManifestIcons,
* iconPngResponse,
* } from 'kody:@kentcdodds/package-app-kit/icons'
* import { buildWebManifest } from 'kody:@kentcdodds/package-app-kit/sw'
*
* buildWebManifest({
* appBasePath,
* name: 'My App',
* icons: kitServedManifestIcons(appBasePath),
* })
* // routes: /icons/icon-192.png → iconPngResponse(192), same for 512
*/
export function kitServedManifestIcons(appBasePath: string): ManifestIcon[] {
return defaultManifestIcons(appBasePath)
}
/** Minimal branded SVG mark (UTF-8 shippable — fine for `packageSave` / assets). */
export function kitIconSvg(options?: {
/** Background fill (default kit teal-dark). */
bg?: string
/** Foreground mark fill. */
fg?: string
}): string {
const bg = options?.bg ?? '#0f766e'
const fg = options?.fg ?? '#ecfeff'
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64"><rect width="64" height="64" rx="14" fill="${bg}"/><circle cx="32" cy="32" r="14" fill="${fg}"/></svg>`
}
/**
* `200 image/svg+xml` Response for a UTF-8 SVG icon (favicon / apple-touch).
* Does **not** replace PNG 192+512 for Chromium installability.
*/
export function iconSvgResponse(svg = kitIconSvg()): Response {
const body = svg
return new Response(body, {
headers: {
'content-type': 'image/svg+xml; charset=utf-8',
'cache-control': 'public, max-age=86400',
},
})
}
/**
* Manifest icon entry for an SVG at `{assetBasePath}/icon.svg` (or custom src).
* Use as a supplement; keep PNG 192+512 for Chromium URL-bar Install.
*/
export function svgManifestIcon(
src: string,
options?: { purpose?: string },
): ManifestIcon {
return {
src,
sizes: 'any',
type: 'image/svg+xml',
purpose: options?.purpose ?? 'any',
}
}
/** Primary callable export for this subpath. */
export default iconPngResponse