astryx-dracula 0.1.5 → 0.2.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 (90) hide show
  1. package/README.md +2 -0
  2. package/USAGE.md +13 -0
  3. package/astryx.integration.mjs +3 -0
  4. package/package.json +4 -2
  5. package/templates/ai-chat-landing.template.mjs +6 -0
  6. package/templates/ai-chat-landing.tsx +476 -0
  7. package/templates/ai-chat.template.mjs +6 -0
  8. package/templates/ai-chat.tsx +762 -0
  9. package/templates/blank.template.mjs +6 -0
  10. package/templates/blank.tsx +16 -0
  11. package/templates/centered-hero.template.mjs +6 -0
  12. package/templates/centered-hero.tsx +188 -0
  13. package/templates/classic-gallery.template.mjs +6 -0
  14. package/templates/classic-gallery.tsx +181 -0
  15. package/templates/contact-form.template.mjs +6 -0
  16. package/templates/contact-form.tsx +292 -0
  17. package/templates/dashboard-portfolio.template.mjs +6 -0
  18. package/templates/dashboard-portfolio.tsx +800 -0
  19. package/templates/dashboard.template.mjs +6 -0
  20. package/templates/dashboard.tsx +774 -0
  21. package/templates/detail-page.template.mjs +6 -0
  22. package/templates/detail-page.tsx +695 -0
  23. package/templates/documentation-design.template.mjs +6 -0
  24. package/templates/documentation-design.tsx +751 -0
  25. package/templates/documentation-technical.template.mjs +6 -0
  26. package/templates/documentation-technical.tsx +292 -0
  27. package/templates/documentation.template.mjs +6 -0
  28. package/templates/documentation.tsx +347 -0
  29. package/templates/editor.template.mjs +6 -0
  30. package/templates/editor.tsx +959 -0
  31. package/templates/file-explorer.template.mjs +6 -0
  32. package/templates/file-explorer.tsx +577 -0
  33. package/templates/form-two-column.template.mjs +6 -0
  34. package/templates/form-two-column.tsx +314 -0
  35. package/templates/gallery-hero.template.mjs +6 -0
  36. package/templates/gallery-hero.tsx +287 -0
  37. package/templates/ide.template.mjs +6 -0
  38. package/templates/ide.tsx +533 -0
  39. package/templates/incident-console.template.mjs +6 -0
  40. package/templates/incident-console.tsx +584 -0
  41. package/templates/kanban-board.template.mjs +6 -0
  42. package/templates/kanban-board.tsx +746 -0
  43. package/templates/library.template.mjs +6 -0
  44. package/templates/library.tsx +489 -0
  45. package/templates/login-card.template.mjs +6 -0
  46. package/templates/login-card.tsx +200 -0
  47. package/templates/login-split.template.mjs +6 -0
  48. package/templates/login-split.tsx +318 -0
  49. package/templates/login-sso.template.mjs +6 -0
  50. package/templates/login-sso.tsx +277 -0
  51. package/templates/login.template.mjs +6 -0
  52. package/templates/login.tsx +98 -0
  53. package/templates/messaging-shell.template.mjs +6 -0
  54. package/templates/messaging-shell.tsx +675 -0
  55. package/templates/mixed-gallery.template.mjs +6 -0
  56. package/templates/mixed-gallery.tsx +190 -0
  57. package/templates/payment-form.template.mjs +6 -0
  58. package/templates/payment-form.tsx +990 -0
  59. package/templates/product-detail.template.mjs +6 -0
  60. package/templates/product-detail.tsx +366 -0
  61. package/templates/product-gallery.template.mjs +6 -0
  62. package/templates/product-gallery.tsx +173 -0
  63. package/templates/settings-dialog.template.mjs +6 -0
  64. package/templates/settings-dialog.tsx +920 -0
  65. package/templates/settings-sidebar.template.mjs +6 -0
  66. package/templates/settings-sidebar.tsx +952 -0
  67. package/templates/settings.template.mjs +6 -0
  68. package/templates/settings.tsx +235 -0
  69. package/templates/shell-nav.template.mjs +6 -0
  70. package/templates/shell-nav.tsx +306 -0
  71. package/templates/shell-side-nav.template.mjs +6 -0
  72. package/templates/shell-side-nav.tsx +238 -0
  73. package/templates/shell-top-nav.template.mjs +6 -0
  74. package/templates/shell-top-nav.tsx +243 -0
  75. package/templates/side-gallery.template.mjs +6 -0
  76. package/templates/side-gallery.tsx +179 -0
  77. package/templates/table-grouped.template.mjs +6 -0
  78. package/templates/table-grouped.tsx +1174 -0
  79. package/templates/table-page-chart.template.mjs +6 -0
  80. package/templates/table-page-chart.tsx +693 -0
  81. package/templates/table-page-heatmap-status.template.mjs +6 -0
  82. package/templates/table-page-heatmap-status.tsx +547 -0
  83. package/templates/table-page-shoe-store-heatmap.template.mjs +6 -0
  84. package/templates/table-page-shoe-store-heatmap.tsx +1038 -0
  85. package/templates/table-page.template.mjs +6 -0
  86. package/templates/table-page.tsx +445 -0
  87. package/templates/table.template.mjs +6 -0
  88. package/templates/table.tsx +84 -0
  89. package/templates/theme-showcase.template.mjs +6 -0
  90. package/templates/theme-showcase.tsx +1282 -0
