Skip to content

Commit e4adf66

Browse files
committed
feat: add a typed component registry with registry-driven CLI scaffolding, docs metadata integration, and flat-config ESLint across the monorepo
1 parent 7bf1647 commit e4adf66

67 files changed

Lines changed: 998 additions & 310 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ tokens, releases, and governance into ad hoc tooling.
66

77
The repository is intentionally structured as a product, not just a package:
88

9+
- `packages/registry` is the typed component registry shared by CLI and docs.
910
- `packages/tokens` is the source of truth for design tokens and semantic themes.
1011
- `packages/ui` ships the baseline React component library.
1112
- `packages/utils` holds shared utilities with low abstraction overhead.
@@ -32,6 +33,7 @@ apps/
3233
packages/
3334
cli/ CLI foundation for init/add workflows
3435
eslint-config/ Shared ESLint presets
36+
registry/ Typed component manifests for docs and CLI
3537
tokens/ Design tokens, semantic themes, CSS variables
3638
tsconfig/ Shared TypeScript presets
3739
ui/ React UI component package
@@ -45,6 +47,8 @@ tooling/ Repo-level helper scripts and test setup
4547

4648
- `@nim-ui/tokens`
4749
Primitive palette, semantic theme mapping, and exported CSS variables.
50+
- `@nim-ui/registry`
51+
Typed component manifests, dependency metadata, and future registry surface.
4852
- `@nim-ui/ui`
4953
Baseline component library: `Button`, `Input`, `Card`, `Badge`.
5054
- `@nim-ui/utils`
@@ -123,6 +127,8 @@ ownership, domains, and environment configuration.
123127

124128
The CLI package currently includes:
125129

130+
- registry-driven component lookup
131+
- `nim-ui.config.ts` resolution with defaults
126132
- config resolution
127133
- `init`
128134
- local registry metadata
@@ -132,6 +138,20 @@ The CLI package currently includes:
132138
Future work can layer remote registry manifests, project adapters, and canary
133139
channels on top of the existing package boundaries without rewriting the CLI.
134140

141+
## Canary Releases
142+
143+
Canary publishing is intentionally not wired into default automation yet, but the
144+
repository is ready for it through Changesets pre mode. A practical next step is
145+
to add maintainer scripts for:
146+
147+
- `changeset pre enter canary`
148+
- `changeset version`
149+
- `changeset publish --tag canary`
150+
- `changeset pre exit`
151+
152+
That approach keeps stable releases and canary channels separate without forcing
153+
extra complexity into the default OSS workflow.
154+
135155
## Open-Source Baseline
136156

137157
This repository includes:

apps/docs/.eslintrc.cjs

Lines changed: 0 additions & 15 deletions
This file was deleted.

apps/docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Next.js App Router docs and demo application for Nim UI.
55
Goals:
66

77
- show the path from tokens to semantic CSS variables to UI components
8+
- show registry metadata such as dependencies and token usage
89
- keep documentation close to the code that maintainers ship
910
- make content easy to extend without changing routing architecture
1011
- provide a polished demo surface for open-source adoption

apps/docs/components/component-page-template.tsx

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import { CodeBlock } from './code-block';
32
import { ComponentPreview } from './component-preview';
43

