@primer/primitives 11.4.0 → 11.4.1-rc.14fb4bb1
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.
- package/DESIGN_TOKENS_GUIDE.md +185 -0
- package/DESIGN_TOKENS_SPEC.md +565 -0
- package/dist/build/formats/jsonFigma.js +8 -1
- package/dist/build/formats/markdownLlmGuidelines.d.ts +7 -6
- package/dist/build/formats/markdownLlmGuidelines.js +1034 -60
- package/dist/build/schemas/borderToken.d.ts +61 -5
- package/dist/build/schemas/borderToken.js +2 -1
- package/dist/build/schemas/colorToken.d.ts +639 -30
- package/dist/build/schemas/colorToken.js +3 -2
- package/dist/build/schemas/colorW3cValue.d.ts +28 -0
- package/dist/build/schemas/colorW3cValue.js +42 -0
- package/dist/build/schemas/cubicBezierToken.d.ts +1 -1
- package/dist/build/schemas/dimensionToken.d.ts +9 -2
- package/dist/build/schemas/dimensionValue.d.ts +12 -1
- package/dist/build/schemas/dimensionValue.js +10 -13
- package/dist/build/schemas/durationToken.d.ts +8 -2
- package/dist/build/schemas/durationValue.d.ts +11 -1
- package/dist/build/schemas/durationValue.js +13 -3
- package/dist/build/schemas/fontFamilyToken.d.ts +1 -1
- package/dist/build/schemas/fontWeightToken.d.ts +1 -1
- package/dist/build/schemas/gradientToken.d.ts +23 -2
- package/dist/build/schemas/gradientToken.js +2 -1
- package/dist/build/schemas/numberToken.d.ts +1 -1
- package/dist/build/schemas/shadowToken.d.ts +1751 -127
- package/dist/build/schemas/shadowToken.js +8 -2
- package/dist/build/schemas/stringToken.d.ts +1 -1
- package/dist/build/schemas/stringToken.js +1 -1
- package/dist/build/schemas/tokenType.d.ts +1 -1
- package/dist/build/schemas/transitionToken.d.ts +15 -3
- package/dist/build/schemas/typographyToken.d.ts +19 -5
- package/dist/build/schemas/typographyToken.js +1 -1
- package/dist/build/schemas/validTokenType.d.ts +1 -1
- package/dist/build/schemas/validTokenType.js +1 -1
- package/dist/build/schemas/viewportRangeToken.d.ts +1 -1
- package/dist/build/transformers/borderToCss.js +19 -1
- package/dist/build/transformers/colorAlphaToCss.js +6 -3
- package/dist/build/transformers/colorToHex.js +5 -2
- package/dist/build/transformers/colorToRgbAlpha.js +5 -2
- package/dist/build/transformers/colorToRgbaFloat.js +5 -0
- package/dist/build/transformers/dimensionToPixelUnitless.d.ts +3 -2
- package/dist/build/transformers/dimensionToPixelUnitless.js +22 -26
- package/dist/build/transformers/dimensionToRem.d.ts +2 -1
- package/dist/build/transformers/dimensionToRem.js +21 -22
- package/dist/build/transformers/dimensionToRemPxArray.d.ts +2 -1
- package/dist/build/transformers/dimensionToRemPxArray.js +21 -22
- package/dist/build/transformers/durationToCss.d.ts +2 -1
- package/dist/build/transformers/durationToCss.js +18 -11
- package/dist/build/transformers/gradientToCss.js +2 -1
- package/dist/build/transformers/shadowToCss.js +15 -1
- package/dist/build/transformers/utilities/normalizeColorValue.d.ts +23 -0
- package/dist/build/transformers/utilities/normalizeColorValue.js +74 -0
- package/dist/build/transformers/utilities/parseDimension.d.ts +12 -0
- package/dist/build/transformers/utilities/parseDimension.js +31 -0
- package/dist/build/types/borderTokenValue.d.ts +5 -2
- package/dist/build/types/dimensionTokenValue.d.ts +9 -0
- package/dist/build/types/shadowTokenValue.d.ts +8 -5
- package/dist/css/functional/themes/dark-colorblind-high-contrast.css +32 -28
- package/dist/css/functional/themes/dark-colorblind.css +32 -28
- package/dist/css/functional/themes/dark-dimmed-high-contrast.css +32 -28
- package/dist/css/functional/themes/dark-dimmed.css +32 -28
- package/dist/css/functional/themes/dark-high-contrast.css +32 -28
- package/dist/css/functional/themes/dark-tritanopia-high-contrast.css +32 -28
- package/dist/css/functional/themes/dark-tritanopia.css +32 -28
- package/dist/css/functional/themes/dark.css +32 -28
- package/dist/css/functional/themes/light-colorblind-high-contrast.css +32 -28
- package/dist/css/functional/themes/light-colorblind.css +32 -28
- package/dist/css/functional/themes/light-high-contrast.css +32 -28
- package/dist/css/functional/themes/light-tritanopia-high-contrast.css +32 -28
- package/dist/css/functional/themes/light-tritanopia.css +32 -28
- package/dist/css/functional/themes/light.css +32 -28
- package/dist/css/primitives.css +4 -0
- package/dist/docs/base/motion/motion.json +96 -24
- package/dist/docs/base/size/size.json +76 -19
- package/dist/docs/base/typography/typography.json +24 -6
- package/dist/docs/functional/size/border.json +26 -11
- package/dist/docs/functional/size/breakpoints.json +24 -6
- package/dist/docs/functional/size/radius.json +16 -4
- package/dist/docs/functional/size/size.json +60 -15
- package/dist/docs/functional/themes/dark-colorblind-high-contrast.json +2629 -346
- package/dist/docs/functional/themes/dark-colorblind.json +2629 -346
- package/dist/docs/functional/themes/dark-dimmed-high-contrast.json +2629 -346
- package/dist/docs/functional/themes/dark-dimmed.json +2629 -346
- package/dist/docs/functional/themes/dark-high-contrast.json +2629 -346
- package/dist/docs/functional/themes/dark-tritanopia-high-contrast.json +2629 -346
- package/dist/docs/functional/themes/dark-tritanopia.json +2629 -346
- package/dist/docs/functional/themes/dark.json +2629 -346
- package/dist/docs/functional/themes/light-colorblind-high-contrast.json +2635 -352
- package/dist/docs/functional/themes/light-colorblind.json +2632 -349
- package/dist/docs/functional/themes/light-high-contrast.json +2635 -352
- package/dist/docs/functional/themes/light-tritanopia-high-contrast.json +2635 -352
- package/dist/docs/functional/themes/light-tritanopia.json +2632 -349
- package/dist/docs/functional/themes/light.json +2632 -349
- package/dist/docs/functional/typography/typography.json +8 -2
- package/dist/fallbacks/base/motion/motion.json +48 -12
- package/dist/figma/themes/light-colorblind.json +4 -4
- package/dist/figma/themes/light-high-contrast.json +4 -4
- package/dist/figma/themes/light-tritanopia.json +4 -4
- package/dist/figma/themes/light.json +4 -4
- package/dist/internalCss/dark-colorblind-high-contrast.css +28 -28
- package/dist/internalCss/dark-colorblind.css +28 -28
- package/dist/internalCss/dark-dimmed-high-contrast.css +28 -28
- package/dist/internalCss/dark-dimmed.css +28 -28
- package/dist/internalCss/dark-high-contrast.css +28 -28
- package/dist/internalCss/dark-tritanopia-high-contrast.css +28 -28
- package/dist/internalCss/dark-tritanopia.css +28 -28
- package/dist/internalCss/dark.css +28 -28
- package/dist/internalCss/light-colorblind-high-contrast.css +28 -28
- package/dist/internalCss/light-colorblind.css +28 -28
- package/dist/internalCss/light-high-contrast.css +28 -28
- package/dist/internalCss/light-tritanopia-high-contrast.css +28 -28
- package/dist/internalCss/light-tritanopia.css +28 -28
- package/dist/internalCss/light.css +28 -28
- package/dist/styleLint/base/motion/motion.json +96 -24
- package/dist/styleLint/base/size/size.json +76 -19
- package/dist/styleLint/base/typography/typography.json +30 -12
- package/dist/styleLint/functional/size/border.json +27 -12
- package/dist/styleLint/functional/size/breakpoints.json +24 -6
- package/dist/styleLint/functional/size/radius.json +17 -5
- package/dist/styleLint/functional/size/size-coarse.json +3 -3
- package/dist/styleLint/functional/size/size-fine.json +3 -3
- package/dist/styleLint/functional/size/size.json +111 -66
- package/dist/styleLint/functional/themes/dark-colorblind-high-contrast.json +2757 -366
- package/dist/styleLint/functional/themes/dark-colorblind.json +2757 -366
- package/dist/styleLint/functional/themes/dark-dimmed-high-contrast.json +2757 -366
- package/dist/styleLint/functional/themes/dark-dimmed.json +2757 -366
- package/dist/styleLint/functional/themes/dark-high-contrast.json +2757 -366
- package/dist/styleLint/functional/themes/dark-tritanopia-high-contrast.json +2757 -366
- package/dist/styleLint/functional/themes/dark-tritanopia.json +2757 -366
- package/dist/styleLint/functional/themes/dark.json +2757 -366
- package/dist/styleLint/functional/themes/light-colorblind-high-contrast.json +2763 -372
- package/dist/styleLint/functional/themes/light-colorblind.json +2760 -369
- package/dist/styleLint/functional/themes/light-high-contrast.json +2763 -372
- package/dist/styleLint/functional/themes/light-tritanopia-high-contrast.json +2763 -372
- package/dist/styleLint/functional/themes/light-tritanopia.json +2760 -369
- package/dist/styleLint/functional/themes/light.json +2760 -369
- package/dist/styleLint/functional/typography/typography.json +28 -22
- package/package.json +9 -7
- package/src/tokens/base/motion/timing.json5 +12 -12
- package/src/tokens/base/size/size.json5 +194 -194
- package/src/tokens/base/typography/typography.json5 +6 -6
- package/src/tokens/component/avatar.json5 +72 -44
- package/src/tokens/component/button.json5 +1545 -1193
- package/src/tokens/functional/border/border.json5 +4 -1
- package/src/tokens/functional/color/bgColor.json5 +8 -0
- package/src/tokens/functional/color/display.json5 +7 -0
- package/src/tokens/functional/color/fgColor.json5 +8 -0
- package/src/tokens/functional/color/syntax.json5 +14 -0
- package/src/tokens/functional/shadow/shadow.json5 +1254 -163
- package/src/tokens/functional/size/border.json5 +8 -8
- package/src/tokens/functional/size/breakpoints.json5 +6 -6
- package/src/tokens/functional/size/radius.json5 +4 -4
- package/src/tokens/functional/size/size.json5 +15 -15
- package/src/tokens/functional/typography/typography.json5 +8 -4
- package/dist/build/parsers/index.d.ts +0 -1
- package/dist/build/parsers/index.js +0 -1
- package/dist/build/parsers/w3cJsonParser.d.ts +0 -6
- package/dist/build/parsers/w3cJsonParser.js +0 -25
- package/dist/removed/testing.json5 +0 -4
- package/guidelines/color.llm.md +0 -16
- package/guidelines/guidelines.llm.md +0 -34
- package/guidelines/motion.llm.md +0 -41
- package/guidelines/spacing.llm.md +0 -20
- package/guidelines/typography.llm.md +0 -14
- package/src/tokens/removed/testing.json5 +0 -4
- package/token-guidelines.llm.md +0 -695
|
@@ -8,12 +8,613 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { sortByName } from 'style-dictionary/utils';
|
|
11
|
+
// Semantic sets that should be grouped into tables
|
|
12
|
+
const SEMANTIC_SETS = [
|
|
13
|
+
'accent',
|
|
14
|
+
'danger',
|
|
15
|
+
'success',
|
|
16
|
+
'attention',
|
|
17
|
+
'severe',
|
|
18
|
+
'open',
|
|
19
|
+
'closed',
|
|
20
|
+
'done',
|
|
21
|
+
'neutral',
|
|
22
|
+
'sponsors',
|
|
23
|
+
'upsell',
|
|
24
|
+
];
|
|
25
|
+
// Categories that can be merged with wildcard patterns
|
|
26
|
+
const MERGEABLE_CATEGORIES = ['bgColor', 'borderColor', 'fgColor'];
|
|
27
|
+
// Global semantic key definitions - these explain what each semantic color means
|
|
28
|
+
// so we don't repeat this info in every table row
|
|
29
|
+
const SEMANTIC_KEY = {
|
|
30
|
+
danger: {
|
|
31
|
+
meaning: 'Errors, destructive actions, critical warnings',
|
|
32
|
+
usage: 'delete buttons, error messages, validation errors',
|
|
33
|
+
textPairing: 'fg.danger (muted bg) or fg.onEmphasis (emphasis bg)',
|
|
34
|
+
},
|
|
35
|
+
success: {
|
|
36
|
+
meaning: 'Positive states, confirmations, completed actions',
|
|
37
|
+
usage: 'merge buttons, success messages, confirmations',
|
|
38
|
+
textPairing: 'fg.success (muted bg) or fg.onEmphasis (emphasis bg)',
|
|
39
|
+
},
|
|
40
|
+
attention: {
|
|
41
|
+
meaning: 'Warnings, caution states requiring user awareness',
|
|
42
|
+
usage: 'warning banners, caution labels, pending states',
|
|
43
|
+
textPairing: 'fg.attention (muted bg) or fg.default (emphasis bg, due to yellow contrast)',
|
|
44
|
+
},
|
|
45
|
+
severe: {
|
|
46
|
+
meaning: 'High-priority warnings, more urgent than attention',
|
|
47
|
+
usage: 'urgent messages, escalations, high-priority indicators',
|
|
48
|
+
textPairing: 'fg.severe (muted bg) or fg.onEmphasis (emphasis bg)',
|
|
49
|
+
},
|
|
50
|
+
accent: {
|
|
51
|
+
meaning: 'Selected, focused, or highlighted interactive elements',
|
|
52
|
+
usage: 'active states, selected rows, focus indicators',
|
|
53
|
+
textPairing: 'fg.accent (muted bg) or fg.onEmphasis (emphasis bg)',
|
|
54
|
+
},
|
|
55
|
+
neutral: {
|
|
56
|
+
meaning: 'Non-semantic, secondary UI elements',
|
|
57
|
+
usage: 'secondary buttons, tags, labels without status meaning',
|
|
58
|
+
textPairing: 'fg.default (muted bg) or fg.onEmphasis (emphasis bg)',
|
|
59
|
+
},
|
|
60
|
+
open: {
|
|
61
|
+
meaning: 'Open/active state indicators (GitHub issues, PRs)',
|
|
62
|
+
usage: 'open issues, open PRs, active discussions',
|
|
63
|
+
textPairing: 'fg.open (muted bg) or fg.onEmphasis (emphasis bg)',
|
|
64
|
+
},
|
|
65
|
+
closed: {
|
|
66
|
+
meaning: 'Closed/declined state indicators (GitHub issues, PRs)',
|
|
67
|
+
usage: 'closed issues, closed PRs, declined items',
|
|
68
|
+
textPairing: 'fg.closed (muted bg) or fg.onEmphasis (emphasis bg)',
|
|
69
|
+
},
|
|
70
|
+
done: {
|
|
71
|
+
meaning: 'Completed/merged state indicators',
|
|
72
|
+
usage: 'merged PRs, completed tasks, finished items',
|
|
73
|
+
textPairing: 'fg.done (muted bg) or fg.onEmphasis (emphasis bg)',
|
|
74
|
+
},
|
|
75
|
+
sponsors: {
|
|
76
|
+
meaning: 'GitHub Sponsors content only',
|
|
77
|
+
usage: 'sponsor buttons, funding prompts, sponsor cards',
|
|
78
|
+
textPairing: 'fg.sponsors (muted bg) or fg.onEmphasis (emphasis bg)',
|
|
79
|
+
},
|
|
80
|
+
upsell: {
|
|
81
|
+
meaning: 'Upgrade prompts, premium features, promotional content',
|
|
82
|
+
usage: 'upgrade buttons, premium badges, promotional banners',
|
|
83
|
+
textPairing: 'fg.upsell (muted bg) or fg.onEmphasis (emphasis bg)',
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
// Typography role groupings for better LLM comprehension
|
|
87
|
+
const TYPOGRAPHY_ROLES = [
|
|
88
|
+
{
|
|
89
|
+
role: 'Headings',
|
|
90
|
+
description: 'Title and display text styles for headings and hero sections.',
|
|
91
|
+
patterns: ['text-title-', 'text-display-', 'text-subtitle-'],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
role: 'Body',
|
|
95
|
+
description: 'Body text and caption styles for content and UI labels.',
|
|
96
|
+
patterns: ['text-body-', 'text-caption-'],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
role: 'Code',
|
|
100
|
+
description: 'Monospace text styles for code blocks and inline code.',
|
|
101
|
+
patterns: ['text-code'],
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
// Size scales for pattern-based compression
|
|
105
|
+
const SIZE_SCALES = ['xsmall', 'small', 'medium', 'large', 'xlarge'];
|
|
106
|
+
const DENSITY_SCALES = ['condensed', 'normal', 'spacious'];
|
|
107
|
+
// Categories that use pattern-based compression for size tokens
|
|
108
|
+
const PATTERN_COMPRESSED_CATEGORIES = {
|
|
109
|
+
control: {
|
|
110
|
+
scaleNote: 'Use xsmall/small for dense layouts, medium for default UI, large/xlarge for prominent CTAs.',
|
|
111
|
+
sizeScale: SIZE_SCALES,
|
|
112
|
+
densityScale: DENSITY_SCALES,
|
|
113
|
+
stateGroups: ['checked', 'transparent'],
|
|
114
|
+
},
|
|
115
|
+
controlStack: {
|
|
116
|
+
scaleNote: 'Match gap size to control size. Use condensed for tight groupings, spacious for separated actions.',
|
|
117
|
+
sizeScale: ['small', 'medium', 'large'],
|
|
118
|
+
densityScale: ['condensed', 'spacious'],
|
|
119
|
+
},
|
|
120
|
+
overlay: {
|
|
121
|
+
scaleNote: 'Use xsmall/small for menus and tooltips, medium for dialogs, large/xlarge for complex modals or sheets.',
|
|
122
|
+
sizeScale: ['xsmall', 'small', 'medium', 'large', 'xlarge'],
|
|
123
|
+
densityScale: ['condensed', 'normal'],
|
|
124
|
+
},
|
|
125
|
+
spinner: {
|
|
126
|
+
scaleNote: 'Use small for inline loading, medium for buttons/cards, large for full-page states.',
|
|
127
|
+
sizeScale: ['small', 'medium', 'large'],
|
|
128
|
+
},
|
|
129
|
+
stack: {
|
|
130
|
+
scaleNote: 'Use condensed for dense lists, normal for standard layouts, spacious for prominent sections.',
|
|
131
|
+
densityScale: DENSITY_SCALES,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Outputs pattern-compressed tokens for a category
|
|
136
|
+
*/
|
|
137
|
+
function outputPatternCompressedCategory(category, tokens, config, lines) {
|
|
138
|
+
var _a;
|
|
139
|
+
const tokenNames = tokens.map(t => t.name);
|
|
140
|
+
// Output scale note
|
|
141
|
+
lines.push(`**Scale:** ${config.scaleNote}`);
|
|
142
|
+
lines.push('');
|
|
143
|
+
// Categorize tokens
|
|
144
|
+
const sizeTokens = [];
|
|
145
|
+
const stateTokens = new Map();
|
|
146
|
+
const otherTokens = [];
|
|
147
|
+
for (const name of tokenNames) {
|
|
148
|
+
const rest = name.replace(`${category}-`, '');
|
|
149
|
+
// Check if it's a state token (checked, transparent)
|
|
150
|
+
let isStateToken = false;
|
|
151
|
+
if (config.stateGroups) {
|
|
152
|
+
for (const state of config.stateGroups) {
|
|
153
|
+
if (rest.startsWith(`${state}-`)) {
|
|
154
|
+
if (!stateTokens.has(state)) {
|
|
155
|
+
stateTokens.set(state, []);
|
|
156
|
+
}
|
|
157
|
+
stateTokens.get(state).push(rest.replace(`${state}-`, ''));
|
|
158
|
+
isStateToken = true;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (isStateToken)
|
|
164
|
+
continue;
|
|
165
|
+
// Check if it's a size token (exact match on part, not substring)
|
|
166
|
+
const parts = rest.split('-');
|
|
167
|
+
const hasSizeScale = (_a = config.sizeScale) === null || _a === void 0 ? void 0 : _a.some(s => parts.includes(s));
|
|
168
|
+
if (hasSizeScale) {
|
|
169
|
+
sizeTokens.push(name);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
otherTokens.push(name);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Output size patterns
|
|
176
|
+
if (sizeTokens.length > 0 && config.sizeScale) {
|
|
177
|
+
// Group by property - handle both prefix-size and size-suffix patterns
|
|
178
|
+
// e.g., control-medium-gap (size first) vs overlay-width-medium (size last)
|
|
179
|
+
const byPattern = new Map();
|
|
180
|
+
for (const name of sizeTokens) {
|
|
181
|
+
const rest = name.replace(`${category}-`, '');
|
|
182
|
+
const parts = rest.split('-');
|
|
183
|
+
// Find exact size match in parts
|
|
184
|
+
for (const size of config.sizeScale) {
|
|
185
|
+
const sizeIdx = parts.indexOf(size);
|
|
186
|
+
if (sizeIdx >= 0) {
|
|
187
|
+
const prefix = parts.slice(0, sizeIdx).join('-');
|
|
188
|
+
const suffix = parts.slice(sizeIdx + 1).join('-');
|
|
189
|
+
// Create pattern key with placeholder for size
|
|
190
|
+
let patternKey;
|
|
191
|
+
if (prefix && suffix) {
|
|
192
|
+
patternKey = `${prefix}-[size]-${suffix}`;
|
|
193
|
+
}
|
|
194
|
+
else if (prefix) {
|
|
195
|
+
patternKey = `${prefix}-[size]`;
|
|
196
|
+
}
|
|
197
|
+
else if (suffix) {
|
|
198
|
+
patternKey = `[size]-${suffix}`;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
patternKey = '[size]';
|
|
202
|
+
}
|
|
203
|
+
if (!byPattern.has(patternKey)) {
|
|
204
|
+
byPattern.set(patternKey, new Set());
|
|
205
|
+
}
|
|
206
|
+
byPattern.get(patternKey).add(size);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Group patterns by their size sets for compact output
|
|
212
|
+
const bySizeSet = new Map();
|
|
213
|
+
for (const [pattern, sizes] of byPattern) {
|
|
214
|
+
const sizeKey = [...sizes].sort((a, b) => config.sizeScale.indexOf(a) - config.sizeScale.indexOf(b)).join(',');
|
|
215
|
+
if (!bySizeSet.has(sizeKey)) {
|
|
216
|
+
bySizeSet.set(sizeKey, []);
|
|
217
|
+
}
|
|
218
|
+
bySizeSet.get(sizeKey).push(pattern);
|
|
219
|
+
}
|
|
220
|
+
lines.push('**Size patterns:**');
|
|
221
|
+
for (const [sizeKey, patterns] of bySizeSet) {
|
|
222
|
+
const sizes = sizeKey.split(',');
|
|
223
|
+
const sizeNotation = sizes.length > 1 ? `[${sizes.join(', ')}]` : sizes[0];
|
|
224
|
+
// Replace [size] placeholder with actual size notation
|
|
225
|
+
const formattedPatterns = patterns.map(p => p.replace('[size]', sizeNotation)).sort();
|
|
226
|
+
// Try to merge patterns with same size but different properties
|
|
227
|
+
if (formattedPatterns.length > 1) {
|
|
228
|
+
// Check if they share a common structure
|
|
229
|
+
const suffixes = formattedPatterns.map(p => {
|
|
230
|
+
const escapedSizeNotation = sizeNotation.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
231
|
+
const match = p.match(new RegExp(`${escapedSizeNotation}-(.+)$`));
|
|
232
|
+
return match ? match[1] : null;
|
|
233
|
+
});
|
|
234
|
+
if (suffixes.every(s => s !== null)) {
|
|
235
|
+
const uniqueSuffixes = [...new Set(suffixes)].sort();
|
|
236
|
+
if (uniqueSuffixes.length > 1) {
|
|
237
|
+
lines.push(`- \`${category}-${sizeNotation}-[${uniqueSuffixes.join(', ')}]\``);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
lines.push(`- \`${category}-${sizeNotation}-${uniqueSuffixes[0]}\``);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
// Output each pattern separately
|
|
245
|
+
for (const pattern of formattedPatterns) {
|
|
246
|
+
lines.push(`- \`${category}-${pattern}\``);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
lines.push(`- \`${category}-${formattedPatterns[0]}\``);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
lines.push('');
|
|
255
|
+
}
|
|
256
|
+
// Output state tokens
|
|
257
|
+
if (stateTokens.size > 0) {
|
|
258
|
+
lines.push('**State variants:**');
|
|
259
|
+
for (const [state, suffixes] of stateTokens) {
|
|
260
|
+
const sortedSuffixes = [...new Set(suffixes)].sort();
|
|
261
|
+
const suffixNotation = sortedSuffixes.length > 1 ? `[${sortedSuffixes.join(', ')}]` : sortedSuffixes[0];
|
|
262
|
+
lines.push(`- \`${category}-${state}-${suffixNotation}\``);
|
|
263
|
+
}
|
|
264
|
+
lines.push('');
|
|
265
|
+
}
|
|
266
|
+
// Output other tokens (colors, misc)
|
|
267
|
+
if (otherTokens.length > 0) {
|
|
268
|
+
// Group by type (bgColor, fgColor, etc.)
|
|
269
|
+
const byType = new Map();
|
|
270
|
+
for (const name of otherTokens) {
|
|
271
|
+
const rest = name.replace(`${category}-`, '');
|
|
272
|
+
const parts = rest.split('-');
|
|
273
|
+
// Find the type (bgColor, fgColor, borderColor, etc.)
|
|
274
|
+
let type = parts[0];
|
|
275
|
+
let variant = parts.slice(1).join('-') || 'default';
|
|
276
|
+
// Handle compound types like minTarget
|
|
277
|
+
if (parts.length >= 2 && !['bgColor', 'fgColor', 'borderColor', 'iconColor'].includes(type)) {
|
|
278
|
+
type = parts.slice(0, -1).join('-');
|
|
279
|
+
variant = parts[parts.length - 1];
|
|
280
|
+
}
|
|
281
|
+
if (!byType.has(type)) {
|
|
282
|
+
byType.set(type, []);
|
|
283
|
+
}
|
|
284
|
+
byType.get(type).push(variant);
|
|
285
|
+
}
|
|
286
|
+
lines.push('**Other tokens:**');
|
|
287
|
+
for (const [type, variants] of [...byType.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
288
|
+
const sortedVariants = [...new Set(variants)].sort();
|
|
289
|
+
if (sortedVariants.length > 1) {
|
|
290
|
+
lines.push(`- \`${category}-${type}-[${sortedVariants.join(', ')}]\``);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
lines.push(`- \`${category}-${type}-${sortedVariants[0]}\``);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
lines.push('');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Outputs pattern-compressed display color tokens
|
|
301
|
+
* Extracts unique colors and properties to show as display-[color]-[property] pattern
|
|
302
|
+
*/
|
|
303
|
+
function outputDisplayColorPattern(tokens, lines) {
|
|
304
|
+
const tokenNames = tokens.map(t => t.name);
|
|
305
|
+
// Extract unique colors and properties
|
|
306
|
+
const colors = new Set();
|
|
307
|
+
const properties = new Set();
|
|
308
|
+
const hasScale = new Set();
|
|
309
|
+
for (const name of tokenNames) {
|
|
310
|
+
// Parse: display-{color}-{property} or display-{color}-scale-{n}
|
|
311
|
+
const parts = name.replace('display-', '').split('-');
|
|
312
|
+
if (parts.length < 2)
|
|
313
|
+
continue;
|
|
314
|
+
const color = parts[0];
|
|
315
|
+
colors.add(color);
|
|
316
|
+
// Check for scale tokens (display-blue-scale-0)
|
|
317
|
+
if (parts[1] === 'scale' && parts.length >= 3) {
|
|
318
|
+
hasScale.add(parts[2]);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
// Property tokens (display-blue-bgColor-emphasis, display-blue-fgColor)
|
|
322
|
+
const property = parts.slice(1).join('-');
|
|
323
|
+
properties.add(property);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// Sort colors alphabetically
|
|
327
|
+
const sortedColors = [...colors].sort();
|
|
328
|
+
const sortedProperties = [...properties].sort((a, b) => {
|
|
329
|
+
// Sort by type first (bgColor, borderColor, fgColor)
|
|
330
|
+
const typeOrder = ['bgColor-emphasis', 'bgColor-muted', 'borderColor-emphasis', 'borderColor-muted', 'fgColor'];
|
|
331
|
+
const aIdx = typeOrder.indexOf(a);
|
|
332
|
+
const bIdx = typeOrder.indexOf(b);
|
|
333
|
+
if (aIdx !== -1 && bIdx !== -1)
|
|
334
|
+
return aIdx - bIdx;
|
|
335
|
+
if (aIdx !== -1)
|
|
336
|
+
return -1;
|
|
337
|
+
if (bIdx !== -1)
|
|
338
|
+
return 1;
|
|
339
|
+
return a.localeCompare(b);
|
|
340
|
+
});
|
|
341
|
+
// Output pattern with variables
|
|
342
|
+
lines.push('**Pattern:** `display-[color]-[property]`');
|
|
343
|
+
lines.push('');
|
|
344
|
+
lines.push('**Variables:**');
|
|
345
|
+
lines.push(`- **color:** ${sortedColors.join(' | ')}`);
|
|
346
|
+
lines.push(`- **property:** ${sortedProperties.join(' | ')}`);
|
|
347
|
+
lines.push('');
|
|
348
|
+
// Output scale pattern if present
|
|
349
|
+
if (hasScale.size > 0) {
|
|
350
|
+
const sortedScales = [...hasScale].sort((a, b) => parseInt(a) - parseInt(b));
|
|
351
|
+
lines.push('**Scale pattern:** `display-[color]-scale-[n]`');
|
|
352
|
+
lines.push(`- **n:** ${sortedScales.join(' | ')}`);
|
|
353
|
+
lines.push('');
|
|
354
|
+
lines.push('*Scale 0-2: lighter (backgrounds), 3-5: mid-tones, 6-9: darker (foregrounds/borders)*');
|
|
355
|
+
lines.push('');
|
|
356
|
+
}
|
|
357
|
+
}
|
|
11
358
|
/**
|
|
12
|
-
*
|
|
359
|
+
* Outputs pattern-compressed data visualization tokens
|
|
360
|
+
* Extracts unique colors and variants to show as data-[color]-color-[variant] pattern
|
|
361
|
+
*/
|
|
362
|
+
function outputDataVisColorPattern(tokens, lines) {
|
|
363
|
+
const tokenNames = tokens.map(t => t.name);
|
|
364
|
+
// Extract unique colors and variants
|
|
365
|
+
const colors = new Set();
|
|
366
|
+
const variants = new Set();
|
|
367
|
+
for (const name of tokenNames) {
|
|
368
|
+
// Parse: data-{color}-color-{variant}
|
|
369
|
+
const parts = name.replace('data-', '').split('-');
|
|
370
|
+
if (parts.length < 3)
|
|
371
|
+
continue;
|
|
372
|
+
const color = parts[0];
|
|
373
|
+
colors.add(color);
|
|
374
|
+
// Variant is the last part (emphasis, muted)
|
|
375
|
+
// Format: color-color-variant -> parts[1] is 'color', parts[2] is variant
|
|
376
|
+
if (parts[1] === 'color' && parts.length >= 3) {
|
|
377
|
+
variants.add(parts[2]);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Sort colors alphabetically
|
|
381
|
+
const sortedColors = [...colors].sort();
|
|
382
|
+
const sortedVariants = [...variants].sort((a, b) => {
|
|
383
|
+
// emphasis first, then muted
|
|
384
|
+
const order = ['emphasis', 'muted'];
|
|
385
|
+
return order.indexOf(a) - order.indexOf(b);
|
|
386
|
+
});
|
|
387
|
+
// Output pattern with variables
|
|
388
|
+
lines.push('**Pattern:** `data-[color]-color-[variant]`');
|
|
389
|
+
lines.push('');
|
|
390
|
+
lines.push('**Variables:**');
|
|
391
|
+
lines.push(`- **color:** ${sortedColors.join(' | ')}`);
|
|
392
|
+
lines.push(`- **variant:** ${sortedVariants.join(' | ')}`);
|
|
393
|
+
lines.push('');
|
|
394
|
+
// Add usage guidance
|
|
395
|
+
lines.push('**Variant usage:**');
|
|
396
|
+
lines.push('- **emphasis:** Lines, bars, borders, data points');
|
|
397
|
+
lines.push('- **muted:** Area fills, backgrounds, subtle regions');
|
|
398
|
+
lines.push('');
|
|
399
|
+
lines.push('*Pair emphasis with muted variants of the same color for cohesive chart styling.*');
|
|
400
|
+
lines.push('');
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Outputs pattern-compressed ANSI color tokens
|
|
404
|
+
* Extracts unique colors and variants (default/bright) to show as color-ansi-[color]-[variant] pattern
|
|
405
|
+
*/
|
|
406
|
+
function outputAnsiColorPattern(tokens, lines) {
|
|
407
|
+
const tokenNames = tokens.map(t => t.name);
|
|
408
|
+
// Extract unique colors and variants
|
|
409
|
+
const colors = new Set();
|
|
410
|
+
const hasVariants = new Set();
|
|
411
|
+
for (const name of tokenNames) {
|
|
412
|
+
// Parse: color-ansi-{color} or color-ansi-{color}-bright
|
|
413
|
+
const parts = name.replace('color-ansi-', '').split('-');
|
|
414
|
+
if (parts.length < 1)
|
|
415
|
+
continue;
|
|
416
|
+
const color = parts[0];
|
|
417
|
+
colors.add(color);
|
|
418
|
+
// Check for bright variant
|
|
419
|
+
if (parts.length > 1 && parts[1] === 'bright') {
|
|
420
|
+
hasVariants.add('bright');
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
hasVariants.add('default');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// Sort colors in ANSI order
|
|
427
|
+
const ansiOrder = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'gray'];
|
|
428
|
+
const sortedColors = [...colors].sort((a, b) => {
|
|
429
|
+
const aIdx = ansiOrder.indexOf(a);
|
|
430
|
+
const bIdx = ansiOrder.indexOf(b);
|
|
431
|
+
if (aIdx !== -1 && bIdx !== -1)
|
|
432
|
+
return aIdx - bIdx;
|
|
433
|
+
if (aIdx !== -1)
|
|
434
|
+
return -1;
|
|
435
|
+
if (bIdx !== -1)
|
|
436
|
+
return 1;
|
|
437
|
+
return a.localeCompare(b);
|
|
438
|
+
});
|
|
439
|
+
// Output pattern with variables
|
|
440
|
+
lines.push('**Pattern:** `color-ansi-[color]` or `color-ansi-[color]-bright`');
|
|
441
|
+
lines.push('');
|
|
442
|
+
lines.push('**Variables:**');
|
|
443
|
+
lines.push(`- **color:** ${sortedColors.join(' | ')}`);
|
|
444
|
+
if (hasVariants.has('bright')) {
|
|
445
|
+
lines.push('- **variant:** default (no suffix) | bright');
|
|
446
|
+
}
|
|
447
|
+
lines.push('');
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Outputs pattern-compressed prettylights syntax tokens using prefix grouping
|
|
451
|
+
* Groups tokens by their primary element category
|
|
452
|
+
*/
|
|
453
|
+
function outputPrettylightsPattern(tokens, lines) {
|
|
454
|
+
const tokenNames = tokens.map(t => t.name);
|
|
455
|
+
// Common single-word elements (no sub-variants)
|
|
456
|
+
const simpleElements = [];
|
|
457
|
+
// Grouped elements with sub-variants (markup-*, brackethighlighter-*, etc.)
|
|
458
|
+
const groupedElements = new Map();
|
|
459
|
+
// Elements with bg/text variants - track the base name and which suffixes exist
|
|
460
|
+
const bgTextVariants = new Map();
|
|
461
|
+
for (const name of tokenNames) {
|
|
462
|
+
// Parse: color-prettylights-syntax-{element} or color-prettylights-syntax-{group}-{sub}
|
|
463
|
+
const element = name.replace('color-prettylights-syntax-', '');
|
|
464
|
+
const parts = element.split('-');
|
|
465
|
+
if (parts.length === 1) {
|
|
466
|
+
// Simple element: comment, constant, entity, keyword, string, variable
|
|
467
|
+
simpleElements.push(parts[0]);
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
const group = parts[0];
|
|
471
|
+
const rest = parts.slice(1).join('-');
|
|
472
|
+
// Check for bg/text suffix patterns (e.g., markup-changed-bg, markup-changed-text)
|
|
473
|
+
if (rest.endsWith('-bg') || rest.endsWith('-text')) {
|
|
474
|
+
const baseName = rest.replace(/-bg$/, '').replace(/-text$/, '');
|
|
475
|
+
const suffix = rest.endsWith('-bg') ? 'bg' : 'text';
|
|
476
|
+
const key = `${group}:${baseName}`; // Use colon to separate group from base
|
|
477
|
+
if (!bgTextVariants.has(key)) {
|
|
478
|
+
bgTextVariants.set(key, new Set());
|
|
479
|
+
}
|
|
480
|
+
bgTextVariants.get(key).add(suffix);
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
// Regular grouped element
|
|
484
|
+
if (!groupedElements.has(group)) {
|
|
485
|
+
groupedElements.set(group, new Set());
|
|
486
|
+
}
|
|
487
|
+
groupedElements.get(group).add(rest);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Output pattern
|
|
492
|
+
lines.push('**Pattern:** `color-prettylights-syntax-[element]`');
|
|
493
|
+
lines.push('');
|
|
494
|
+
// Output simple elements
|
|
495
|
+
if (simpleElements.length > 0) {
|
|
496
|
+
const sorted = [...new Set(simpleElements)].sort();
|
|
497
|
+
lines.push(`**Core elements:** ${sorted.join(', ')}`);
|
|
498
|
+
}
|
|
499
|
+
// Merge bg/text variants into grouped elements for cleaner output
|
|
500
|
+
// Group bg/text variants by their prefix group
|
|
501
|
+
const bgTextByGroup = new Map();
|
|
502
|
+
for (const [key] of bgTextVariants) {
|
|
503
|
+
const [group, baseName] = key.split(':');
|
|
504
|
+
if (!bgTextByGroup.has(group)) {
|
|
505
|
+
bgTextByGroup.set(group, []);
|
|
506
|
+
}
|
|
507
|
+
bgTextByGroup.get(group).push(baseName);
|
|
508
|
+
}
|
|
509
|
+
// Output grouped elements
|
|
510
|
+
if (groupedElements.size > 0 || bgTextByGroup.size > 0) {
|
|
511
|
+
lines.push('');
|
|
512
|
+
lines.push('**Compound elements:**');
|
|
513
|
+
// Get all groups (both regular and bg/text)
|
|
514
|
+
const allGroups = new Set([...groupedElements.keys(), ...bgTextByGroup.keys()]);
|
|
515
|
+
for (const group of [...allGroups].sort()) {
|
|
516
|
+
const regularSubs = groupedElements.get(group) || new Set();
|
|
517
|
+
const bgTextBases = bgTextByGroup.get(group) || [];
|
|
518
|
+
// Combine regular subs
|
|
519
|
+
if (regularSubs.size > 0) {
|
|
520
|
+
const uniqueSubs = [...regularSubs].sort();
|
|
521
|
+
lines.push(`- \`${group}-[${uniqueSubs.join(', ')}]\``);
|
|
522
|
+
}
|
|
523
|
+
// Output bg/text variants separately for clarity
|
|
524
|
+
if (bgTextBases.length > 0) {
|
|
525
|
+
const uniqueBases = [...new Set(bgTextBases)].sort();
|
|
526
|
+
lines.push(`- \`${group}-[${uniqueBases.join(', ')}]-[bg, text]\``);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
lines.push('');
|
|
531
|
+
}
|
|
532
|
+
// Human-readable category names and descriptions
|
|
533
|
+
const CATEGORY_INFO = {
|
|
534
|
+
bgColor: {
|
|
535
|
+
name: 'Background Colors',
|
|
536
|
+
description: 'Background color tokens for surfaces, containers, and UI elements.',
|
|
537
|
+
},
|
|
538
|
+
borderColor: {
|
|
539
|
+
name: 'Border Colors',
|
|
540
|
+
description: 'Border color tokens for boundaries, dividers, and outlines.',
|
|
541
|
+
},
|
|
542
|
+
fgColor: {
|
|
543
|
+
name: 'Foreground Colors',
|
|
544
|
+
description: 'Text and icon color tokens.',
|
|
545
|
+
},
|
|
546
|
+
borderRadius: { name: 'Border Radius', description: 'Corner radius tokens for rounded elements.' },
|
|
547
|
+
borderWidth: { name: 'Border Width', description: 'Border thickness tokens.' },
|
|
548
|
+
border: { name: 'Border', description: 'Composite border tokens combining color, width, and style.' },
|
|
549
|
+
control: { name: 'Controls', description: 'Tokens for interactive controls like buttons, inputs, and selects.' },
|
|
550
|
+
controlKnob: {
|
|
551
|
+
name: 'Control Knob',
|
|
552
|
+
description: 'Tokens for toggle switch knobs (the circular handle that moves along the track).',
|
|
553
|
+
},
|
|
554
|
+
controlStack: {
|
|
555
|
+
name: 'Control Stack',
|
|
556
|
+
description: 'Gap tokens for groups of controls arranged in a row or column.',
|
|
557
|
+
},
|
|
558
|
+
controlTrack: {
|
|
559
|
+
name: 'Control Track',
|
|
560
|
+
description: 'Tokens for toggle switch tracks (the background rail that the knob slides along).',
|
|
561
|
+
},
|
|
562
|
+
data: {
|
|
563
|
+
name: 'Data Visualization',
|
|
564
|
+
description: 'Color tokens for charts, graphs, and diagrams. Use emphasis variants for lines/bars, muted variants for fills.',
|
|
565
|
+
},
|
|
566
|
+
display: {
|
|
567
|
+
name: 'Display Colors',
|
|
568
|
+
description: 'Decorative colors for categorization without semantic meaning. Use for labels, tags, avatars, and user-assigned colors. Do NOT use for success/error/warning—use semantic colors instead.',
|
|
569
|
+
},
|
|
570
|
+
easing: { name: 'Easing', description: 'Animation easing function tokens.' },
|
|
571
|
+
focus: { name: 'Focus', description: 'Focus ring and outline tokens for keyboard navigation accessibility.' },
|
|
572
|
+
fontStack: { name: 'Font Stacks', description: 'Font family tokens.' },
|
|
573
|
+
outline: { name: 'Outline', description: 'Outline tokens for focus indicators.' },
|
|
574
|
+
overlay: { name: 'Overlay', description: 'Tokens for modals, dialogs, popovers, and dropdown menus.' },
|
|
575
|
+
selection: { name: 'Selection', description: 'Tokens for text selection highlights.' },
|
|
576
|
+
shadow: { name: 'Shadow', description: 'Box shadow tokens for elevation and depth.' },
|
|
577
|
+
spinner: { name: 'Spinner', description: 'Loading spinner size and stroke tokens.' },
|
|
578
|
+
stack: { name: 'Stack', description: 'Spacing tokens for Stack layout components.' },
|
|
579
|
+
text: { name: 'Typography', description: 'Text style shorthand tokens for consistent typography across the UI.' },
|
|
580
|
+
};
|
|
581
|
+
/**
|
|
582
|
+
* Densifies description by removing filler words
|
|
583
|
+
*/
|
|
584
|
+
function densifyDescription(description) {
|
|
585
|
+
return description
|
|
586
|
+
.replace(/^Use this for\s+/i, 'For ')
|
|
587
|
+
.replace(/^This is used for\s+/i, 'For ')
|
|
588
|
+
.replace(/^Used for\s+/i, 'For ')
|
|
589
|
+
.replace(/^Use for\s+/i, 'For ')
|
|
590
|
+
.replace(/^This is\s+/i, '')
|
|
591
|
+
.replace(/^This\s+/i, '');
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Shortens rules by applying key shorthands
|
|
595
|
+
*/
|
|
596
|
+
function shortenRules(rules) {
|
|
597
|
+
return rules
|
|
598
|
+
.replace(/Pair with\s+/gi, 'Pair -> ')
|
|
599
|
+
.replace(/\bfgColor\./g, 'fg.')
|
|
600
|
+
.replace(/\bbgColor\./g, 'bg.')
|
|
601
|
+
.replace(/\bborderColor\./g, 'border.');
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Limits usage to max 3 most relevant items
|
|
605
|
+
*/
|
|
606
|
+
function limitUsage(usage) {
|
|
607
|
+
if (usage.length <= 3)
|
|
608
|
+
return usage;
|
|
609
|
+
return usage.slice(0, 3);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Creates a unique key for grouping tokens with identical guidelines within a category
|
|
13
613
|
*/
|
|
14
614
|
function createGuidelineKey(guideline) {
|
|
15
615
|
var _a;
|
|
16
616
|
return JSON.stringify({
|
|
617
|
+
category: guideline.category,
|
|
17
618
|
description: guideline.description || '',
|
|
18
619
|
usage: ((_a = guideline.usage) === null || _a === void 0 ? void 0 : _a.sort()) || [],
|
|
19
620
|
rules: guideline.rules || '',
|
|
@@ -21,8 +622,6 @@ function createGuidelineKey(guideline) {
|
|
|
21
622
|
}
|
|
22
623
|
/**
|
|
23
624
|
* Extracts category from token name
|
|
24
|
-
* - For "base-*" tokens, uses second word (e.g., "base-easing-ease" -> "easing")
|
|
25
|
-
* - Otherwise uses first word (e.g., "bgColor-danger-emphasis" -> "bgColor")
|
|
26
625
|
*/
|
|
27
626
|
function extractCategory(tokenName) {
|
|
28
627
|
const parts = tokenName.split('-');
|
|
@@ -31,22 +630,49 @@ function extractCategory(tokenName) {
|
|
|
31
630
|
}
|
|
32
631
|
return parts[0] || 'other';
|
|
33
632
|
}
|
|
633
|
+
/**
|
|
634
|
+
* Extracts semantic subcategory from token name (e.g., "bgColor-danger-emphasis" -> "danger")
|
|
635
|
+
*/
|
|
636
|
+
function extractSemanticSubcategory(tokenName) {
|
|
637
|
+
const parts = tokenName.split('-');
|
|
638
|
+
if (parts.length >= 2) {
|
|
639
|
+
const subcat = parts[1];
|
|
640
|
+
if (SEMANTIC_SETS.includes(subcat)) {
|
|
641
|
+
return subcat;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Extracts variant from token name (e.g., "bgColor-danger-emphasis" -> "emphasis")
|
|
648
|
+
*/
|
|
649
|
+
function extractVariant(tokenName) {
|
|
650
|
+
const parts = tokenName.split('-');
|
|
651
|
+
if (parts.length >= 3) {
|
|
652
|
+
return parts.slice(2).join('-');
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
34
656
|
/**
|
|
35
657
|
* Formats category name for display
|
|
36
658
|
*/
|
|
37
659
|
function formatCategoryName(category) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
42
|
-
if (categoryMap[category]) {
|
|
43
|
-
return categoryMap[category];
|
|
44
|
-
}
|
|
45
|
-
// Capitalize first letter
|
|
660
|
+
if (category in CATEGORY_INFO) {
|
|
661
|
+
return CATEGORY_INFO[category].name;
|
|
662
|
+
}
|
|
46
663
|
return category.charAt(0).toUpperCase() + category.slice(1);
|
|
47
664
|
}
|
|
48
665
|
/**
|
|
49
|
-
*
|
|
666
|
+
* Gets category description if available
|
|
667
|
+
*/
|
|
668
|
+
function getCategoryDescription(category) {
|
|
669
|
+
if (category in CATEGORY_INFO) {
|
|
670
|
+
return CATEGORY_INFO[category].description;
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Creates a key for grouping by usage and rules only
|
|
50
676
|
*/
|
|
51
677
|
function createUsageRulesKey(guideline) {
|
|
52
678
|
var _a;
|
|
@@ -55,19 +681,31 @@ function createUsageRulesKey(guideline) {
|
|
|
55
681
|
rules: guideline.rules || '',
|
|
56
682
|
});
|
|
57
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* Creates a key for cross-category pattern matching
|
|
686
|
+
*/
|
|
687
|
+
function createPatternKey(description, rules) {
|
|
688
|
+
const normalizedDesc = description
|
|
689
|
+
.replace(/background/gi, 'COLOR_TYPE')
|
|
690
|
+
.replace(/border/gi, 'COLOR_TYPE')
|
|
691
|
+
.replace(/text/gi, 'COLOR_TYPE')
|
|
692
|
+
.replace(/foreground/gi, 'COLOR_TYPE');
|
|
693
|
+
const normalizedRules = rules
|
|
694
|
+
.replace(/bgColor/g, 'COLOR_TOKEN')
|
|
695
|
+
.replace(/borderColor/g, 'COLOR_TOKEN')
|
|
696
|
+
.replace(/fgColor/g, 'COLOR_TOKEN');
|
|
697
|
+
return JSON.stringify({ description: normalizedDesc, rules: normalizedRules });
|
|
698
|
+
}
|
|
58
699
|
/**
|
|
59
700
|
* Extracts a subcategory name from token names for headings
|
|
60
|
-
* e.g., "border-accent-emphasis" -> "accent"
|
|
61
701
|
*/
|
|
62
702
|
function extractSubcategory(tokenNames) {
|
|
63
703
|
if (tokenNames.length < 2)
|
|
64
704
|
return null;
|
|
65
|
-
// Get the second part of each token name
|
|
66
705
|
const subcategories = tokenNames.map(name => {
|
|
67
706
|
const parts = name.split('-');
|
|
68
707
|
return parts[1] || null;
|
|
69
708
|
});
|
|
70
|
-
// Check if all tokens share the same subcategory
|
|
71
709
|
const uniqueSubcats = [...new Set(subcategories.filter(Boolean))];
|
|
72
710
|
if (uniqueSubcats.length === 1) {
|
|
73
711
|
return uniqueSubcats[0];
|
|
@@ -75,12 +713,50 @@ function extractSubcategory(tokenNames) {
|
|
|
75
713
|
return null;
|
|
76
714
|
}
|
|
77
715
|
/**
|
|
78
|
-
*
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
716
|
+
* Outputs typography tokens grouped by role (Headings, Body, Code)
|
|
717
|
+
*/
|
|
718
|
+
function outputTypographyByRole(tokens, lines) {
|
|
719
|
+
var _a;
|
|
720
|
+
for (const { role, description, patterns } of TYPOGRAPHY_ROLES) {
|
|
721
|
+
const roleTokens = tokens.filter(t => patterns.some(p => t.name.startsWith(p)));
|
|
722
|
+
if (roleTokens.length === 0)
|
|
723
|
+
continue;
|
|
724
|
+
lines.push(`### ${role}`);
|
|
725
|
+
lines.push('');
|
|
726
|
+
lines.push(description);
|
|
727
|
+
lines.push('');
|
|
728
|
+
// Create table for role tokens
|
|
729
|
+
const headers = ['Token', 'Description', 'U:', 'R:'];
|
|
730
|
+
lines.push(`| ${headers.join(' | ')} |`);
|
|
731
|
+
lines.push(`|${headers.map(() => '---').join('|')}|`);
|
|
732
|
+
for (const token of roleTokens.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
733
|
+
const cells = [
|
|
734
|
+
`**${token.name}**`,
|
|
735
|
+
escapeTableCell(token.description || '-'),
|
|
736
|
+
((_a = token.usage) === null || _a === void 0 ? void 0 : _a.join(', ')) || '-',
|
|
737
|
+
escapeTableCell(token.rules || '-'),
|
|
738
|
+
];
|
|
739
|
+
lines.push(`| ${cells.join(' | ')} |`);
|
|
740
|
+
}
|
|
741
|
+
lines.push('');
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Escapes pipe characters for markdown tables
|
|
746
|
+
*/
|
|
747
|
+
function escapeTableCell(text) {
|
|
748
|
+
// First escape backslashes, then escape pipe characters, to avoid
|
|
749
|
+
// existing backslashes altering how pipes are interpreted.
|
|
750
|
+
return text.replace(/\\/g, '\\\\').replace(/\|/g, '\\|');
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* @description Outputs a hyper-optimized markdown file with LLM token guidelines.
|
|
754
|
+
* Optimizations:
|
|
755
|
+
* - Bracket notation for tokens with identical guidelines
|
|
756
|
+
* - Global category rules extracted to paragraph
|
|
757
|
+
* - Shortened keys (U:, R:, Pair ->)
|
|
758
|
+
* - Max 3 usage items
|
|
759
|
+
* - No boilerplate
|
|
84
760
|
*/
|
|
85
761
|
export const markdownLlmGuidelines = (_a) => __awaiter(void 0, [_a], void 0, function* ({ dictionary }) {
|
|
86
762
|
var _b;
|
|
@@ -95,13 +771,13 @@ export const markdownLlmGuidelines = (_a) => __awaiter(void 0, [_a], void 0, fun
|
|
|
95
771
|
category: extractCategory(token.name),
|
|
96
772
|
};
|
|
97
773
|
if (token.$description && typeof token.$description === 'string') {
|
|
98
|
-
guideline.description = token.$description;
|
|
774
|
+
guideline.description = densifyDescription(token.$description);
|
|
99
775
|
}
|
|
100
776
|
if (llmExt.usage && Array.isArray(llmExt.usage)) {
|
|
101
|
-
guideline.usage = llmExt.usage;
|
|
777
|
+
guideline.usage = limitUsage(llmExt.usage);
|
|
102
778
|
}
|
|
103
779
|
if (llmExt.rules && typeof llmExt.rules === 'string') {
|
|
104
|
-
guideline.rules = llmExt.rules;
|
|
780
|
+
guideline.rules = shortenRules(llmExt.rules);
|
|
105
781
|
}
|
|
106
782
|
guidelines.push(guideline);
|
|
107
783
|
}
|
|
@@ -113,82 +789,380 @@ export const markdownLlmGuidelines = (_a) => __awaiter(void 0, [_a], void 0, fun
|
|
|
113
789
|
}
|
|
114
790
|
grouped[guideline.category].push(guideline);
|
|
115
791
|
}
|
|
116
|
-
|
|
117
|
-
|
|
792
|
+
const lines = [
|
|
793
|
+
'# Primer Design Token Guidelines',
|
|
794
|
+
'',
|
|
795
|
+
'> Metadata: This file is a Dictionary of tokens. For usage rules, contrast requirements, and motion logic, refer to DESIGN_TOKENS_GUIDE.md.',
|
|
796
|
+
'',
|
|
797
|
+
'Reference for using GitHub Primer design tokens.',
|
|
798
|
+
'',
|
|
799
|
+
'## Legend',
|
|
800
|
+
'',
|
|
801
|
+
'- **U:** Use cases',
|
|
802
|
+
'- **R:** Token-specific rules (see Semantic Key for general meaning)',
|
|
803
|
+
'- **emphasis** variant: Strong/prominent version, use `fg.onEmphasis` for text',
|
|
804
|
+
'- **muted** variant: Subtle version, use matching `fg.*` color for text',
|
|
805
|
+
'- **[a, b]** Bracket notation groups related tokens',
|
|
806
|
+
'',
|
|
807
|
+
'## Semantic Key',
|
|
808
|
+
'',
|
|
809
|
+
'These semantic meanings apply across all token types (bgColor, borderColor, fgColor, border).',
|
|
810
|
+
'',
|
|
811
|
+
'| Semantic | Meaning | Example Usage | Text Pairing |',
|
|
812
|
+
'|---|---|---|---|',
|
|
813
|
+
];
|
|
814
|
+
// Output semantic key table
|
|
815
|
+
for (const [key, info] of Object.entries(SEMANTIC_KEY)) {
|
|
816
|
+
lines.push(`| **${key}** | ${info.meaning} | ${info.usage} | ${info.textPairing} |`);
|
|
817
|
+
}
|
|
818
|
+
lines.push('');
|
|
819
|
+
// Collect semantic tokens across mergeable categories for cross-category patterns
|
|
820
|
+
const semanticTokensByPattern = new Map();
|
|
821
|
+
for (const category of MERGEABLE_CATEGORIES) {
|
|
822
|
+
if (!(category in grouped))
|
|
823
|
+
continue;
|
|
824
|
+
for (const guideline of grouped[category]) {
|
|
825
|
+
const subcategory = extractSemanticSubcategory(guideline.name);
|
|
826
|
+
const variant = extractVariant(guideline.name);
|
|
827
|
+
if (subcategory && variant) {
|
|
828
|
+
const patternKey = createPatternKey(guideline.description || '', guideline.rules || '');
|
|
829
|
+
const key = `${subcategory}-${variant}-${patternKey}`;
|
|
830
|
+
if (!semanticTokensByPattern.has(key)) {
|
|
831
|
+
semanticTokensByPattern.set(key, []);
|
|
832
|
+
}
|
|
833
|
+
semanticTokensByPattern.get(key).push({ subcategory, variant, categories: [category], guideline });
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
// Find patterns spanning multiple categories (must have entries from at least 2 different categories)
|
|
838
|
+
const mergedEntries = [];
|
|
839
|
+
const mergedTokens = new Set();
|
|
840
|
+
for (const [, entries] of semanticTokensByPattern) {
|
|
841
|
+
// Get unique categories in this pattern
|
|
842
|
+
const uniqueCategories = new Set(entries.map(e => e.categories[0]));
|
|
843
|
+
if (uniqueCategories.size > 1) {
|
|
844
|
+
// Only merge if pattern spans multiple categories
|
|
845
|
+
mergedEntries.push({
|
|
846
|
+
subcategory: entries[0].subcategory,
|
|
847
|
+
variant: entries[0].variant,
|
|
848
|
+
guideline: entries[0].guideline,
|
|
849
|
+
});
|
|
850
|
+
for (const e of entries) {
|
|
851
|
+
mergedTokens.add(`${e.categories[0]}-${e.subcategory}-${e.variant}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
// Output compact semantic color reference
|
|
856
|
+
// Group by variant (emphasis/muted) and list all applicable semantics
|
|
857
|
+
if (mergedEntries.length > 0) {
|
|
858
|
+
lines.push('## Semantic Colors');
|
|
859
|
+
lines.push('');
|
|
860
|
+
lines.push('Apply to `bgColor-*`, `borderColor-*`, `fgColor-*`, and `border-*` tokens.');
|
|
861
|
+
lines.push('Refer to Semantic Key above for meaning and text pairings.');
|
|
862
|
+
lines.push('');
|
|
863
|
+
// Group entries by variant
|
|
864
|
+
const byVariant = new Map();
|
|
865
|
+
for (const entry of mergedEntries) {
|
|
866
|
+
if (!byVariant.has(entry.variant)) {
|
|
867
|
+
byVariant.set(entry.variant, []);
|
|
868
|
+
}
|
|
869
|
+
byVariant.get(entry.variant).push(entry.subcategory);
|
|
870
|
+
}
|
|
871
|
+
// Output as compact list
|
|
872
|
+
lines.push('| Pattern | Semantics |');
|
|
873
|
+
lines.push('|---|---|');
|
|
874
|
+
for (const [variant, semantics] of byVariant) {
|
|
875
|
+
const sortedSemantics = [...new Set(semantics)].sort();
|
|
876
|
+
lines.push(`| **\\*-[${sortedSemantics.join(', ')}]-${variant}** | See Semantic Key |`);
|
|
877
|
+
}
|
|
878
|
+
lines.push('');
|
|
879
|
+
// Add special notes for tokens with unique constraints
|
|
880
|
+
lines.push('**Special cases:**');
|
|
881
|
+
lines.push('- `*-attention-emphasis`: Use `fg.default` for text (yellow has poor contrast)');
|
|
882
|
+
lines.push('- `*-sponsors-*`: GitHub Sponsors only, not for general pink UI');
|
|
883
|
+
lines.push('- `*-upsell-*`: Promotional content only, not for regular features');
|
|
884
|
+
lines.push('- `*-open/*-closed/*-done`: GitHub issue/PR states specifically');
|
|
885
|
+
lines.push('');
|
|
886
|
+
}
|
|
118
887
|
for (const category of Object.keys(grouped).sort()) {
|
|
119
|
-
lines.push(`## ${formatCategoryName(category)}`, '');
|
|
120
888
|
const categoryGuidelines = grouped[category];
|
|
121
|
-
//
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
889
|
+
// Separate semantic and non-semantic tokens
|
|
890
|
+
const semanticTokens = [];
|
|
891
|
+
const nonSemanticTokens = [];
|
|
892
|
+
for (const guideline of categoryGuidelines) {
|
|
893
|
+
if (mergedTokens.has(guideline.name))
|
|
894
|
+
continue;
|
|
895
|
+
const subcategory = extractSemanticSubcategory(guideline.name);
|
|
896
|
+
if (subcategory) {
|
|
897
|
+
semanticTokens.push(guideline);
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
nonSemanticTokens.push(guideline);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (semanticTokens.length === 0 && nonSemanticTokens.length === 0)
|
|
904
|
+
continue;
|
|
905
|
+
lines.push(`## ${formatCategoryName(category)}`);
|
|
906
|
+
// Special handling for typography - group by role
|
|
907
|
+
if (category === 'text') {
|
|
908
|
+
const categoryDesc = getCategoryDescription(category);
|
|
909
|
+
if (categoryDesc) {
|
|
910
|
+
lines.push('');
|
|
911
|
+
lines.push(categoryDesc);
|
|
132
912
|
}
|
|
133
913
|
lines.push('');
|
|
914
|
+
outputTypographyByRole(nonSemanticTokens, lines);
|
|
915
|
+
continue;
|
|
134
916
|
}
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
917
|
+
// Special handling for data visualization colors - use pattern compression
|
|
918
|
+
if (category === 'data') {
|
|
919
|
+
const categoryDesc = getCategoryDescription(category);
|
|
920
|
+
if (categoryDesc) {
|
|
921
|
+
lines.push('');
|
|
922
|
+
lines.push(categoryDesc);
|
|
923
|
+
}
|
|
924
|
+
lines.push('');
|
|
925
|
+
// Get usage and rules from the first token with LLM metadata
|
|
926
|
+
const firstWithMeta = nonSemanticTokens.find(t => t.usage || t.rules);
|
|
927
|
+
if (firstWithMeta) {
|
|
928
|
+
if (firstWithMeta.usage && firstWithMeta.usage.length > 0) {
|
|
929
|
+
lines.push(`**U:** ${firstWithMeta.usage.join(', ')}`);
|
|
930
|
+
}
|
|
931
|
+
if (firstWithMeta.rules) {
|
|
932
|
+
lines.push(`**R:** ${firstWithMeta.rules}`);
|
|
933
|
+
}
|
|
934
|
+
lines.push('');
|
|
935
|
+
}
|
|
936
|
+
outputDataVisColorPattern(nonSemanticTokens, lines);
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
// Special handling for display colors - use pattern compression
|
|
940
|
+
if (category === 'display') {
|
|
941
|
+
const categoryDesc = getCategoryDescription(category);
|
|
942
|
+
if (categoryDesc) {
|
|
943
|
+
lines.push('');
|
|
944
|
+
lines.push(categoryDesc);
|
|
945
|
+
}
|
|
946
|
+
lines.push('');
|
|
947
|
+
// Get usage and rules from the first token with LLM metadata
|
|
948
|
+
const firstWithMeta = nonSemanticTokens.find(t => t.usage || t.rules);
|
|
949
|
+
if (firstWithMeta) {
|
|
950
|
+
if (firstWithMeta.usage && firstWithMeta.usage.length > 0) {
|
|
951
|
+
lines.push(`**U:** ${firstWithMeta.usage.join(', ')}`);
|
|
952
|
+
}
|
|
953
|
+
if (firstWithMeta.rules) {
|
|
954
|
+
lines.push(`**R:** ${firstWithMeta.rules}`);
|
|
955
|
+
}
|
|
956
|
+
lines.push('');
|
|
957
|
+
}
|
|
958
|
+
outputDisplayColorPattern(nonSemanticTokens, lines);
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
// Special handling for color category (ansi, prettylights subcategories)
|
|
962
|
+
if (category === 'color') {
|
|
963
|
+
// Separate tokens by subcategory
|
|
964
|
+
const ansiTokens = nonSemanticTokens.filter(t => t.name.startsWith('color-ansi-'));
|
|
965
|
+
const prettylightsTokens = nonSemanticTokens.filter(t => t.name.startsWith('color-prettylights-'));
|
|
966
|
+
const otherTokens = nonSemanticTokens.filter(t => !t.name.startsWith('color-ansi-') && !t.name.startsWith('color-prettylights-'));
|
|
967
|
+
// Output ANSI tokens with pattern compression
|
|
968
|
+
if (ansiTokens.length > 0) {
|
|
969
|
+
lines.push('### ANSI Terminal Colors');
|
|
970
|
+
lines.push('');
|
|
971
|
+
const ansiMeta = ansiTokens.find(t => t.description);
|
|
972
|
+
if (ansiMeta === null || ansiMeta === void 0 ? void 0 : ansiMeta.description) {
|
|
973
|
+
lines.push(ansiMeta.description);
|
|
974
|
+
lines.push('');
|
|
975
|
+
}
|
|
976
|
+
const ansiWithMeta = ansiTokens.find(t => t.usage || t.rules);
|
|
977
|
+
if (ansiWithMeta) {
|
|
978
|
+
if (ansiWithMeta.usage && ansiWithMeta.usage.length > 0) {
|
|
979
|
+
lines.push(`**U:** ${ansiWithMeta.usage.join(', ')}`);
|
|
980
|
+
}
|
|
981
|
+
if (ansiWithMeta.rules) {
|
|
982
|
+
lines.push(`**R:** ${ansiWithMeta.rules}`);
|
|
983
|
+
}
|
|
984
|
+
lines.push('');
|
|
985
|
+
}
|
|
986
|
+
outputAnsiColorPattern(ansiTokens, lines);
|
|
987
|
+
}
|
|
988
|
+
// Output prettylights tokens with prefix grouping
|
|
989
|
+
if (prettylightsTokens.length > 0) {
|
|
990
|
+
lines.push('### Syntax Highlighting (prettylights)');
|
|
991
|
+
lines.push('');
|
|
992
|
+
const plMeta = prettylightsTokens.find(t => t.description);
|
|
993
|
+
if (plMeta === null || plMeta === void 0 ? void 0 : plMeta.description) {
|
|
994
|
+
lines.push(plMeta.description);
|
|
995
|
+
lines.push('');
|
|
996
|
+
}
|
|
997
|
+
const plWithMeta = prettylightsTokens.find(t => t.usage || t.rules);
|
|
998
|
+
if (plWithMeta) {
|
|
999
|
+
if (plWithMeta.usage && plWithMeta.usage.length > 0) {
|
|
1000
|
+
lines.push(`**U:** ${plWithMeta.usage.join(', ')}`);
|
|
1001
|
+
}
|
|
1002
|
+
if (plWithMeta.rules) {
|
|
1003
|
+
lines.push(`**R:** ${plWithMeta.rules}`);
|
|
1004
|
+
}
|
|
1005
|
+
lines.push('');
|
|
1006
|
+
}
|
|
1007
|
+
outputPrettylightsPattern(prettylightsTokens, lines);
|
|
1008
|
+
}
|
|
1009
|
+
// Output any other color tokens normally
|
|
1010
|
+
if (otherTokens.length > 0) {
|
|
1011
|
+
for (const token of otherTokens) {
|
|
1012
|
+
lines.push(`### ${token.name}`);
|
|
1013
|
+
if (token.description) {
|
|
1014
|
+
lines.push(token.description);
|
|
1015
|
+
}
|
|
1016
|
+
if (token.usage && token.usage.length > 0) {
|
|
1017
|
+
lines.push(`**U:** ${token.usage.join(', ')}`);
|
|
1018
|
+
}
|
|
1019
|
+
if (token.rules) {
|
|
1020
|
+
lines.push(`**R:** ${token.rules}`);
|
|
1021
|
+
}
|
|
1022
|
+
lines.push('');
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
// Special handling for pattern-compressed categories (control, overlay, stack, spinner)
|
|
1028
|
+
if (category in PATTERN_COMPRESSED_CATEGORIES) {
|
|
1029
|
+
const categoryDesc = getCategoryDescription(category);
|
|
1030
|
+
if (categoryDesc) {
|
|
1031
|
+
lines.push('');
|
|
1032
|
+
lines.push(categoryDesc);
|
|
1033
|
+
}
|
|
1034
|
+
lines.push('');
|
|
1035
|
+
// Include semantic tokens in the output for pattern compression
|
|
1036
|
+
const allTokens = [...semanticTokens, ...nonSemanticTokens];
|
|
1037
|
+
outputPatternCompressedCategory(category, allTokens, PATTERN_COMPRESSED_CATEGORIES[category], lines);
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
// Determine best category description: prefer token description for single-group categories
|
|
1041
|
+
const consolidatedGroupsPreview = new Map();
|
|
1042
|
+
for (const guideline of nonSemanticTokens) {
|
|
138
1043
|
const key = createGuidelineKey(guideline);
|
|
139
|
-
if (!
|
|
140
|
-
|
|
1044
|
+
if (!consolidatedGroupsPreview.has(key)) {
|
|
1045
|
+
consolidatedGroupsPreview.set(key, []);
|
|
141
1046
|
}
|
|
142
|
-
|
|
1047
|
+
consolidatedGroupsPreview.get(key).push(guideline);
|
|
1048
|
+
}
|
|
1049
|
+
// Use token description if there's only one group with multiple tokens that has a description
|
|
1050
|
+
const singleGroupWithDesc = consolidatedGroupsPreview.size === 1 &&
|
|
1051
|
+
nonSemanticTokens.length > 1 &&
|
|
1052
|
+
nonSemanticTokens[0].description &&
|
|
1053
|
+
semanticTokens.length === 0;
|
|
1054
|
+
const categoryDesc = singleGroupWithDesc ? nonSemanticTokens[0].description : getCategoryDescription(category);
|
|
1055
|
+
if (categoryDesc) {
|
|
1056
|
+
lines.push('');
|
|
1057
|
+
lines.push(categoryDesc);
|
|
143
1058
|
}
|
|
1059
|
+
lines.push('');
|
|
1060
|
+
// Output semantic tokens as compact reference (details in Semantic Key)
|
|
1061
|
+
// Track if we've output shared usage/rules to avoid duplication
|
|
1062
|
+
let outputSharedUsage = false;
|
|
1063
|
+
let outputSharedRules = false;
|
|
1064
|
+
if (semanticTokens.length > 0) {
|
|
1065
|
+
// Group semantic tokens by variant for compact display
|
|
1066
|
+
const byVariant = new Map();
|
|
1067
|
+
const noVariantTokens = []; // For single-level semantic tokens like fgColor-danger
|
|
1068
|
+
for (const token of semanticTokens) {
|
|
1069
|
+
const subcategory = extractSemanticSubcategory(token.name);
|
|
1070
|
+
const variant = extractVariant(token.name);
|
|
1071
|
+
if (subcategory) {
|
|
1072
|
+
if (variant) {
|
|
1073
|
+
if (!byVariant.has(variant)) {
|
|
1074
|
+
byVariant.set(variant, []);
|
|
1075
|
+
}
|
|
1076
|
+
byVariant.get(variant).push(subcategory);
|
|
1077
|
+
}
|
|
1078
|
+
else {
|
|
1079
|
+
noVariantTokens.push(subcategory);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
// Output compact semantic reference
|
|
1084
|
+
if (byVariant.size > 0 || noVariantTokens.length > 0) {
|
|
1085
|
+
lines.push('**Semantic tokens** (see Semantic Key for meaning):');
|
|
1086
|
+
// Tokens with variants (bgColor-danger-emphasis, etc.)
|
|
1087
|
+
for (const [variant, semantics] of byVariant) {
|
|
1088
|
+
const uniqueSemantics = [...new Set(semantics)].sort();
|
|
1089
|
+
lines.push(`- \`${category}-[${uniqueSemantics.join(', ')}]-${variant}\``);
|
|
1090
|
+
}
|
|
1091
|
+
// Single-level semantic tokens (fgColor-danger, etc.)
|
|
1092
|
+
if (noVariantTokens.length > 0) {
|
|
1093
|
+
const uniqueSemantics = [...new Set(noVariantTokens)].sort();
|
|
1094
|
+
lines.push(`- \`${category}-[${uniqueSemantics.join(', ')}]\``);
|
|
1095
|
+
}
|
|
1096
|
+
lines.push('');
|
|
1097
|
+
outputSharedUsage = true;
|
|
1098
|
+
outputSharedRules = true;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
// Check shared usage/rules for non-semantic tokens
|
|
1102
|
+
const usageRulesKeys = new Set(nonSemanticTokens.map(createUsageRulesKey));
|
|
1103
|
+
const sharedUsageRules = usageRulesKeys.size === 1 && nonSemanticTokens.length > 1;
|
|
1104
|
+
// Only output if not already output for semantic tokens and content is different
|
|
1105
|
+
if (sharedUsageRules && nonSemanticTokens.length > 0) {
|
|
1106
|
+
const first = nonSemanticTokens[0];
|
|
1107
|
+
const shouldOutputUsage = first.usage && first.usage.length > 0 && !outputSharedUsage;
|
|
1108
|
+
const shouldOutputRules = first.rules && !outputSharedRules;
|
|
1109
|
+
if (shouldOutputUsage || shouldOutputRules) {
|
|
1110
|
+
if (shouldOutputUsage) {
|
|
1111
|
+
lines.push(`**U:** ${first.usage.join(', ')}`);
|
|
1112
|
+
}
|
|
1113
|
+
if (shouldOutputRules) {
|
|
1114
|
+
lines.push(`**R:** ${first.rules}`);
|
|
1115
|
+
}
|
|
1116
|
+
lines.push('');
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
// Group non-semantic tokens with identical guidelines (reuse preview)
|
|
1120
|
+
const consolidatedGroups = consolidatedGroupsPreview;
|
|
144
1121
|
for (const [, guidelinesGroup] of consolidatedGroups) {
|
|
145
1122
|
const first = guidelinesGroup[0];
|
|
146
1123
|
const tokenNames = guidelinesGroup.map(g => g.name);
|
|
147
1124
|
if (guidelinesGroup.length > 1) {
|
|
148
|
-
// Multiple tokens share the same guidelines - consolidate
|
|
149
1125
|
const subcategory = extractSubcategory(tokenNames);
|
|
150
|
-
if
|
|
1126
|
+
// Only add heading if there's a meaningful subcategory and multiple groups
|
|
1127
|
+
if (subcategory && consolidatedGroups.size > 1) {
|
|
151
1128
|
lines.push(`### ${subcategory}`);
|
|
152
1129
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (first.description && consolidatedGroups.size > 1) {
|
|
156
|
-
lines.push(`### general`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (first.description) {
|
|
1130
|
+
// Only output description if no category description was already shown
|
|
1131
|
+
if (first.description && !categoryDesc) {
|
|
160
1132
|
lines.push(first.description);
|
|
161
1133
|
}
|
|
162
|
-
// Only show usage/rules if not already shown at category level
|
|
163
1134
|
if (!sharedUsageRules) {
|
|
164
1135
|
if (first.usage && first.usage.length > 0) {
|
|
165
|
-
lines.push(`**
|
|
1136
|
+
lines.push(`**U:** ${first.usage.join(', ')}`);
|
|
166
1137
|
}
|
|
167
1138
|
if (first.rules) {
|
|
168
|
-
lines.push(`**
|
|
1139
|
+
lines.push(`**R:** ${first.rules}`);
|
|
169
1140
|
}
|
|
170
1141
|
}
|
|
171
1142
|
lines.push(`**Tokens:** ${tokenNames.join(', ')}`);
|
|
172
1143
|
lines.push('');
|
|
173
1144
|
}
|
|
174
1145
|
else {
|
|
175
|
-
// Single token - output individually
|
|
176
1146
|
lines.push(`### ${first.name}`);
|
|
177
1147
|
if (first.description) {
|
|
178
1148
|
lines.push(first.description);
|
|
179
1149
|
}
|
|
180
|
-
// Only show usage/rules if not already shown at category level
|
|
181
1150
|
if (!sharedUsageRules) {
|
|
182
1151
|
if (first.usage && first.usage.length > 0) {
|
|
183
|
-
lines.push(`**
|
|
1152
|
+
lines.push(`**U:** ${first.usage.join(', ')}`);
|
|
184
1153
|
}
|
|
185
1154
|
if (first.rules) {
|
|
186
|
-
lines.push(`**
|
|
1155
|
+
lines.push(`**R:** ${first.rules}`);
|
|
187
1156
|
}
|
|
188
1157
|
}
|
|
189
1158
|
lines.push('');
|
|
190
1159
|
}
|
|
191
1160
|
}
|
|
192
1161
|
}
|
|
1162
|
+
// Add final directive for AI
|
|
1163
|
+
lines.push('---');
|
|
1164
|
+
lines.push('');
|
|
1165
|
+
lines.push('**Final Directive for AI**:');
|
|
1166
|
+
lines.push('Always cross-reference the `Semantic Key` at the top of this SPEC before confirming a token choice. If a specific component token is missing, derive it using the `[category]-[semantic]-[variant]` pattern.');
|
|
193
1167
|
return lines.join('\n');
|
|
194
1168
|
});
|