lucent-ui 0.1.1 → 0.3.0
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/dist/index.cjs +22 -17
- package/dist/index.d.ts +76 -1
- package/dist/index.js +917 -515
- package/dist-cli/cli/figma.js +41 -0
- package/dist-cli/cli/index.js +120 -0
- package/dist-cli/cli/mapper.js +162 -0
- package/dist-cli/cli/template.manifest.json +83 -0
- package/dist-cli/cli/types.js +1 -0
- package/dist-cli/src/tokens/types.js +1 -0
- package/dist-server/src/manifest/examples/button.manifest.js +1 -1
- package/dist-server/src/manifest/index.js +1 -1
- package/package.json +6 -3
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// ─── Figma Variables API types ────────────────────────────────────────────────
|
|
2
|
+
// ─── API client ───────────────────────────────────────────────────────────────
|
|
3
|
+
export async function fetchFigmaVariables(figmaToken, fileKey) {
|
|
4
|
+
const url = `https://api.figma.com/v1/files/${fileKey}/variables/local`;
|
|
5
|
+
const res = await fetch(url, {
|
|
6
|
+
headers: {
|
|
7
|
+
'X-Figma-Token': figmaToken,
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
if (!res.ok) {
|
|
11
|
+
const body = await res.text().catch(() => '');
|
|
12
|
+
throw new Error(`Figma API error ${res.status}: ${res.statusText}${body ? `\n${body}` : ''}`);
|
|
13
|
+
}
|
|
14
|
+
return res.json();
|
|
15
|
+
}
|
|
16
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
17
|
+
/** Convert Figma RGBA (0–1 channels) to a CSS hex string. */
|
|
18
|
+
export function figmaColorToHex(color) {
|
|
19
|
+
const toHex = (n) => Math.round(Math.min(1, Math.max(0, n)) * 255)
|
|
20
|
+
.toString(16)
|
|
21
|
+
.padStart(2, '0');
|
|
22
|
+
const rgb = `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}`;
|
|
23
|
+
// Include alpha channel only when not fully opaque
|
|
24
|
+
if (color.a < 0.9999) {
|
|
25
|
+
return `${rgb}${toHex(color.a)}`;
|
|
26
|
+
}
|
|
27
|
+
return rgb;
|
|
28
|
+
}
|
|
29
|
+
/** True if value is a FigmaColor object (has r/g/b/a number fields). */
|
|
30
|
+
export function isFigmaColor(v) {
|
|
31
|
+
return (typeof v === 'object' &&
|
|
32
|
+
v !== null &&
|
|
33
|
+
'r' in v &&
|
|
34
|
+
typeof v.r === 'number');
|
|
35
|
+
}
|
|
36
|
+
/** True if value is a VARIABLE_ALIAS reference. */
|
|
37
|
+
export function isAlias(v) {
|
|
38
|
+
return (typeof v === 'object' &&
|
|
39
|
+
v !== null &&
|
|
40
|
+
v.type === 'VARIABLE_ALIAS');
|
|
41
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lucent-manifest init
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx lucent-manifest init --figma-token <token> --file-key <key> [options]
|
|
7
|
+
* npx lucent-manifest init --template (manual fallback)
|
|
8
|
+
*
|
|
9
|
+
* Options:
|
|
10
|
+
* --figma-token <token> Figma personal access token
|
|
11
|
+
* --file-key <key> Figma file key (from the file URL)
|
|
12
|
+
* --light-mode <name> Mode name to treat as light theme (default: "light")
|
|
13
|
+
* --dark-mode <name> Mode name to treat as dark theme (default: "dark")
|
|
14
|
+
* --name <name> Design system name written into the manifest
|
|
15
|
+
* --out <path> Output file path (default: lucent.manifest.json)
|
|
16
|
+
* --template Write an empty JSON template instead of fetching Figma
|
|
17
|
+
*/
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { fetchFigmaVariables } from './figma.js';
|
|
21
|
+
import { mapFigmaToTokens } from './mapper.js';
|
|
22
|
+
// ─── Arg parsing ──────────────────────────────────────────────────────────────
|
|
23
|
+
function parseArgs(argv) {
|
|
24
|
+
const args = {};
|
|
25
|
+
for (let i = 0; i < argv.length; i++) {
|
|
26
|
+
const arg = argv[i];
|
|
27
|
+
if (arg.startsWith('--')) {
|
|
28
|
+
const key = arg.slice(2);
|
|
29
|
+
const next = argv[i + 1];
|
|
30
|
+
if (next !== undefined && !next.startsWith('--')) {
|
|
31
|
+
args[key] = next;
|
|
32
|
+
i++;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
args[key] = true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return args;
|
|
40
|
+
}
|
|
41
|
+
function required(args, key) {
|
|
42
|
+
const v = args[key];
|
|
43
|
+
if (!v || v === true) {
|
|
44
|
+
console.error(`Error: --${key} is required.`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
return v;
|
|
48
|
+
}
|
|
49
|
+
function optional(args, key, fallback) {
|
|
50
|
+
const v = args[key];
|
|
51
|
+
return typeof v === 'string' ? v : fallback;
|
|
52
|
+
}
|
|
53
|
+
// ─── Template fallback ────────────────────────────────────────────────────────
|
|
54
|
+
function writeTemplate(outPath) {
|
|
55
|
+
const templateSrc = new URL('./template.manifest.json', import.meta.url);
|
|
56
|
+
fs.copyFileSync(templateSrc, outPath);
|
|
57
|
+
console.log(`Template written to ${outPath}`);
|
|
58
|
+
console.log('Fill in the token values and load it with LucentProvider.');
|
|
59
|
+
}
|
|
60
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
61
|
+
async function main() {
|
|
62
|
+
const args = parseArgs(process.argv.slice(2));
|
|
63
|
+
const outPath = path.resolve(optional(args, 'out', 'lucent.manifest.json'));
|
|
64
|
+
if (args['template']) {
|
|
65
|
+
writeTemplate(outPath);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const figmaToken = required(args, 'figma-token');
|
|
69
|
+
const fileKey = required(args, 'file-key');
|
|
70
|
+
const lightModeName = optional(args, 'light-mode', 'light');
|
|
71
|
+
const darkModeName = optional(args, 'dark-mode', 'dark');
|
|
72
|
+
const systemName = optional(args, 'name', 'My Design System');
|
|
73
|
+
console.log(`Fetching Figma variables for file ${fileKey}…`);
|
|
74
|
+
const response = await fetchFigmaVariables(figmaToken, fileKey);
|
|
75
|
+
const manifest = {
|
|
76
|
+
version: '1.0',
|
|
77
|
+
name: systemName,
|
|
78
|
+
tokens: {},
|
|
79
|
+
};
|
|
80
|
+
// Light theme
|
|
81
|
+
try {
|
|
82
|
+
const { tokens: lightTokens, unmapped: lightUnmapped } = mapFigmaToTokens(response, lightModeName);
|
|
83
|
+
manifest.tokens.light = lightTokens;
|
|
84
|
+
console.log(`Light theme: mapped ${Object.keys(lightTokens).length} token(s).`);
|
|
85
|
+
if (lightUnmapped.length > 0) {
|
|
86
|
+
console.warn(` ${lightUnmapped.length} variable(s) didn't match a Lucent token key and were skipped:`);
|
|
87
|
+
for (const u of lightUnmapped) {
|
|
88
|
+
console.warn(` "${u.figmaName}" → candidate "${u.candidateKey}" (value: ${u.value})`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
console.warn(`Skipping light theme: ${err.message}`);
|
|
94
|
+
}
|
|
95
|
+
// Dark theme
|
|
96
|
+
try {
|
|
97
|
+
const { tokens: darkTokens, unmapped: darkUnmapped } = mapFigmaToTokens(response, darkModeName);
|
|
98
|
+
manifest.tokens.dark = darkTokens;
|
|
99
|
+
console.log(`Dark theme: mapped ${Object.keys(darkTokens).length} token(s).`);
|
|
100
|
+
if (darkUnmapped.length > 0) {
|
|
101
|
+
console.warn(` ${darkUnmapped.length} variable(s) didn't match a Lucent token key and were skipped:`);
|
|
102
|
+
for (const u of darkUnmapped) {
|
|
103
|
+
console.warn(` "${u.figmaName}" → candidate "${u.candidateKey}" (value: ${u.value})`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
console.warn(`Skipping dark theme: ${err.message}`);
|
|
109
|
+
}
|
|
110
|
+
if (!manifest.tokens.light && !manifest.tokens.dark) {
|
|
111
|
+
console.error('No tokens were mapped. Check your --light-mode and --dark-mode names match the Figma file.');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
fs.writeFileSync(outPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
115
|
+
console.log(`\nManifest written to ${outPath}`);
|
|
116
|
+
}
|
|
117
|
+
main().catch(err => {
|
|
118
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { figmaColorToHex, isFigmaColor, isAlias, } from './figma.js';
|
|
2
|
+
// ─── Token key registry ───────────────────────────────────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* Every valid key in LucentTokens — used to validate candidate names produced
|
|
5
|
+
* by the name normaliser before writing them into the output manifest.
|
|
6
|
+
*/
|
|
7
|
+
const LUCENT_TOKEN_KEYS = new Set([
|
|
8
|
+
// SemanticColorTokens
|
|
9
|
+
'bgBase', 'bgSubtle', 'bgMuted', 'bgOverlay',
|
|
10
|
+
'surfaceDefault', 'surfaceRaised', 'surfaceOverlay',
|
|
11
|
+
'borderDefault', 'borderSubtle', 'borderStrong',
|
|
12
|
+
'textPrimary', 'textSecondary', 'textDisabled', 'textInverse', 'textOnAccent',
|
|
13
|
+
'accentDefault', 'accentHover', 'accentActive', 'accentSubtle',
|
|
14
|
+
'successDefault', 'successSubtle', 'successText',
|
|
15
|
+
'warningDefault', 'warningSubtle', 'warningText',
|
|
16
|
+
'dangerDefault', 'dangerHover', 'dangerSubtle', 'dangerText',
|
|
17
|
+
'infoDefault', 'infoSubtle', 'infoText',
|
|
18
|
+
'focusRing',
|
|
19
|
+
// TypographyTokens
|
|
20
|
+
'fontFamilyBase', 'fontFamilyMono', 'fontFamilyDisplay',
|
|
21
|
+
'fontSizeXs', 'fontSizeSm', 'fontSizeMd', 'fontSizeLg',
|
|
22
|
+
'fontSizeXl', 'fontSize2xl', 'fontSize3xl',
|
|
23
|
+
'fontWeightRegular', 'fontWeightMedium', 'fontWeightSemibold', 'fontWeightBold',
|
|
24
|
+
'lineHeightTight', 'lineHeightBase', 'lineHeightRelaxed',
|
|
25
|
+
'letterSpacingTight', 'letterSpacingBase', 'letterSpacingWide',
|
|
26
|
+
// SpacingTokens
|
|
27
|
+
'space0', 'space1', 'space2', 'space3', 'space4', 'space5',
|
|
28
|
+
'space6', 'space8', 'space10', 'space12', 'space16', 'space20', 'space24',
|
|
29
|
+
// RadiusTokens
|
|
30
|
+
'radiusNone', 'radiusSm', 'radiusMd', 'radiusLg', 'radiusXl', 'radiusFull',
|
|
31
|
+
// ShadowTokens
|
|
32
|
+
'shadowNone', 'shadowSm', 'shadowMd', 'shadowLg', 'shadowXl',
|
|
33
|
+
// MotionTokens
|
|
34
|
+
'durationFast', 'durationBase', 'durationSlow',
|
|
35
|
+
'easingDefault', 'easingEmphasized', 'easingDecelerate',
|
|
36
|
+
]);
|
|
37
|
+
// ─── Name normalisation ───────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Top-level Figma collection category prefixes that carry no semantic meaning
|
|
40
|
+
* in the Lucent token schema and should be dropped before camelCasing.
|
|
41
|
+
*/
|
|
42
|
+
const CATEGORY_PREFIXES = new Set([
|
|
43
|
+
'color', 'colour', 'typography', 'type', 'spacing', 'space',
|
|
44
|
+
'radius', 'shadow', 'motion', 'animation',
|
|
45
|
+
]);
|
|
46
|
+
/**
|
|
47
|
+
* Convert a Figma variable name (slash-separated path, may include hyphens)
|
|
48
|
+
* into a camelCase candidate token key.
|
|
49
|
+
*
|
|
50
|
+
* Examples:
|
|
51
|
+
* "color/bg/base" → "bgBase"
|
|
52
|
+
* "color/text/primary" → "textPrimary"
|
|
53
|
+
* "typography/font-size/xl" → "fontSizeXl"
|
|
54
|
+
* "spacing/space-4" → "space4"
|
|
55
|
+
* "radius/radius-md" → "radiusMd"
|
|
56
|
+
* "accentDefault" → "accentDefault" (no change needed)
|
|
57
|
+
*/
|
|
58
|
+
export function normalizeName(figmaName) {
|
|
59
|
+
// Split path segments on /
|
|
60
|
+
const segments = figmaName.split('/').map(s => s.trim()).filter(Boolean);
|
|
61
|
+
// Drop a leading category prefix if present
|
|
62
|
+
if (segments.length > 1 && CATEGORY_PREFIXES.has(segments[0].toLowerCase())) {
|
|
63
|
+
segments.shift();
|
|
64
|
+
}
|
|
65
|
+
// Further split each segment on hyphens
|
|
66
|
+
const parts = segments.flatMap(s => s.split('-').filter(Boolean));
|
|
67
|
+
if (parts.length === 0)
|
|
68
|
+
return figmaName;
|
|
69
|
+
// camelCase: lowercase first part, title-case the rest
|
|
70
|
+
return parts
|
|
71
|
+
.map((p, i) => i === 0 ? p.toLowerCase() : p.charAt(0).toUpperCase() + p.slice(1).toLowerCase())
|
|
72
|
+
.join('');
|
|
73
|
+
}
|
|
74
|
+
// ─── Alias resolution ─────────────────────────────────────────────────────────
|
|
75
|
+
/**
|
|
76
|
+
* Resolve a variable value for a given mode, following VARIABLE_ALIAS chains.
|
|
77
|
+
* Returns `undefined` if the chain cannot be resolved (dangling alias, missing
|
|
78
|
+
* mode, non-string/non-color resolved type).
|
|
79
|
+
*/
|
|
80
|
+
function resolveValue(variable, modeId, allVariables, visited = new Set()) {
|
|
81
|
+
if (visited.has(variable.id))
|
|
82
|
+
return undefined; // circular alias guard
|
|
83
|
+
visited.add(variable.id);
|
|
84
|
+
const value = variable.valuesByMode[modeId];
|
|
85
|
+
if (value === undefined)
|
|
86
|
+
return undefined;
|
|
87
|
+
if (isAlias(value)) {
|
|
88
|
+
const target = allVariables[value.id];
|
|
89
|
+
if (!target)
|
|
90
|
+
return undefined;
|
|
91
|
+
// Aliases preserve their own modeId in the target collection — use the
|
|
92
|
+
// same modeId; if absent the target may use its defaultModeId but we
|
|
93
|
+
// can't determine that here without the collections map, so fall back to
|
|
94
|
+
// returning undefined rather than guessing.
|
|
95
|
+
return resolveValue(target, modeId, allVariables, visited);
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
// ─── Value → CSS string ───────────────────────────────────────────────────────
|
|
100
|
+
function toCssValue(value, resolvedType) {
|
|
101
|
+
if (resolvedType === 'COLOR' && isFigmaColor(value)) {
|
|
102
|
+
return figmaColorToHex(value);
|
|
103
|
+
}
|
|
104
|
+
if (resolvedType === 'FLOAT' && typeof value === 'number') {
|
|
105
|
+
// Figma stores spacing/radius in px without units; emit as "Npx"
|
|
106
|
+
return `${value}px`;
|
|
107
|
+
}
|
|
108
|
+
if (resolvedType === 'STRING' && typeof value === 'string') {
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Map a Figma Variables API response for a single named mode (e.g. "light" or
|
|
115
|
+
* "dark") into a `Partial<LucentTokens>` token override object.
|
|
116
|
+
*
|
|
117
|
+
* @param response Full response from `fetchFigmaVariables`
|
|
118
|
+
* @param modeName Case-insensitive mode name to extract (e.g. "Light", "dark")
|
|
119
|
+
*/
|
|
120
|
+
export function mapFigmaToTokens(response, modeName) {
|
|
121
|
+
const { variables, variableCollections } = response.meta;
|
|
122
|
+
// Find the modeId matching the requested name across all collections
|
|
123
|
+
const targetModeName = modeName.toLowerCase();
|
|
124
|
+
const modeIdByCollection = new Map();
|
|
125
|
+
for (const collection of Object.values(variableCollections)) {
|
|
126
|
+
const match = collection.modes.find(m => m.name.toLowerCase() === targetModeName);
|
|
127
|
+
if (match) {
|
|
128
|
+
modeIdByCollection.set(collection.id, match.modeId);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (modeIdByCollection.size === 0) {
|
|
132
|
+
const available = Object.values(variableCollections)
|
|
133
|
+
.flatMap(c => c.modes.map(m => m.name))
|
|
134
|
+
.filter((v, i, a) => a.indexOf(v) === i)
|
|
135
|
+
.join(', ');
|
|
136
|
+
throw new Error(`No mode named "${modeName}" found in Figma file.\nAvailable modes: ${available || '(none)'}`);
|
|
137
|
+
}
|
|
138
|
+
const tokens = {};
|
|
139
|
+
const unmapped = [];
|
|
140
|
+
for (const variable of Object.values(variables)) {
|
|
141
|
+
// Skip booleans — no LucentTokens field uses boolean values
|
|
142
|
+
if (variable.resolvedType === 'BOOLEAN')
|
|
143
|
+
continue;
|
|
144
|
+
const modeId = modeIdByCollection.get(variable.variableCollectionId);
|
|
145
|
+
if (modeId === undefined)
|
|
146
|
+
continue;
|
|
147
|
+
const raw = resolveValue(variable, modeId, variables);
|
|
148
|
+
if (raw === undefined)
|
|
149
|
+
continue;
|
|
150
|
+
const cssValue = toCssValue(raw, variable.resolvedType);
|
|
151
|
+
if (cssValue === undefined)
|
|
152
|
+
continue;
|
|
153
|
+
const candidate = normalizeName(variable.name);
|
|
154
|
+
if (LUCENT_TOKEN_KEYS.has(candidate)) {
|
|
155
|
+
tokens[candidate] = cssValue;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
unmapped.push({ figmaName: variable.name, candidateKey: candidate, value: cssValue });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return { tokens, unmapped };
|
|
162
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"name": "My Design System",
|
|
4
|
+
"description": "Optional description of your design system",
|
|
5
|
+
"tokens": {
|
|
6
|
+
"light": {
|
|
7
|
+
"bgBase": "#ffffff",
|
|
8
|
+
"bgSubtle": "#f9fafb",
|
|
9
|
+
"bgMuted": "#f3f4f6",
|
|
10
|
+
"bgOverlay": "rgb(0 0 0 / 0.4)",
|
|
11
|
+
"surfaceDefault": "#ffffff",
|
|
12
|
+
"surfaceRaised": "#ffffff",
|
|
13
|
+
"surfaceOverlay": "#ffffff",
|
|
14
|
+
"borderDefault": "#e5e7eb",
|
|
15
|
+
"borderSubtle": "#f3f4f6",
|
|
16
|
+
"borderStrong": "#9ca3af",
|
|
17
|
+
"textPrimary": "#111827",
|
|
18
|
+
"textSecondary": "#6b7280",
|
|
19
|
+
"textDisabled": "#9ca3af",
|
|
20
|
+
"textInverse": "#ffffff",
|
|
21
|
+
"textOnAccent": "#ffffff",
|
|
22
|
+
"accentDefault": "#111827",
|
|
23
|
+
"accentHover": "#1f2937",
|
|
24
|
+
"accentActive": "#374151",
|
|
25
|
+
"accentSubtle": "#f3f4f6",
|
|
26
|
+
"successDefault": "#16a34a",
|
|
27
|
+
"successSubtle": "#f0fdf4",
|
|
28
|
+
"successText": "#15803d",
|
|
29
|
+
"warningDefault": "#d97706",
|
|
30
|
+
"warningSubtle": "#fffbeb",
|
|
31
|
+
"warningText": "#b45309",
|
|
32
|
+
"dangerDefault": "#dc2626",
|
|
33
|
+
"dangerHover": "#b91c1c",
|
|
34
|
+
"dangerSubtle": "#fef2f2",
|
|
35
|
+
"dangerText": "#b91c1c",
|
|
36
|
+
"infoDefault": "#2563eb",
|
|
37
|
+
"infoSubtle": "#eff6ff",
|
|
38
|
+
"infoText": "#1d4ed8",
|
|
39
|
+
"focusRing": "#111827",
|
|
40
|
+
"fontFamilyBase": "\"Inter\", sans-serif",
|
|
41
|
+
"fontFamilyMono": "\"Fira Code\", monospace",
|
|
42
|
+
"fontFamilyDisplay": "\"Inter\", sans-serif"
|
|
43
|
+
},
|
|
44
|
+
"dark": {
|
|
45
|
+
"bgBase": "#0f172a",
|
|
46
|
+
"bgSubtle": "#1e293b",
|
|
47
|
+
"bgMuted": "#334155",
|
|
48
|
+
"bgOverlay": "rgb(0 0 0 / 0.6)",
|
|
49
|
+
"surfaceDefault": "#1e293b",
|
|
50
|
+
"surfaceRaised": "#334155",
|
|
51
|
+
"surfaceOverlay": "#1e293b",
|
|
52
|
+
"borderDefault": "#334155",
|
|
53
|
+
"borderSubtle": "#1e293b",
|
|
54
|
+
"borderStrong": "#64748b",
|
|
55
|
+
"textPrimary": "#f8fafc",
|
|
56
|
+
"textSecondary": "#94a3b8",
|
|
57
|
+
"textDisabled": "#475569",
|
|
58
|
+
"textInverse": "#0f172a",
|
|
59
|
+
"textOnAccent": "#0f172a",
|
|
60
|
+
"accentDefault": "#f8fafc",
|
|
61
|
+
"accentHover": "#e2e8f0",
|
|
62
|
+
"accentActive": "#cbd5e1",
|
|
63
|
+
"accentSubtle": "#1e293b",
|
|
64
|
+
"successDefault": "#22c55e",
|
|
65
|
+
"successSubtle": "#052e16",
|
|
66
|
+
"successText": "#4ade80",
|
|
67
|
+
"warningDefault": "#f59e0b",
|
|
68
|
+
"warningSubtle": "#431407",
|
|
69
|
+
"warningText": "#fbbf24",
|
|
70
|
+
"dangerDefault": "#ef4444",
|
|
71
|
+
"dangerHover": "#f87171",
|
|
72
|
+
"dangerSubtle": "#450a0a",
|
|
73
|
+
"dangerText": "#f87171",
|
|
74
|
+
"infoDefault": "#3b82f6",
|
|
75
|
+
"infoSubtle": "#0f1f47",
|
|
76
|
+
"infoText": "#60a5fa",
|
|
77
|
+
"focusRing": "#f8fafc",
|
|
78
|
+
"fontFamilyBase": "\"Inter\", sans-serif",
|
|
79
|
+
"fontFamilyMono": "\"Fira Code\", monospace",
|
|
80
|
+
"fontFamilyDisplay": "\"Inter\", sans-serif"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -3,7 +3,7 @@ export const ButtonManifest = {
|
|
|
3
3
|
name: 'Button',
|
|
4
4
|
tier: 'atom',
|
|
5
5
|
domain: 'neutral',
|
|
6
|
-
specVersion: '0
|
|
6
|
+
specVersion: '1.0',
|
|
7
7
|
description: 'A clickable control that triggers an action. The primary interactive primitive in Lucent UI.',
|
|
8
8
|
designIntent: 'Buttons communicate available actions. Variant conveys hierarchy: use "primary" for the ' +
|
|
9
9
|
'single most important action in a view, "secondary" for supporting actions, "ghost" for ' +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lucent-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "An AI-first React component library with machine-readable manifests.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
"./styles": "./dist/index.css"
|
|
16
16
|
},
|
|
17
17
|
"bin": {
|
|
18
|
-
"lucent-mcp": "./dist-server/server/index.js"
|
|
18
|
+
"lucent-mcp": "./dist-server/server/index.js",
|
|
19
|
+
"lucent-manifest": "./dist-cli/cli/index.js"
|
|
19
20
|
},
|
|
20
21
|
"sideEffects": false,
|
|
21
22
|
"files": [
|
|
22
23
|
"dist",
|
|
23
|
-
"dist-server"
|
|
24
|
+
"dist-server",
|
|
25
|
+
"dist-cli"
|
|
24
26
|
],
|
|
25
27
|
"keywords": [
|
|
26
28
|
"react",
|
|
@@ -55,6 +57,7 @@
|
|
|
55
57
|
"dev": "vite --config vite.dev.config.ts",
|
|
56
58
|
"build": "vite build",
|
|
57
59
|
"build:server": "tsc -p server/tsconfig.json",
|
|
60
|
+
"build:cli": "tsc -p cli/tsconfig.json && cp cli/template.manifest.json dist-cli/cli/template.manifest.json",
|
|
58
61
|
"test": "echo \"No tests yet\" && exit 0",
|
|
59
62
|
"changeset": "changeset",
|
|
60
63
|
"version-packages": "changeset version",
|