@@ -35,6 +34,53 @@ export function ComponentPageTemplate({ page }: { page: ComponentPage }) {
3534
<CodeBlock code={page.code} label={`${page.title} example`} language="tsx" />
3635
</section>
3736

37+
<section className="space-y-5">
38+
<h2 className="font-display text-3xl font-semibold">
39+
System metadata
40+
</h2>
41+
<div className="grid gap-4 md:grid-cols-3">
42+
<div className="rounded-[1.25rem] border border-border bg-white p-5">
43+
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-[var(--color-brand-red)]">
44+
Registry
45+
</p>
46+
<p className="mt-3 text-lg font-semibold">{page.meta.registry}</p>
47+
<p className="mt-2 text-sm text-muted-foreground">
48+
Category: {page.manifest.category}
49+
</p>
50+
</div>
51+
<div className="rounded-[1.25rem] border border-border bg-white p-5">
52+
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-[var(--color-brand-red)]">
53+
Tokens
54+
</p>
55+
<div className="mt-3 flex flex-wrap gap-2">
56+
{page.meta.tokens.map((token) => (
57+
<span
58+
className="rounded-full bg-accent px-3 py-1 text-xs font-medium text-accent-foreground"
59+
key={token}
60+
>
61+
{token}
62+
</span>
63+
))}
64+
</div>
65+
</div>
66+
<div className="rounded-[1.25rem] border border-border bg-white p-5">
67+
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-[var(--color-brand-red)]">
68+
Dependencies
69+
</p>
70+
<div className="mt-3 flex flex-wrap gap-2">
71+
{page.meta.dependencies.map((dependency) => (
72+
<span
73+
className="rounded-full border border-border px-3 py-1 text-xs font-medium text-foreground"
74+
key={dependency}
75+
>
76+
{dependency}
77+
</span>
78+
))}
79+
</div>
80+
</div>
81+
</div>
82+
</section>
83+
3884
{page.sections.map((section) => (
3985
<section className="space-y-5" key={section.title}>
4086
<h2 className="font-display text-3xl font-semibold">
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import type { ComponentPage } from '../types';
1+
import { createComponentPage, defineComponentMeta } from '../types';
22

3-
export const badgePage: ComponentPage = {
4-
slug: 'badge',
3+
export const meta = defineComponentMeta({
54
title: 'Badge',
65
description:
76
'Badges provide compact labels for status, categorization, and lightweight emphasis.',
7+
tokens: ['primary', 'primary-foreground', 'secondary', 'secondary-foreground', 'accent', 'accent-foreground', 'border', 'ring'],
8+
dependencies: ['utils', 'tokens'],
9+
registry: 'badge',
10+
});
11+
12+
export const badgePage = createComponentPage(meta, {
13+
slug: 'badge',
814
eyebrow: 'Component',
915
preview: 'badge',
1016
code: `import { Badge } from '@nim-ui/ui';\n\nexport function Example() {\n return (\n <div className="flex gap-2">\n <Badge>Stable</Badge>\n <Badge variant="secondary">Community</Badge>\n <Badge variant="outline">Docs</Badge>\n </div>\n );\n}`,
@@ -16,4 +22,4 @@ export const badgePage: ComponentPage = {
1622
],
1723
},
1824
],
19-
};
25+
});
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import type { ComponentPage } from '../types';
1+
import { createComponentPage, defineComponentMeta } from '../types';
22

3-
export const buttonPage: ComponentPage = {
4-
slug: 'button',
3+
export const meta = defineComponentMeta({
54
title: 'Button',
65
description:
76
'Buttons provide semantic variants and size options with accessible focus styles and SSR-safe rendering.',
7+
tokens: ['primary', 'primary-foreground', 'radius', 'ring', 'secondary', 'border'],
8+
dependencies: ['utils', 'tokens'],
9+
registry: 'button',
10+
});
11+
12+
export const buttonPage = createComponentPage(meta, {
13+
slug: 'button',
814
eyebrow: 'Component',
915
preview: 'button',
1016
code: `import { Button } from '@nim-ui/ui';\n\nexport function Example() {\n return (\n <div className="flex gap-3">\n <Button>Primary</Button>\n <Button variant="secondary">Secondary</Button>\n <Button variant="outline">Outline</Button>\n </div>\n );\n}`,
@@ -16,4 +22,4 @@ export const buttonPage: ComponentPage = {
1622
],
1723
},
1824
],
19-
};
25+
});
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import type { ComponentPage } from '../types';
1+
import { createComponentPage, defineComponentMeta } from '../types';
22

3-
export const cardPage: ComponentPage = {
4-
slug: 'card',
3+
export const meta = defineComponentMeta({
54
title: 'Card',
65
description:
76
'Card primitives help structure panels, settings surfaces, and summary blocks without introducing behavior-specific abstractions.',
7+
tokens: ['background', 'card', 'card-foreground', 'border', 'muted-foreground', 'radius'],
8+
dependencies: ['utils', 'tokens'],
9+
registry: 'card',
10+
});
11+
12+
export const cardPage = createComponentPage(meta, {
13+
slug: 'card',
814
eyebrow: 'Component',
915
preview: 'card',
1016
code: `import {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@nim-ui/ui';\n\nexport function Example() {\n return (\n <Card>\n <CardHeader>\n <CardTitle>Workspace analytics</CardTitle>\n <CardDescription>Weekly delivery overview.</CardDescription>\n </CardHeader>\n <CardContent>Track releases and contributor activity.</CardContent>\n </Card>\n );\n}`,
@@ -16,4 +22,4 @@ export const cardPage: ComponentPage = {
1622
],
1723
},
1824
],
19-
};
25+
});
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import type { ComponentPage } from '../types';
1+
import { createComponentPage, defineComponentMeta } from '../types';
22

