lucent-ui 0.33.0 → 0.34.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 (26) hide show
  1. package/dist/{LucentProvider-BAYI38i6.js → LucentProvider-BoiXYqA4.js} +2 -2
  2. package/dist/{LucentProvider-DQ46gmxN.cjs → LucentProvider-we0nRXn-.cjs} +2 -2
  3. package/dist/devtools.cjs +1 -1
  4. package/dist/devtools.js +1 -1
  5. package/dist/index.cjs +1 -1
  6. package/dist/index.js +2 -2
  7. package/dist-cli/cli/entry.js +0 -0
  8. package/dist-cli/cli/index.js +0 -0
  9. package/dist-server/server/index.js +0 -0
  10. package/dist-server/server/pattern-registry.js +8 -0
  11. package/dist-server/server/recipe-registry.js +18 -0
  12. package/dist-server/src/manifest/patterns/bulk-action-bar.pattern.js +71 -0
  13. package/dist-server/src/manifest/patterns/confirmation-dialog.pattern.js +95 -0
  14. package/dist-server/src/manifest/patterns/notification-card.pattern.js +108 -0
  15. package/dist-server/src/manifest/patterns/product-item-card.pattern.js +110 -0
  16. package/dist-server/src/manifest/recipes/action-bar.recipe.js +91 -0
  17. package/dist-server/src/manifest/recipes/collapsible-card.recipe.js +100 -0
  18. package/dist-server/src/manifest/recipes/empty-state-card.recipe.js +72 -0
  19. package/dist-server/src/manifest/recipes/form-layout.recipe.js +98 -0
  20. package/dist-server/src/manifest/recipes/index.js +8 -0
  21. package/dist-server/src/manifest/recipes/profile-card.recipe.js +101 -0
  22. package/dist-server/src/manifest/recipes/search-filter-bar.recipe.js +122 -0
  23. package/dist-server/src/manifest/recipes/settings-panel.recipe.js +167 -0
  24. package/dist-server/src/manifest/recipes/stats-row.recipe.js +106 -0
  25. package/dist-server/src/manifest/validate.test.js +28 -0
  26. package/package.json +15 -13
