lucent-ui 0.30.0 → 0.32.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.
Files changed (27) hide show
  1. package/dist/index.cjs +78 -42
  2. package/dist/index.d.ts +34 -0
  3. package/dist/index.js +1347 -1041
  4. package/dist-cli/cli/entry.js +0 -0
  5. package/dist-cli/cli/index.js +0 -0
  6. package/dist-server/server/index.js +29 -29
  7. package/dist-server/server/pattern-registry.js +18 -0
  8. package/dist-server/src/components/molecules/Card/Card.manifest.js +2 -2
  9. package/dist-server/src/components/molecules/Collapsible/Collapsible.manifest.js +4 -4
  10. package/dist-server/src/components/molecules/Stepper/Stepper.manifest.js +115 -0
  11. package/dist-server/src/manifest/{recipes/action-bar.recipe.js → patterns/action-bar.pattern.js} +1 -1
  12. package/dist-server/src/manifest/{recipes/collapsible-card.recipe.js → patterns/collapsible-card.pattern.js} +1 -1
  13. package/dist-server/src/manifest/patterns/dashboard-header.pattern.js +98 -0
  14. package/dist-server/src/manifest/{recipes/empty-state-card.recipe.js → patterns/empty-state-card.pattern.js} +1 -1
  15. package/dist-server/src/manifest/{recipes/form-layout.recipe.js → patterns/form-layout.pattern.js} +1 -1
  16. package/dist-server/src/manifest/patterns/index.js +12 -0
  17. package/dist-server/src/manifest/patterns/notification-feed.pattern.js +91 -0
  18. package/dist-server/src/manifest/patterns/onboarding-flow.pattern.js +107 -0
  19. package/dist-server/src/manifest/patterns/pricing-table.pattern.js +108 -0
  20. package/dist-server/src/manifest/{recipes/profile-card.recipe.js → patterns/profile-card.pattern.js} +1 -1
  21. package/dist-server/src/manifest/{recipes/search-filter-bar.recipe.js → patterns/search-filter-bar.pattern.js} +1 -1
  22. package/dist-server/src/manifest/{recipes/settings-panel.recipe.js → patterns/settings-panel.pattern.js} +1 -1
  23. package/dist-server/src/manifest/{recipes/stats-row.recipe.js → patterns/stats-row.pattern.js} +1 -1
  24. package/package.json +13 -15
  25. package/dist-server/server/recipe-registry.js +0 -18
  26. package/dist-server/src/manifest/recipes/index.js +0 -8
  27. package/dist-server/src/manifest/validate.test.js +0 -28
File without changes
File without changes
@@ -3,7 +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 { ALL_RECIPES } from './recipe-registry.js';
6
+ import { ALL_PATTERNS } from './pattern-registry.js';
7
7
  import { PALETTES, SHAPES, DENSITIES, SHADOWS, COMBINED, generatePresetConfig } from './presets.js';
8
8
  // ─── Auth stub ───────────────────────────────────────────────────────────────
9
9
  // LUCENT_API_KEY is reserved for the future paid tier.
@@ -38,11 +38,11 @@ function scoreManifest(m, query) {
38
38
  }
39
39
  return score;
40
40
  }
