lucent-ui 0.9.1 → 0.10.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.
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lucent-ui CLI
4
+ *
5
+ * Usage:
6
+ * npx lucent-ui init — Interactive preset setup
7
+ * npx lucent-ui manifest — Alias for lucent-manifest (see lucent-manifest --help)
8
+ */
9
+ const command = process.argv[2];
10
+ switch (command) {
11
+ case 'init': {
12
+ const { runInit } = await import('./init/index.js');
13
+ await runInit();
14
+ break;
15
+ }
16
+ case 'manifest': {
17
+ // Delegate to the existing manifest CLI by re-running it
18
+ await import('./index.js');
19
+ break;
20
+ }
21
+ default:
22
+ console.log('lucent-ui — CLI tools for Lucent UI\n');
23
+ console.log('Commands:');
24
+ console.log(' init Interactive design preset setup');
25
+ console.log(' manifest Generate component manifests from Figma\n');
26
+ console.log('Usage: npx lucent-ui <command>');
27
+ if (command) {
28
+ console.error(`\nUnknown command: "${command}"`);
29
+ process.exit(1);
30
+ }
31
+ }
32
+ export {};
@@ -0,0 +1,115 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { choose, close } from './prompts.js';
4
+ const PALETTES = [
5
+ { label: 'Default', value: 'default', description: 'Monochrome — neutral grays, near-black accent' },
6
+ { label: 'Brand', value: 'brand', description: 'Gold accent with warm cream-tinted grays' },
7
+ { label: 'Indigo', value: 'indigo', description: 'Violet-indigo accent (#6366f1) with cool-tinted grays' },
8
+ { label: 'Emerald', value: 'emerald', description: 'Green accent (#10b981) with cool green-tinted grays' },
9
+ { label: 'Rose', value: 'rose', description: 'Pink-rose accent (#f43f5e) with warm-tinted grays' },
10
+ { label: 'Ocean', value: 'ocean', description: 'Sky-blue accent (#0ea5e9) with cool blue-tinted grays' },
11
+ ];
12
+ const SHAPES = [
13
+ { label: 'Sharp', value: 'sharp', description: 'Minimal border radius — enterprise / professional' },
14
+ { label: 'Rounded', value: 'rounded', description: 'Balanced border radius (default)' },
15
+ { label: 'Pill', value: 'pill', description: 'Large border radius — playful / modern' },
16
+ ];
17
+ const DENSITIES = [
18
+ { label: 'Compact', value: 'compact', description: 'Tighter spacing — data-dense UIs' },
19
+ { label: 'Default', value: 'default', description: 'Balanced spacing' },
20
+ { label: 'Spacious', value: 'spacious', description: 'More breathing room — marketing / consumer' },
21
+ ];
22
+ const SHADOWS = [
23
+ { label: 'Flat', value: 'flat', description: 'No shadows — minimalist, flat design' },
24
+ { label: 'Subtle', value: 'subtle', description: 'Light shadows (default)' },
25
+ { label: 'Elevated', value: 'elevated', description: 'Pronounced shadows — material / depth' },
26
+ ];
27
+ const COMBINED = [
28
+ { label: 'Modern', value: 'modern', description: 'Indigo + rounded + default spacing + subtle shadows' },
29
+ { label: 'Enterprise', value: 'enterprise', description: 'Monochrome + sharp + compact + flat shadows' },
30
+ { label: 'Playful', value: 'playful', description: 'Rose + pill + spacious + elevated shadows' },
31
+ { label: 'Custom', value: 'custom', description: 'Mix and match each dimension yourself' },
32
+ ];
33
+ function isDefaultConfig(config) {
34
+ return config.palette === 'default' && config.shape === 'rounded' &&
35
+ config.density === 'default' && config.shadow === 'subtle';
36
+ }
37
+ function generateConfigFile(config) {
38
+ const lines = [
39
+ `import type { PresetProp } from 'lucent-ui';`,
40
+ '',
41
+ `export const preset: PresetProp = {`,
42
+ ` palette: '${config.palette}',`,
43
+ ` shape: '${config.shape}',`,
44
+ ` density: '${config.density}',`,
45
+ ` shadow: '${config.shadow}',`,
46
+ `};`,
47
+ '',
48
+ ];
49
+ return lines.join('\n');
50
+ }
51
+ function generateCombinedConfigFile(presetName) {
52
+ const lines = [
53
+ `import type { PresetProp } from 'lucent-ui';`,
54
+ '',
55
+ `export const preset: PresetProp = '${presetName}';`,
56
+ '',
57
+ ];
58
+ return lines.join('\n');
59
+ }
60
+ function printSnippet() {
61
+ console.log(`
62
+ Add this to your app entry point:
63
+
64
+ import { LucentProvider } from 'lucent-ui';
65
+ import { preset } from './lucent.config';
66
+
67
+ function App() {
68
+ return (
69
+ <LucentProvider preset={preset}>
70
+ {/* your app */}
71
+ </LucentProvider>
72
+ );
73
+ }
74
+ `);
75
+ }
76
+ const COMBINED_PRESETS = {
77
+ modern: { palette: 'indigo', shape: 'rounded', density: 'default', shadow: 'subtle' },
78
+ enterprise: { palette: 'default', shape: 'sharp', density: 'compact', shadow: 'flat' },
79
+ playful: { palette: 'rose', shape: 'pill', density: 'spacious', shadow: 'elevated' },
80
+ };
81
+ export async function runInit() {
82
+ console.log('\n Lucent UI — Design Preset Setup\n');
83
+ const mode = await choose('How would you like to configure your design?', COMBINED);
84
+ let configContent;
85
+ if (mode !== 'custom') {
86
+ configContent = generateCombinedConfigFile(mode);
87
+ const details = COMBINED_PRESETS[mode];
88
+ if (details) {
89
+ console.log(`\n Preset "${mode}":`);
90
+ console.log(` Palette: ${details.palette}`);
91
+ console.log(` Shape: ${details.shape}`);
92
+ console.log(` Density: ${details.density}`);
93
+ console.log(` Shadow: ${details.shadow}`);
94
+ }
95
+ }
96
+ else {
97
+ const palette = await choose('Choose a color palette:', PALETTES);
98
+ const shape = await choose('Choose a shape style:', SHAPES);
99
+ const density = await choose('Choose a density:', DENSITIES);
100
+ const shadow = await choose('Choose a shadow style:', SHADOWS);
101
+ const config = { palette, shape, density, shadow };
102
+ if (isDefaultConfig(config)) {
103
+ console.log('\n Your selections match the built-in defaults — no config file needed!');
104
+ console.log(' Just use <LucentProvider> without any preset prop.\n');
105
+ close();
106
+ return;
107
+ }
108
+ configContent = generateConfigFile(config);
109
+ }
110
+ const outPath = path.resolve('lucent.config.ts');
111
+ fs.writeFileSync(outPath, configContent, 'utf8');
112
+ console.log(`\n Config written to ${outPath}`);
113
+ printSnippet();
114
+ close();
115
+ }
@@ -0,0 +1,25 @@
1
+ import * as readline from 'node:readline';
2
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3
+ function ask(question) {
4
+ return new Promise((resolve) => {
5
+ rl.question(question, (answer) => resolve(answer.trim()));
6
+ });
7
+ }
8
+ export async function choose(title, options) {
9
+ console.log(`\n${title}\n`);
10
+ for (let i = 0; i < options.length; i++) {
11
+ console.log(` ${i + 1}) ${options[i].label} — ${options[i].description}`);
12
+ }
13
+ console.log();
14
+ while (true) {
15
+ const input = await ask(` Enter choice (1–${options.length}): `);
16
+ const idx = parseInt(input, 10) - 1;
17
+ if (idx >= 0 && idx < options.length) {
18
+ return options[idx].value;
19
+ }
20
+ console.log(` Invalid choice. Please enter a number between 1 and ${options.length}.`);
21
+ }
22
+ }
23
+ export function close() {
24
+ rl.close();
25
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -3,6 +3,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { z } from 'zod';
5
5
  import { ALL_MANIFESTS } from './registry.js';
6
+ import { PALETTES, SHAPES, DENSITIES, SHADOWS, COMBINED, generatePresetConfig } from './presets.js';
6
7
  // ─── Auth stub ───────────────────────────────────────────────────────────────
7
8
  // LUCENT_API_KEY is reserved for the future paid tier.
8
9
  // When set, the server acknowledges it but does not yet enforce it.
@@ -106,6 +107,50 @@ server.tool('search_components', 'Searches Lucent UI components by description o
106
107
  ],
107
108
  };