package/README.md CHANGED
@@ -16,6 +16,7 @@ Live showcase: https://yuzu-octopus.github.io/astryx-dracula/
16
16
  - WCAG contrast gates enforced in CI via `bun run audit`
17
17
  - Vendored JetBrains Mono fonts and Lucide icons, zero theme dependencies
18
18
  - Agent skill, `llms.txt`, and copy-paste quickstart for AI-assisted adoption
19
+ - 43 themed pages in `templates/`, published as an Astryx integration pack and viewable live at `/astryx-dracula/#/templates`
19
20
 
20
21
  ## Installation
21
22
 
@@ -48,6 +49,7 @@ theme.css prebuilt output of bun run theme:build
48
49
  tokens.css plain-CSS :root fallback for any stack
49
50
  icons.tsx vendored Lucide icon registry
50
51
  fonts/ JetBrains Mono woff2 files
52
+ templates/ 43 themed pages plus integration specs
51
53
  scripts/check.ts palette plus contrast gates
52
54
  demo/ showcase source, deployed to GitHub Pages
53
55
  ```
package/USAGE.md CHANGED
@@ -71,6 +71,19 @@ Then use `var(--color-primary)`, `var(--dracula-purple)`, `var(--space-gap)`.
71
71
  3. Replace: delete the old theme provider and `:root` overrides, point imports at the kit prebuilt path, swap raw elements for Astryx components.
72
72
  4. Verify: build, screenshot key pages, confirm no stray hexes remain in `src/`.
73
73
 
74
+ ## Templates
75
+
76
+ 43 themed pages ship in `templates/`, each with a `<id>.template.mjs` spec, published
77
+ as an Astryx integration pack (`astryx.integration.mjs`). List this package in your
78
+ `astryx.config`, then scaffold any of them:
79
+
80
+ ```bash
81
+ bunx astryx template dashboard --package astryx-dracula
82
+ ```
83
+
84
+ View all 43 live at `/astryx-dracula/#/templates` on the showcase. Pack rules: templates
85
+ import React plus `@astryxdesign/core` only.
86
+
74
87
  ## Fonts
75
88
 
76
89
  Copy `fonts/` to your app's served static dir (Vite: `public/fonts/`).
@@ -0,0 +1,3 @@
1
+ export default {
2
+ templates: './templates',
3
+ };
package/package.json CHANGED
@@ -40,7 +40,9 @@
40
40
  "fonts/",
41
41
  "icons.tsx",
42
42
  "theme.css",
43
- "tokens.css"
43
+ "tokens.css",
44
+ "templates/",
45
+ "astryx.integration.mjs"
44
46
  ],
45
47
  "license": "MIT",
46
48
  "name": "astryx-dracula",
@@ -62,5 +64,5 @@
62
64
  "@astryxdesign/cli"
63
65
  ],
64
66
  "type": "module",
65
- "version": "0.1.5"
67
+ "version": "0.2.0"
66
68
  }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ type: 'page',
