@keegancodes/llm-kit-ui
v0.1.0
Published
Shared, themeable React UI components for Keegan's apps. Starts with a full application header shell (brand + primary nav + user menu).
Readme
@keegancodes/llm-kit-ui
Shared, themeable React UI for Keegan's apps. First component: AppHeader — a
full application header shell (brand + primary nav + user menu) used by
coding-stats and market-dashboard.
- Themeable via a
themeprop that maps to CSS custom properties — reskin the whole header (accent, surfaces, text, radius) without forking. - Pluggable navigation — pass a custom
linkComponent(e.g. a progress-reporting link) or fall back tonext/link. - Opinionated icons — nav items take
lucide-reacticon components; the header sizes and colors them consistently.
Install
pnpm add @keegancodes/llm-kit-ui
# peers (both apps already have these):
pnpm add next react react-dom lucide-reactTailwind setup (required, one line)
The component ships compiled utility classes. Tailwind v4 ignores node_modules
by default, so tell it to scan this package in your global stylesheet:
@import "tailwindcss";
@source "../../node_modules/@keegancodes/llm-kit-ui/dist";The @source path is relative to the CSS file. From src/app/globals.css the
path above is correct.
Usage
import { AppHeader } from "@keegancodes/llm-kit-ui";
import { LayoutDashboard, Rocket, Settings } from "lucide-react";
<AppHeader
brand={<Wordmark />}
entries={[
{ href: "/dashboard", label: "Dashboard", icon: LayoutDashboard },
{
label: "Activity",
icon: Rocket,
items: [{ href: "/deployments", label: "Deployments", icon: Rocket }],
},
]}
user={{ name, email, image }}
userMenuItems={[{ href: "/settings", label: "Settings", icon: Settings }]}
onSignOut={signOutAction}
theme={{ accent: "#34d399", accentSoft: "rgba(52,211,153,0.12)", accentText: "#d1fae5" }}
/>;Theming
Pass any subset of NavThemeTokens to theme (see src/theme.ts). Anything you
omit falls back to the default dark/white skin (DEFAULT_THEME). Tokens are
plain CSS values, so you can also drive them from your own CSS variables.
Timeline
A vertical event timeline with two modes, sharing spine/grouping/marker scaffolding. Cards, dots, and on-spine labels are consumer slots.
mode="alternating"— cards fan out on alternating sides of a centered spine (single-sided belowsm); each item shows aspineLabel(e.g. a time pill). Optional sticky date markers (stickyDateMarkers, default on).mode="rail"— cards to the right of a left rail; each item shows a dot (dotClassName), with a date chip on the rail at each group boundary.
Items are grouped by their date (a YYYY-MM-DD string) into date markers.
Theme the spine/markers with TimelineThemeTokens; format labels with
dateFormatOptions (serializable — safe from server components) or a
formatDateLabel function (client only).
import { Timeline, type TimelineItem } from "@keegancodes/llm-kit-ui";
const items: TimelineItem[] = trades.map((t) => ({
id: t.id,
date: t.tradeDate, // YYYY-MM-DD
dotClassName: t.side === "buy" ? "border-emerald-500 bg-emerald-500/20" : "border-red-500 bg-red-500/20",
card: <TradeCard trade={t} />,
}));
<Timeline mode="rail" items={items} dateFormatOptions={{ weekday: "short", month: "short", day: "numeric", year: "numeric" }} />;Local dev note: the
card/spineLabelslots are React nodes. From a server component, pass only serializable content (no event handlers) and usedateFormatOptionsrather thanformatDateLabel.
Publishing
This package is consumed as a published dependency (so Vercel builds resolve it). After changes:
pnpm run build # emits dist/
npm version patch # or minor/major
npm publish # requires your npm auth; access is publicThen bump the version in each consuming app and reinstall.
During local development you can link instead of publishing:
pnpm add file:../keegandonley-uiin each app. Switch back to the semver range before committing, since Vercel can't resolve a local path.pnpm snapshots a
file:dependency at install time, so after you add or remove files in this package (not just edit existing ones), re-runpnpm add file:../keegandonley-uiin each consumer or the new modules won't resolve. (link:avoids the snapshot but Turbopack fails to resolve it, so stick withfile:+ reinstall.)