@@ -0,0 +1,95 @@
1
+ export const PATTERN = {
2
+ id: 'confirmation-dialog',
3
+ name: 'Confirmation Dialog',
4
+ description: 'Centered modal-style card for destructive action confirmations with danger button pairing. For delete/remove/reset flows.',
5
+ category: 'action',
6
+ components: ['card', 'icon', 'text', 'button', 'input', 'stack', 'row'],
7
+ structure: `
8
+ Card (elevated, padding="lg", maxWidth=400)
9
+ └── Stack gap="8" align="center"
10
+ ├── Icon (lg, danger color) ← warning icon
11
+ ├── Stack gap="1" align="center"
12
+ │ ├── Text (lg, semibold, center) ← "Delete project?"
13
+ │ └── Text (sm, secondary, center) ← consequence description
14
+ └── Row gap="3"
15
+ ├── Button (outline, full width) ← "Cancel"
16
+ └── Button (danger, full width) ← "Delete"
17
+ `.trim(),
18
+ code: `<Card variant="elevated" padding="lg" style={{ maxWidth: 400 }}>
19
+ <Stack gap="8" align="center">
20
+ <Icon size="xl" color="var(--lucent-danger-text)">
21
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
22
+ <path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
23
+ <line x1={12} y1={9} x2={12} y2={13} />
24
+ <line x1={12} y1={17} x2={12.01} y2={17} />
25
+ </svg>
26
+ </Icon>
27
+ <Stack gap="1" align="center">
28
+ <Text size="lg" weight="semibold">Delete project?</Text>
29
+ <Text size="sm" color="secondary" align="center">This will permanently delete "Acme Corp" and all of its data. This action cannot be undone.</Text>
30
+ </Stack>
31
+ <Row gap="3" style={{ width: '100%' }}>
32
+ <Button variant="outline" style={{ flex: 1 }}>Cancel</Button>
33
+ <Button variant="danger" style={{ flex: 1 }}>Delete</Button>
34
+ </Row>
35
+ </Stack>
36
+ </Card>`,
37
+ variants: [
38
+ {
39
+ title: 'With typed confirmation',
40
+ code: `<Card variant="elevated" padding="lg" style={{ maxWidth: 400 }}>
41
+ <Stack gap="8" align="center">
42
+ <Icon size="xl" color="var(--lucent-danger-text)">
43
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
44
+ <path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
45
+ <line x1={12} y1={9} x2={12} y2={13} />
46
+ <line x1={12} y1={17} x2={12.01} y2={17} />
47
+ </svg>
48
+ </Icon>
49
+ <Stack gap="1" align="center">
50
+ <Text size="lg" weight="semibold">Delete your account?</Text>
51
+ <Text size="sm" color="secondary" align="center">All projects, data, and billing history will be permanently removed. Type DELETE to confirm.</Text>
52
+ </Stack>
53
+ <Input placeholder="Type DELETE to confirm" size="md" style={{ width: '100%' }} />
54
+ <Row gap="3" style={{ width: '100%' }}>
55
+ <Button variant="outline" style={{ flex: 1 }}>Cancel</Button>
56
+ <Button variant="danger" style={{ flex: 1 }} disabled>Delete account</Button>
57
+ </Row>
58
+ </Stack>
59
+ </Card>`,
60
+ },
61
+ {
62
+ title: 'Non-destructive confirmation',
63
+ code: `<Card variant="elevated" padding="lg" style={{ maxWidth: 400 }}>
64
+ <Stack gap="8" align="center">
65
+ <Icon size="xl" color="var(--lucent-info-text)">
66
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
67
+ <circle cx={12} cy={12} r={10} />
68
+ <line x1={12} y1={16} x2={12} y2={12} />
69
+ <line x1={12} y1={8} x2={12.01} y2={8} />
70
+ </svg>
71
+ </Icon>
72
+ <Stack gap="1" align="center">
73
+ <Text size="lg" weight="semibold">Publish changes?</Text>
74
+ <Text size="sm" color="secondary" align="center">This will make your draft visible to all team members. You can unpublish later from settings.</Text>
75
+ </Stack>
76
+ <Row gap="3" style={{ width: '100%' }}>
77
+ <Button variant="outline" style={{ flex: 1 }}>Keep as draft</Button>
78
+ <Button variant="primary" style={{ flex: 1 }}>Publish</Button>
79
+ </Row>
80
+ </Stack>
81
+ </Card>`,
82
+ },
83
+ ],
84
+ designNotes: 'The centered layout with align="center" creates a focused, modal-like feel ' +
85
+ 'even though this is a Card, not a true modal. The warning Icon at the top ' +
86
+ 'provides an immediate visual signal of severity. The text stack uses gap="1" ' +
87
+ 'to tightly couple the title and consequence description. Buttons use flex: 1 ' +
88
+ 'inside a full-width Row so they split evenly — Cancel (outline) on the left, ' +
89
+ 'destructive action (danger) on the right, following the convention of placing ' +
90
+ 'the primary action on the right. The typed confirmation variant adds an Input ' +
91
+ 'between the text and buttons with the danger Button disabled until the user ' +
92
+ 'types the confirmation text. The non-destructive variant swaps the danger ' +
93
+ 'Button for primary and the warning Icon for info, reusing the same centered ' +
94
+ 'layout for any "are you sure?" flow.',
95
+ };
@@ -0,0 +1,108 @@
1
+ export const PATTERN = {
2
+ id: 'announcement-card',
3
+ name: 'Announcement Card',
4
+ description: 'Rich announcement card with optional media slot, status icon, title, message, and action buttons. For in-page announcements, feature launches, promotions, or system notices.',
5
+ category: 'card',
6
+ components: ['card', 'icon', 'text', 'button', 'chip', 'stack', 'row'],
7
+ structure: `
8
+ Card (elevated, padding="lg", media=<img>)
9
+ └── Stack gap="4"
10
+ ├── Row gap="2" wrap ← optional badge row
11
+ │ └── Chip (accent/success, sm) ← category / "New"
12
+ ├── Stack gap="1"
13
+ │ ├── Text (md, semibold) ← title
14
+ │ └── Text (sm, secondary) ← message body
15
+ └── Row gap="2"
16
+ ├── Button (primary, sm) ← action
17
+ └── Button (ghost, sm) ← dismiss
18
+ `.trim(),
19
+ code: `<Card
20
+ variant="elevated"
21
+ padding="lg"
22
+ style={{ width: 380 }}
23
+ media={<img src="/announcements/feature-launch.jpg" alt="New dashboard" style={{ width: '100%', height: 160, objectFit: 'cover', display: 'block' }} />}
24
+ >
25
+ <Stack gap="4">
26
+ <Row gap="2">
27
+ <Chip variant="accent" size="sm">New Feature</Chip>
28
+ </Row>
29
+ <Stack gap="1">
30
+ <Text size="md" weight="semibold">Redesigned Analytics Dashboard</Text>
31
+ <Text size="sm" color="secondary">Track key metrics at a glance with our new real-time dashboard. Includes custom date ranges, export to CSV, and team sharing.</Text>
32
+ </Stack>
33
+ <Row gap="2">
34
+ <Button variant="primary" size="sm">Try it now</Button>
35
+ <Button variant="ghost" size="sm">Learn more</Button>
36
+ </Row>
37
+ </Stack>
38
+ </Card>`,
39
+ variants: [
40
+ {
41
+ title: 'System notice (no media, with status icon)',
42
+ code: `<Card variant="outline" padding="md" style={{ width: 400 }}>
43
+ <Row gap="3" align="start">
44
+ <Icon size="lg" color="var(--lucent-warning-text)">
45
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
46
+ <path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
47
+ <line x1={12} y1={9} x2={12} y2={13} />
48
+ <line x1={12} y1={17} x2={12.01} y2={17} />
49
+ </svg>
50
+ </Icon>
51
+ <Stack gap="2" style={{ flex: 1 }}>
52
+ <Text size="sm" weight="semibold">Storage almost full</Text>
53
+ <Text size="sm" color="secondary">You've used 92% of your storage. Upgrade your plan or delete unused files to free up space.</Text>
54
+ <Row gap="2">
55
+ <Button variant="primary" size="sm">Upgrade plan</Button>
56
+ <Button variant="ghost" size="sm">Manage storage</Button>
57
+ </Row>
58
+ </Stack>
59
+ </Row>
60
+ </Card>`,
61
+ },
62
+ {
63
+ title: 'Promotional banner with media',
64
+ code: `<Card variant="filled" padding="md" hoverable style={{ width: 380 }}>
65
+ <Row gap="5" align="center">
66
+ <div style={{ width: 80, height: 80, borderRadius: 'var(--lucent-radius-lg)', overflow: 'hidden', flexShrink: 0 }}>
67
+ <img src="/promo/spring-sale.jpg" alt="Spring sale" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
68
+ </div>
69
+ <Stack gap="2" style={{ flex: 1 }}>
70
+ <Row gap="2" align="center">
71
+ <Text size="md" weight="semibold">Spring Sale</Text>
72
+ <Chip variant="success" size="sm">-30%</Chip>
73
+ </Row>
74
+ <Text size="sm" color="secondary">All Pro plans are 30% off through April. Upgrade now and lock in the price for a year.</Text>
75
+ <Button variant="primary" size="sm" style={{ alignSelf: 'flex-start' }}>Claim offer</Button>
76
+ </Stack>
77
+ </Row>
78
+ </Card>`,
79
+ },
80
+ {
81
+ title: 'Success confirmation',
82
+ code: `<Card variant="outline" padding="md" style={{ width: 400, borderColor: 'var(--lucent-success-default)' }}>
83
+ <Row gap="3" align="start">
84
+ <Icon size="lg" color="var(--lucent-success-text)">
85
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
86
+ <path d="M22 11.08V12a10 10 0 11-5.93-9.14" />
87
+ <polyline points="22 4 12 14.01 9 11.01" />
88
+ </svg>
89
+ </Icon>
90
+ <Stack gap="1" style={{ flex: 1 }}>
91
+ <Text size="sm" weight="semibold">Payment received</Text>
92
+ <Text size="sm" color="secondary">Your invoice #1042 for $2,400.00 has been paid successfully.</Text>
93
+ </Stack>
94
+ <Button variant="ghost" size="sm">Dismiss</Button>
95
+ </Row>
96
+ </Card>`,
97
+ },
98
+ ],
99
+ designNotes: 'The default announcement card uses the Card media slot for a full-bleed hero ' +
100
+ 'image that grabs attention, followed by an optional Chip badge for categorization, ' +
101
+ 'then title + description + action buttons. This distinguishes it from the Alert ' +
102
+ 'molecule which is a lightweight status banner — the Announcement Card is richer, ' +
103
+ 'supports media, and drives the user toward an action. The system notice variant ' +
104
+ 'drops the media for a compact icon-led layout similar to Alert but with action ' +
105
+ 'buttons. The promotional variant uses an inline thumbnail (Row with rounded image) ' +
106
+ 'for a side-by-side layout that works well for offers and upsells. The success ' +
107
+ 'variant uses a tinted border to reinforce status at a glance.',
108
+ };
@@ -0,0 +1,110 @@
1
+ export const PATTERN = {
2
+ id: 'product-item-card',
3
+ name: 'Product/Item Card',
4
+ description: 'Versatile content card with media slot, title, tags, metadata, and CTA. Reusable for products, articles, team members, or any entity list.',
5
+ category: 'card',
6
+ components: ['card', 'avatar', 'icon', 'text', 'chip', 'button', 'stack', 'row'],
7
+ structure: `
8
+ Card (elevated, padding="lg", media=<img>)
9
+ └── Stack gap="4"
10
+ ├── Stack gap="1"
11
+ │ ├── Text (md, semibold) ← title
12
+ │ └── Text (sm, secondary) ← subtitle
13
+ ├── Row gap="2" wrap
14
+ │ └── Chip[] (neutral, sm) ← tags / categories
15
+ ├── Row gap="4" justify="between" align="baseline"
16
+ │ ├── Text (lg, bold, display) ← price / key metric
17
+ │ └── Text (xs, secondary) ← secondary info
18
+ └── Button (primary, full width) ← CTA
19
+ `.trim(),
20
+ code: `<Card
21
+ variant="elevated"
22
+ padding="lg"
23
+ style={{ width: 320 }}
24
+ media={<img src="/products/headphones.jpg" alt="Wireless Headphones" style={{ width: '100%', height: 180, objectFit: 'cover', display: 'block' }} />}
25
+ >
26
+ <Stack gap="4">
27
+ <Stack gap="1">
28
+ <Text size="md" weight="semibold">Wireless Headphones</Text>
29
+ <Text size="sm" color="secondary">Active noise cancelling, 40hr battery</Text>
30
+ </Stack>
31
+ <Row gap="2" wrap>
32
+ <Chip variant="neutral" size="sm">Audio</Chip>
33
+ <Chip variant="neutral" size="sm">Bluetooth</Chip>
34
+ <Chip variant="neutral" size="sm">ANC</Chip>
35
+ </Row>
36
+ <Row gap="4" justify="between" align="baseline">
37
+ <Text size="lg" weight="bold" family="display">$249.00</Text>
38
+ <Text size="xs" color="secondary">Free shipping</Text>
39
+ </Row>
40
+ <Button variant="primary" style={{ width: '100%' }}>Add to Cart</Button>
41
+ </Stack>
42
+ </Card>`,
43
+ variants: [
44
+ {
45
+ title: 'Article card',
46
+ code: `<Card variant="outline" padding="lg" hoverable style={{ width: 340 }}>
47
+ <Stack gap="4">
48
+ <Row gap="3" align="start">
49
+ <Icon size={20} color="var(--lucent-text-secondary)">
50
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
51
+ <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
52
+ <polyline points="14 2 14 8 20 8" />
53
+ <line x1={16} y1={13} x2={8} y2={13} />
54
+ <line x1={16} y1={17} x2={8} y2={17} />
55
+ </svg>
56
+ </Icon>
57
+ <Stack gap="1" style={{ flex: 1 }}>
58
+ <Text size="md" weight="semibold">Design Tokens at Scale</Text>
59
+ <Text size="xs" color="secondary">Published Mar 15, 2026</Text>
60
+ </Stack>
61
+ </Row>
62
+ <Text size="sm" color="secondary">
63
+ How we unified spacing, color, and typography across 12 product surfaces using a single token system.
64
+ </Text>
65
+ <Row gap="2" wrap>
66
+ <Chip variant="neutral" size="sm">Design Systems</Chip>
67
+ <Chip variant="neutral" size="sm">Tokens</Chip>
68
+ </Row>
69
+ <Row gap="4" justify="between" align="center">
70
+ <Text size="xs" color="secondary">8 min read</Text>
71
+ <Button variant="ghost" size="sm">Read more</Button>
72
+ </Row>
73
+ </Stack>
74
+ </Card>`,
75
+ },
76
+ {
77
+ title: 'Team member card',
78
+ code: `<Card variant="filled" padding="lg" style={{ width: 280 }}>
79
+ <Stack gap="4">
80
+ <Stack gap="3" align="center">
81
+ <Avatar src="/avatars/alex.jpg" alt="Alex Chen" size="lg" />
82
+ <Stack gap="0" align="center">
83
+ <Text size="md" weight="semibold">Alex Chen</Text>
84
+ <Text size="sm" color="secondary">Engineering Lead</Text>
85
+ </Stack>
86
+ </Stack>
87
+ <Row gap="2" wrap justify="center">
88
+ <Chip variant="neutral" size="sm">React</Chip>
89
+ <Chip variant="neutral" size="sm">Go</Chip>
90
+ <Chip variant="neutral" size="sm">Platform</Chip>
91
+ </Row>
92
+ <Row gap="3">
93
+ <Button variant="outline" style={{ flex: 1 }} size="sm">Profile</Button>
94
+ <Button variant="primary" style={{ flex: 1 }} size="sm">Message</Button>
95
+ </Row>
96
+ </Stack>
97
+ </Card>`,
98
+ },
99
+ ],
100
+ designNotes: 'The default product card uses the Card media slot for a full-bleed hero image ' +
101
+ 'at the top, keeping the content area clean with just title, tags, price, and CTA. ' +
102
+ 'Tags use neutral Chips at sm size for low visual weight — they inform without ' +
103
+ 'competing with the title. The price/metric row uses justify="between" with ' +
104
+ 'align="baseline" to anchor the large display-font value and the secondary caption ' +
105
+ 'to the same text baseline. A full-width primary Button as the sole CTA creates a ' +
106
+ 'clear action target. The article variant swaps the media slot for an inline Icon + ' +
107
+ 'text header and replaces the price row with read-time + ghost button, keeping the ' +
108
+ 'same vertical rhythm. The team member variant centers the content for a profile-style ' +
109
+ 'layout and uses a dual-button footer with flex: 1 for equal width.',
110
+ };
@@ -0,0 +1,91 @@
1
+ export const RECIPE = {
2
+ id: 'action-bar',
3
+ name: 'Action Bar',
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.',
5
+ category: 'action',
6
+ components: ['text', 'button', 'row', 'stack', 'breadcrumb', 'divider', 'card'],
7
+ structure: `
8
+ Page header:
9
+ Stack gap="4"
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" ← actions
16
+ │ ├── Button (outline, sm)
17
+ │ └── Button (primary, sm)
18
+ └── Divider
19
+
20
+ Card header:
21
+ Row justify="between" align="start"
22
+ ├── Stack gap="1"
23
+ │ ├── Text (xs, secondary, uppercase, tight) ← category label
24
+ │ └── Text (md, semibold, tight) ← section title
25
+ └── Row gap="1" ← compact actions (top-aligned)
26
+ └── Button[] (ghost, xs)
27
+ `.trim(),
28
+ code: `<Stack gap="4">
29
+ <Breadcrumb items={[
30
+ { label: 'Home', href: '#' },
31
+ { label: 'Projects', href: '#' },
32
+ { label: 'Acme Corp' },
33
+ ]} />
34
+ <Row justify="between" align="end">
35
+ <Stack gap="1">
36
+ <Text as="h1" size="3xl" weight="bold" family="display">Acme Corp</Text>
37
+ <Text size="sm" color="secondary">Last updated 5 minutes ago</Text>
38
+ </Stack>
39
+ <Row gap="2">
40
+ <Button variant="outline" size="sm">Export</Button>
41
+ <Button variant="primary" size="sm">New report</Button>
42
+ </Row>
43
+ </Row>
44
+ <Divider />
45
+ </Stack>`,
46
+ variants: [
47
+ {
48
+ title: 'Page header — danger zone',
49
+ code: `<Stack gap="4">
50
+ <Breadcrumb items={[
51
+ { label: 'Home', href: '#' },
52
+ { label: 'Settings', href: '#' },
53
+ { label: 'Danger zone' },
54
+ ]} />
55
+ <Row justify="between" align="end">
56
+ <Stack gap="1">
57
+ <Text as="h1" size="3xl" weight="bold" family="display">Danger zone</Text>
58
+ <Text size="sm" color="secondary">These actions are irreversible.</Text>
59
+ </Stack>
60
+ <Button variant="danger" size="sm">Delete project</Button>
61
+ </Row>
62
+ <Divider />
63
+ </Stack>`,
64
+ },
65
+ {
66
+ title: 'Card header (compact with label)',
67
+ code: `<Card variant="outline" padding="md" style={{ width: 400 }}>
68
+ <Stack gap="4">
69
+ <Row justify="between" align="start">
70
+ <Stack gap="1">
71
+ <Text size="xs" color="secondary" weight="medium" style={{ letterSpacing: 'var(--lucent-letter-spacing-tight)', textTransform: 'uppercase' }}>Activity</Text>
72
+ <Text size="md" weight="semibold" style={{ letterSpacing: 'var(--lucent-letter-spacing-base)' }}>Recent activity</Text>
73
+ </Stack>
74
+ <Row gap="1">
75
+ <Button variant="ghost" size="xs">Filter</Button>
76
+ <Button variant="ghost" size="xs">Export</Button>
77
+ </Row>
78
+ </Row>
79
+ <Text size="xs" color="secondary">No activity to show yet.</Text>
80
+ </Stack>
81
+ </Card>`,
82
+ },
83
+ ],
84
+ designNotes: 'Page headers and card headers have very different scales. Page headers use 3xl ' +
85
+ 'display font for the title, Breadcrumb for navigation context, and a Divider to ' +
86
+ 'separate the header from page content below. align="end" on the Row anchors the ' +
87
+ 'buttons to the baseline of the title block so they sit level with the subtitle. ' +
88
+ 'Card headers are compact: sm semibold text with xs/ghost buttons that recede ' +
89
+ 'visually. The primary action is always rightmost following natural reading order. ' +
90
+ 'Both patterns use justify="between" to push title and actions to opposite edges.',
91
+ };
@@ -0,0 +1,100 @@
1
+ export const RECIPE = {
2
+ id: 'collapsible-card',
3
+ name: 'Collapsible Card',
4
+ description: 'Card with an expandable/collapsible section using smooth height animation, available in all card variants.',
5
+ category: 'card',
6
+ components: ['card', 'collapsible', 'text'],
7
+ structure: `
8
+ Card (padding="none", hoverable)
9
+ └── Collapsible
10
+ ├── trigger: Text (sm, semibold) ← clickable header
11
+ └── children ← collapsible body
12
+ └── Text (sm, secondary) ← content
13
+ `.trim(),
14
+ code: `<Card variant="outline" padding="none" hoverable style={{ width: 360 }}>
15
+ <Collapsible
16
+ trigger={<Text as="span" weight="semibold" size="sm">Details</Text>}
17
+ defaultOpen
18
+ >
19
+ <Text size="sm" color="secondary">
20
+ The Collapsible auto-detects its Card parent and bleeds the trigger
21
+ full-width. Content inherits the card's padding.
22
+ </Text>
23
+ </Collapsible>
24
+ </Card>`,
25
+ variants: [
26
+ {
27
+ title: 'Ghost variant',
28
+ code: `<Card variant="ghost" padding="none" hoverable style={{ width: 360 }}>
29
+ <Collapsible trigger={<Text as="span" weight="semibold" size="sm">FAQ item</Text>}>
30
+ <Text size="sm" color="secondary">
31
+ Transparent container — content floats directly on the page surface.
32
+ </Text>
33
+ </Collapsible>
34
+ </Card>`,
35
+ },
36
+ {
37
+ title: 'Elevated variant',
38
+ code: `<Card variant="elevated" padding="none" hoverable style={{ width: 360 }}>
39
+ <Collapsible trigger={<Text as="span" weight="semibold" size="sm">Advanced options</Text>}>
40
+ <Text size="sm" color="secondary">
41
+ Elevated cards cast a shadow — use for prominent collapsible sections.
42
+ </Text>
43
+ </Collapsible>
44
+ </Card>`,
45
+ },
46
+ {
47
+ title: 'Combo (two-tone nested cards)',
48
+ code: `<Card variant="filled" padding="none" hoverable style={{ width: 360 }}>
49
+ <Collapsible
50
+ padded={false}
51
+ trigger={<Text as="span" weight="semibold" size="sm">Combo layout</Text>}
52
+ >
53
+ <Card
54
+ variant="elevated"
55
+ padding="sm"
56
+ style={{ margin: 'var(--lucent-space-1) var(--lucent-space-2) var(--lucent-space-2)' }}
57
+ >
58
+ <Text size="sm" color="secondary">
59
+ Two-tone layout — flat trigger surface, elevated body.
60
+ </Text>
61
+ </Card>
62
+ </Collapsible>
63
+ </Card>`,
64
+ },
65
+ {
66
+ title: 'With localStorage persistence',
67
+ code: `function CollapsibleSection({ id, title, children }) {
68
+ const storageKey = \`collapsible-\${id}\`;
69
+ const [open, setOpen] = React.useState(() => {
70
+ const saved = localStorage.getItem(storageKey);
71
+ return saved !== null ? saved === 'true' : true;
72
+ });
73
+
74
+ const handleChange = (next) => {
75
+ setOpen(next);
76
+ localStorage.setItem(storageKey, String(next));
77
+ };
78
+
79
+ return (
80
+ <Card variant="outline" padding="none" hoverable>
81
+ <Collapsible
82
+ open={open}
83
+ onOpenChange={handleChange}
84
+ trigger={<Text as="span" weight="semibold" size="sm">{title}</Text>}
85
+ >
86
+ {children}
87
+ </Collapsible>
88
+ </Card>
89
+ );
90
+ }`,
91
+ },
92
+ ],
93
+ designNotes: 'Card uses padding="none" because the Collapsible auto-detects its Card parent ' +
94
+ 'via CardPaddingContext and bleeds the trigger full-width while applying card ' +
95
+ 'padding to the body content. The hoverable prop gives hover lift feedback ' +
96
+ 'without making the card itself interactive — the Collapsible trigger handles ' +
97
+ 'clicks. For the combo variant, padded={false} on Collapsible removes its built-in ' +
98
+ 'content padding so the nested elevated Card can control its own spacing with ' +
99
+ 'custom margin. This creates a two-tone visual: flat trigger + elevated body.',
100
+ };
@@ -0,0 +1,72 @@
1
+ export const RECIPE = {
2
+ id: 'empty-state-card',
3
+ name: 'Empty State Card',
4
+ description: 'Centered empty state with illustration icon, heading, description, and call-to-action button inside a card.',
5
+ category: 'card',
6
+ components: ['card', 'empty-state', 'icon', 'button'],
7
+ structure: `
8
+ Card (outline, padding="lg")
9
+ └── EmptyState
10
+ ├── illustration: Icon (xl) ← decorative SVG
11
+ ├── title: string ← heading
12
+ ├── description: string ← explanatory text
13
+ └── action: Button (secondary / primary) ← CTA
14
+ `.trim(),
15
+ code: `<Card variant="outline" padding="lg" style={{ width: 400 }}>
16
+ <EmptyState
17
+ illustration={
18
+ <Icon size="xl">
19
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
20
+ <circle cx={11} cy={11} r={8} />
21
+ <path d="M21 21l-4.35-4.35" />
22
+ </svg>
23
+ </Icon>
24
+ }
25
+ title="No results found"
26
+ description="Try adjusting your search or filters to find what you're looking for."
27
+ action={<Button variant="secondary" size="sm">Clear filters</Button>}
28
+ />
29
+ </Card>`,
30
+ variants: [
31
+ {
32
+ title: 'Getting started empty state',
33
+ code: `<Card variant="elevated" padding="lg" style={{ width: 400 }}>
34
+ <EmptyState
35
+ illustration={
36
+ <Icon size="xl">
37
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
38
+ <path d="M12 5v14M5 12h14" />
39
+ </svg>
40
+ </Icon>
41
+ }
42
+ title="No projects yet"
43
+ description="Create your first project to get started."
44
+ action={<Button variant="primary" size="sm">Create project</Button>}
45
+ />
46
+ </Card>`,
47
+ },
48
+ {
49
+ title: 'Error empty state with retry',
50
+ code: `<Card variant="outline" padding="lg" style={{ width: 400 }}>
51
+ <EmptyState
52
+ illustration={
53
+ <Icon size="xl">
54
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
55
+ <circle cx={12} cy={12} r={10} />
56
+ <path d="M12 8v4M12 16h.01" />
57
+ </svg>
58
+ </Icon>
59
+ }
60
+ title="Something went wrong"
61
+ description="We couldn't load your data. Please try again."
62
+ action={<Button variant="outline" size="sm">Retry</Button>}
63
+ />
64
+ </Card>`,
65
+ },
66
+ ],
67
+ designNotes: 'EmptyState handles internal centering and spacing — no need for manual Stack/Row ' +
68
+ 'layout inside it. The illustration uses Icon at xl size for visual weight without ' +
69
+ 'overwhelming the text. Card variant should match the context: outline for inline ' +
70
+ 'empty states (e.g. a table with no rows), elevated for standalone pages. The ' +
71
+ 'action button uses size="sm" to avoid competing with the heading for attention.',
72
+ };