guardian-framework 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,351 @@
1
+ ---
2
+ name: design-system-enterprise-codegen
3
+ description: Full reference for design system architecture with DDD + adapter pattern. Covers design tokens, CSS framework integration (Tailwind/MUI/Material/CSS Modules), component API contracts, theming, accessibility, and testing. Framework-agnostic — use with any component library.
4
+ ---
5
+
6
+ # Design System & CSS Architecture — DDD with Adapter Pattern
7
+
8
+ > Canonical skill for building enterprise design systems using DDD principles.
9
+ > **Framework-agnostic** — works with Next.js, Angular, Vue, or any component framework.
10
+ > **CSS-agnostic** — works with Tailwind, MUI, Material, CSS Modules, or any styling approach.
11
+ >
12
+ > Use alongside `.pi/skills/agents/nextjs-codegen.md` or `.pi/skills/agents/angular-codegen.md`.
13
+
14
+ ---
15
+
16
+ ## The Core Idea: Domain → Infrastructure Adapter
17
+
18
+ A design system shouldn't be coupled to a specific CSS framework. The DDD layer structure enforces this:
19
+
20
+ ```
21
+ domain/tokens/ ← Pure TypeScript: color values, spacing, typography
22
+ │ (zero CSS imports, zero framework imports)
23
+
24
+ application/ ← Logic: variant resolution, theme switching
25
+ │ (pure functions, no rendering)
26
+
27
+ infrastructure/ ← CSS ADAPTER: translates tokens into Tailwind/MUI/CSS Modules
28
+ │ (THIS is the only layer that knows about your CSS framework)
29
+
30
+ interfaces/button/ ← Components: use the adapter, render the UI
31
+ ```
32
+
33
+ | If you use... | Infrastructure adapter does | Components stay the same? |
34
+ |---------------|---------------------------|---------------------------|
35
+ | **Tailwind** | Generates `bg-primary text-white` class strings from tokens | ✅ Yes |
36
+ | **CSS Modules** | Generates `.module.css` with `var(--color-primary)` | ✅ Yes |
37
+ | **MUI** | Wraps MUI's `<Button>` component, maps tokens to MUI theme | ✅ Yes |
38
+ | **shadcn/ui** | Re-exports with token-based variants | ✅ Yes |
39
+ | **Styled Components** | Generates template literals | ✅ (but breaks RSC) |
40
+
41
+ ---
42
+
43
+ ## 1. Design System Structure (DDD Pattern)
44
+
45
+ ```
46
+ shared/
47
+ ui/
48
+ domain/ # Framework-free. Zero CSS imports.
49
+ tokens/ # Design tokens as typed value objects
50
+ colors.ts # ColorToken — typed color palette
51
+ typography.ts # TypeScale — font families, sizes
52
+ spacing.ts # SpacingScale — margin, padding units
53
+ shadows.ts # ShadowToken — elevation levels
54
+ breakpoints.ts # Breakpoint — responsive breakpoints
55
+ types.ts # Component API contracts
56
+ component-api.ts # Base interfaces for all components
57
+ application/ # Logic layer (pure functions)
58
+ variant-engine.ts # Variant resolution (cva-like)
59
+ theme-service.ts # Dark/light theme switching
60
+ token-resolver.ts # Token → value lookup
61
+ infrastructure/ # CSS ADAPTER — swap this per framework
62
+ tailwind-adapter.ts # Tailwind class generation from tokens
63
+ css-modules-adapter.ts # CSS Modules variable injection
64
+ mui-adapter.ts # MUI theme provider wrapper
65
+ material-adapter.ts # Angular Material theme mapping
66
+ interfaces/ # Rendered components
67
+ button/
68
+ button.tsx # Component (imports from application + infrastructure)
69
+ button.test.tsx # Tests
70
+ button.stories.tsx # Storybook stories
71
+ card/
72
+ modal/
73
+ form/
74
+ ```
75
+
76
+ ---
77
+
78
+ ## 2. Domain Layer — Design Tokens (Framework-Free)
79
+
80
+ This layer has zero CSS, zero framework, zero styling. It's pure TypeScript values.
81
+
82
+ ```typescript
83
+ // domain/tokens/colors.ts
84
+ export class ColorToken {
85
+ private constructor(
86
+ public readonly name: string,
87
+ public readonly value: string, // Hex: '#0f766e'
88
+ public readonly description: string,
89
+ ) {}
90
+
91
+ static readonly PRIMARY = new ColorToken('primary', '#0f766e', 'Primary brand color');
92
+ static readonly PRIMARY_HOVER = new ColorToken('primary-hover', '#0d5e56');
93
+ static readonly DESTRUCTIVE = new ColorToken('destructive', '#dc2626');
94
+ static readonly BACKGROUND = new ColorToken('background', '#ffffff');
95
+ static readonly FOREGROUND = new ColorToken('foreground', '#0f172a');
96
+
97
+ // Dark mode overrides — still pure data, no CSS
98
+ static readonly DARK: Record<string, string> = {
99
+ background: '#0f172a',
100
+ foreground: '#f8fafc',
101
+ };
102
+
103
+ static all(): ColorToken[] {
104
+ return Object.values(this).filter(v => v instanceof ColorToken);
105
+ }
106
+ }
107
+
108
+ // domain/tokens/spacing.ts
109
+ export class SpacingToken {
110
+ constructor(
111
+ public readonly name: string,
112
+ public readonly value: string, // CSS value: '0.25rem'
113
+ public readonly px: number, // Design value: 4
114
+ ) {}
115
+
116
+ static readonly XS = new SpacingToken('xs', '0.25rem', 4);
117
+ static readonly SM = new SpacingToken('sm', '0.5rem', 8);
118
+ static readonly MD = new SpacingToken('md', '1rem', 16);
119
+ static readonly LG = new SpacingToken('lg', '1.5rem', 24);
120
+ static readonly XL = new SpacingToken('xl', '2rem', 32);
121
+ }
122
+
123
+ // domain/tokens/typography.ts
124
+ export class TypeScale {
125
+ constructor(
126
+ public readonly name: string,
127
+ public readonly fontSize: string,
128
+ public readonly lineHeight: string,
129
+ public readonly fontWeight: number,
130
+ ) {}
131
+
132
+ static readonly H1 = new TypeScale('h1', '2.5rem', '1.2', 700);
133
+ static readonly H2 = new TypeScale('h2', '2rem', '1.3', 600);
134
+ static readonly BODY = new TypeScale('body', '1rem', '1.5', 400);
135
+ static readonly SMALL = new TypeScale('small', '0.875rem', '1.5', 400);
136
+ }
137
+ ```
138
+
139
+ ### Why tokens as domain objects?
140
+
141
+ - **Type-safe** — `ColorToken.PRIMARY.value` is a `string`, can't pass an invalid color
142
+ - **Documented** — each token has a `description` field, self-documenting
143
+ - **Enumerable** — `ColorToken.all()` can generate CSS variables, Tailwind config, or MUI theme
144
+ - **Framework-independent** — the exact same tokens feed Tailwind, CSS Modules, or MUI
145
+
146
+ ---
147
+
148
+ ## 3. Application Layer — Variant Engine
149
+
150
+ ```typescript
151
+ // application/variant-engine.ts
152
+ export type VariantDefinition = {
153
+ base: string;
154
+ variants: Record<string, Record<string, string>>;
155
+ defaults: Record<string, string>;
156
+ };
157
+
158
+ export function defineVariants(config: VariantDefinition) {
159
+ return (props: Record<string, string>): string => {
160
+ const classes = [config.base];
161
+ for (const [key, values] of Object.entries(config.variants)) {
162
+ const value = props[key] ?? config.defaults[key];
163
+ if (value && values[value]) classes.push(values[value]);
164
+ }
165
+ return classes.filter(Boolean).join(' ');
166
+ };
167
+ }
168
+ ```
169
+
170
+ ---
171
+
172
+ ## 4. Infrastructure — CSS Framework Adapters
173
+
174
+ ### Tailwind Adapter
175
+
176
+ ```typescript
177
+ // infrastructure/tailwind-adapter.ts
178
+ // Translates domain tokens → Tailwind utility classes
179
+ import { ColorToken } from '../domain/tokens/colors';
180
+ import { SpacingToken } from '../domain/tokens/spacing';
181
+
182
+ export function tailwindColor(token: ColorToken): string {
183
+ const map: Record<string, string> = {
184
+ '#0f766e': 'bg-teal-700 text-white',
185
+ '#dc2626': 'bg-red-600 text-white',
186
+ '#ffffff': 'bg-white text-gray-900',
187
+ '#0f172a': 'bg-slate-900 text-white',
188
+ };
189
+ return map[token.value] ?? '';
190
+ }
191
+
192
+ export function tailwindSpacing(token: SpacingToken): string {
193
+ const map: Record<number, string> = {
194
+ 4: 'p-1', 8: 'p-2', 16: 'p-4', 24: 'p-6', 32: 'p-8',
195
+ };
196
+ return map[token.px] ?? '';
197
+ }
198
+ ```
199
+
200
+ ### CSS Modules Adapter
201
+
202
+ ```typescript
203
+ // infrastructure/css-modules-adapter.ts
204
+ // Generates CSS custom properties from domain tokens
205
+ import { ColorToken } from '../domain/tokens/colors';
206
+ import { SpacingToken } from '../domain/tokens/spacing';
207
+
208
+ export function generateCSSVariables(): string {
209
+ const colors = ColorToken.all().map(t =>
210
+ ` --color-${t.name}: ${t.value};`
211
+ ).join('\n');
212
+
213
+ return `:root {\n${colors}\n}`;
214
+ }
215
+ ```
216
+
217
+ ### MUI Adapter
218
+
219
+ ```typescript
220
+ // infrastructure/mui-adapter.ts
221
+ // Maps domain tokens to MUI theme structure
222
+ import { createTheme } from '@mui/material';
223
+ import { ColorToken } from '../domain/tokens/colors';
224
+
225
+ export function createMuiTheme() {
226
+ return createTheme({
227
+ palette: {
228
+ primary: { main: ColorToken.PRIMARY.value },
229
+ background: { default: ColorToken.BACKGROUND.value },
230
+ },
231
+ });
232
+ }
233
+ ```
234
+
235
+ ### Choosing an Adapter
236
+
237
+ | Adapter | When to use | Tradeoffs |
238
+ |---------|-------------|-----------|
239
+ | **Tailwind** | Startups, rapid prototyping, utility-first | Class strings get long; needs `clsx`/`tailwind-merge` |
240
+ | **CSS Modules** | Enterprise, strict design systems, component libraries | More boilerplate; full control over CSS |
241
+ | **MUI** | Need a complete component library out of the box | Bundle size; customization can be complex |
242
+ | **Material (Angular)** | Angular projects needing Material Design | Framework-locked; heavy |
243
+ | **shadcn/ui** | Next.js projects wanting Radix primitives with Tailwind | Requires Tailwind; new ecosystem |
244
+
245
+ **Rule:** Pick one adapter per project. Never mix. The adapter is the only file that changes when you switch CSS frameworks.
246
+
247
+ ---
248
+
249
+ ## 5. Component Implementation Pattern
250
+
251
+ ```typescript
252
+ // interfaces/button/button.tsx
253
+ import { defineVariants } from '../../application/variant-engine';
254
+ // ↑ import from application, not infrastructure
255
+ // Components DON'T import the adapter directly
256
+
257
+ const buttonVariants = defineVariants({
258
+ base: 'inline-flex items-center justify-center rounded-md transition-colors',
259
+ variants: {
260
+ variant: {
261
+ primary: 'bg-primary text-white hover:bg-primary-hover',
262
+ secondary: 'border border-primary text-primary hover:bg-primary/10',
263
+ ghost: 'hover:bg-accent',
264
+ },
265
+ size: { sm: 'h-9 px-3', md: 'h-10 px-4', lg: 'h-11 px-8' },
266
+ },
267
+ defaults: { variant: 'primary', size: 'md' },
268
+ });
269
+
270
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
271
+ variant?: 'primary' | 'secondary' | 'ghost';
272
+ size?: 'sm' | 'md' | 'lg';
273
+ }
274
+
275
+ export function Button({ variant, size, className, ...props }: ButtonProps) {
276
+ return (
277
+ <button
278
+ className={[buttonVariants({ variant, size }), className].filter(Boolean).join(' ')}
279
+ {...props}
280
+ />
281
+ );
282
+ }
283
+ ```
284
+
285
+ **Key rule:** Components import from `application/variant-engine.ts`, not from `infrastructure/`. The infrastructure adapter is only used at the app root (theme provider, CSS variable injection).
286
+
287
+ ---
288
+
289
+ ## 6. Theming (Dark Mode)
290
+
291
+ ```typescript
292
+ // application/theme-service.ts
293
+ import { ColorToken } from '../domain/tokens/colors';
294
+
295
+ export type Theme = 'light' | 'dark';
296
+
297
+ export class ThemeService {
298
+ private current: Theme = 'light';
299
+
300
+ get theme(): Theme { return this.current; }
301
+
302
+ setTheme(theme: Theme): void {
303
+ this.current = theme;
304
+ document.documentElement.setAttribute('data-theme', theme);
305
+ }
306
+
307
+ toggle(): void {
308
+ this.setTheme(this.current === 'light' ? 'dark' : 'light');
309
+ }
310
+ }
311
+ ```
312
+
313
+ ---
314
+
315
+ ## 7. Testing — Framework-Independent
316
+
317
+ ```typescript
318
+ // domain/tokens/colors.test.ts
319
+ describe('ColorToken', () => {
320
+ it('all tokens have valid hex values', () => {
321
+ for (const t of ColorToken.all()) {
322
+ expect(t.value).toMatch(/^#[0-9a-fA-F]{6}$/);
323
+ }
324
+ });
325
+ });
326
+
327
+ // application/variant-engine.test.ts
328
+ describe('VariantEngine', () => {
329
+ it('resolves variant classes', () => {
330
+ const button = defineVariants({ base: 'btn', variants: { color: { red: 'bg-red' } }, defaults: { color: 'red' } });
331
+ expect(button({})).toBe('btn bg-red');
332
+ });
333
+ });
334
+ ```
335
+
336
+ ---
337
+
338
+ ## 8. Choosing Your Stack — Quick Reference
339
+
340
+ | Project type | Recommended adapter | Why |
341
+ |-------------|-------------------|-----|
342
+ | Next.js app, small team | Tailwind + shadcn/ui | Fastest path, good defaults, RSC-compatible |
343
+ | Next.js app, large team | CSS Modules + custom tokens | Full control, strict design system |
344
+ | Angular app, Material look | Angular Material adapter | Native integration, theming built-in |
345
+ | Angular app, custom design | CSS Modules + SCSS tokens | Full control, SCSS features |
346
+ | Multi-framework design system | CSS Modules + CSS Custom Properties | Works everywhere, zero framework lock-in |
347
+
348
+ ---
349
+
350
+ *Version: 1.1.0*
351
+ *Last updated: 2026-07-03*