lucent-ui 0.7.0 → 0.9.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.
@@ -6,8 +6,8 @@ import { figmaColorToHex, isFigmaColor, isAlias, } from './figma.js';
6
6
  */
7
7
  const LUCENT_TOKEN_KEYS = new Set([
8
8
  // SemanticColorTokens
9
- 'bgBase', 'bgSubtle', 'bgMuted', 'bgOverlay',
10
- 'surfaceDefault', 'surfaceRaised', 'surfaceOverlay',
9
+ 'bgBase', 'bgSubtle', 'bgOverlay',
10
+ 'surface', 'surfaceSecondary', 'surfaceRaised', 'surfaceOverlay',
11
11
  'borderDefault', 'borderSubtle', 'borderStrong',
12
12
  'textPrimary', 'textSecondary', 'textDisabled', 'textInverse', 'textOnAccent',
13
13
  'accentDefault', 'accentHover', 'accentActive', 'accentSubtle',
@@ -0,0 +1,91 @@
1
+ export const COMPONENT_MANIFEST = {
2
+ id: 'color-picker',
3
+ name: 'ColorPicker',
4
+ tier: 'atom',
5
+ domain: 'neutral',
6
+ specVersion: '0.1',
7
+ description: 'A color swatch button that opens a popover with preset swatches and HEX / RGB / RGBA / HSL inputs.',
8
+ designIntent: 'ColorPicker surfaces color selection without navigating away. The swatch button shows the ' +
9
+ 'current color at a glance; the popover provides precise input through four format modes. ' +
10
+ 'Presets accelerate common choices — provide a curated palette relevant to the product. ' +
11
+ 'Always pair with a visible label so the purpose of the picker is clear.',
12
+ props: [
13
+ {
14
+ name: 'value',
15
+ type: 'string',
16
+ required: false,
17
+ description: 'Controlled color value. Accepts hex (#rrggbb / #rrggbbaa), rgb(), rgba(), or hsl() strings.',
18
+ },
19
+ {
20
+ name: 'onChange',
21
+ type: 'function',
22
+ required: false,
23
+ description: 'Called with the new hex string (#rrggbb, or #rrggbbaa when alpha < 1) whenever the color changes.',
24
+ },
25
+ {
26
+ name: 'label',
27
+ type: 'string',
28
+ required: false,
29
+ description: 'Visible label rendered above the swatch button.',
30
+ },
31
+ {
32
+ name: 'presets',
33
+ type: 'string[]',
34
+ required: false,
35
+ description: 'Array of hex color strings shown as clickable swatches. Defaults to a 16-color palette.',
36
+ },
37
+ {
38
+ name: 'disabled',
39
+ type: 'boolean',
40
+ required: false,
41
+ default: 'false',
42
+ description: 'Prevents interaction and dims the label.',
43
+ },
44
+ {
45
+ name: 'id',
46
+ type: 'string',
47
+ required: false,
48
+ description: 'HTML id for the swatch button. Auto-generated if omitted.',
49
+ },
50
+ {
51
+ name: 'style',
52
+ type: 'CSSProperties',
53
+ required: false,
54
+ description: 'Inline styles applied to the outer wrapper.',
55
+ },
56
+ ],
57
+ usageExamples: [
58
+ {
59
+ title: 'Basic controlled',
60
+ code: `const [color, setColor] = useState('#3b82f6');
61
+ <ColorPicker value={color} onChange={setColor} label="Brand color" />`,
62
+ },
63
+ {
64
+ title: 'Custom presets',
65
+ code: `<ColorPicker
66
+ value={accent}
67
+ onChange={setAccent}
68
+ label="Accent"
69
+ presets={['#111827', '#3b82f6', '#8b5cf6', '#ec4899', '#ef4444', '#f97316', '#22c55e']}
70
+ />`,
71
+ },
72
+ {
73
+ title: 'No presets',
74
+ code: `<ColorPicker value={color} onChange={setColor} presets={[]} label="Custom color" />`,
75
+ },
76
+ {
77
+ title: 'Disabled',
78
+ code: `<ColorPicker value="#6b7280" disabled label="Theme color" />`,
79
+ },
80
+ ],
81
+ compositionGraph: [],
82
+ accessibility: {
83
+ role: 'button (trigger) + dialog (popover)',
84
+ ariaAttributes: ['aria-expanded', 'aria-haspopup', 'aria-label'],
85
+ keyboardInteractions: [
86
+ 'Enter / Space — opens the color picker popover',
87
+ 'Escape / click outside — closes the popover',
88
+ 'Tab — moves through format tabs and input fields within the popover',
89
+ ],
90
+ },
91
+ };
@@ -0,0 +1,39 @@
1
+ export const COMPONENT_MANIFEST = {
2
+ id: 'color-swatch',
3
+ name: 'ColorSwatch',
4
+ tier: 'atom',
5
+ domain: 'neutral',
6
+ specVersion: '0.1',
7
+ description: 'A circular color swatch that can be static or interactive.',
8
+ designIntent: 'ColorSwatch is the smallest unit of color representation in the system. ' +
9
+ 'Use it wherever a color needs to be shown at a glance — palette pickers, ' +
10
+ 'token lists, or inline color indicators. The selected state shows an ' +
11
+ 'accent-colored ring to indicate the active choice.',
12
+ props: [
13
+ { name: 'color', type: 'string', required: true, description: 'CSS color value to display.' },
14
+ { name: 'size', type: "'sm' | 'md' | 'lg'", required: false, default: "'md'", description: 'Swatch diameter: sm=16px, md=22px, lg=28px.' },
15
+ { name: 'selected', type: 'boolean', required: false, default: 'false', description: 'Shows an accent ring when true.' },
16
+ { name: 'onClick', type: 'function', required: false, description: 'Click handler. Omit for a non-interactive display swatch.' },
17
+ { name: 'disabled', type: 'boolean', required: false, default: 'false', description: 'Dims the swatch and prevents interaction.' },
18
+ { name: 'title', type: 'string', required: false, description: 'Tooltip text. Defaults to the color value.' },
19
+ { name: 'style', type: 'CSSProperties', required: false, description: 'Inline styles applied to the swatch button.' },
20
+ ],
21
+ usageExamples: [
22
+ {
23
+ title: 'Static palette',
24
+ code: `['#ef4444', '#f97316', '#22c55e'].map(c => (
25
+ <ColorSwatch key={c} color={c} />
26
+ ))`,
27
+ },
28
+ {
29
+ title: 'Selectable swatch',
30
+ code: `<ColorSwatch color="#3b82f6" selected={active === '#3b82f6'} onClick={() => setActive('#3b82f6')} />`,
31
+ },
32
+ ],
33
+ compositionGraph: [],
34
+ accessibility: {
35
+ role: 'button',
36
+ ariaAttributes: ['title (tooltip)'],
37
+ keyboardInteractions: ['Enter / Space — triggers onClick'],
38
+ },
39
+ };
@@ -0,0 +1,102 @@
1
+ export const COMPONENT_MANIFEST = {
2
+ id: 'segmented-control',
3
+ name: 'SegmentedControl',
4
+ tier: 'atom',
5
+ domain: 'neutral',
6
+ specVersion: '0.1',
7
+ description: 'A pill-group selector where exactly one option is always active, with a sliding indicator.',
8
+ designIntent: 'Use SegmentedControl for mutually exclusive view switches or mode selectors that have ' +
9
+ '2–5 options and where all options should be visible simultaneously (unlike Select). ' +
10
+ 'Common uses: view mode (Grid / List / Table), time range (Day / Week / Month), ' +
11
+ 'format picker (Hex / RGB / HSL). Keep labels short (1–2 words) so the control stays compact. ' +
12
+ 'For more than 5 options or dynamic option lists, prefer Select.',
13
+ props: [
14
+ {
15
+ name: 'options',
16
+ type: 'SegmentedOption[]',
17
+ required: true,
18
+ description: 'Array of { value, label, disabled? }. 2–5 options recommended.',
19
+ },
20
+ {
21
+ name: 'value',
22
+ type: 'string',
23
+ required: false,
24
+ description: 'Controlled selected value. Pair with onChange.',
25
+ },
26
+ {
27
+ name: 'defaultValue',
28
+ type: 'string',
29
+ required: false,
30
+ description: 'Initial value for uncontrolled usage. Defaults to the first option.',
31
+ },
32
+ {
33
+ name: 'onChange',
34
+ type: 'function',
35
+ required: false,
36
+ description: 'Called with the newly selected value.',
37
+ },
38
+ {
39
+ name: 'size',
40
+ type: 'enum',
41
+ required: false,
42
+ default: 'md',
43
+ description: 'Height and font size of the control.',
44
+ enumValues: ['sm', 'md', 'lg'],
45
+ },
46
+ {
47
+ name: 'disabled',
48
+ type: 'boolean',
49
+ required: false,
50
+ default: 'false',
51
+ description: 'Disables all options.',
52
+ },
53
+ {
54
+ name: 'fullWidth',
55
+ type: 'boolean',
56
+ required: false,
57
+ default: 'false',
58
+ description: 'Stretches the control to fill its container, distributing options equally.',
59
+ },
60
+ ],
61
+ usageExamples: [
62
+ {
63
+ title: 'Controlled',
64
+ code: `const [view, setView] = useState('grid');
65
+ <SegmentedControl
66
+ value={view}
67
+ onChange={setView}
68
+ options={[
69
+ { value: 'grid', label: 'Grid' },
70
+ { value: 'list', label: 'List' },
71
+ { value: 'table', label: 'Table' },
72
+ ]}
73
+ />`,
74
+ },
75
+ {
76
+ title: 'With icons',
77
+ code: `<SegmentedControl
78
+ defaultValue="week"
79
+ options={[
80
+ { value: 'day', label: <><CalIcon /> Day</> },
81
+ { value: 'week', label: <><CalIcon /> Week</> },
82
+ { value: 'month', label: <><CalIcon /> Month</> },
83
+ ]}
84
+ />`,
85
+ },
86
+ {
87
+ title: 'Full-width, sizes',
88
+ code: `<SegmentedControl fullWidth size="sm" defaultValue="a" options={[{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }]} />
89
+ <SegmentedControl fullWidth size="md" defaultValue="a" options={[{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }]} />
90
+ <SegmentedControl fullWidth size="lg" defaultValue="a" options={[{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }]} />`,
91
+ },
92
+ ],
93
+ compositionGraph: [],
94
+ accessibility: {
95
+ role: 'group (radiogroup)',
96
+ ariaAttributes: ['aria-checked', 'role="radio"', 'disabled'],
97
+ keyboardInteractions: [
98
+ 'Tab — focuses the group',
99
+ 'Click — selects an option',
100
+ ],
101
+ },
102
+ };
@@ -17,13 +17,13 @@ export const COMPONENT_MANIFEST = {
17
17
  type: 'boolean',
18
18
  required: false,
19
19
  default: 'false',
20
- description: 'Applies alternating bgMuted backgrounds to even tbody rows.',
20
+ description: 'Applies alternating surfaceSecondary backgrounds to even tbody rows.',
21
21
  },
22
22
  {
23
23
  name: 'Table.Head',
24
24
  type: 'component',
25
25
  required: false,
26
- description: 'Renders <thead> with bgMuted background. Accepts Table.Row children.',
26
+ description: 'Renders <thead> with surfaceSecondary background. Accepts Table.Row children.',
27
27
  },
28
28
  {
29
29
  name: 'Table.Body',
@@ -35,7 +35,7 @@ export const COMPONENT_MANIFEST = {
35
35
  name: 'Table.Foot',
36
36
  type: 'component',
37
37
  required: false,
38
- description: 'Renders <tfoot> with bgMuted background.',
38
+ description: 'Renders <tfoot> with surfaceSecondary background.',
39
39
  },
40
40
  {
41
41
  name: 'Table.Row',
@@ -9,7 +9,11 @@ export const COMPONENT_MANIFEST = {
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
- 'children respect the border-radius without needing additional clipping.',
12
+ 'children respect the border-radius without needing additional clipping.\n\n' +
13
+ 'Token rule: Card uses surface for its background. Never use bgBase or bgSubtle on a Card — ' +
14
+ 'those tokens are reserved for the page canvas (body, sidebar, layout regions). Content nested ' +
15
+ 'inside a Card that needs a tinted fill (e.g. a footer, inset panel, or disabled input) should use ' +
16
+ 'surfaceSecondary.',
13
17
  props: [
14
18
  {
15
19
  name: 'children',
@@ -27,7 +27,7 @@ export function validateManifest(manifest) {
27
27
  errors.push(err('id', 'Must be kebab-case (e.g. "button", "form-field")'));
28
28
  }
29
29
  // tier
30
- const validTiers = ['atom', 'molecule', 'block', 'flow', 'overlay'];
30
+ const validTiers = ['atom', 'molecule', 'block', 'flow', 'overlay', 'provider'];
31
31
  if (!validTiers.includes(m['tier'])) {
32
32
  errors.push(err('tier', `Must be one of: ${validTiers.join(', ')}`));
33
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lucent-ui",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "An AI-first React component library with machine-readable manifests.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -31,6 +31,11 @@
31
31
  "mcp",
32
32
  "ai"
33
33
  ],
34
+ "homepage": "https://lucentui.dev",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/rozinashopify/lucent-ui"
38
+ },
34
39
  "author": "Rozina Szogyenyi",
35
40
  "license": "MIT",
36
41
  "peerDependencies": {
@@ -43,11 +48,13 @@
43
48
  "@types/react": "^18.3.28",
44
49
  "@types/react-dom": "^18.3.7",
45
50
  "@vitejs/plugin-react": "^4.7.0",
51
+ "@vitest/ui": "^4.0.18",
46
52
  "react": "^18.3.1",
47
53
  "react-dom": "^18.3.1",
48
54
  "typescript": "^5.9.3",
49
55
  "vite": "^6.4.1",
50
- "vite-plugin-dts": "^4.5.4"
56
+ "vite-plugin-dts": "^4.5.4",
57
+ "vitest": "^4.0.18"
51
58
  },
52
59
  "dependencies": {
53
60
  "@modelcontextprotocol/sdk": "^1.27.1",
@@ -58,7 +65,8 @@
58
65
  "build": "vite build",
59
66
  "build:server": "tsc -p server/tsconfig.json",
60
67
  "build:cli": "tsc -p cli/tsconfig.json && cp cli/template.manifest.json dist-cli/cli/template.manifest.json",
61
- "test": "echo \"No tests yet\" && exit 0",
68
+ "test": "vitest run",
69
+ "test:watch": "vitest",
62
70
  "changeset": "changeset",
63
71
  "version-packages": "changeset version",
64
72
  "release": "pnpm prepublishOnly && changeset publish"