@urbicon-ui/mcp-server 6.1.4
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/README.md +161 -0
- package/dist/data/catalog-loader.d.ts +37 -0
- package/dist/data/catalog-loader.d.ts.map +1 -0
- package/dist/data/catalog-loader.js +15 -0
- package/dist/data/catalog-loader.js.map +1 -0
- package/dist/data/component-loader.d.ts +2 -0
- package/dist/data/component-loader.d.ts.map +1 -0
- package/dist/data/component-loader.js +17 -0
- package/dist/data/component-loader.js.map +1 -0
- package/dist/data/recipe-loader.d.ts +4 -0
- package/dist/data/recipe-loader.d.ts.map +1 -0
- package/dist/data/recipe-loader.js +102 -0
- package/dist/data/recipe-loader.js.map +1 -0
- package/dist/data/template-loader.d.ts +8 -0
- package/dist/data/template-loader.d.ts.map +1 -0
- package/dist/data/template-loader.js +33 -0
- package/dist/data/template-loader.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/catalog.d.ts +3 -0
- package/dist/resources/catalog.d.ts.map +1 -0
- package/dist/resources/catalog.js +20 -0
- package/dist/resources/catalog.js.map +1 -0
- package/dist/resources/component.d.ts +3 -0
- package/dist/resources/component.d.ts.map +1 -0
- package/dist/resources/component.js +29 -0
- package/dist/resources/component.js.map +1 -0
- package/dist/resources/guides.d.ts +3 -0
- package/dist/resources/guides.d.ts.map +1 -0
- package/dist/resources/guides.js +36 -0
- package/dist/resources/guides.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/find-components.d.ts +3 -0
- package/dist/tools/find-components.d.ts.map +1 -0
- package/dist/tools/find-components.js +21 -0
- package/dist/tools/find-components.js.map +1 -0
- package/dist/tools/get-recipe.d.ts +3 -0
- package/dist/tools/get-recipe.d.ts.map +1 -0
- package/dist/tools/get-recipe.js +48 -0
- package/dist/tools/get-recipe.js.map +1 -0
- package/dist/tools/suggest-implementation.d.ts +3 -0
- package/dist/tools/suggest-implementation.d.ts.map +1 -0
- package/dist/tools/suggest-implementation.js +178 -0
- package/dist/tools/suggest-implementation.js.map +1 -0
- package/dist/transports/http.d.ts +2 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +77 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/transports/stdio.d.ts +3 -0
- package/dist/transports/stdio.d.ts.map +1 -0
- package/dist/transports/stdio.js +6 -0
- package/dist/transports/stdio.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/format-catalog.d.ts +7 -0
- package/dist/utils/format-catalog.d.ts.map +1 -0
- package/dist/utils/format-catalog.js +93 -0
- package/dist/utils/format-catalog.js.map +1 -0
- package/dist/utils/paths.d.ts +7 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +23 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/search.d.ts +3 -0
- package/dist/utils/search.d.ts.map +1 -0
- package/dist/utils/search.js +44 -0
- package/dist/utils/search.js.map +1 -0
- package/package.json +42 -0
- package/src/data/catalog-loader.test.ts +42 -0
- package/src/data/catalog-loader.ts +78 -0
- package/src/data/component-loader.ts +68 -0
- package/src/data/design-system-loader.test.ts +82 -0
- package/src/data/design-system-loader.ts +125 -0
- package/src/data/icon-loader.test.ts +85 -0
- package/src/data/icon-loader.ts +90 -0
- package/src/data/recipe-loader.test.ts +49 -0
- package/src/data/recipe-loader.ts +131 -0
- package/src/data/template-loader.ts +55 -0
- package/src/design-linter/heuristics.ts +162 -0
- package/src/design-linter/index.ts +14 -0
- package/src/design-linter/linter.test.ts +257 -0
- package/src/design-linter/linter.ts +62 -0
- package/src/design-linter/rules.ts +348 -0
- package/src/design-linter/tokens.test.ts +80 -0
- package/src/design-linter/tokens.ts +203 -0
- package/src/design-linter/types.ts +66 -0
- package/src/design-manifest/index.ts +20 -0
- package/src/design-manifest/manifest.test.ts +175 -0
- package/src/design-manifest/manifest.ts +250 -0
- package/src/design-manifest/scan.test.ts +51 -0
- package/src/design-manifest/scan.ts +74 -0
- package/src/design-manifest/types.ts +40 -0
- package/src/design-rubric/rubric.test.ts +43 -0
- package/src/design-rubric/rubric.ts +140 -0
- package/src/eval/briefs.ts +104 -0
- package/src/eval/eval.test.ts +99 -0
- package/src/eval/index.ts +11 -0
- package/src/eval/score.ts +112 -0
- package/src/index.ts +75 -0
- package/src/prompts/design-prompts.test.ts +51 -0
- package/src/prompts/design-prompts.ts +127 -0
- package/src/resources/catalog.ts +23 -0
- package/src/resources/guides.ts +60 -0
- package/src/server.test.ts +69 -0
- package/src/server.ts +48 -0
- package/src/tools/find-components.ts +83 -0
- package/src/tools/find-icons.ts +77 -0
- package/src/tools/get-checklist.ts +139 -0
- package/src/tools/get-component.ts +204 -0
- package/src/tools/get-css-reference.ts +446 -0
- package/src/tools/get-design-context.ts +43 -0
- package/src/tools/get-design-principles.ts +72 -0
- package/src/tools/get-pattern.ts +69 -0
- package/src/tools/get-recipe.ts +80 -0
- package/src/tools/record-design-decision.ts +99 -0
- package/src/tools/suggest-implementation.ts +251 -0
- package/src/tools/sync-design-manifest.ts +92 -0
- package/src/tools/validate-design.ts +84 -0
- package/src/transports/http.ts +79 -0
- package/src/transports/stdio.ts +7 -0
- package/src/utils/format-catalog.test.ts +144 -0
- package/src/utils/format-catalog.ts +130 -0
- package/src/utils/paths.test.ts +101 -0
- package/src/utils/paths.ts +78 -0
- package/src/utils/search.test.ts +141 -0
- package/src/utils/search.ts +106 -0
- package/tsconfig.json +27 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { ComponentCatalogEntry } from '../data/catalog-loader.js';
|
|
4
|
+
import { loadCatalog } from '../data/catalog-loader.js';
|
|
5
|
+
import type { LlmTxtSection } from '../data/component-loader.js';
|
|
6
|
+
import { extractSection, loadComponentLlmTxt } from '../data/component-loader.js';
|
|
7
|
+
|
|
8
|
+
const VALID_SECTIONS: LlmTxtSection[] = ['overview', 'examples', 'variants', 'api', 'slots'];
|
|
9
|
+
|
|
10
|
+
/** Count prop rows in the llm.txt API section markdown table */
|
|
11
|
+
function countPropsInApiSection(content: string): number {
|
|
12
|
+
const apiSection = extractSection(content, 'api');
|
|
13
|
+
if (!apiSection) return 0;
|
|
14
|
+
|
|
15
|
+
const lines = apiSection.split('\n');
|
|
16
|
+
// Count table rows (start with `|` but skip header and separator rows)
|
|
17
|
+
let count = 0;
|
|
18
|
+
let pastHeader = false;
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
const trimmed = line.trim();
|
|
21
|
+
if (!trimmed.startsWith('|')) continue;
|
|
22
|
+
if (trimmed.startsWith('| ---') || trimmed.startsWith('|---')) {
|
|
23
|
+
pastHeader = true;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (trimmed.startsWith('| Prop')) continue; // header row
|
|
27
|
+
if (pastHeader) count++;
|
|
28
|
+
}
|
|
29
|
+
return count;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Extract the first ```svelte code block from the examples section */
|
|
33
|
+
function extractFirstExample(content: string): string | null {
|
|
34
|
+
const examples = extractSection(content, 'examples');
|
|
35
|
+
if (!examples) return null;
|
|
36
|
+
|
|
37
|
+
const match = examples.match(/```svelte\n([\s\S]*?)```/);
|
|
38
|
+
return match?.[1]?.trim() ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** PascalCase component name to kebab-case slug */
|
|
42
|
+
function toSlug(name: string): string {
|
|
43
|
+
return name.replace(/([A-Z])/g, (_, c, i) => (i > 0 ? '-' : '') + c.toLowerCase());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Generate compact summary from catalog data + llm.txt */
|
|
47
|
+
function generateCompactView(entry: ComponentCatalogEntry, llmContent: string): string {
|
|
48
|
+
let md = `# ${entry.name}\n\n`;
|
|
49
|
+
md += `${entry.description}\n\n`;
|
|
50
|
+
md += `**Import:** \`${entry.import}\`\n\n`;
|
|
51
|
+
|
|
52
|
+
// Key props table from keyPropTypes
|
|
53
|
+
const propEntries = Object.entries(entry.keyPropTypes);
|
|
54
|
+
const totalProps = countPropsInApiSection(llmContent);
|
|
55
|
+
const shownCount = propEntries.length;
|
|
56
|
+
|
|
57
|
+
if (shownCount > 0) {
|
|
58
|
+
md += `## Key Props`;
|
|
59
|
+
if (totalProps > 0) md += ` (${shownCount} of ${totalProps})`;
|
|
60
|
+
md += '\n\n';
|
|
61
|
+
md += '| Prop | Type | Default |\n';
|
|
62
|
+
md += '|---|---|---|\n';
|
|
63
|
+
|
|
64
|
+
// Build a default lookup from variants
|
|
65
|
+
const defaults = new Map(
|
|
66
|
+
entry.variants.filter((v) => v.default).map((v) => [v.name, v.default])
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
for (const [prop, type] of propEntries) {
|
|
70
|
+
const def = defaults.get(prop) ?? '—';
|
|
71
|
+
md += `| ${prop} | \`${type}\` | ${def} |\n`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (totalProps > shownCount) {
|
|
75
|
+
md += `\n> ${totalProps - shownCount} more props available via \`get_component("${entry.slug}", section="api")\`\n`;
|
|
76
|
+
}
|
|
77
|
+
md += '\n';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Variants (if any meaningful ones exist beyond what's in keyPropTypes)
|
|
81
|
+
const meaningfulVariants = entry.variants.filter((v) => {
|
|
82
|
+
const sorted = [...v.values].sort();
|
|
83
|
+
return !(
|
|
84
|
+
(sorted.length === 1 && (sorted[0] === 'true' || sorted[0] === 'false')) ||
|
|
85
|
+
(sorted.length === 2 && sorted[0] === 'false' && sorted[1] === 'true')
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (meaningfulVariants.length > 0) {
|
|
90
|
+
md += '## Variants\n\n';
|
|
91
|
+
for (const v of meaningfulVariants) {
|
|
92
|
+
const def = v.default ? ` (default: ${v.default})` : '';
|
|
93
|
+
md += `- **${v.name}**: ${v.values.join(' / ')}${def}\n`;
|
|
94
|
+
}
|
|
95
|
+
md += '\n';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// First example
|
|
99
|
+
const example = extractFirstExample(llmContent);
|
|
100
|
+
if (example) {
|
|
101
|
+
md += '## Example\n\n';
|
|
102
|
+
md += `\`\`\`svelte\n${example}\n\`\`\`\n\n`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Slots
|
|
106
|
+
if (entry.slots.length > 0) {
|
|
107
|
+
md += `## Slots\n\n\`${entry.slots.join('`, `')}\`\n\n`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Related components
|
|
111
|
+
if (entry.relatedComponents.length > 0) {
|
|
112
|
+
md += `## See Also\n\n`;
|
|
113
|
+
for (const rel of entry.relatedComponents) {
|
|
114
|
+
md += `- \`get_component("${toSlug(rel)}")\` — ${rel}\n`;
|
|
115
|
+
}
|
|
116
|
+
md += '\n';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Drill-down hints
|
|
120
|
+
md += '---\n\n';
|
|
121
|
+
md += '**More details:**\n';
|
|
122
|
+
md += `- \`get_component("${entry.slug}", section="api")\` — full props table\n`;
|
|
123
|
+
md += `- \`get_component("${entry.slug}", section="examples")\` — all code examples\n`;
|
|
124
|
+
md += `- \`get_component("${entry.slug}", section="full")\` — complete documentation\n`;
|
|
125
|
+
md += '- `get_css_reference()` — CSS token names and override patterns\n';
|
|
126
|
+
|
|
127
|
+
return md;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function registerGetComponentTool(server: McpServer): void {
|
|
131
|
+
server.tool(
|
|
132
|
+
'get_component',
|
|
133
|
+
'Get API documentation for a specific Urbicon UI component. Default: compact summary with key props, one example, and slots. Use section="full" for complete docs, or section="api"/"examples"/"variants"/"slots" for specific parts.',
|
|
134
|
+
{
|
|
135
|
+
name: z
|
|
136
|
+
.string()
|
|
137
|
+
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Slug must be lowercase alphanumeric with hyphens')
|
|
138
|
+
.describe('Component slug, e.g. "button", "input", "date-picker", "command-palette"'),
|
|
139
|
+
section: z
|
|
140
|
+
.enum(['overview', 'examples', 'variants', 'api', 'slots', 'full'])
|
|
141
|
+
.optional()
|
|
142
|
+
.describe(
|
|
143
|
+
'Section to return. overview, examples, variants, api (full props table), slots, or full (complete docs). Omit for compact summary.'
|
|
144
|
+
)
|
|
145
|
+
},
|
|
146
|
+
{ readOnlyHint: true },
|
|
147
|
+
async ({ name, section }) => {
|
|
148
|
+
const content = await loadComponentLlmTxt(name);
|
|
149
|
+
|
|
150
|
+
if (!content) {
|
|
151
|
+
return {
|
|
152
|
+
content: [
|
|
153
|
+
{
|
|
154
|
+
type: 'text' as const,
|
|
155
|
+
text: `Component "${name}" not found. Use \`find_components\` to browse available components.`
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// section="full" → return complete llm.txt (old default behavior)
|
|
162
|
+
if (section === 'full') {
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: 'text' as const, text: content }]
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Specific section → extract and return
|
|
169
|
+
if (section) {
|
|
170
|
+
const extracted = extractSection(content, section);
|
|
171
|
+
|
|
172
|
+
if (!extracted) {
|
|
173
|
+
return {
|
|
174
|
+
content: [
|
|
175
|
+
{
|
|
176
|
+
type: 'text' as const,
|
|
177
|
+
text: `Section "${section}" not found for component "${name}". Available sections: ${VALID_SECTIONS.join(', ')}, full.`
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
content: [{ type: 'text' as const, text: extracted }]
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// No section → compact summary from catalog + llm.txt
|
|
189
|
+
const catalog = await loadCatalog();
|
|
190
|
+
const entry = catalog.components.find((c) => c.slug === name);
|
|
191
|
+
|
|
192
|
+
if (entry) {
|
|
193
|
+
return {
|
|
194
|
+
content: [{ type: 'text' as const, text: generateCompactView(entry, content) }]
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fallback: if not in catalog, return full content
|
|
199
|
+
return {
|
|
200
|
+
content: [{ type: 'text' as const, text: content }]
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
}
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const OVERVIEW = `# Urbicon UI — CSS Design Tokens
|
|
5
|
+
|
|
6
|
+
## Architecture
|
|
7
|
+
Three CSS layers, imported in order:
|
|
8
|
+
1. \`foundation.css\` — Raw OKLCH color scales (neutral, primary, secondary, success, warning, danger)
|
|
9
|
+
2. \`semantic.css\` — Purpose-based tokens that reference foundation (\`--color-surface-base\`, \`--color-text-primary\`, etc.)
|
|
10
|
+
3. \`interaction.css\` — Animation timing, easing, shadows, focus rings
|
|
11
|
+
|
|
12
|
+
## Naming Convention
|
|
13
|
+
ALL semantic tokens use the \`--color-*\` CSS variable prefix.
|
|
14
|
+
Tailwind utilities map directly:
|
|
15
|
+
|
|
16
|
+
| Tailwind Utility | CSS Variable |
|
|
17
|
+
|---|---|
|
|
18
|
+
| \`bg-surface-base\` | \`var(--color-surface-base)\` |
|
|
19
|
+
| \`text-text-primary\` | \`var(--color-text-primary)\` |
|
|
20
|
+
| \`border-border-default\` | \`var(--color-border-default)\` |
|
|
21
|
+
| \`bg-primary\` | \`var(--color-primary)\` |
|
|
22
|
+
| \`text-success\` | \`var(--color-success)\` |
|
|
23
|
+
|
|
24
|
+
## Theme Override
|
|
25
|
+
Override tokens in your app CSS using Tailwind's \`@theme\` block:
|
|
26
|
+
\`\`\`css
|
|
27
|
+
@theme {
|
|
28
|
+
--color-surface-base: #080818;
|
|
29
|
+
--color-surface-elevated: #0e0e2a;
|
|
30
|
+
--color-text-primary: #e0e0ff;
|
|
31
|
+
}
|
|
32
|
+
\`\`\`
|
|
33
|
+
|
|
34
|
+
## Dark Mode
|
|
35
|
+
Mechanism: \`@media (prefers-color-scheme: dark)\` on \`:root\` in \`semantic.css\`.
|
|
36
|
+
Manual override: add \`.light\` or \`.dark\` class to \`<html>\` (via \`ThemeSwitcher\` component).
|
|
37
|
+
|
|
38
|
+
To override a token for ALL modes (light, dark, and manual overrides):
|
|
39
|
+
\`\`\`css
|
|
40
|
+
@theme {
|
|
41
|
+
--color-surface-base: #080818;
|
|
42
|
+
}
|
|
43
|
+
:root, :root.light, :root.dark {
|
|
44
|
+
--color-surface-base: #080818;
|
|
45
|
+
}
|
|
46
|
+
\`\`\`
|
|
47
|
+
The \`@theme\` block sets the Tailwind utility value. The \`:root\` rule overrides the runtime value for all theme modes.
|
|
48
|
+
|
|
49
|
+
## Available Sections
|
|
50
|
+
→ \`get_css_reference(section="surfaces")\` — 11 surface background tokens
|
|
51
|
+
→ \`get_css_reference(section="text")\` — 9 text color tokens
|
|
52
|
+
→ \`get_css_reference(section="borders")\` — 4 border color tokens
|
|
53
|
+
→ \`get_css_reference(section="intents")\` — 6 intent palettes (primary, success, danger, etc.)
|
|
54
|
+
→ \`get_css_reference(section="shadows")\` — 5 shadow tokens + z-index scale
|
|
55
|
+
→ \`get_css_reference(section="theming")\` — How to create custom themes, available presets
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
const SURFACES = `# Surface Tokens
|
|
59
|
+
|
|
60
|
+
11 tokens for background colors. All auto-switch in dark mode.
|
|
61
|
+
|
|
62
|
+
| CSS Variable | Tailwind Utility | Purpose |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| \`--color-surface-base\` | \`bg-surface-base\` | Page background |
|
|
65
|
+
| \`--color-surface-quiet\` | \`bg-surface-quiet\` | Softly tinted in-page zone (Lighter default) |
|
|
66
|
+
| \`--color-surface-subtle\` | \`bg-surface-subtle\` | Subtle differentiation |
|
|
67
|
+
| \`--color-surface-elevated\` | \`bg-surface-elevated\` | Cards, panels (floating with shadow) |
|
|
68
|
+
| \`--color-surface-overlay\` | \`bg-surface-overlay\` | Modals, popovers |
|
|
69
|
+
| \`--color-surface-interactive\` | \`bg-surface-interactive\` | Interactive element backgrounds |
|
|
70
|
+
| \`--color-surface-hover\` | \`bg-surface-hover\` | Hover state |
|
|
71
|
+
| \`--color-surface-active\` | \`bg-surface-active\` | Active/pressed state |
|
|
72
|
+
| \`--color-surface-disabled\` | \`bg-surface-disabled\` | Disabled elements |
|
|
73
|
+
| \`--color-surface-selected\` | \`bg-surface-selected\` | Selected items (uses primary-50) |
|
|
74
|
+
| \`--color-surface-inverted\` | \`bg-surface-inverted\` | Inverted surfaces (tooltips) |
|
|
75
|
+
|
|
76
|
+
Light → Dark mapping examples:
|
|
77
|
+
- \`surface-base\`: neutral-0 (white) → neutral-900 (near-black)
|
|
78
|
+
- \`surface-quiet\`: neutral-25 → neutral-850
|
|
79
|
+
- \`surface-elevated\`: neutral-50 → neutral-800
|
|
80
|
+
- \`surface-hover\`: neutral-100 → neutral-750
|
|
81
|
+
|
|
82
|
+
Override example (dark neon theme):
|
|
83
|
+
\`\`\`css
|
|
84
|
+
@theme {
|
|
85
|
+
--color-surface-base: #080818;
|
|
86
|
+
--color-surface-quiet: #0a0a20;
|
|
87
|
+
--color-surface-elevated: #0e0e2a;
|
|
88
|
+
--color-surface-overlay: #12123a;
|
|
89
|
+
}
|
|
90
|
+
\`\`\`
|
|
91
|
+
`;
|
|
92
|
+
|
|
93
|
+
const TEXT = `# Text Tokens
|
|
94
|
+
|
|
95
|
+
9 tokens for text colors. All auto-switch in dark mode.
|
|
96
|
+
|
|
97
|
+
| CSS Variable | Tailwind Utility | Purpose |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| \`--color-text-primary\` | \`text-text-primary\` | Main text |
|
|
100
|
+
| \`--color-text-secondary\` | \`text-text-secondary\` | Supporting text |
|
|
101
|
+
| \`--color-text-tertiary\` | \`text-text-tertiary\` | Muted text, metadata |
|
|
102
|
+
| \`--color-text-quaternary\` | \`text-text-quaternary\` | Most subtle text |
|
|
103
|
+
| \`--color-text-disabled\` | \`text-text-disabled\` | Disabled text |
|
|
104
|
+
| \`--color-text-inverted\` | \`text-text-inverted\` | Text on inverted surfaces |
|
|
105
|
+
| \`--color-text-on-primary\` | \`text-text-on-primary\` | Text on intent-colored backgrounds |
|
|
106
|
+
| \`--color-text-on-dark\` | \`text-text-on-dark\` | Text on dark surfaces |
|
|
107
|
+
| \`--color-text-on-surface\` | \`text-text-on-surface\` | Text on any surface (auto-contrast) |
|
|
108
|
+
|
|
109
|
+
Light → Dark mapping:
|
|
110
|
+
- \`text-primary\`: neutral-900 (dark) → neutral-100 (light)
|
|
111
|
+
- \`text-secondary\`: neutral-700 → neutral-300
|
|
112
|
+
- \`text-tertiary\`: neutral-500 → neutral-400
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
const BORDERS = `# Border Tokens
|
|
116
|
+
|
|
117
|
+
4 tokens for border colors. All auto-switch in dark mode.
|
|
118
|
+
|
|
119
|
+
| CSS Variable | Tailwind Utility | Purpose |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| \`--color-border-subtle\` | \`border-border-subtle\` | Gentle grouping |
|
|
122
|
+
| \`--color-border-default\` | \`border-border-default\` | Standard borders |
|
|
123
|
+
| \`--color-border-emphasis\` | \`border-border-emphasis\` | Emphasized borders |
|
|
124
|
+
| \`--color-border-strong\` | \`border-border-strong\` | High-contrast borders |
|
|
125
|
+
|
|
126
|
+
Light → Dark mapping:
|
|
127
|
+
- \`border-subtle\`: neutral-200 → neutral-700
|
|
128
|
+
- \`border-default\`: neutral-300 → neutral-600
|
|
129
|
+
- \`border-emphasis\`: neutral-400 → neutral-500
|
|
130
|
+
- \`border-strong\`: neutral-500 → neutral-400
|
|
131
|
+
|
|
132
|
+
Also available for intent-colored borders:
|
|
133
|
+
\`border-primary\`, \`border-success\`, \`border-warning\`, \`border-danger\`, \`border-secondary\`, \`border-neutral\`
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
const INTENTS = `# Intent Color System
|
|
137
|
+
|
|
138
|
+
6 intent palettes, each with 5 semantic variants + 11 foundation steps.
|
|
139
|
+
|
|
140
|
+
## Semantic Intent Tokens (auto dark mode)
|
|
141
|
+
|
|
142
|
+
Each intent has these variants (example: \`primary\`):
|
|
143
|
+
|
|
144
|
+
| CSS Variable | Tailwind Utility | Purpose | Light | Dark |
|
|
145
|
+
|---|---|---|---|---|
|
|
146
|
+
| \`--color-primary\` | \`bg-primary\` / \`text-primary\` | Base intent color | primary-600 | primary-500 |
|
|
147
|
+
| \`--color-primary-hover\` | \`bg-primary-hover\` | Hover state | primary-700 | primary-400 |
|
|
148
|
+
| \`--color-primary-active\` | \`bg-primary-active\` | Pressed state | primary-800 | — |
|
|
149
|
+
| \`--color-primary-subtle\` | \`bg-primary-subtle\` | Soft background | primary-50 | primary-900 |
|
|
150
|
+
| \`--color-primary-emphasis\` | \`bg-primary-emphasis\` | Strong/dark variant | primary-900 | — |
|
|
151
|
+
|
|
152
|
+
Same pattern applies to: \`success-*\`, \`warning-*\`, \`danger-*\`, \`secondary-*\`, \`neutral-*\`
|
|
153
|
+
|
|
154
|
+
## Foundation Intent Scales
|
|
155
|
+
|
|
156
|
+
Each intent has 11 numbered steps (50–950) for granular control:
|
|
157
|
+
\`\`\`
|
|
158
|
+
--color-primary-50 through --color-primary-950
|
|
159
|
+
--color-success-50 through --color-success-950
|
|
160
|
+
--color-warning-50 through --color-warning-950
|
|
161
|
+
--color-danger-50 through --color-danger-950
|
|
162
|
+
--color-secondary-50 through --color-secondary-950
|
|
163
|
+
--color-neutral-50 through --color-neutral-950
|
|
164
|
+
\`\`\`
|
|
165
|
+
|
|
166
|
+
Tailwind usage: \`bg-primary-500\`, \`text-danger-700\`, \`border-success-300\`, etc.
|
|
167
|
+
|
|
168
|
+
## Feedback Tokens (for status messages)
|
|
169
|
+
|
|
170
|
+
| CSS Variable | Tailwind Utility | Maps to |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| \`--color-feedback-info\` | \`bg-feedback-info\` / \`text-feedback-info\` | primary-500 |
|
|
173
|
+
| \`--color-feedback-info-subtle\` | \`bg-feedback-info-subtle\` | primary-50 |
|
|
174
|
+
| \`--color-feedback-success\` | \`bg-feedback-success\` / \`text-feedback-success\` | success-500 |
|
|
175
|
+
| \`--color-feedback-success-subtle\` | \`bg-feedback-success-subtle\` | success-50 |
|
|
176
|
+
| \`--color-feedback-warning\` | \`bg-feedback-warning\` / \`text-feedback-warning\` | warning-500 |
|
|
177
|
+
| \`--color-feedback-warning-subtle\` | \`bg-feedback-warning-subtle\` | warning-50 |
|
|
178
|
+
| \`--color-feedback-error\` | \`bg-feedback-error\` / \`text-feedback-error\` | danger-500 |
|
|
179
|
+
| \`--color-feedback-error-subtle\` | \`bg-feedback-error-subtle\` | danger-50 |
|
|
180
|
+
|
|
181
|
+
## Interactive Tokens
|
|
182
|
+
|
|
183
|
+
| CSS Variable | Tailwind Utility | Purpose |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| \`--color-interactive-hover\` | \`bg-interactive-hover\` | 10% primary overlay |
|
|
186
|
+
| \`--color-interactive-active\` | \`bg-interactive-active\` | 20% primary overlay |
|
|
187
|
+
| \`--color-interactive-focus\` | \`ring-interactive-focus\` | Focus ring color (primary-500) |
|
|
188
|
+
| \`--color-interactive-disabled\` | \`bg-interactive-disabled\` | Disabled state (neutral-200) |
|
|
189
|
+
|
|
190
|
+
## Override Example: Custom Intent
|
|
191
|
+
\`\`\`css
|
|
192
|
+
@theme {
|
|
193
|
+
--color-primary-50: oklch(0.95 0.03 280);
|
|
194
|
+
--color-primary-100: oklch(0.9 0.06 280);
|
|
195
|
+
--color-primary-200: oklch(0.82 0.09 280);
|
|
196
|
+
--color-primary-300: oklch(0.74 0.12 280);
|
|
197
|
+
--color-primary-400: oklch(0.66 0.14 280);
|
|
198
|
+
--color-primary-500: oklch(0.58 0.14 280);
|
|
199
|
+
--color-primary-600: oklch(0.52 0.14 280);
|
|
200
|
+
--color-primary-700: oklch(0.44 0.12 280);
|
|
201
|
+
--color-primary-800: oklch(0.36 0.1 280);
|
|
202
|
+
--color-primary-900: oklch(0.28 0.07 280);
|
|
203
|
+
--color-primary-950: oklch(0.18 0.04 280);
|
|
204
|
+
}
|
|
205
|
+
\`\`\`
|
|
206
|
+
Semantic tokens (\`--color-primary\`, \`--color-primary-hover\`, etc.) automatically reference the new scale.
|
|
207
|
+
`;
|
|
208
|
+
|
|
209
|
+
const SHADOWS = `# Shadow & Z-Index Tokens
|
|
210
|
+
|
|
211
|
+
## Shadow Tokens
|
|
212
|
+
|
|
213
|
+
| CSS Variable | Tailwind Utility | Purpose |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| \`--color-shadow-xs\` | \`shadow-[var(--blocks-shadow-xs)]\` | Minimal shadow |
|
|
216
|
+
| \`--color-shadow-sm\` | \`shadow-[var(--blocks-shadow-sm)]\` | Buttons, small elements |
|
|
217
|
+
| \`--color-shadow-base\` | \`shadow-[var(--blocks-shadow-base)]\` | Default elevation |
|
|
218
|
+
| \`--color-shadow-md\` | \`shadow-[var(--blocks-shadow-md)]\` | Hover states, menus |
|
|
219
|
+
| \`--color-shadow-lg\` | \`shadow-[var(--blocks-shadow-lg)]\` | Modals, popovers |
|
|
220
|
+
|
|
221
|
+
Shadows automatically increase opacity in dark mode (0.05 → 0.2 for xs, etc.).
|
|
222
|
+
|
|
223
|
+
## Z-Index Scale
|
|
224
|
+
|
|
225
|
+
| CSS Variable | Value | Purpose |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| \`--z-hide\` | -1 | Hidden |
|
|
228
|
+
| \`--z-base\` | 0 | Default |
|
|
229
|
+
| \`--z-docked\` | 10 | Docked elements |
|
|
230
|
+
| \`--z-dropdown\` | 9999 | Menus, listboxes, floating panels |
|
|
231
|
+
| \`--z-sticky\` | 1100 | Sticky headers |
|
|
232
|
+
| \`--z-banner\` | 1200 | Banners |
|
|
233
|
+
| \`--z-overlay\` | 1300 | Overlays |
|
|
234
|
+
| \`--z-sidebar\` | 1350 | Sidebars |
|
|
235
|
+
| \`--z-modal\` | 1400 | Modals |
|
|
236
|
+
| \`--z-popover\` | 1500 | Popovers |
|
|
237
|
+
| \`--z-toast\` | 1700 | Toasts |
|
|
238
|
+
| \`--z-tooltip\` | 1800 | Tooltips |
|
|
239
|
+
|
|
240
|
+
Usage: \`z-[var(--z-modal)]\`
|
|
241
|
+
|
|
242
|
+
## Border Radius Scale
|
|
243
|
+
|
|
244
|
+
| CSS Variable | Tailwind | Value |
|
|
245
|
+
|---|---|---|
|
|
246
|
+
| \`--radius-xs\` | \`rounded-xs\` | 0.125rem |
|
|
247
|
+
| \`--radius-sm\` | \`rounded-sm\` | 0.25rem |
|
|
248
|
+
| \`--radius-md\` | \`rounded-md\` | 0.375rem |
|
|
249
|
+
| \`--radius-lg\` | \`rounded-lg\` | 0.5rem |
|
|
250
|
+
| \`--radius-xl\` | \`rounded-xl\` | 0.75rem |
|
|
251
|
+
| \`--radius-2xl\` | \`rounded-2xl\` | 1rem |
|
|
252
|
+
| \`--radius-3xl\` | \`rounded-3xl\` | 1.5rem |
|
|
253
|
+
| \`--radius-4xl\` | \`rounded-4xl\` | 2rem |
|
|
254
|
+
|
|
255
|
+
## Duration & Easing Tokens
|
|
256
|
+
|
|
257
|
+
| CSS Variable | Value |
|
|
258
|
+
|---|---|
|
|
259
|
+
| \`--blocks-duration-instant\` | 75ms |
|
|
260
|
+
| \`--blocks-duration-fast\` | 150ms |
|
|
261
|
+
| \`--blocks-duration-normal\` | 250ms |
|
|
262
|
+
| \`--blocks-duration-slow\` | 350ms |
|
|
263
|
+
| \`--blocks-duration-slower\` | 500ms |
|
|
264
|
+
| \`--blocks-ease-confident\` | Standard transitions |
|
|
265
|
+
| \`--blocks-ease-springy\` | Bouncy animations |
|
|
266
|
+
| \`--blocks-ease-smooth\` | Gentle animations |
|
|
267
|
+
| \`--blocks-ease-snappy\` | Quick, decisive |
|
|
268
|
+
`;
|
|
269
|
+
|
|
270
|
+
const THEMING = `# Theming Guide
|
|
271
|
+
|
|
272
|
+
## Built-in Themes
|
|
273
|
+
|
|
274
|
+
Import AFTER base styles:
|
|
275
|
+
\`\`\`css
|
|
276
|
+
@import '@urbicon-ui/blocks/style/index.css';
|
|
277
|
+
@import '@urbicon-ui/blocks/style/themes/ocean.css';
|
|
278
|
+
\`\`\`
|
|
279
|
+
|
|
280
|
+
Available: \`ocean.css\` (blue-teal), \`forest.css\` (green), \`sunset.css\` (orange-amber), \`rose.css\` (pink), \`neutral.css\` (grayscale)
|
|
281
|
+
|
|
282
|
+
Each theme overrides three foundation ramps: \`--color-primary-*\`, \`--color-secondary-*\`, and \`--color-neutral-*\`. The neutral ramp is the chassis — surfaces, text and borders all derive from it (see below), so each theme re-tints it to match the accent's temperature (Sunset shifts it warm, Ocean cool). Each colored theme additionally re-tunes only the intent ramps that would collide with its accent (e.g. Forest pushes success/warning off its green/lime, Ocean moves info off the brand blue), sets a matching \`--blocks-shadow-tint\`, and re-tints the NEUTRAL intent (\`bg-neutral\` / \`text-neutral\` / neutral borders) to its temperature via \`--neutral-chrome-hue\`. Other non-colliding intents (success/warning/danger/info) stay at the library defaults. The Neutral theme inherits the cool default chrome and leaves those intents + shadow tint untouched.
|
|
283
|
+
|
|
284
|
+
The neutral intent is special: it keeps the \`--color-warm-neutral-*\` ramp's lightness profile (tuned so white-on-\`bg-neutral\` AND \`text-neutral\`-on-surface both stay legible across light/dark) and only swaps the hue via \`--neutral-chrome-hue\`. Set that token (default 240) in a \`:root\` rule to re-tint neutral controls without touching contrast — e.g. \`:root { --neutral-chrome-hue: 50; }\` for a warm theme. Do NOT repoint the neutral intent onto the chassis \`--color-neutral-*\` ramp: its surface-tuned lightness breaks the white-text / neutral-text contrast balance.
|
|
285
|
+
|
|
286
|
+
## The neutral ramp IS the chassis — match its temperature to your accent
|
|
287
|
+
|
|
288
|
+
This is the single most common theming mistake: recolor \`--color-primary-*\` only, ship it, and the warm brand button ends up sitting on cool blue-grey cards. Reason: \`surface-*\`, \`text-*\` and \`border-*\` do NOT derive from primary — they derive from \`--color-neutral-*\`, which defaults to a cool Hue 240. A warm accent on a Hue-240 chassis reads broken.
|
|
289
|
+
|
|
290
|
+
Fix: when you change the accent hue, re-tint the neutral ramp to the same temperature family. Keep each stop's lightness and chroma identical to the foundation ramp (chroma stays tiny, ≤0.017) and shift only the hue — this keeps the chassis near-grey, preserves WCAG contrast, and the warmth/coolness flows automatically to every surface, text and border token via \`semantic.css\`. No per-token surface overrides needed.
|
|
291
|
+
|
|
292
|
+
## Creating a Custom Theme
|
|
293
|
+
|
|
294
|
+
Pick an accent hue (0–360) and a chassis hue in the same temperature family (often the accent hue itself, or pulled slightly toward grey). Generate matched OKLCH ramps — the Theme Builder at \`/customization/theme-builder\` does this for you:
|
|
295
|
+
\`\`\`css
|
|
296
|
+
/* my-theme.css — warm brand on a warm chassis */
|
|
297
|
+
@theme {
|
|
298
|
+
/* Primary: hue 280 (purple) */
|
|
299
|
+
--color-primary-50: oklch(0.95 0.03 280);
|
|
300
|
+
--color-primary-100: oklch(0.9 0.06 280);
|
|
301
|
+
--color-primary-200: oklch(0.82 0.09 280);
|
|
302
|
+
--color-primary-300: oklch(0.74 0.12 280);
|
|
303
|
+
--color-primary-400: oklch(0.66 0.14 280);
|
|
304
|
+
--color-primary-500: oklch(0.58 0.14 280);
|
|
305
|
+
--color-primary-600: oklch(0.52 0.14 280);
|
|
306
|
+
--color-primary-700: oklch(0.44 0.12 280);
|
|
307
|
+
--color-primary-800: oklch(0.36 0.1 280);
|
|
308
|
+
--color-primary-900: oklch(0.28 0.07 280);
|
|
309
|
+
--color-primary-950: oklch(0.18 0.04 280);
|
|
310
|
+
|
|
311
|
+
/* Secondary: hue 320 (magenta) */
|
|
312
|
+
--color-secondary-50: oklch(0.95 0.03 320);
|
|
313
|
+
/* ... same pattern with hue 320 ... */
|
|
314
|
+
--color-secondary-950: oklch(0.18 0.04 320);
|
|
315
|
+
|
|
316
|
+
/* Chassis: neutral ramp re-tinted to hue 290 (matches the purple accent).
|
|
317
|
+
Same L + C as the foundation neutral ramp — only the hue moves. */
|
|
318
|
+
--color-neutral-25: oklch(0.99 0.002 290);
|
|
319
|
+
--color-neutral-50: oklch(0.98 0.005 290);
|
|
320
|
+
--color-neutral-100: oklch(0.95 0.008 290);
|
|
321
|
+
--color-neutral-200: oklch(0.89 0.012 290);
|
|
322
|
+
--color-neutral-300: oklch(0.83 0.014 290);
|
|
323
|
+
--color-neutral-400: oklch(0.7 0.015 290);
|
|
324
|
+
--color-neutral-500: oklch(0.55 0.016 290);
|
|
325
|
+
--color-neutral-600: oklch(0.42 0.017 290);
|
|
326
|
+
--color-neutral-650: oklch(0.38 0.016 290);
|
|
327
|
+
--color-neutral-700: oklch(0.32 0.016 290);
|
|
328
|
+
--color-neutral-750: oklch(0.28 0.014 290);
|
|
329
|
+
--color-neutral-800: oklch(0.23 0.015 290);
|
|
330
|
+
--color-neutral-850: oklch(0.18 0.014 290);
|
|
331
|
+
--color-neutral-900: oklch(0.15 0.012 290);
|
|
332
|
+
--color-neutral-950: oklch(0.08 0.008 290);
|
|
333
|
+
}
|
|
334
|
+
\`\`\`
|
|
335
|
+
For a temperature-free, true grayscale chassis (content-focused UIs), set chroma to 0 on every neutral stop instead of shifting the hue.
|
|
336
|
+
|
|
337
|
+
## Intent-hue collisions
|
|
338
|
+
|
|
339
|
+
If your accent hue lands near an intent hue, the two become hard to tell apart. The library intents sit at: success 140, warning 80, danger 25, info 220, secondary 280. A green brand (~140) collides with success; an amber brand (~80) collides with warning. When that happens, re-tune the colliding intent ramp away from the accent (push the hue ±15–25° and/or drop its lightness so it reads as "status", not "brand"). \`apps/docs/src/lib/style/editorial.css\` is a worked example (green brand → success pushed to 150 + darkened, warning pulled to amber 55).
|
|
340
|
+
|
|
341
|
+
## Overriding Semantic Tokens
|
|
342
|
+
|
|
343
|
+
To override semantic tokens directly (e.g. a fully art-directed dark theme, or custom surface stops beyond what re-tinting the neutral ramp gives you):
|
|
344
|
+
\`\`\`css
|
|
345
|
+
@theme {
|
|
346
|
+
--color-surface-base: #080818;
|
|
347
|
+
--color-surface-elevated: #0e0e2a;
|
|
348
|
+
--color-text-primary: #e0e0ff;
|
|
349
|
+
}
|
|
350
|
+
\`\`\`
|
|
351
|
+
|
|
352
|
+
If your override should apply regardless of light/dark mode, also set:
|
|
353
|
+
\`\`\`css
|
|
354
|
+
:root, :root.light, :root.dark {
|
|
355
|
+
--color-surface-base: #080818;
|
|
356
|
+
--color-surface-elevated: #0e0e2a;
|
|
357
|
+
--color-text-primary: #e0e0ff;
|
|
358
|
+
}
|
|
359
|
+
\`\`\`
|
|
360
|
+
|
|
361
|
+
## Gotcha: SCOPED themes must re-declare derived tokens
|
|
362
|
+
|
|
363
|
+
This trap only applies when you scope a theme to a CLASS (e.g. \`.theme-sunset { ... }\` toggled at runtime) instead of a global \`@theme\`/\`:root\` block.
|
|
364
|
+
|
|
365
|
+
Custom-property substitution happens per element at computed-value time, and inheritance passes the ALREADY-substituted value. \`semantic.css\` defines derived tokens at \`:root\`, e.g.
|
|
366
|
+
\`\`\`css
|
|
367
|
+
:root { --color-primary: light-dark(var(--color-primary-600), var(--color-primary-500)); }
|
|
368
|
+
\`\`\`
|
|
369
|
+
That \`var(--color-primary-600)\` resolves against the \`:root\` ramp. If you override \`--color-primary-600\` only inside \`.theme-sunset\`, the element inherits the already-resolved (default) \`--color-primary\` — your new ramp is ignored for every derived token.
|
|
370
|
+
|
|
371
|
+
Fix: inside the scoped block, re-declare the derived tokens too, so substitution re-runs at that level:
|
|
372
|
+
\`\`\`css
|
|
373
|
+
.theme-sunset {
|
|
374
|
+
--color-primary-600: oklch(0.55 0.15 55);
|
|
375
|
+
/* ...rest of the ramp... */
|
|
376
|
+
|
|
377
|
+
/* re-declare so the derived tokens re-substitute against the new ramp */
|
|
378
|
+
--color-primary: light-dark(var(--color-primary-600), var(--color-primary-500));
|
|
379
|
+
--color-primary-hover: light-dark(var(--color-primary-700), var(--color-primary-400));
|
|
380
|
+
--color-primary-subtle: light-dark(var(--color-primary-50), var(--color-primary-900));
|
|
381
|
+
/* ...and the same for any neutral-derived surface/text/border tokens you rely on */
|
|
382
|
+
}
|
|
383
|
+
\`\`\`
|
|
384
|
+
A global \`@theme\` block (the built-in themes, the Theme Builder output) does NOT hit this — everything lands on \`:root\`, the same element where the derived tokens compute, so re-declaration is unnecessary. Prefer global themes unless you genuinely need multiple themes live on one page. \`apps/docs/src/lib/style/editorial.css\` is the canonical scoped example.
|
|
385
|
+
|
|
386
|
+
## Component-Level Overrides
|
|
387
|
+
|
|
388
|
+
Use \`BlocksProvider\` to change component defaults globally:
|
|
389
|
+
\`\`\`svelte
|
|
390
|
+
<BlocksProvider defaults={{
|
|
391
|
+
Card: { slotClasses: { base: 'rounded-2xl shadow-lg' } },
|
|
392
|
+
Button: { slotClasses: { base: 'rounded-full font-bold uppercase' } }
|
|
393
|
+
}}>
|
|
394
|
+
\`\`\`
|
|
395
|
+
|
|
396
|
+
Or override per-instance:
|
|
397
|
+
\`\`\`svelte
|
|
398
|
+
<Card class="rounded-2xl" padding="lg">...</Card>
|
|
399
|
+
<Button unstyled slotClasses={{ base: 'custom-button-class' }}>...</Button>
|
|
400
|
+
\`\`\`
|
|
401
|
+
|
|
402
|
+
## Tailwind 4 — Scanning Component Classes
|
|
403
|
+
|
|
404
|
+
Tailwind 4 does not scan \`node_modules\` by default, so the responsive utilities (\`lg:hidden\`, \`md:grid-cols-2\`, etc.) used inside Urbicon UI components must be registered as content sources. **The library does this for you:** \`@urbicon-ui/blocks/style/index.css\` ships the \`@source\` directives that point Tailwind at the component classes, and \`@urbicon-ui/table/style/index.css\` does the same for the Table.
|
|
405
|
+
|
|
406
|
+
So the only requirement is to import \`index.css\` (your app owns the Tailwind import; it comes first):
|
|
407
|
+
\`\`\`css
|
|
408
|
+
@import 'tailwindcss';
|
|
409
|
+
@import '@urbicon-ui/blocks/style/index.css'; /* tokens + @source directives */
|
|
410
|
+
@import '@urbicon-ui/table/style/index.css'; /* if using Table */
|
|
411
|
+
\`\`\`
|
|
412
|
+
|
|
413
|
+
**Do NOT add manual \`@source\` directives, and do NOT import the \`foundation\`/\`semantic\`/\`interaction\` subfiles instead of \`index.css\`** — the subfiles omit the \`@source\` directives (and global classes), which is the usual cause of "responsive layouts break in production".
|
|
414
|
+
`;
|
|
415
|
+
|
|
416
|
+
const SECTIONS: Record<string, string> = {
|
|
417
|
+
surfaces: SURFACES,
|
|
418
|
+
text: TEXT,
|
|
419
|
+
borders: BORDERS,
|
|
420
|
+
intents: INTENTS,
|
|
421
|
+
shadows: SHADOWS,
|
|
422
|
+
theming: THEMING
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
export function registerGetCssReferenceTool(server: McpServer): void {
|
|
426
|
+
server.tool(
|
|
427
|
+
'get_css_reference',
|
|
428
|
+
'Get CSS variable names, Tailwind utility mappings, and override patterns for the Urbicon UI design token system. Essential for theming and custom styling.',
|
|
429
|
+
{
|
|
430
|
+
section: z
|
|
431
|
+
.enum(['surfaces', 'text', 'borders', 'intents', 'shadows', 'theming'])
|
|
432
|
+
.optional()
|
|
433
|
+
.describe(
|
|
434
|
+
'Token category. Omit for overview with naming conventions and dark mode mechanism.'
|
|
435
|
+
)
|
|
436
|
+
},
|
|
437
|
+
{ readOnlyHint: true },
|
|
438
|
+
async ({ section }) => {
|
|
439
|
+
const text = section ? SECTIONS[section] : OVERVIEW;
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
content: [{ type: 'text' as const, text: text ?? OVERVIEW }]
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
}
|