Skip to content

Commit bde692f

Browse files
authored
fix: render primitive types from spec config (#131)
* fix: render primitive types from spec config * chore: retrigger cla check --------- Co-authored-by: Julio César Suástegui <juliosuas@users.noreply.github.com>
1 parent e17180d commit bde692f

9 files changed

Lines changed: 108 additions & 24 deletions

File tree

docs/spec.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,24 @@ components:
5959
6060
The `<scale-level>` placeholder represents a named level in a sizing or spacing scale. Common level names include `xs`, `sm`, `md`, `lg`, `xl`, and `full`. Any descriptive string key is valid.
6161

62-
**Color**: A color value is any valid CSS color string. Supported formats include:
62+
**Color**: A color value is any valid CSS color string.
6363

64-
* Hex: `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`
65-
* Named colors: `red`, `cornflowerblue`, `transparent`
66-
* Functional: `rgb()`, `rgba()`, `hsl()`, `hsla()`, `hwb()`
67-
* Wide-gamut: `oklch()`, `oklab()`, `lch()`, `lab()`
68-
* Mixing: `color-mix(in srgb, ...)`
64+
Supported formats include:
65+
66+
- Hex: `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`
67+
- Named colors: `red`, `cornflowerblue`, `transparent`
68+
- Functional: `rgb()`, `rgba()`, `hsl()`, `hsla()`, `hwb()`
69+
- Wide-gamut: `oklch()`, `oklab()`, `lch()`, `lab()`
70+
- Mixing: `color-mix(in srgb, ...)`
6971

7072
All color values are internally converted to sRGB for WCAG contrast checking. The original format is preserved for display and export.
7173

7274
Hex notation (`#RRGGBB`) remains the recommended default for simplicity and broad tooling support.
7375

76+
**Dimension**: A dimension value is a string with a unit suffix.
77+
78+
Valid units are: px, em, rem.
79+
7480
- `fontFamily` (string)
7581
- `fontSize` (Dimension)
7682
- `fontWeight` (number) - A numeric font weight value (e.g., `400`, `700`). In YAML, this may be expressed as either a bare number or a quoted string; both are equivalent.
@@ -81,8 +87,6 @@ Hex notation (`#RRGGBB`) remains the recommended default for simplicity and broa
8187
- `fontVariation` (string) - configures
8288
[`font-variation-settings`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-variation-settings).
8389

84-
**Dimension**: A dimension value is a string with a unit suffix. Valid units are: px, em, rem.
85-
8690
**Token References**: A token reference must be wrapped in curly braces, and contain an object path to another value in the YAML tree. For most token groups, the reference must point to a primitive value (e.g., `colors.primary-60`), not a group (e.g., `colors`). Within the `components` section, references to composite values (e.g., `{typography.label-md}`) are permitted.
8791

8892
# Sections

packages/cli/src/linter/spec-config.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
loadSpecConfig,
1919
getSpecConfig,
2020
STANDARD_UNITS,
21+
SPEC_TYPES,
2122
SECTIONS,
2223
TYPOGRAPHY_PROPERTIES,
2324
COMPONENT_SUB_TOKENS,
@@ -159,6 +160,14 @@ describe('spec-config structural invariants', () => {
159160
expect(new Set(STANDARD_UNITS).size).toBe(STANDARD_UNITS.length);
160161
});
161162

163+
it('primitive type definitions are non-empty', () => {
164+
expect(Object.keys(SPEC_TYPES).length).toBeGreaterThan(0);
165+
for (const [name, typeDef] of Object.entries(SPEC_TYPES)) {
166+
expect(name.length).toBeGreaterThan(0);
167+
expect(typeDef.description.length).toBeGreaterThan(0);
168+
}
169+
});
170+
162171
it('recommended token categories are non-empty', () => {
163172
for (const [category, tokens] of Object.entries(RECOMMENDED_TOKENS)) {
164173
expect(tokens.length).toBeGreaterThan(0);

packages/cli/src/linter/spec-config.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,22 @@ const PropertyDefSchema = z.object({
3838
description: z.string().optional(),
3939
});
4040

41+
const TypeDefSchema = z.object({
42+
description: z.string(),
43+
formats: z.array(z.string()).optional(),
44+
units: z.array(z.string()).optional(),
45+
note: z.string().optional(),
46+
recommendation: z.string().optional(),
47+
});
48+
4149
const ConfigSchema = z.object({
4250
version: z.string(),
4351
limits: z.object({
4452
max_token_nesting_depth: z.number().default(20),
4553
max_reference_depth: z.number().default(10),
4654
}).default({}),
4755
units: z.array(z.string()).min(1),
56+
types: z.record(z.string(), TypeDefSchema),
4857
sections: z.array(z.object({
4958
canonical: z.string(),
5059
aliases: z.array(z.string()).optional(),
@@ -112,6 +121,19 @@ export interface ComponentSubTokenDef {
112121
description?: string | undefined;
113122
}
114123

124+
export interface TypeDef {
125+
/** One-sentence definition for the type. */
126+
description: string;
127+
/** Accepted formats rendered as a bullet list in the generated spec. */
128+
formats?: readonly string[] | undefined;
129+
/** Accepted units for dimensional types. */
130+
units?: readonly string[] | undefined;
131+
/** Additional normative or implementation note. */
132+
note?: string | undefined;
133+
/** Non-normative authoring recommendation. */
134+
recommendation?: string | undefined;
135+
}
136+
115137
// ── Constant exports ─────────────────────────────────────────────────
116138
// These are eagerly initialized from the lazy singleton on first import.
117139
// The singleton cache ensures the YAML file is read exactly once.
@@ -129,6 +151,9 @@ export const MAX_REFERENCE_DEPTH = config.limits.max_reference_depth;
129151
export const STANDARD_UNITS = config.units;
130152
export type StandardUnit = (typeof STANDARD_UNITS)[number];
131153

154+
/** Primitive type definitions rendered into the generated spec. */
155+
export const SPEC_TYPES: Record<string, TypeDef> = config.types;
156+
132157
export const SECTIONS = config.sections;
133158

134159
export const TYPOGRAPHY_PROPERTIES: readonly TypographyPropertyDef[] = config.typography_properties;
@@ -175,6 +200,7 @@ export interface SpecConfig {
175200
MAX_TOKEN_NESTING_DEPTH: typeof MAX_TOKEN_NESTING_DEPTH;
176201
MAX_REFERENCE_DEPTH: typeof MAX_REFERENCE_DEPTH;
177202
STANDARD_UNITS: typeof STANDARD_UNITS;
203+
SPEC_TYPES: typeof SPEC_TYPES;
178204
SECTIONS: typeof SECTIONS;
179205
TYPOGRAPHY_PROPERTIES: typeof TYPOGRAPHY_PROPERTIES;
180206
COMPONENT_SUB_TOKENS: typeof COMPONENT_SUB_TOKENS;
@@ -189,6 +215,7 @@ export const SPEC_CONFIG: SpecConfig = {
189215
MAX_TOKEN_NESTING_DEPTH,
190216
MAX_REFERENCE_DEPTH,
191217
STANDARD_UNITS,
218+
SPEC_TYPES,
192219
SECTIONS,
193220
TYPOGRAPHY_PROPERTIES,
194221
COMPONENT_SUB_TOKENS,

packages/cli/src/linter/spec-config.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ units:
2828
- em
2929
- rem
3030

31+
types:
32+
Color:
33+
description: A color value is any valid CSS color string.
34+
formats:
35+
- "Hex: `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`"
36+
- "Named colors: `red`, `cornflowerblue`, `transparent`"
37+
- "Functional: `rgb()`, `rgba()`, `hsl()`, `hsla()`, `hwb()`"
38+
- "Wide-gamut: `oklch()`, `oklab()`, `lch()`, `lab()`"
39+
- "Mixing: `color-mix(in srgb, ...)`"
40+
note: All color values are internally converted to sRGB for WCAG contrast checking. The original format is preserved for display and export.
41+
recommendation: Hex notation (`#RRGGBB`) remains the recommended default for simplicity and broad tooling support.
42+
Dimension:
43+
description: A dimension value is a string with a unit suffix.
44+
units:
45+
- px
46+
- em
47+
- rem
48+
3149
sections:
3250
- canonical: Overview
3351
aliases:

packages/cli/src/linter/spec-gen/compiler.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ describe('compileMdx', () => {
7070
typographyExample: () => renderers.typographyExample(cfg),
7171
componentsExample: () => renderers.componentsExample(cfg),
7272
typographyPropertyList: () => renderers.typographyPropertyList(cfg),
73+
typeDefinitions: () => renderers.typeDefinitions(cfg),
7374
sectionOrderList: () => renderers.sectionOrderList(cfg),
7475
componentSubTokenList: () => renderers.componentSubTokenList(cfg),
7576
recommendedTokens: () => renderers.recommendedTokens(cfg),

packages/cli/src/linter/spec-gen/generate.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ async function main() {
4646
typographyExample: () => renderers.typographyExample(cfg),
4747
componentsExample: () => renderers.componentsExample(cfg),
4848
typographyPropertyList: () => renderers.typographyPropertyList(cfg),
49+
typeDefinitions: () => renderers.typeDefinitions(cfg),
4950
sectionOrderList: () => renderers.sectionOrderList(cfg),
5051
componentSubTokenList: () => renderers.componentSubTokenList(cfg),
5152
recommendedTokens: () => renderers.recommendedTokens(cfg),

packages/cli/src/linter/spec-gen/renderers.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* Each function returns a ready-to-embed markdown string.
2020
*/
2121

22-
import type { SpecConfig, TypographyPropertyDef, SectionDef, ComponentSubTokenDef } from '../spec-config.js';
22+
import type { SpecConfig, TypographyPropertyDef, SectionDef, ComponentSubTokenDef, TypeDef } from '../spec-config.js';
2323

2424
// ── YAML code block helpers ─────────────────────────────────────
2525

@@ -95,6 +95,36 @@ export function typographyPropertyList(config: SpecConfig): string {
9595
).join('\n');
9696
}
9797

98+
/** Primitive type definitions for the schema section. */
99+
export function typeDefinitions(config: SpecConfig): string {
100+
return Object.entries(config.SPEC_TYPES)
101+
.map(([name, typeDef]) => typeDefinition(name, typeDef))
102+
.join('\n\n');
103+
}
104+
105+
function typeDefinition(name: string, typeDef: TypeDef): string {
106+
const lines = [`**${name}**: ${typeDef.description}`];
107+
108+
if (typeDef.formats?.length) {
109+
lines.push('', 'Supported formats include:', '');
110+
lines.push(...typeDef.formats.map(format => `- ${format}`));
111+
}
112+
113+
if (typeDef.units?.length) {
114+
lines.push('', `Valid units are: ${typeDef.units.join(', ')}.`);
115+
}
116+
117+
if (typeDef.note) {
118+
lines.push('', typeDef.note);
119+
}
120+
121+
if (typeDef.recommendation) {
122+
lines.push('', typeDef.recommendation);
123+
}
124+
125+
return lines.join('\n');
126+
}
127+
98128
/** Numbered section order list with aliases. */
99129
export function sectionOrderList(config: SpecConfig): string {
100130
return config.SECTIONS.map((s: SectionDef, i: number) => {

packages/cli/src/linter/spec-gen/spec-helpers.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ describe('getSpecContent', () => {
5858
expect(content.length).toBeGreaterThan(1000);
5959
});
6060

61+
it('renders primitive type definitions from spec config', () => {
62+
const content = getSpecContent();
63+
expect(content).toContain('**Color**: A color value is any valid CSS color string.');
64+
expect(content).toContain('**Dimension**: A dimension value is a string with a unit suffix.');
65+
});
66+
6167
it('accepts an explicit specPath override', () => {
6268
// This tests the explicit-path contract. If someone passes a path,
6369
// it should use that path exactly — no guessing.

packages/cli/src/linter/spec-gen/spec.mdx

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { SPEC_VERSION, STANDARD_UNITS, SECTIONS, TYPOGRAPHY_PROPERTIES, COMPONENT_SUB_TOKENS, CORE_COLOR_ROLES, RECOMMENDED_TOKENS, EXAMPLES } from '../spec-config.js'
2-
import { frontmatterExample, colorsExample, typographyExample, componentsExample, typographyPropertyList, sectionOrderList, componentSubTokenList, recommendedTokens } from './renderers.js'
1+
import { SPEC_VERSION, SECTIONS, TYPOGRAPHY_PROPERTIES, COMPONENT_SUB_TOKENS, CORE_COLOR_ROLES, RECOMMENDED_TOKENS, EXAMPLES } from '../spec-config.js'
2+
import { frontmatterExample, colorsExample, typographyExample, componentsExample, typographyPropertyList, typeDefinitions, sectionOrderList, componentSubTokenList, recommendedTokens } from './renderers.js'
33

44
# DESIGN.md Format
55

@@ -43,22 +43,10 @@ components:
4343
4444
The `<scale-level>` placeholder represents a named level in a sizing or spacing scale. Common level names include `xs`, `sm`, `md`, `lg`, `xl`, and `full`. Any descriptive string key is valid.
4545

46-
**Color**: A color value is any valid CSS color string. Supported formats include:
47-
48-
- Hex: `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`
49-
- Named colors: `red`, `cornflowerblue`, `transparent`
50-
- Functional: `rgb()`, `rgba()`, `hsl()`, `hsla()`, `hwb()`
51-
- Wide-gamut: `oklch()`, `oklab()`, `lch()`, `lab()`
52-
- Mixing: `color-mix(in srgb, ...)`
53-
54-
All color values are internally converted to sRGB for WCAG contrast checking. The original format is preserved for display and export.
55-
56-
Hex notation (`#RRGGBB`) remains the recommended default for simplicity and broad tooling support.
46+
{typeDefinitions()}
5747

5848
{typographyPropertyList()}
5949

60-
**Dimension**: A dimension value is a string with a unit suffix. Valid units are: {STANDARD_UNITS.join(', ')}.
61-
6250
**Token References**: A token reference must be wrapped in curly braces, and contain an object path to another value in the YAML tree. For most token groups, the reference must point to a primitive value (e.g., `colors.primary-60`), not a group (e.g., `colors`). Within the `components` section, references to composite values (e.g., `{typography.label-md}`) are permitted.
6351

6452
# Sections

0 commit comments

Comments
 (0)