108
109
  });
110
+ // Tool: list_presets
111
+ server.tool('list_presets', 'Lists all available Lucent UI design presets. Returns combined presets (modern, enterprise, playful) and individual dimensions (palettes, shapes, densities, shadows) that can be mixed and matched.', {}, async () => {
112
+ return {
113
+ content: [
114
+ {
115
+ type: 'text',
116
+ text: JSON.stringify({
117
+ combined: COMBINED,
118
+ palettes: PALETTES,
119
+ shapes: SHAPES,
120
+ densities: DENSITIES,
121
+ shadows: SHADOWS,
122
+ }, null, 2),
123
+ },
124
+ ],
125
+ };
126
+ });
127
+ // Tool: get_preset_config
128
+ server.tool('get_preset_config', 'Returns the LucentProvider configuration code for a given preset selection. Pass a combined preset name OR individual dimension names to get a ready-to-use config file and provider snippet.', {
129
+ preset: z.string().optional().describe('Combined preset name: "modern", "enterprise", or "playful"'),
130
+ palette: z.string().optional().describe('Palette name: "default", "brand", "indigo", "emerald", "rose", or "ocean"'),
131
+ shape: z.string().optional().describe('Shape name: "sharp", "rounded", or "pill"'),
132
+ density: z.string().optional().describe('Density name: "compact", "default", or "spacious"'),
133
+ shadow: z.string().optional().describe('Shadow name: "flat", "subtle", or "elevated"'),
134
+ }, async ({ preset, palette, shape, density, shadow }) => {
135
+ const result = generatePresetConfig({ preset, palette, shape, density, shadow });
136
+ if ('error' in result) {
137
+ return {
138
+ content: [{ type: 'text', text: JSON.stringify({ error: result.error }) }],
139
+ isError: true,
140
+ };
141
+ }
142
+ return {
143
+ content: [
144
+ {
145
+ type: 'text',
146
+ text: JSON.stringify({
147
+ configFile: result.configFile,
148
+ providerSnippet: result.providerSnippet,
149
+ }, null, 2),
150
+ },
151
+ ],
152
+ };
153
+ });
109
154
  // ─── Start ────────────────────────────────────────────────────────────────────