41
- function findRecipe(nameOrId) {
41
+ function findPattern(nameOrId) {
42
42
  const q = nameOrId.trim().toLowerCase();
43
- return ALL_RECIPES.find((r) => r.id.toLowerCase() === q || r.name.toLowerCase() === q);
43
+ return ALL_PATTERNS.find((r) => r.id.toLowerCase() === q || r.name.toLowerCase() === q);
44
44
  }
45
- function scoreRecipe(r, query) {
45
+ function scorePattern(r, query) {
46
46
  const q = query.toLowerCase();
47
47
  let score = 0;
48
48
  if (r.name.toLowerCase().includes(q))
@@ -110,7 +110,7 @@ server.tool('get_component_manifest', 'Returns the full manifest JSON for a Luce
110
110
  };
111
111
  });
112
112
  // Tool: search_components
113
- server.tool('search_components', 'Searches Lucent UI components and composition recipes by description or concept. Returns matching components and recipes ranked by relevance.', { query: z.string().describe('Natural language or keyword query, e.g. "loading indicator", "form validation", or "profile card"') }, async ({ query }) => {
113
+ server.tool('search_components', 'Searches Lucent UI components and composition patterns by description or concept. Returns matching components and patterns ranked by relevance.', { query: z.string().describe('Natural language or keyword query, e.g. "loading indicator", "form validation", or "profile card"') }, async ({ query }) => {
114
114
  const componentResults = ALL_MANIFESTS
115
115
  .map((m) => ({ manifest: m, score: scoreManifest(m, query) }))
116
116
  .filter(({ score }) => score > 0)
@@ -122,41 +122,41 @@ server.tool('search_components', 'Searches Lucent UI components and composition
122
122
  description: manifest.description,
123
123
  score,
124
124
  }));
125
- const recipeResults = ALL_RECIPES
126
- .map((r) => ({ recipe: r, score: scoreRecipe(r, query) }))
125
+ const patternResults = ALL_PATTERNS
126
+ .map((r) => ({ pattern: r, score: scorePattern(r, query) }))
127
127
  .filter(({ score }) => score > 0)
128
128
  .sort((a, b) => b.score - a.score)
129
- .map(({ recipe, score }) => ({
130
- id: recipe.id,
131
- name: recipe.name,
132
- category: recipe.category,
133
- description: recipe.description,
129
+ .map(({ pattern, score }) => ({
130
+ id: pattern.id,
131
+ name: pattern.name,
132
+ category: pattern.category,
133
+ description: pattern.description,
134
134
  score,
135
135
  }));
136
136
  return {
137
137
  content: [
138
138
  {
139
139
  type: 'text',
140
- text: JSON.stringify({ query, components: componentResults, recipes: recipeResults }, null, 2),
140
+ text: JSON.stringify({ query, components: componentResults, patterns: patternResults }, null, 2),
141
141
  },
142
142
  ],
143
143
  };
144
144
  });
145
- // Tool: get_composition_recipe
146
- server.tool('get_composition_recipe', 'Returns a full composition recipe with structure tree, working JSX code, variants, and design notes. Query by recipe name/id or by category to get all recipes in that category.', {
147
- name: z.string().optional().describe('Recipe name or id, e.g. "Profile Card" or "settings-panel"'),
148
- category: z.string().optional().describe('Recipe category: "card", "form", "nav", "dashboard", "settings", or "action"'),
145
+ // Tool: get_composition_pattern
146
+ server.tool('get_composition_pattern', 'Returns a full composition pattern with structure tree, working JSX code, variants, and design notes. Query by pattern name/id or by category to get all patterns in that category.', {
147
+ name: z.string().optional().describe('Pattern name or id, e.g. "Profile Card" or "settings-panel"'),
148
+ category: z.string().optional().describe('Pattern category: "card", "form", "nav", "dashboard", "settings", or "action"'),
149
149
  }, async ({ name, category }) => {
150
150
  if (name) {
151
- const recipe = findRecipe(name);
152
- if (!recipe) {
151
+ const pattern = findPattern(name);
152
+ if (!pattern) {
153
153
  return {
154
154
  content: [
155
155
  {
156
156
  type: 'text',
157
157
  text: JSON.stringify({
158
- error: `Recipe "${name}" not found.`,
159
- available: ALL_RECIPES.map((r) => ({ id: r.id, name: r.name, category: r.category })),
158
+ error: `Pattern "${name}" not found.`,
159
+ available: ALL_PATTERNS.map((r) => ({ id: r.id, name: r.name, category: r.category })),
160
160
  }),
161
161
  },
162
162
  ],
@@ -167,22 +167,22 @@ server.tool('get_composition_recipe', 'Returns a full composition recipe with st
167
167
  content: [
168
168
  {
169
169
  type: 'text',
170
- text: JSON.stringify(recipe, null, 2),
170
+ text: JSON.stringify(pattern, null, 2),
171
171
  },
172
172
  ],
173
173
  };
174
174
  }
175
175
  if (category) {
176
176
  const cat = category.trim().toLowerCase();
177
- const recipes = ALL_RECIPES.filter((r) => r.category === cat);
178
- if (recipes.length === 0) {
177
+ const patterns = ALL_PATTERNS.filter((r) => r.category === cat);
178
+ if (patterns.length === 0) {
179
179
  return {
180
180
  content: [
181
181
  {
182
182
  type: 'text',
183
183
  text: JSON.stringify({
184
- error: `No recipes found in category "${category}".`,
185
- availableCategories: [...new Set(ALL_RECIPES.map((r) => r.category))],
184
+ error: `No patterns found in category "${category}".`,
185
+ availableCategories: [...new Set(ALL_PATTERNS.map((r) => r.category))],
186
186
  }),
187
187
  },
188
188
  ],
@@ -193,18 +193,18 @@ server.tool('get_composition_recipe', 'Returns a full composition recipe with st
193
193
  content: [
194
194
  {
195
195
  type: 'text',
196
- text: JSON.stringify({ category: cat, recipes }, null, 2),
196
+ text: JSON.stringify({ category: cat, patterns }, null, 2),
197
197
  },
198
198
  ],
199
199
  };
200
200
  }
201
- // No filter — return all recipes
201
+ // No filter — return all patterns
202
202
  return {
203
203
  content: [
204
204
  {
205
205
  type: 'text',
206
206
  text: JSON.stringify({
207
- recipes: ALL_RECIPES.map((r) => ({
207
+ patterns: ALL_PATTERNS.map((r) => ({
208
208
  id: r.id,
209
209
  name: r.name,
210
210
  category: r.category,
@@ -0,0 +1,18 @@
1
+ import { PATTERN as ProfileCard } from '../src/manifest/patterns/profile-card.pattern.js';
2
+ import { PATTERN as SettingsPanel } from '../src/manifest/patterns/settings-panel.pattern.js';
3
+ import { PATTERN as StatsRow } from '../src/manifest/patterns/stats-row.pattern.js';
4
+ import { PATTERN as ActionBar } from '../src/manifest/patterns/action-bar.pattern.js';
5
+ import { PATTERN as FormLayout } from '../src/manifest/patterns/form-layout.pattern.js';
6
+ import { PATTERN as EmptyStateCard } from '../src/manifest/patterns/empty-state-card.pattern.js';
7
+ import { PATTERN as CollapsibleCard } from '../src/manifest/patterns/collapsible-card.pattern.js';
8
+ import { PATTERN as SearchFilterBar } from '../src/manifest/patterns/search-filter-bar.pattern.js';
9
+ export const ALL_PATTERNS = [
10
+ ProfileCard,
11
+ SettingsPanel,
12
+ StatsRow,
13
+ ActionBar,
14
+ FormLayout,
15
+ EmptyStateCard,
16
+ CollapsibleCard,
17
+ SearchFilterBar,
18
+ ];
@@ -274,7 +274,7 @@ export const COMPONENT_MANIFEST = {
274
274
  </Card>`,
275
275
  },
276
276
  {
277
- title: 'CollapsibleCard recipe — outline',
277
+ title: 'CollapsibleCard pattern — outline',
278
278
  code: `<Card variant="outline" hoverable>
279
279
  <Collapsible trigger={<Text as="span" weight="semibold" size="sm">Filters</Text>} defaultOpen>
280
280
  <Text size="sm" color="secondary">Card + Collapsible composed together. Collapsible auto-bleeds to card edges.</Text>
@@ -282,7 +282,7 @@ export const COMPONENT_MANIFEST = {
282
282
  </Card>`,
283
283
  },
284
284
  {
285
- title: 'CollapsibleCard recipe — combo (filled + elevated)',
285
+ title: 'CollapsibleCard pattern — combo (filled + elevated)',
286
286
  code: `<Card variant="filled" hoverable>
287
287
  <Collapsible trigger={<Text as="span" weight="semibold" size="sm">Details</Text>} padded={false}>
288
288
  <Card variant="elevated" padding="sm" style={{ margin: 'var(--lucent-space-1) var(--lucent-space-2) var(--lucent-space-2)' }}>
@@ -13,7 +13,7 @@ export const COMPONENT_MANIFEST = {
13
13
  'Content fades in/out with opacity + translateY(-4px) at 80ms, while height transitions at 180ms ' +
14
14
  'using the easing-default token. The animated content wrapper uses overflow:hidden only during the ' +
15
15
  'height transition and switches to overflow:visible once open, so nested child shadows (e.g. an ' +
16
- 'elevated Card in the combo recipe) are never clipped in the resting state.\n\n' +
16
+ 'elevated Card in the combo pattern) are never clipped in the resting state.\n\n' +
17
17
  'A built-in chevron rotates 180° on open, giving clear directional affordance. ' +
18
18
  'Hover feedback uses a CSS rule via data-lucent-collapsible-trigger (same pattern as NavMenu): ' +
19
19
  '5% text-primary tint on the trigger background, chevron darkens to text-primary. ' +
@@ -70,7 +70,7 @@ export const COMPONENT_MANIFEST = {
70
70
  default: 'true',
71
71
  description: 'When true (default), applies built-in content padding (space-2 top, space-4 sides, space-3 bottom). ' +
72
72
  'Set to false when children provide their own padding — e.g. when nesting a Card inside the Collapsible ' +
73
- 'for the CollapsibleCard combo recipe.',
73
+ 'for the CollapsibleCard combo pattern.',
74
74
  },
75
75
  {
76
76
  name: 'style',
@@ -101,7 +101,7 @@ export const COMPONENT_MANIFEST = {
101
101
  </Collapsible>`,
102
102
  },
103
103
  {
104
- title: 'CollapsibleCard recipe (auto-bleed)',
104
+ title: 'CollapsibleCard pattern (auto-bleed)',
105
105
  code: `<Card variant="outline" hoverable>
106
106
  <Collapsible trigger={<Text as="span" weight="semibold" size="sm">Filters</Text>} defaultOpen>
107
107
  <Text size="sm" color="secondary">Card + Collapsible composed together. No padding="none" needed.</Text>
@@ -109,7 +109,7 @@ export const COMPONENT_MANIFEST = {
109
109
  </Card>`,
110
110
  },
111
111
  {
112
- title: 'CollapsibleCard combo recipe (padded={false})',
112
+ title: 'CollapsibleCard combo pattern (padded={false})',
113
113
  code: `<Card variant="filled" hoverable>
114
114
  <Collapsible trigger={<Text as="span" weight="semibold" size="sm">Details</Text>} padded={false}>
115
115
  <Card variant="elevated" padding="sm" style={{ margin: 'var(--lucent-space-1) var(--lucent-space-2) var(--lucent-space-2)' }}>
@@ -0,0 +1,115 @@
1
+ export const COMPONENT_MANIFEST = {
2
+ id: 'stepper',
3
+ name: 'Stepper',
4
+ tier: 'molecule',
5
+ domain: 'neutral',
6
+ specVersion: '1.0',
7
+ description: 'A step indicator for multi-step flows — onboarding, wizards, checkout. Supports horizontal and vertical orientations with animated transitions.',
8
+ designIntent: 'Use Stepper to visualise progress through a sequence of discrete steps. ' +
9
+ 'Steps can be simple strings or objects with label, description, and custom icon. ' +
10
+ 'Completed steps show an animated checkmark (spring scale 0→1.2→1), the current step ' +
11
+ 'is highlighted with accent, and future steps are subdued. In horizontal mode the connector ' +
12
+ 'track runs behind all circles as a continuous bar; the filled portion animates smoothly ' +
13
+ 'between steps. First/last labels align left/right; middle labels center under their circles. ' +
14
+ 'Enable numbered to show "STEP N" prefixes and showStatus for Completed/In Progress/Pending ' +
15
+ 'Chip badges (success/accent/neutral variants). The vertical orientation suits sidebar layouts ' +
16
+ 'and forms with longer descriptive steps. ' +
17
+ 'Unlike Progress, Stepper is for discrete named stages, not continuous percentages.',
18
+ props: [
19
+ {
20
+ name: 'steps',
21
+ type: 'array',
22
+ required: true,
23
+ description: 'Step definitions. Each element is either a string (used as label) or an object ' +
24
+ '{ label: string, description?: string, icon?: ReactNode }. Custom icons override ' +
25
+ 'the default number/checkmark in the circle.',
26
+ },
27
+ { name: 'current', type: 'number', required: true, description: 'Zero-based index of the active step. Steps before this index show as completed; steps after show as pending.' },
28
+ { name: 'size', type: 'enum', required: false, default: 'md', description: 'Controls circle diameter (sm=24, md=32, lg=40), checkmark size, connector thickness, and label font size.', enumValues: ['sm', 'md', 'lg'] },
29
+ { name: 'orientation', type: 'enum', required: false, default: 'horizontal', description: 'Layout direction. Horizontal shows circles in a row with a connector track behind them; vertical stacks them with a connector column on the left.', enumValues: ['horizontal', 'vertical'] },
30
+ { name: 'numbered', type: 'boolean', required: false, default: 'false', description: 'Show uppercase "STEP N" prefix above each step label.' },
31
+ { name: 'showStatus', type: 'boolean', required: false, default: 'false', description: 'Show a Chip badge below each label — "Completed" (success), "In Progress" (accent), or "Pending" (neutral).' },
32
+ { name: 'style', type: 'object', required: false, description: 'Inline style overrides for the root container.' },
33
+ ],
34
+ usageExamples: [
35
+ {
36
+ title: 'Basic horizontal',
37
+ code: `<Stepper steps={['Profile', 'Preferences', 'Confirm']} current={1} />`,
38
+ description: 'Minimal stepper — string labels, no extras.',
39
+ },
40
+ {
41
+ title: 'Numbered with status badges',
42
+ code: `<Stepper
43
+ steps={['Basic Details', 'Company Details', 'Subscription', 'Payment']}
44
+ current={2}
45
+ numbered
46
+ showStatus
47
+ />`,
48
+ description: 'STEP N prefixes and Completed/In Progress/Pending chips.',
49
+ },
50
+ {
51
+ title: 'Large size',
52
+ code: `<Stepper steps={['Cart', 'Shipping', 'Payment']} current={2} size="lg" showStatus />`,
53
+ description: 'Larger circles and text for prominent placement.',
54
+ },
55
+ {
56
+ title: 'Vertical with descriptions',
57
+ code: `<Stepper
58
+ orientation="vertical"
59
+ numbered
60
+ showStatus
61
+ size="lg"
62
+ current={1}
63
+ steps={[
64
+ { label: 'Basic Details', description: 'Name, email, and contact info' },
65
+ { label: 'Company Details', description: 'Organization and team size' },
66
+ { label: 'Subscription Plan', description: 'Choose your plan and billing' },
67
+ { label: 'Payment Details', description: 'Card information and billing address' },
68
+ ]}
69
+ />`,
70
+ description: 'Vertical layout with step descriptions for sidebar or form flows.',
71
+ },
72
+ {
73
+ title: 'Custom icons',
74
+ code: `<Stepper
75
+ current={1}
76
+ steps={[
77
+ { label: 'Cart', icon: <CartIcon /> },
78
+ { label: 'Shipping', icon: <TruckIcon /> },
79
+ { label: 'Payment', icon: <CreditCardIcon /> },
80
+ ]}
81
+ />`,
82
+ description: 'Custom icon per step overrides the default number/checkmark.',
83
+ },
84
+ {
85
+ title: 'In onboarding flow',
86
+ code: `const [step, setStep] = useState(0);
87
+
88
+ <Card variant="elevated" padding="lg">
89
+ <Stack gap="6">
90
+ <Stepper steps={['Profile', 'Preferences', 'Confirm']} current={step} />
91
+ <Divider />
92
+ {/* Step content */}
93
+ <Row justify="between">
94
+ <Button variant="ghost" size="sm" onClick={() => setStep(s => s - 1)} disabled={step === 0}>Back</Button>
95
+ <Button variant="primary" size="sm" onClick={() => setStep(s => s + 1)}>Continue</Button>
96
+ </Row>
97
+ </Stack>
98
+ </Card>`,
99
+ description: 'Stepper paired with step content and navigation buttons inside a Card.',
100
+ },
101
+ ],
102
+ compositionGraph: [
103
+ { componentId: 'chip', componentName: 'Chip', role: 'Status badge for each step (Completed/In Progress/Pending)', required: false },
104
+ ],
105
+ accessibility: {
106
+ role: 'group',
107
+ ariaAttributes: ['aria-label', 'aria-current'],
108
+ keyboardInteractions: [],
109
+ notes: 'Root element has role="group" with aria-label="Progress steps". ' +
110
+ 'The current step circle receives aria-current="step" so screen readers ' +
111
+ 'can identify which step is active. Step labels are visible text, not aria-only. ' +
112
+ 'The checkmark animation uses prefers-reduced-motion: the spring scale is purely ' +
113
+ 'decorative and does not affect content comprehension.',
114
+ },
115
+ };
@@ -1,4 +1,4 @@
1
- export const RECIPE = {
1
+ export const PATTERN = {
2
2
  id: 'action-bar',
3
3
  name: 'Action Bar',
4
4
  description: 'Page-level or card-level header pairing a title with action buttons. Page headers use breadcrumb, large display title, and a divider below; card headers are compact with small text.',
@@ -1,4 +1,4 @@
1
- export const RECIPE = {
1
+ export const PATTERN = {
2
2
  id: 'collapsible-card',
3
3
  name: 'Collapsible Card',
4
4
  description: 'Card with an expandable/collapsible section using smooth height animation, available in all card variants.',
@@ -0,0 +1,98 @@
1
+ export const PATTERN = {
2
+ id: 'dashboard-header',
3
+ name: 'Dashboard Header',
4
+ description: 'Full dashboard header combining breadcrumb navigation, page title with action buttons, and a row of KPI stat cards with trend indicators.',
5
+ category: 'dashboard',
6
+ components: ['text', 'button', 'chip', 'card', 'stack', 'row', 'breadcrumb', 'divider'],
7
+ structure: `
8
+ Stack gap="5"
9
+ ├── Stack gap="4" ← action bar
10
+ │ ├── Breadcrumb ← navigation context
11
+ │ ├── Row justify="between" align="end"
12
+ │ │ ├── Stack gap="1"
13
+ │ │ │ ├── Text (h1, 3xl, bold, display) ← page title
14
+ │ │ │ └── Text (sm, secondary) ← subtitle
15
+ │ │ └── Row gap="2"
16
+ │ │ ├── Button (outline, sm, leftIcon) ← Export
17
+ │ │ └── Button (primary, sm, leftIcon) ← New report
18
+ │ └── Divider
19
+ └── Row gap="3" wrap ← stats row
20
+ └── Card[] (outline, padding="md", flex=1)
21
+ └── Stack gap="3"
22
+ ├── Text (xs, secondary, medium) ← metric label
23
+ ├── Text (2xl, bold, display) ← value
24
+ └── Row gap="2" align="center"
25
+ ├── Chip (success/danger, sm, borderless) ← trend
26
+ └── Text (xs, secondary) ← comparison
27
+ `.trim(),
28
+ code: `<Stack gap="5" style={{ maxWidth: 860 }}>
29
+ <Stack gap="4">
30
+ <Breadcrumb items={[
31
+ { label: 'Home', href: '#' },
32
+ { label: 'Analytics', href: '#' },
33
+ { label: 'Overview' },
34
+ ]} />
35
+ <Row justify="between" align="end">
36
+ <Stack gap="1">
37
+ <Text as="h1" size="3xl" weight="bold" family="display">Analytics Overview</Text>
38
+ <Text size="sm" color="secondary">Last updated 5 minutes ago</Text>
39
+ </Stack>
40
+ <Row gap="2">
41
+ <Button variant="outline" size="sm" leftIcon={<DownloadIcon />}>Export</Button>
42
+ <Button variant="primary" size="sm" leftIcon={<PlusIcon />}>New report</Button>
43
+ </Row>
44
+ </Row>
45
+ <Divider />
46
+ </Stack>
47
+ <Row gap="3" wrap>
48
+ <Card variant="outline" padding="md" style={{ flex: 1, minWidth: 180 }}>
49
+ <Stack gap="3">
50
+ <Text size="xs" color="secondary" weight="medium">Total Visitors</Text>
51
+ <Text size="2xl" weight="bold" family="display">24.8k</Text>
52
+ <Row gap="2" align="center">
53
+ <Chip variant="success" size="sm" borderless>+18%</Chip>
54
+ <Text size="xs" color="secondary">20.9k last month</Text>
55
+ </Row>
56
+ </Stack>
57
+ </Card>
58
+ <Card variant="outline" padding="md" style={{ flex: 1, minWidth: 180 }}>
59
+ <Stack gap="3">
60
+ <Text size="xs" color="secondary" weight="medium">Conversion Rate</Text>
61
+ <Text size="2xl" weight="bold" family="display">3.24%</Text>
62
+ <Row gap="2" align="center">
63
+ <Chip variant="success" size="sm" borderless>+0.4%</Chip>
64
+ <Text size="xs" color="secondary">2.84% last month</Text>
65
+ </Row>
66
+ </Stack>
67
+ </Card>
68
+ <Card variant="outline" padding="md" style={{ flex: 1, minWidth: 180 }}>
69
+ <Stack gap="3">
70
+ <Text size="xs" color="secondary" weight="medium">Avg. Session</Text>
71
+ <Text size="2xl" weight="bold" family="display">4m 32s</Text>
72
+ <Row gap="2" align="center">
73
+ <Chip variant="danger" size="sm" borderless>-12%</Chip>
74
+ <Text size="xs" color="secondary">5m 08s last month</Text>
75
+ </Row>
76
+ </Stack>
77
+ </Card>
78
+ <Card variant="outline" padding="md" style={{ flex: 1, minWidth: 180 }}>
79
+ <Stack gap="3">
80
+ <Text size="xs" color="secondary" weight="medium">Revenue</Text>
81
+ <Text size="2xl" weight="bold" family="display">$48.2k</Text>
82
+ <Row gap="2" align="center">
83
+ <Chip variant="success" size="sm" borderless>+24%</Chip>
84
+ <Text size="xs" color="secondary">$38.9k last month</Text>
85
+ </Row>
86
+ </Stack>
87
+ </Card>
88
+ </Row>
89
+ </Stack>`,
90
+ designNotes: 'This composition combines two common dashboard patterns: the ActionBar and StatsRow. ' +
91
+ 'The breadcrumb provides navigation context above the title. align="end" on the ' +
92
+ 'title row anchors the action buttons to the baseline of the subtitle. Button ' +
93
+ 'leftIcon props add visual affordance to Export (download icon) and New report ' +
94
+ '(plus icon). The Divider separates the header chrome from the metric content below. ' +
95
+ 'Stat cards use flex: 1 with minWidth: 180 for responsive equal sizing. Display font ' +
96
+ 'on metric values creates instant visual hierarchy. Trend chips use success/danger ' +
97
+ 'variants with borderless styling so they read as inline metadata, not interactive tags.',
98
+ };
@@ -1,4 +1,4 @@
1
- export const RECIPE = {
1
+ export const PATTERN = {
2
2
  id: 'empty-state-card',
3
3
  name: 'Empty State Card',
4
4
  description: 'Centered empty state with illustration icon, heading, description, and call-to-action button inside a card.',
@@ -1,4 +1,4 @@
1
- export const RECIPE = {
1
+ export const PATTERN = {
2
2
  id: 'form-layout',
3
3
  name: 'Form Layout',
4
4
  description: 'Stacked form with grouped sections, FormField labels, validation hints, and a submit/cancel footer.',
@@ -0,0 +1,12 @@
1
+ export { PATTERN as ProfileCard } from './profile-card.pattern.js';
2
+ export { PATTERN as SettingsPanel } from './settings-panel.pattern.js';
3
+ export { PATTERN as StatsRow } from './stats-row.pattern.js';
4
+ export { PATTERN as ActionBar } from './action-bar.pattern.js';
5
+ export { PATTERN as FormLayout } from './form-layout.pattern.js';
6
+ export { PATTERN as EmptyStateCard } from './empty-state-card.pattern.js';
7
+ export { PATTERN as CollapsibleCard } from './collapsible-card.pattern.js';
8
+ export { PATTERN as SearchFilterBar } from './search-filter-bar.pattern.js';
9
+ export { PATTERN as PricingTable } from './pricing-table.pattern.js';
10
+ export { PATTERN as NotificationFeed } from './notification-feed.pattern.js';
11
+ export { PATTERN as OnboardingFlow } from './onboarding-flow.pattern.js';
12
+ export { PATTERN as DashboardHeader } from './dashboard-header.pattern.js';
@@ -0,0 +1,91 @@
1
+ export const PATTERN = {
2
+ id: 'notification-feed',
3
+ name: 'Notification Feed',
4
+ description: 'Scrollable list of notification items with avatar, message, timestamp, type chip, and contextual action buttons. Unread items are visually distinct.',
5
+ category: 'card',
6
+ components: ['card', 'avatar', 'text', 'chip', 'button', 'stack', 'row'],
7
+ structure: `
8
+ Card (elevated, padding via style)
9
+ └── Stack gap="3"
10
+ ├── Row justify="between" align="center" ← header
11
+ │ ├── Row gap="2" align="center"
12
+ │ │ ├── Text (lg, semibold) ← title
13
+ │ │ └── Chip (accent, sm) ← unread count
14
+ │ └── Button (ghost, xs) ← mark all read
15
+ └── Stack gap="2" ← notification list
16
+ └── Card[] (filled for unread, ghost for read)
17
+ └── Row gap="3" align="start" justify="between"
18
+ ├── Row gap="3" align="start"
19
+ │ ├── Avatar (sm)
20
+ │ └── Stack gap="1"
21
+ │ ├── Text (sm) ← message with bold name + target
22
+ │ └── Row gap="2"
23
+ │ ├── Chip (type-colored, sm, borderless)
24
+ │ └── Text (xs, secondary) ← timestamp
25
+ └── Row gap="1" ← actions
26
+ ├── Button? (primary/outline, xs) ← contextual
27
+ └── Button (ghost, xs) ← dismiss
28
+ `.trim(),
29
+ code: `<Card variant="elevated" padding="none" style={{ width: 520, padding: 'var(--lucent-space-4)' }}>
30
+ <Stack gap="3">
31
+ <Row justify="between" align="center" style={{ padding: '0 var(--lucent-space-2)' }}>
32
+ <Row gap="2" align="center">
33
+ <Text size="lg" weight="semibold">Notifications</Text>
34
+ <Chip variant="accent" size="sm">3 new</Chip>
35
+ </Row>
36
+ <Button variant="ghost" size="xs">Mark all read</Button>
37
+ </Row>
38
+ <Stack gap="2">
39
+ <Card variant="filled" padding="md" style={{ borderLeft: '3px solid var(--lucent-accent-default)' }}>
40
+ <Row gap="3" align="start" justify="between">
41
+ <Row gap="3" align="start" style={{ flex: 1, minWidth: 0 }}>
42
+ <Avatar alt="Alice Chen" size="sm" />
43
+ <Stack gap="1" style={{ flex: 1, minWidth: 0 }}>
44
+ <Text size="sm">
45
+ <Text as="span" size="sm" weight="semibold">Alice Chen</Text>
46
+ {' '}mentioned you in{' '}
47
+ <Text as="span" size="sm" weight="medium">Design system roadmap</Text>
48
+ </Text>
49
+ <Row gap="2" align="center">
50
+ <Chip variant="accent" size="sm" borderless>Mention</Chip>
51
+ <Text size="xs" color="secondary">2 min ago</Text>
52
+ </Row>
53
+ </Stack>
54
+ </Row>
55
+ <Button variant="ghost" size="xs">Dismiss</Button>
56
+ </Row>
57
+ </Card>
58
+ <Card variant="ghost" padding="md">
59
+ <Row gap="3" align="start" justify="between">
60
+ <Row gap="3" align="start" style={{ flex: 1, minWidth: 0 }}>
61
+ <Avatar alt="Carol Park" size="sm" />
62
+ <Stack gap="1" style={{ flex: 1, minWidth: 0 }}>
63
+ <Text size="sm">
64
+ <Text as="span" size="sm" weight="semibold">Carol Park</Text>
65
+ {' '}requested your review on{' '}
66
+ <Text as="span" size="sm" weight="medium">PR #139: Add Slider atom</Text>
67
+ </Text>
68
+ <Row gap="2" align="center">
69
+ <Chip variant="warning" size="sm" borderless>Review</Chip>
70
+ <Text size="xs" color="secondary">1 hour ago</Text>
71
+ </Row>
72
+ </Stack>
73
+ </Row>
74
+ <Row gap="1">
75
+ <Button variant="primary" size="xs">Review</Button>
76
+ <Button variant="ghost" size="xs">Dismiss</Button>
77
+ </Row>
78
+ </Row>
79
+ </Card>
80
+ </Stack>
81
+ </Stack>
82
+ </Card>`,
83
+ designNotes: 'Unread notifications use Card variant="filled" with an accent left border to create ' +
84
+ 'a visual distinction from read items (variant="ghost"). The accent border is a ' +
85
+ 'familiar "unread indicator" pattern. Each notification item is a self-contained Row ' +
86
+ 'with the avatar and message on the left (flex: 1, minWidth: 0 for text truncation) ' +
87
+ 'and contextual actions on the right. Type chips (mention, comment, review, invite) ' +
88
+ 'use semantic variant colors (accent, info, warning, success) with borderless styling ' +
89
+ 'so they read as metadata, not interactive elements. Action buttons are contextual: ' +
90
+ 'review requests get a primary "Review" button, invites get "Accept", others just "Dismiss".',
91
+ };