3
+ name: 'AI Chat Landing',
4
+ description: 'Composer, greeting, and category toggles',
5
+ category: 'marketing',
6
+ };
@@ -0,0 +1,476 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {useRef, useState, type CSSProperties} from 'react';
4
+
5
+ import {Layout, LayoutContent, VStack, HStack} from '@astryxdesign/core/Layout';
6
+ import {Text, Heading} from '@astryxdesign/core/Text';
7
+ import {
8
+ ChatComposer,
9
+ ChatComposerDrawer,
10
+ ChatComposerInput,
11
+ ChatDictationButton,
12
+ useChatDictation,
13
+ type ChatComposerInputHandle,
14
+ type ChatComposerTrigger,
15
+ } from '@astryxdesign/core/Chat';
16
+ import {
17
+ createStaticSource,
18
+ TypeaheadItem,
19
+ type SearchableItem,
20
+ } from '@astryxdesign/core/Typeahead';
21
+ import {ToggleButton, ToggleButtonGroup} from '@astryxdesign/core/ToggleButton';
22
+ import {Token} from '@astryxdesign/core/Token';
23
+ import {ClickableCard} from '@astryxdesign/core/ClickableCard';
24
+ import {Grid} from '@astryxdesign/core/Grid';
25
+ import {Icon} from '@astryxdesign/core/Icon';
26
+ import {DropdownMenu, DropdownMenuItem} from '@astryxdesign/core/DropdownMenu';
27
+ import {
28
+ Settings,
29
+ AtSign,
30
+ Sparkles,
31
+ SquarePen,
32
+ CodeXml,
33
+ Search,
34
+ Lock,
35
+ Clock,
36
+ Lightbulb,
37
+ } from 'lucide-react';
38
+
39
+ // Fill the content area so the greeting and composer stay vertically centered.
40
+ const pageStyle: CSSProperties = {minHeight: '100%'};
41
+ const composerInput: CSSProperties = {minHeight: 84};
42
+ const categories: CSSProperties = {paddingInline: 'var(--space-viewport)'};
43
+
44
+ // Suggestion cards shown once a category is selected.
45
+ const CATEGORY_SUGGESTIONS: Record<
46
+ string,
47
+ Array<{heading: string; body: string; prompt: string}>
48
+ > = {
49
+ writing: [
50
+ {
51
+ heading: 'Draft a release announcement',
52
+ body: 'Compose a clear, polished note for the night-shift crew',
53
+ prompt: 'Help me draft a release announcement',
54
+ },
55
+ {
56
+ heading: 'Improve my writing',
57
+ body: 'Enhance the clarity, tone, and flow of my text',
58
+ prompt: 'Review and improve the following text:',
59
+ },
60
+ {
61
+ heading: 'Create a palette proposal',
62
+ body: 'Write a proposal with hues, ramps, and contrast proofs',
63
+ prompt: 'Help me write a palette proposal for',
64
+ },
65
+ {
66
+ heading: 'Summarize a document',
67
+ body: 'Condense a long grimoire into key takeaways',
68
+ prompt: 'Summarize the following document into key points:',
69
+ },
70
+ ],
71
+ coding: [
72
+ {
73
+ heading: 'Debug my code',
74
+ body: 'Find and fix issues in a moonlit code snippet',
75
+ prompt: 'Help me debug the following code:',
76
+ },
77
+ {
78
+ heading: 'Write a function',
79
+ body: 'Generate a well-typed function with error handling',
80
+ prompt: 'Write a function that',
81
+ },
82
+ {
83
+ heading: 'Theme my editor',
84
+ body: 'Port this palette to a Dracula editor theme',
85
+ prompt: 'Convert the following colors to a Dracula theme:',
86
+ },
87
+ {
88
+ heading: 'Review my pull request',
89
+ body: 'Check for bugs, contrast, and best practices',
90
+ prompt: 'Review this code for bugs and improvements:',
91
+ },
92
+ ],
93
+ research: [
94
+ {
95
+ heading: 'Compare purple hues',
96
+ body: 'Analyze the spectral trade-offs of each shade',
97
+ prompt: 'Compare the pros and cons of',
98
+ },
99
+ {
100
+ heading: 'Explain a concept',
101
+ body: 'Break down a complex topic in simple terms',
102
+ prompt: 'Explain the concept of',
103
+ },
104
+ {
105
+ heading: 'Find contrast proofs',
106
+ body: 'Research AA pairings for text on dark surfaces',
107
+ prompt: 'What are the best practices for',
108
+ },
109
+ {
110
+ heading: 'Summarize findings',
111
+ body: 'Compile research into a structured overview',
112
+ prompt: 'Summarize the key findings on',
113
+ },
114
+ ],
115
+ creative: [
116
+ {
117
+ heading: 'Brainstorm theme variants',
118
+ body: 'Generate nocturnal concepts for a new variant',
119
+ prompt: 'Brainstorm ideas for',
120
+ },
121
+ {
122
+ heading: 'Write a dark tale',
123
+ body: 'Create an engaging narrative with creatures',
124
+ prompt: 'Write a short story about',
125
+ },
126
+ {
127
+ heading: 'Design a concept',
128
+ body: 'Explore product or visual design ideas',
129
+ prompt: 'Help me design a concept for',
130
+ },
131
+ {
132
+ heading: 'Name a new shade',
133
+ body: 'Craft a memorable name for a color or token',
134
+ prompt: 'Create a catchy name for',
135
+ },
136
+ ],
137
+ };
138
+
139
+ // Category filters, shared by the toggle group and the composer mode menu.
140
+ const CATEGORIES = [
141
+ {key: 'writing', label: 'Writing', icon: SquarePen},
142
+ {key: 'coding', label: 'Coding', icon: CodeXml},
143
+ {key: 'research', label: 'Research', icon: Search},
144
+ {key: 'creative', label: 'Creative', icon: Lightbulb},
145
+ ] as const;
146
+
147
+ // Composer mode menu: categories plus special modes.
148
+ const MODE_OPTIONS = [
149
+ {key: 'auto', label: 'Auto', icon: Sparkles},
150
+ ...CATEGORIES,
151
+ {key: 'sensitive', label: 'Sensitive', icon: Lock},
152
+ {key: 'deep', label: 'Deep Mode', icon: Clock},
153
+ ] as const;
154
+
155
+ // Modes that insert a composer token instead of switching the active category.
156
+ const TOKEN_MODES: Record<string, string> = {
157
+ sensitive: '/sensitive',
158
+ deep: '/deep-mode',
159
+ };
160
+
161
+ // Composer trigger data: @ mentions and / commands.
162
+ const MENTION_ITEMS: SearchableItem<{role: string}>[] = [
163
+ {id: 'vesper', label: 'Vesper Lin', auxiliaryData: {role: 'Design Systems'}},
164
+ {id: 'alex', label: 'Alex Nocturne', auxiliaryData: {role: 'Frontend'}},
165
+ {id: 'sam', label: 'Sam Sable', auxiliaryData: {role: 'Backend'}},
166
+ {id: 'jordan', label: 'Jordan Hex', auxiliaryData: {role: 'Product'}},
167
+ {id: 'taylor', label: 'Taylor Twilight', auxiliaryData: {role: 'Design'}},
168
+ {id: 'morgan', label: 'Morgan Morrow', auxiliaryData: {role: 'Infrastructure'}},
169
+ ];
170
+
171
+ const COMMAND_ITEMS: SearchableItem<{description: string}>[] = [
172
+ {
173
+ id: 'summarize',
174
+ label: 'summarize',
175
+ auxiliaryData: {description: 'Summarize the conversation'},
176
+ },
177
+ {
178
+ id: 'translate',
179
+ label: 'translate',
180
+ auxiliaryData: {description: 'Translate text to another language'},
181
+ },
182
+ {
183
+ id: 'search',
184
+ label: 'search',
185
+ auxiliaryData: {description: 'Search the web or documents'},
186
+ },
187
+ {
188
+ id: 'code',
189
+ label: 'code',
190
+ auxiliaryData: {description: 'Generate or explain code'},
191
+ },
192
+ {
193
+ id: 'help',
194
+ label: 'help',
195
+ auxiliaryData: {description: 'Show available commands'},
196
+ },
197
+ ];
198
+
199
+ const mentionTrigger: ChatComposerTrigger = {
200
+ character: '@',
201
+ searchSource: createStaticSource(MENTION_ITEMS),
202
+ renderItem: item => (
203
+ <TypeaheadItem
204
+ item={item}
205
+ description={(item.auxiliaryData as {role: string})?.role}
206
+ />
207
+ ),
208
+ onSelect: item => ({
209
+ value: `@${item.id}`,
210
+ label: item.label,
211
+ variant: 'cyan',
212
+ }),
213
+ };
214
+
215
+ const commandTrigger: ChatComposerTrigger = {
216
+ character: '/',
217
+ searchSource: createStaticSource(COMMAND_ITEMS),
218
+ renderItem: item => (
219
+ <TypeaheadItem
220
+ item={item}
221
+ description={(item.auxiliaryData as {description: string})?.description}
222
+ />
223
+ ),
224
+ onSelect: item => ({
225
+ value: `/${item.label}`,
226
+ label: `/${item.label}`,
227
+ variant: 'yellow',
228
+ }),
229
+ };
230
+
231
+ const composerTriggers = [mentionTrigger, commandTrigger];
232
+
233
+ // Main component
234
+
235
+ export default function AiChatLanding() {
236
+ const [mode, setMode] = useState<string | null>('auto');
237
+ const [category, setCategory] = useState<string | null>(null);
238
+ const [attachments, setAttachments] = useState<string[]>([
239
+ 'palette_brief.pdf',
240
+ 'nocturne_v2.fig',
241
+ 'api_spec.yaml',
242
+ 'contrast_audit.csv',
243
+ 'dracula_spec.pdf',
244
+ ]);
245
+ const [isModeMenuOpen, setIsModeMenuOpen] = useState(false);
246
+ const composerInputRef = useRef<ChatComposerInputHandle>(null);
247
+ const shouldFocusComposerRef = useRef(false);
248
+ const dictation = useChatDictation({inputRef: composerInputRef});
249
+
250
+ const activeMode = MODE_OPTIONS.find(m => m.key === mode) ?? MODE_OPTIONS[0];
251
+ const suggestions = category ? CATEGORY_SUGGESTIONS[category] : null;
252
+
253
+ // The composer's imperative insert methods mutate the DOM without emitting a
254
+ // change, so dispatch an input event to sync its value and clear the placeholder.
255
+ const syncComposerValue = () => {
256
+ document.activeElement?.dispatchEvent(new Event('input', {bubbles: true}));
257
+ };
258
+
259
+ const applySuggestion = (prompt: string) => {
260
+ const input = composerInputRef.current;
261
+ if (!input) {
262
+ return;
263
+ }
264
+ input.focus();
265
+ if (document.activeElement) {
266
+ window.getSelection()?.selectAllChildren(document.activeElement);
267
+ }
268
+ input.insertText(prompt);
269
+ syncComposerValue();
270
+ };
271
+
272
+ const insertMention = (item: (typeof MENTION_ITEMS)[number]) => {
273
+ const input = composerInputRef.current;
274
+ if (!input) {
275
+ return;
276
+ }
277
+ input.focus();
278
+ if (document.activeElement) {
279
+ const sel = window.getSelection();
280
+ sel?.selectAllChildren(document.activeElement);
281
+ sel?.collapseToEnd();
282
+ }
283
+ input.insertToken({
284
+ value: `@${item.id}`,
285
+ label: item.label,
286
+ variant: 'cyan',
287
+ });
288
+ syncComposerValue();
289
+ };
290
+
291
+ const insertModeToken = (label: string) => {
292
+ composerInputRef.current?.focus();
293
+ composerInputRef.current?.insertToken({
294
+ value: label,
295
+ label,
296
+ variant: 'orange',
297
+ });
298
+ syncComposerValue();
299
+ };
300
+
301
+ return (
302
+ <Layout
303
+ height="fill"
304
+ contentWidth={720}
305
+ padding={6}
306
+ content={
307
+ <LayoutContent>
308
+ <VStack gap={8} vAlign="center" style={pageStyle}>
309
+ {/* Greeting */}
310
+ <VStack gap={1}>
311
+ <HStack gap={2} vAlign="center">
312
+ <Icon icon={Sparkles} size="md" color="accent" />
313
+ <Text type="large">
314
+ Hi, Vlad
315
+ </Text>
316
+ </HStack>
317
+ <Heading level={1} type="display-2">
318
+ Where should we start?
319
+ </Heading>
320
+ </VStack>
321
+
322
+ {/* Composer */}
323
+ <ChatComposer
324
+ onSubmit={() => {}}
325
+ placeholder="Ask anything"
326
+ input={
327
+ <ChatComposerInput
328
+ handleRef={composerInputRef}
329
+ triggers={composerTriggers}
330
+ style={composerInput}
331
+ onFiles={files =>
332
+ setAttachments(prev => [...prev, ...files.map(f => f.name)])
333
+ }
334
+ />
335
+ }
336
+ drawer={
337
+ attachments.length > 0 ? (
338
+ <ChatComposerDrawer count={attachments.length}>
339
+ {attachments.map(name => (
340
+ <Token
341
+ key={name}
342
+ label={name}
343
+ onRemove={() =>
344
+ setAttachments(prev => prev.filter(n => n !== name))
345
+ }
346
+ />
347
+ ))}
348
+ </ChatComposerDrawer>
349
+ ) : undefined
350
+ }
351
+ headerActions={
352
+ <DropdownMenu
353
+ button={{
354
+ label: 'Reference',
355
+ variant: 'ghost',
356
+ size: 'sm',
357
+ icon: <Icon icon={AtSign} size="sm" />,
358
+ isIconOnly: true,
359
+ }}
360
+ hasChevron={false}
361
+ menuWidth={240}>
362
+ {MENTION_ITEMS.map(item => (
363
+ <DropdownMenuItem
364
+ key={item.id}
365
+ label={item.label}
366
+ description={item.auxiliaryData?.role}
367
+ onClick={() => insertMention(item)}
368
+ />
369
+ ))}
370
+ </DropdownMenu>
371
+ }
372
+ footerActions={
373
+ <>
374
+ <DropdownMenu
375
+ button={{
376
+ label: activeMode.label,
377
+ variant: 'ghost',
378
+ size: 'md',
379
+ icon: <Icon icon={activeMode.icon} size="sm" />,
380
+ children: activeMode.label,
381
+ }}
382
+ menuWidth={200}
383
+ isMenuOpen={isModeMenuOpen}
384
+ onOpenChange={isOpen => {
385
+ setIsModeMenuOpen(isOpen);
386
+ // Restore focus to the composer after inserting a mode token.
387
+ if (!isOpen && shouldFocusComposerRef.current) {
388
+ shouldFocusComposerRef.current = false;
389
+ setTimeout(() => composerInputRef.current?.focus(), 50);
390
+ }
391
+ }}
392
+ items={MODE_OPTIONS.flatMap(opt => {
393
+ const item = {
394
+ label: opt.label,
395
+ icon: opt.icon,
396
+ onClick: () => {
397
+ const tokenLabel = TOKEN_MODES[opt.key];
398
+ if (tokenLabel) {
399
+ insertModeToken(tokenLabel);
400
+ shouldFocusComposerRef.current = true;
401
+ } else {
402
+ setMode(opt.key);
403
+ }
404
+ },
405
+ };
406
+ return opt.key === 'sensitive'
407
+ ? [{type: 'divider' as const}, item]
408
+ : [item];
409
+ })}
410
+ />
411
+ <DropdownMenu
412
+ button={{
413
+ label: 'Settings',
414
+ variant: 'ghost',
415
+ size: 'md',
416
+ icon: <Icon icon={Settings} size="sm" />,
417
+ children: 'Settings',
418
+ }}
419
+ menuWidth={200}
420
+ items={[
421
+ {label: 'Preferences', onClick: () => {}},
422
+ {label: 'Keyboard shortcuts', onClick: () => {}},
423
+ {label: 'About', onClick: () => {}},
424
+ ]}
425
+ />
426
+ </>
427
+ }
428
+ sendActions={<ChatDictationButton dictation={dictation} />}
429
+ />
430
+
431
+ {/* Category filters + suggestion cards */}
432
+ <VStack gap={6} style={categories}>
433
+ <ToggleButtonGroup
434
+ label="Category"
435
+ value={category}
436
+ onChange={setCategory}
437
+ size="lg">
438
+ {CATEGORIES.map(cat => (
439
+ <ToggleButton
440
+ key={cat.key}
441
+ value={cat.key}
442
+ label={cat.label}
443
+ icon={<Icon icon={cat.icon} size="sm" />}
444
+ />
445
+ ))}
446
+ </ToggleButtonGroup>
447
+
448
+ {suggestions && (
449
+ <Grid columns={{minWidth: 280}} gap={3}>
450
+ {suggestions.map(suggestion => (
451
+ <ClickableCard
452
+ key={suggestion.heading}
453
+ label={suggestion.heading}
454
+ variant="muted"
455
+ padding={4}
456
+ onClick={() => {
457
+ applySuggestion(suggestion.prompt);
458
+ setMode(category);
459
+ }}>
460
+ <VStack gap={0.5}>
461
+ <Heading level={4}>{suggestion.heading}</Heading>
462
+ <Text type="body" color="secondary">
463
+ {suggestion.body}
464
+ </Text>
465
+ </VStack>
466
+ </ClickableCard>
467
+ ))}
468
+ </Grid>
469
+ )}
470
+ </VStack>
471
+ </VStack>
472
+ </LayoutContent>
473
+ }
474
+ />
475
+ );
476
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ type: 'page',
3
+ name: 'AI Chat',
4
+ description: 'Threaded assistant chat with design-doc artifact',
5
+ category: 'tooling',
6
+ };