110
155
  const transport = new StdioServerTransport();
111
156
  await server.connect(transport);
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Static preset metadata for MCP tools. These are descriptions only —
3
+ * no runtime token resolution happens in the server.
4
+ */
5
+ export const PALETTES = [
6
+ { name: 'default', description: 'Monochrome — neutral grays, near-black accent', accent: '#111827' },
7
+ { name: 'brand', description: 'Gold accent with warm cream-tinted grays', accent: '#e9c96b' },
8
+ { name: 'indigo', description: 'Violet-indigo accent with cool-tinted grays', accent: '#6366f1' },
9
+ { name: 'emerald', description: 'Green accent with cool green-tinted grays', accent: '#10b981' },
10
+ { name: 'rose', description: 'Pink-rose accent with warm-tinted grays', accent: '#f43f5e' },
11
+ { name: 'ocean', description: 'Sky-blue accent with cool blue-tinted grays', accent: '#0ea5e9' },
12
+ ];
13
+ export const SHAPES = [
14
+ { name: 'sharp', description: 'Minimal border radius — enterprise / professional' },
15
+ { name: 'rounded', description: 'Balanced border radius (default)' },
16
+ { name: 'pill', description: 'Large border radius — playful / modern' },
17
+ ];
18
+ export const DENSITIES = [
19
+ { name: 'compact', description: 'Tighter spacing — data-dense UIs' },
20
+ { name: 'default', description: 'Balanced spacing' },
21
+ { name: 'spacious', description: 'More breathing room — marketing / consumer' },
22
+ ];
23
+ export const SHADOWS = [
24
+ { name: 'flat', description: 'No shadows — minimalist, flat design' },
25
+ { name: 'subtle', description: 'Light shadows (default)' },
26
+ { name: 'elevated', description: 'Pronounced shadows — material / depth' },
27
+ ];
28
+ export const COMBINED = [
29
+ { name: 'modern', description: 'Indigo accent with rounded corners and balanced spacing', palette: 'indigo', shape: 'rounded', density: 'default', shadow: 'subtle' },
30
+ { name: 'enterprise', description: 'Monochrome with sharp corners and compact spacing', palette: 'default', shape: 'sharp', density: 'compact', shadow: 'flat' },
31
+ { name: 'playful', description: 'Rose accent with pill corners and spacious layout', palette: 'rose', shape: 'pill', density: 'spacious', shadow: 'elevated' },
32
+ ];
33
+ const VALID_PALETTES = new Set(PALETTES.map((p) => p.name));
34
+ const VALID_SHAPES = new Set(SHAPES.map((s) => s.name));
35
+ const VALID_DENSITIES = new Set(DENSITIES.map((d) => d.name));
36
+ const VALID_SHADOWS = new Set(SHADOWS.map((s) => s.name));
37
+ const VALID_COMBINED = new Set(COMBINED.map((c) => c.name));
38
+ export function generatePresetConfig(selection) {
39
+ // Combined preset
40
+ if (selection.preset) {
41
+ if (!VALID_COMBINED.has(selection.preset)) {
42
+ return { error: `Unknown combined preset "${selection.preset}". Available: ${[...VALID_COMBINED].join(', ')}` };
43
+ }
44
+ const configFile = [
45
+ `import type { PresetProp } from 'lucent-ui';`,
46
+ ``,
47
+ `export const preset: PresetProp = '${selection.preset}';`,
48
+ ].join('\n');
49
+ const providerSnippet = [
50
+ `import { LucentProvider } from 'lucent-ui';`,
51
+ `import { preset } from './lucent.config';`,
52
+ ``,
53
+ `function App() {`,
54
+ ` return (`,
55
+ ` <LucentProvider preset={preset}>`,
56
+ ` {/* your app */}`,
57
+ ` </LucentProvider>`,
58
+ ` );`,
59
+ `}`,
60
+ ].join('\n');
61
+ return { configFile, providerSnippet };
62
+ }
63
+ // Mix-and-match
64
+ const errors = [];
65
+ if (selection.palette && !VALID_PALETTES.has(selection.palette))
66
+ errors.push(`palette "${selection.palette}"`);
67
+ if (selection.shape && !VALID_SHAPES.has(selection.shape))
68
+ errors.push(`shape "${selection.shape}"`);
69
+ if (selection.density && !VALID_DENSITIES.has(selection.density))
70
+ errors.push(`density "${selection.density}"`);
71
+ if (selection.shadow && !VALID_SHADOWS.has(selection.shadow))
72
+ errors.push(`shadow "${selection.shadow}"`);
73
+ if (errors.length > 0) {
74
+ return { error: `Unknown values: ${errors.join(', ')}` };
75
+ }
76
+ const parts = [];
77
+ if (selection.palette)
78
+ parts.push(` palette: '${selection.palette}',`);
79
+ if (selection.shape)
80
+ parts.push(` shape: '${selection.shape}',`);
81
+ if (selection.density)
82
+ parts.push(` density: '${selection.density}',`);
83
+ if (selection.shadow)
84
+ parts.push(` shadow: '${selection.shadow}',`);
85
+ const configFile = [
86
+ `import type { PresetProp } from 'lucent-ui';`,
87
+ ``,
88
+ `export const preset: PresetProp = {`,
89
+ ...parts,
90
+ `};`,
91
+ ].join('\n');
92
+ const providerSnippet = [
93
+ `import { LucentProvider } from 'lucent-ui';`,
94
+ `import { preset } from './lucent.config';`,
95
+ ``,
96
+ `function App() {`,
97
+ ` return (`,
98
+ ` <LucentProvider preset={preset}>`,
99
+ ` {/* your app */}`,
100
+ ` </LucentProvider>`,
101
+ ` );`,
102
+ `}`,
103
+ ].join('\n');
104
+ return { configFile, providerSnippet };
105
+ }
@@ -4,12 +4,16 @@ export const COMPONENT_MANIFEST = {
4
4
  tier: 'molecule',
5
5
  domain: 'neutral',
6
6
  specVersion: '0.1',
7
- description: 'A surface container with optional header, body, and footer slots, configurable padding, shadow, and radius.',
7
+ description: 'A surface container with optional header, body, and footer slots, configurable padding, shadow, and radius. Includes a CardBleed sub-component for edge-to-edge content.',
8
8
  designIntent: 'Card provides a consistent elevated surface for grouping related content. The header and footer slots ' +
9
9
  'are separated from the body by a border-default divider, giving visual structure without requiring ' +
10
10
  'the consumer to manage spacing. Padding, shadow, and radius are all configurable to accommodate ' +
11
11
  'flat/ghost cards, modal-like surfaces, and compact data-dense layouts. The overflow: hidden ensures ' +
12
12
  'children respect the border-radius without needing additional clipping.\n\n' +
13
+ 'CardBleed is a companion component that allows specific children within the Card body to stretch ' +
14
+ 'edge-to-edge, cancelling the horizontal padding via negative margins. Text inside CardBleed stays ' +
15
+ 'aligned with the rest of the card content thanks to matching inner padding. Use it for dividers, ' +
16
+ 'full-width lists, or bordered sections that need to reach the card edges.\n\n' +
13
17
  'Token rule: Card uses surface for its background. Never use bgBase or bgSubtle on a Card — ' +
14
18
  'those tokens are reserved for the page canvas (body, sidebar, layout regions). Content nested ' +
15
19
  'inside a Card that needs a tinted fill (e.g. a footer, inset panel, or disabled input) should use ' +
@@ -84,6 +88,15 @@ export const COMPONENT_MANIFEST = {
84
88
  title: 'Flat variant',
85
89
  code: `<Card shadow="none" radius="sm" padding="sm">
86
90
  <Text size="sm">Compact flat card</Text>
91
+ </Card>`,
92
+ },
93
+ {
94
+ title: 'Edge-to-edge content with CardBleed',
95
+ code: `<Card>
96
+ <Text weight="semibold">Settings</Text>
97
+ <CardBleed style={{ borderTop: '1px solid var(--lucent-border-default)', marginTop: 'var(--lucent-space-4)' }}>
98
+ <Text color="secondary">This section stretches to the card edges.</Text>
99
+ </CardBleed>
87
100
  </Card>`,
88
101
  },
89
102
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lucent-ui",
3
- "version": "0.9.1",
3
+ "version": "0.10.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,6 +15,7 @@
15
15
  "./styles": "./dist/index.css"
16
16
  },
17
17
  "bin": {
18
+ "lucent-ui": "./dist-cli/cli/entry.js",
18
19
  "lucent-mcp": "./dist-server/server/index.js",
19
20
  "lucent-manifest": "./dist-cli/cli/index.js"
20
21
  },