3-
export const inputPage: ComponentPage = {
4-
slug: 'input',
3+
export const meta = defineComponentMeta({
54
title: 'Input',
65
description:
76
'Inputs stay lightweight and accessible while inheriting semantic focus, border, and placeholder behavior from the theme layer.',
7+
tokens: ['background', 'foreground', 'input', 'muted-foreground', 'radius', 'ring'],
8+
dependencies: ['utils', 'tokens'],
9+
registry: 'input',
10+
});
11+
12+
export const inputPage = createComponentPage(meta, {
13+
slug: 'input',
814
eyebrow: 'Component',
915
preview: 'input',
1016
code: `import { Input } from '@nim-ui/ui';\n\nexport function Example() {\n return <Input placeholder="[email protected]" type="email" />;\n}`,
@@ -16,4 +22,4 @@ export const inputPage: ComponentPage = {
1622
],
1723
},
1824
],
19-
};
25+
});

apps/docs/content/docs/theming.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const themingPage: DocPage = {
1111
title: 'Token layers',
1212
paragraphs: [
1313
'Primitive colors live in the tokens package under brand, accent, neutral, and semantic groups. Applications consume semantic variables such as background, card, and primary instead of raw brand values.',
14+
'The pipeline in this repository is explicit: registry metadata points to semantic tokens, the tokens package maps them to CSS variables, the UI package consumes those variables, and the docs app demonstrates the same system without a separate theme fork.',
1415
],
1516
codeBlocks: [
1617
{
@@ -44,5 +45,18 @@ export const themingPage: DocPage = {
4445
},
4546
],
4647
},
48+
{
49+
title: 'End-to-end pipeline',
50+
paragraphs: [
51+
'The docs are not a disconnected marketing surface. Each component page carries typed metadata that mirrors the registry, so tokens and dependencies shown in docs stay aligned with the component ecosystem contract.',
52+
],
53+
codeBlocks: [
54+
{
55+
label: 'Pipeline',
56+
language: 'bash',
57+
code: `registry -> docs meta -> design tokens -> CSS variables -> @nim-ui/ui -> docs and consumer apps`,
58+
},
59+
],
60+
},
4761
],
4862
};

apps/docs/content/types.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import {
2+
getRegistryComponent,
3+
type RegistryComponentManifest,
4+
type RegistryComponentName,
5+
type RegistryDependency,
6+
type RegistryTokenReference,
7+
} from '@nim-ui/registry';
8+
19
export interface CodeBlockContent {
210
label: string;
311
language: 'bash' | 'tsx' | 'css' | 'json';
@@ -20,6 +28,57 @@ export interface DocPage {
2028
}
2129

2230
export interface ComponentPage extends DocPage {
23-
preview: 'button' | 'input' | 'card' | 'badge';
31+
manifest: RegistryComponentManifest;
32+
meta: ComponentDocMeta;
33+
preview: RegistryComponentName;
34+
code: string;
35+
}
36+
37+
export interface ComponentDocMeta {
38+
title: string;
39+
description: string;
40+
tokens: RegistryTokenReference[];
41+
dependencies: RegistryDependency[];
42+
registry: RegistryComponentName;
43+
}
44+
45+
export interface ComponentPageContent {
46+
slug: RegistryComponentName;
47+
eyebrow: string;
48+
preview: RegistryComponentName;
2449
code: string;
50+
sections: DocSection[];
51+
}
52+
53+
export function defineComponentMeta(meta: ComponentDocMeta) {
54+
const manifest = getRegistryComponent(meta.registry);
55+
56+
if (meta.dependencies.join('|') !== manifest.dependencies.join('|')) {
57+
throw new Error(
58+
`Component doc meta for "${meta.registry}" must mirror registry dependencies.`,
59+
);
60+
}
61+
62+
if (meta.tokens.join('|') !== manifest.tokens.join('|')) {
63+
throw new Error(
64+
`Component doc meta for "${meta.registry}" must mirror registry tokens.`,
65+
);
66+
}
67+
68+
return meta;
69+
}
70+
71+
export function createComponentPage(
72+
meta: ComponentDocMeta,
73+
content: ComponentPageContent,
74+
): ComponentPage {
75+
const manifest = getRegistryComponent(meta.registry);
76+
77+
return {
78+
...content,
79+
description: meta.description,
80+
manifest,
81+
meta,
82+
title: meta.title,
83+
};
2584
}

0 commit comments

Comments
 (0)