@astryxdesign/cli 0.1.4-canary.ff77e1c → 0.1.4

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 (73) hide show
  1. package/CHANGELOG.md +1 -2
  2. package/docs/getting-started.doc.mjs +0 -4
  3. package/docs/migration.doc.mjs +0 -134
  4. package/docs/styling.doc.mjs +0 -53
  5. package/docs/working-with-ai.doc.mjs +1 -1
  6. package/package.json +10 -15
  7. package/src/api/component.mjs +1 -2
  8. package/src/api/doctor.mjs +2 -13
  9. package/src/api/template.mjs +3 -14
  10. package/src/codemods/__tests__/registry.test.mjs +0 -1
  11. package/src/codemods/__tests__/runner.test.mjs +0 -38
  12. package/src/codemods/registry.mjs +0 -1
  13. package/src/codemods/runner.mjs +0 -2
  14. package/src/commands/agent-docs.mjs +6 -9
  15. package/src/commands/agent-docs.test.mjs +0 -33
  16. package/src/commands/build-theme.mjs +14 -112
  17. package/src/commands/component-resolution.test.mjs +4 -4
  18. package/src/commands/doctor.test.mjs +0 -34
  19. package/src/commands/init.mjs +1 -1
  20. package/src/commands/swizzle.mjs +0 -34
  21. package/src/commands/swizzle.routing.test.mjs +0 -67
  22. package/src/lib/component-discovery.mjs +4 -8
  23. package/templates/blocks/components/AspectRatio/AspectRatioCircleImage.tsx +6 -1
  24. package/templates/blocks/components/AspectRatio/AspectRatioImageGallery.tsx +7 -2
  25. package/templates/blocks/components/AspectRatio/AspectRatioShowcase.tsx +5 -2
  26. package/templates/blocks/components/AspectRatio/AspectRatioSquareImage.tsx +6 -1
  27. package/templates/blocks/components/AspectRatio/AspectRatioWidescreen.tsx +6 -1
  28. package/templates/blocks/components/Collapsible/CollapsibleWithoutCard.tsx +23 -25
  29. package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupShowcase.tsx +48 -27
  30. package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemShowcase.tsx +54 -31
  31. package/templates/pages/classic-gallery/page.tsx +1 -1
  32. package/templates/pages/dashboard/page.tsx +1 -1
  33. package/templates/pages/documentation/page.tsx +1 -1
  34. package/templates/pages/kanban-board/page.tsx +13 -37
  35. package/templates/pages/mixed-gallery/page.tsx +1 -1
  36. package/templates/pages/payment-form/page.tsx +6 -2
  37. package/templates/pages/product-detail/page.tsx +11 -3
  38. package/templates/pages/product-gallery/page.tsx +1 -1
  39. package/templates/pages/settings/page.tsx +1 -1
  40. package/templates/pages/side-gallery/page.tsx +1 -1
  41. package/templates/pages/table-page/page.tsx +1 -1
  42. package/templates/pages/table-page-chart/page.tsx +14 -4
  43. package/templates/pages/table-page-heatmap-status/page.tsx +1 -1
  44. package/templates/pages/table-page-shoe-store-heatmap/page.tsx +19 -5
  45. package/src/codemods/transforms/v0.1.5/__tests__/rename-switch-label-spacing-default-to-hug.test.mjs +0 -93
  46. package/src/codemods/transforms/v0.1.5/index.mjs +0 -19
  47. package/src/codemods/transforms/v0.1.5/rename-switch-label-spacing-default-to-hug.mjs +0 -140
  48. package/src/commands/build-theme.color-scheme.test.mjs +0 -153
  49. package/src/commands/build-theme.variants.test.mjs +0 -193
  50. package/templates/blocks/components/BaseTypeahead/BaseTypeaheadCustomSearch.doc.mjs +0 -14
  51. package/templates/blocks/components/BaseTypeahead/BaseTypeaheadCustomSearch.tsx +0 -58
  52. package/templates/blocks/components/CodeBlock/CodeBlockTerminal.doc.mjs +0 -14
  53. package/templates/blocks/components/CodeBlock/CodeBlockTerminal.tsx +0 -24
  54. package/templates/blocks/components/Collapsible/CollapsibleDividedAccordion.doc.mjs +0 -13
  55. package/templates/blocks/components/Collapsible/CollapsibleDividedAccordion.tsx +0 -33
  56. package/templates/blocks/components/CommandPaletteInput/CommandPaletteInputBasic.doc.mjs +0 -15
  57. package/templates/blocks/components/CommandPaletteInput/CommandPaletteInputBasic.tsx +0 -39
  58. package/templates/blocks/components/CommandPaletteInput/CommandPaletteInputShowcase.doc.mjs +0 -15
  59. package/templates/blocks/components/CommandPaletteInput/CommandPaletteInputShowcase.tsx +0 -39
  60. package/templates/blocks/components/CommandPaletteList/CommandPaletteListBasic.doc.mjs +0 -15
  61. package/templates/blocks/components/CommandPaletteList/CommandPaletteListBasic.tsx +0 -27
  62. package/templates/blocks/components/CommandPaletteList/CommandPaletteListShowcase.doc.mjs +0 -15
  63. package/templates/blocks/components/CommandPaletteList/CommandPaletteListShowcase.tsx +0 -38
  64. package/templates/blocks/components/ContextMenuItem/ContextMenuItemBasic.doc.mjs +0 -14
  65. package/templates/blocks/components/ContextMenuItem/ContextMenuItemBasic.tsx +0 -44
  66. package/templates/blocks/components/ContextMenuItem/ContextMenuItemShowcase.doc.mjs +0 -15
  67. package/templates/blocks/components/ContextMenuItem/ContextMenuItemShowcase.tsx +0 -107
  68. package/templates/blocks/components/NavHeadingMenu/NavHeadingMenuShowcase.doc.mjs +0 -15
  69. package/templates/blocks/components/NavHeadingMenu/NavHeadingMenuShowcase.tsx +0 -47
  70. package/templates/pages/incident-console/page.tsx +0 -580
  71. package/templates/pages/incident-console/template.doc.mjs +0 -12
  72. package/templates/pages/messaging-shell/page.tsx +0 -676
  73. package/templates/pages/messaging-shell/template.doc.mjs +0 -12
@@ -1,676 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- /**
6
- * Messaging Shell — Slack-style column frame for team messaging tools.
7
- *
8
- * Frame (desktop, left to right):
9
- * workspace rail 68px | channel sidebar 260px | message stream (fill) | thread panel 340px
10
- *
11
- * Container policy: dense rows, zero Cards. Channels and DMs are List/ListItem
12
- * rows, presence is AvatarStatusDot/StatusDot, unread counts are the only
13
- * Badge usage. The stream and thread are built on the Chat component family.
14
- *
15
- * Responsive contract:
16
- * >1024px — full four-column frame (rail | sidebar | stream | thread)
17
- * <=1024px — thread panel hidden; stream takes the reclaimed width
18
- * <=768px — sidebar also hidden; rail + stream keep full width
19
- *
20
- * Fixtures are deterministic: fixed ISO timestamps rendered via <Timestamp>,
21
- * no Date.now()/Math.random() anywhere.
22
- */
23
-
24
- import {useState, type CSSProperties} from 'react';
25
-
26
- import {
27
- Layout,
28
- LayoutContent,
29
- LayoutPanel,
30
- Stack,
31
- StackItem,
32
- HStack,
33
- VStack,
34
- } from '@astryxdesign/core/Layout';
35
- import {Text, Heading} from '@astryxdesign/core/Text';
36
- import {
37
- ChatComposer,
38
- ChatLayout,
39
- ChatMessage,
40
- ChatMessageBubble,
41
- ChatMessageList,
42
- ChatMessageMetadata,
43
- ChatSystemMessage,
44
- } from '@astryxdesign/core/Chat';
45
- import {Avatar, AvatarStatusDot} from '@astryxdesign/core/Avatar';
46
- import {StatusDot} from '@astryxdesign/core/StatusDot';
47
- import {Badge} from '@astryxdesign/core/Badge';
48
- import {List, ListItem} from '@astryxdesign/core/List';
49
- import {Divider} from '@astryxdesign/core/Divider';
50
- import {TextInput} from '@astryxdesign/core/TextInput';
51
- import {Timestamp} from '@astryxdesign/core/Timestamp';
52
- import {EmptyState} from '@astryxdesign/core/EmptyState';
53
- import {Icon} from '@astryxdesign/core/Icon';
54
- import {IconButton} from '@astryxdesign/core/IconButton';
55
- import {useMediaQuery} from '@astryxdesign/core/hooks';
56
-
57
- import {
58
- BellIcon,
59
- BookmarkIcon,
60
- ChatBubbleLeftRightIcon,
61
- Cog6ToothIcon,
62
- HashtagIcon,
63
- HomeIcon,
64
- InboxIcon,
65
- MagnifyingGlassIcon,
66
- PencilSquareIcon,
67
- UserGroupIcon,
68
- XMarkIcon,
69
- } from '@heroicons/react/24/outline';
70
-
71
- // ---------------------------------------------------------------------------
72
- // Styles — plain CSS properties with semantic tokens only.
73
- // ---------------------------------------------------------------------------
74
-
75
- const styles: Record<string, CSSProperties> = {
76
- root: {
77
- height: '100dvh',
78
- width: '100%',
79
- },
80
- rail: {
81
- height: '100%',
82
- alignItems: 'center',
83
- paddingTop: 'var(--spacing-3)',
84
- paddingBottom: 'var(--spacing-3)',
85
- },
86
- railSpacer: {
87
- flex: 1,
88
- },
89
- sidebar: {
90
- height: '100%',
91
- minHeight: 0,
92
- },
93
- sidebarHeader: {
94
- alignItems: 'center',
95
- paddingInline: 'var(--spacing-3)',
96
- paddingBlock: 'var(--spacing-3)',
97
- },
98
- sidebarSearch: {
99
- paddingInline: 'var(--spacing-3)',
100
- paddingBottom: 'var(--spacing-2)',
101
- },
102
- sidebarScroll: {
103
- minHeight: 0,
104
- overflowY: 'auto',
105
- paddingInline: 'var(--spacing-2)',
106
- paddingBottom: 'var(--spacing-3)',
107
- },
108
- sectionGap: {
109
- marginTop: 'var(--spacing-4)',
110
- },
111
- streamColumn: {
112
- height: '100%',
113
- minHeight: 0,
114
- },
115
- streamHeader: {
116
- alignItems: 'center',
117
- paddingInline: 'var(--spacing-4)',
118
- paddingBlock: 'var(--spacing-3)',
119
- },
120
- streamTopic: {
121
- minWidth: 0,
122
- },
123
- chatArea: {
124
- minHeight: 0,
125
- display: 'flex',
126
- flexDirection: 'column',
127
- },
128
- chatFill: {
129
- flex: 1,
130
- minHeight: 0,
131
- },
132
- threadColumn: {
133
- height: '100%',
134
- minHeight: 0,
135
- },
136
- threadHeader: {
137
- alignItems: 'center',
138
- paddingInline: 'var(--spacing-3)',
139
- paddingBlock: 'var(--spacing-2)',
140
- },
141
- threadScroll: {
142
- minHeight: 0,
143
- overflowY: 'auto',
144
- paddingInline: 'var(--spacing-3)',
145
- paddingBlock: 'var(--spacing-3)',
146
- },
147
- threadComposer: {
148
- padding: 'var(--spacing-3)',
149
- },
150
- };
151
-
152
- // ---------------------------------------------------------------------------
153
- // Deterministic fixtures — fixed ISO timestamps, stable ordering.
154
- // ---------------------------------------------------------------------------
155
-
156
- type Presence = 'online' | 'busy' | 'offline';
157
-
158
- interface User {
159
- name: string;
160
- }
161
-
162
- const USERS: Record<string, User> = {
163
- mira: {name: 'Mira Chen'},
164
- devon: {name: 'Devon Park'},
165
- sasha: {name: 'Sasha Ortiz'},
166
- you: {name: 'Riley Quinn'},
167
- };
168
-
169
- interface Channel {
170
- id: string;
171
- name: string;
172
- topic: string;
173
- unread: number;
174
- }
175
-
176
- const CHANNELS: Channel[] = [
177
- {
178
- id: 'design-systems',
179
- name: 'design-systems',
180
- topic: 'Component APIs, tokens, and release coordination',
181
- unread: 0,
182
- },
183
- {
184
- id: 'frontend-guild',
185
- name: 'frontend-guild',
186
- topic: 'Cross-team frontend practices',
187
- unread: 4,
188
- },
189
- {
190
- id: 'releases',
191
- name: 'releases',
192
- topic: 'Release announcements and rollbacks',
193
- unread: 12,
194
- },
195
- {id: 'random', name: 'random', topic: 'Everything else', unread: 0},
196
- ];
197
-
198
- interface DirectMessage {
199
- id: string;
200
- userId: string;
201
- presence: Presence;
202
- unread: number;
203
- }
204
-
205
- const DIRECT_MESSAGES: DirectMessage[] = [
206
- {id: 'dm-mira', userId: 'mira', presence: 'online', unread: 0},
207
- {id: 'dm-devon', userId: 'devon', presence: 'busy', unread: 2},
208
- {id: 'dm-sasha', userId: 'sasha', presence: 'offline', unread: 0},
209
- ];
210
-
211
- // AvatarStatusDot supports success | neutral | error (no warning).
212
- const PRESENCE_VARIANT: Record<Presence, 'success' | 'error' | 'neutral'> = {
213
- online: 'success',
214
- busy: 'error',
215
- offline: 'neutral',
216
- };
217
-
218
- const PRESENCE_LABEL: Record<Presence, string> = {
219
- online: 'Online',
220
- busy: 'Busy',
221
- offline: 'Offline',
222
- };
223
-
224
- /** One message group: consecutive bubbles from the same sender. */
225
- interface StreamMessage {
226
- id: string;
227
- userId: string;
228
- time: string;
229
- bubbles: string[];
230
- }
231
-
232
- const MESSAGES_BY_CHANNEL: Record<string, StreamMessage[]> = {
233
- 'design-systems': [
234
- {
235
- id: 'm1',
236
- userId: 'mira',
237
- time: '2026-06-30T09:12:00',
238
- bubbles: [
239
- 'Morning! The Timestamp component now supports a `system_date` format — worth switching the audit log over.',
240
- 'I put the migration notes in the wiki under Decisions.',
241
- ],
242
- },
243
- {
244
- id: 'm2',
245
- userId: 'devon',
246
- time: '2026-06-30T09:15:00',
247
- bubbles: ['Nice. Does that unblock the incident console timeline work?'],
248
- },
249
- {
250
- id: 'm3',
251
- userId: 'you',
252
- time: '2026-06-30T09:17:00',
253
- bubbles: [
254
- 'It does — I will pick that up after the template review.',
255
- 'One question on the List density defaults, will start a thread.',
256
- ],
257
- },
258
- {
259
- id: 'm4',
260
- userId: 'sasha',
261
- time: '2026-06-30T09:24:00',
262
- bubbles: [
263
- 'Heads up: the token sync job ran clean overnight, no drift between core and the theme packages.',
264
- ],
265
- },
266
- {
267
- id: 'm5',
268
- userId: 'mira',
269
- time: '2026-06-30T09:26:00',
270
- bubbles: ['Great — closing out the drift task then.'],
271
- },
272
- ],
273
- 'frontend-guild': [
274
- {
275
- id: 'g1',
276
- userId: 'devon',
277
- time: '2026-06-30T08:40:00',
278
- bubbles: [
279
- 'Guild sync moved to Thursday this week to avoid the release freeze.',
280
- ],
281
- },
282
- {
283
- id: 'g2',
284
- userId: 'you',
285
- time: '2026-06-30T08:44:00',
286
- bubbles: ['Works for me — agenda doc is updated.'],
287
- },
288
- ],
289
- };
290
-
291
- interface ThreadReply {
292
- id: string;
293
- userId: string;
294
- time: string;
295
- text: string;
296
- }
297
-
298
- const THREAD_ROOT: ThreadReply = {
299
- id: 't0',
300
- userId: 'you',
301
- time: '2026-06-30T09:17:30',
302
- text: 'One question on the List density defaults — should channel sidebars use compact or balanced? The spec shows both.',
303
- };
304
-
305
- const THREAD_REPLIES: ThreadReply[] = [
306
- {
307
- id: 't1',
308
- userId: 'mira',
309
- time: '2026-06-30T09:19:00',
310
- text: 'Compact for navigation surfaces. Balanced is for content lists where descriptions carry weight.',
311
- },
312
- {
313
- id: 't2',
314
- userId: 'devon',
315
- time: '2026-06-30T09:21:00',
316
- text: 'Agreed — the sidebar rows here are a good reference implementation.',
317
- },
318
- ];
319
-
320
- const RAIL_ITEMS = [
321
- {id: 'home', label: 'Home', icon: HomeIcon},
322
- {id: 'dms', label: 'Direct messages', icon: ChatBubbleLeftRightIcon},
323
- {id: 'activity', label: 'Activity', icon: BellIcon},
324
- {id: 'saved', label: 'Saved items', icon: BookmarkIcon},
325
- ];
326
-
327
- // ---------------------------------------------------------------------------
328
- // Stream message group — avatar + name on the first bubble, timestamp on the
329
- // last, `group` positions tighten corner radii between consecutive bubbles.
330
- // ---------------------------------------------------------------------------
331
-
332
- function StreamMessageGroup({message}: {message: StreamMessage}) {
333
- const isSelf = message.userId === 'you';
334
- const user = USERS[message.userId];
335
- const lastIndex = message.bubbles.length - 1;
336
-
337
- return (
338
- <ChatMessage
339
- sender={isSelf ? 'user' : 'assistant'}
340
- avatar={isSelf ? undefined : <Avatar name={user.name} size="small" />}>
341
- {message.bubbles.map((text, index) => (
342
- <ChatMessageBubble
343
- key={`${message.id}-${index}`}
344
- group={
345
- message.bubbles.length === 1
346
- ? undefined
347
- : index === 0
348
- ? 'first'
349
- : index === lastIndex
350
- ? 'last'
351
- : 'middle'
352
- }
353
- name={!isSelf && index === 0 ? user.name : undefined}
354
- metadata={
355
- index === lastIndex ? (
356
- <ChatMessageMetadata
357
- timestamp={<Timestamp value={message.time} format="time" />}
358
- />
359
- ) : undefined
360
- }>
361
- {text}
362
- </ChatMessageBubble>
363
- ))}
364
- </ChatMessage>
365
- );
366
- }
367
-
368
- // ---------------------------------------------------------------------------
369
- // Page
370
- // ---------------------------------------------------------------------------
371
-
372
- export default function MessagingShellPage() {
373
- const [selectedChannelId, setSelectedChannelId] = useState('design-systems');
374
- const [selectedDmId, setSelectedDmId] = useState<string | null>(null);
375
- const [isThreadOpen, setIsThreadOpen] = useState(true);
376
- const [searchQuery, setSearchQuery] = useState('');
377
-
378
- // Responsive contract (see file header).
379
- const isThreadHidden = useMediaQuery('(max-width: 1024px)');
380
- const isSidebarHidden = useMediaQuery('(max-width: 768px)');
381
-
382
- const selectedChannel =
383
- CHANNELS.find(channel => channel.id === selectedChannelId) ?? CHANNELS[0];
384
- const messages = MESSAGES_BY_CHANNEL[selectedChannel.id] ?? [];
385
-
386
- const normalizedQuery = searchQuery.trim().toLowerCase();
387
- const visibleChannels = CHANNELS.filter(channel =>
388
- channel.name.toLowerCase().includes(normalizedQuery),
389
- );
390
- const visibleDms = DIRECT_MESSAGES.filter(dm =>
391
- USERS[dm.userId].name.toLowerCase().includes(normalizedQuery),
392
- );
393
-
394
- const showThreadPanel = isThreadOpen && !isThreadHidden;
395
-
396
- const workspaceRail = (
397
- <VStack gap={2} style={styles.rail}>
398
- <Avatar name="Astryx HQ" size="small" />
399
- {RAIL_ITEMS.map(item => (
400
- <IconButton
401
- key={item.id}
402
- label={item.label}
403
- tooltip={item.label}
404
- icon={<Icon icon={item.icon} size="sm" color="inherit" />}
405
- variant={item.id === 'home' ? 'secondary' : 'ghost'}
406
- onClick={() => {}}
407
- />
408
- ))}
409
- <div style={styles.railSpacer} />
410
- <IconButton
411
- label="Settings"
412
- tooltip="Settings"
413
- icon={<Icon icon={Cog6ToothIcon} size="sm" color="inherit" />}
414
- variant="ghost"
415
- onClick={() => {}}
416
- />
417
- <Avatar
418
- name={USERS.you.name}
419
- size="small"
420
- status={<AvatarStatusDot variant="success" label="Online" />}
421
- />
422
- </VStack>
423
- );
424
-
425
- const channelSidebar = (
426
- <Stack direction="vertical" style={styles.sidebar}>
427
- <HStack gap={2} style={styles.sidebarHeader}>
428
- <StackItem size="fill">
429
- <Heading level={5}>Astryx HQ</Heading>
430
- </StackItem>
431
- <IconButton
432
- label="New message"
433
- tooltip="New message"
434
- icon={<Icon icon={PencilSquareIcon} size="sm" color="inherit" />}
435
- variant="ghost"
436
- size="sm"
437
- onClick={() => {}}
438
- />
439
- </HStack>
440
- <div style={styles.sidebarSearch}>
441
- <TextInput
442
- label="Jump to"
443
- isLabelHidden
444
- size="sm"
445
- placeholder="Jump to..."
446
- startIcon={MagnifyingGlassIcon}
447
- value={searchQuery}
448
- onChange={setSearchQuery}
449
- />
450
- </div>
451
- <StackItem size="fill" style={styles.sidebarScroll}>
452
- <List
453
- density="compact"
454
- hasDividers={false}
455
- header={
456
- <Text type="label" size="sm" color="secondary">
457
- Channels
458
- </Text>
459
- }>
460
- {visibleChannels.map(channel => (
461
- <ListItem
462
- key={channel.id}
463
- label={channel.name}
464
- isSelected={
465
- selectedDmId === null && channel.id === selectedChannelId
466
- }
467
- onClick={() => {
468
- setSelectedChannelId(channel.id);
469
- setSelectedDmId(null);
470
- }}
471
- startContent={
472
- <Icon icon={HashtagIcon} size="sm" color="secondary" />
473
- }
474
- endContent={
475
- channel.unread > 0 ? (
476
- <Badge label={String(channel.unread)} variant="neutral" />
477
- ) : undefined
478
- }
479
- />
480
- ))}
481
- </List>
482
- <div style={styles.sectionGap}>
483
- <List
484
- density="compact"
485
- hasDividers={false}
486
- header={
487
- <Text type="label" size="sm" color="secondary">
488
- Direct messages
489
- </Text>
490
- }>
491
- {visibleDms.map(dm => (
492
- <ListItem
493
- key={dm.id}
494
- label={USERS[dm.userId].name}
495
- isSelected={selectedDmId === dm.id}
496
- onClick={() => setSelectedDmId(dm.id)}
497
- startContent={
498
- <Avatar
499
- name={USERS[dm.userId].name}
500
- size="xsmall"
501
- status={
502
- <AvatarStatusDot
503
- variant={PRESENCE_VARIANT[dm.presence]}
504
- label={PRESENCE_LABEL[dm.presence]}
505
- />
506
- }
507
- />
508
- }
509
- endContent={
510
- dm.unread > 0 ? (
511
- <Badge label={String(dm.unread)} variant="neutral" />
512
- ) : undefined
513
- }
514
- />
515
- ))}
516
- </List>
517
- </div>
518
- </StackItem>
519
- </Stack>
520
- );
521
-
522
- const messageStream = (
523
- <Stack direction="vertical" style={styles.streamColumn}>
524
- <HStack gap={3} style={styles.streamHeader}>
525
- <Icon icon={HashtagIcon} size="sm" color="secondary" />
526
- <Heading level={5}>{selectedChannel.name}</Heading>
527
- <StackItem size="fill" style={styles.streamTopic}>
528
- <Text type="supporting" color="secondary" maxLines={1}>
529
- {selectedChannel.topic}
530
- </Text>
531
- </StackItem>
532
- <StatusDot variant="success" label="12 online" />
533
- <IconButton
534
- label="Members"
535
- tooltip="Members"
536
- icon={<Icon icon={UserGroupIcon} size="sm" color="inherit" />}
537
- variant="ghost"
538
- size="sm"
539
- onClick={() => {}}
540
- />
541
- </HStack>
542
- <Divider />
543
- <StackItem size="fill" style={styles.chatArea}>
544
- <div style={styles.chatFill}>
545
- <ChatLayout
546
- composer={
547
- <ChatComposer
548
- placeholder={`Message #${selectedChannel.name}`}
549
- onSubmit={() => {}}
550
- />
551
- }
552
- emptyState={
553
- <EmptyState
554
- icon={<Icon icon={InboxIcon} size="lg" />}
555
- title="No messages yet"
556
- description="Start the conversation — messages posted here are visible to the whole channel."
557
- />
558
- }>
559
- {messages.length > 0 ? (
560
- <ChatMessageList density="balanced">
561
- <ChatSystemMessage variant="divider">
562
- Tuesday, June 30
563
- </ChatSystemMessage>
564
- <ChatSystemMessage>
565
- Sasha Ortiz joined #{selectedChannel.name}
566
- </ChatSystemMessage>
567
- {messages.map(message => (
568
- <StreamMessageGroup key={message.id} message={message} />
569
- ))}
570
- </ChatMessageList>
571
- ) : null}
572
- </ChatLayout>
573
- </div>
574
- </StackItem>
575
- </Stack>
576
- );
577
-
578
- const threadPanel = (
579
- <Stack direction="vertical" style={styles.threadColumn}>
580
- <HStack gap={2} style={styles.threadHeader}>
581
- <StackItem size="fill">
582
- <HStack gap={2} style={{alignItems: 'baseline'}}>
583
- <Text weight="semibold">Thread</Text>
584
- <Text type="supporting" color="secondary">
585
- #{selectedChannel.name}
586
- </Text>
587
- </HStack>
588
- </StackItem>
589
- <IconButton
590
- label="Close thread"
591
- tooltip="Close thread"
592
- icon={<Icon icon={XMarkIcon} size="sm" color="inherit" />}
593
- variant="ghost"
594
- size="sm"
595
- onClick={() => setIsThreadOpen(false)}
596
- />
597
- </HStack>
598
- <Divider />
599
- <StackItem size="fill" style={styles.threadScroll}>
600
- <ChatMessageList density="compact">
601
- <ChatMessage
602
- sender="assistant"
603
- avatar={
604
- <Avatar name={USERS[THREAD_ROOT.userId].name} size="small" />
605
- }>
606
- <ChatMessageBubble
607
- name={USERS[THREAD_ROOT.userId].name}
608
- metadata={
609
- <ChatMessageMetadata
610
- timestamp={
611
- <Timestamp value={THREAD_ROOT.time} format="time" />
612
- }
613
- />
614
- }>
615
- {THREAD_ROOT.text}
616
- </ChatMessageBubble>
617
- </ChatMessage>
618
- <ChatSystemMessage variant="divider">
619
- {THREAD_REPLIES.length} replies
620
- </ChatSystemMessage>
621
- {THREAD_REPLIES.map(reply => (
622
- <ChatMessage
623
- key={reply.id}
624
- sender="assistant"
625
- avatar={<Avatar name={USERS[reply.userId].name} size="small" />}>
626
- <ChatMessageBubble
627
- name={USERS[reply.userId].name}
628
- metadata={
629
- <ChatMessageMetadata
630
- timestamp={<Timestamp value={reply.time} format="time" />}
631
- />
632
- }>
633
- {reply.text}
634
- </ChatMessageBubble>
635
- </ChatMessage>
636
- ))}
637
- </ChatMessageList>
638
- </StackItem>
639
- <div style={styles.threadComposer}>
640
- <ChatComposer
641
- density="compact"
642
- placeholder="Reply in thread..."
643
- onSubmit={() => {}}
644
- />
645
- </div>
646
- </Stack>
647
- );
648
-
649
- return (
650
- <div style={styles.root}>
651
- <Layout
652
- height="fill"
653
- start={
654
- <>
655
- <LayoutPanel width={68} padding={0}>
656
- {workspaceRail}
657
- </LayoutPanel>
658
- {!isSidebarHidden && (
659
- <LayoutPanel width={260} padding={0}>
660
- {channelSidebar}
661
- </LayoutPanel>
662
- )}
663
- </>
664
- }
665
- end={
666
- showThreadPanel ? (
667
- <LayoutPanel width={340} padding={0}>
668
- {threadPanel}
669
- </LayoutPanel>
670
- ) : undefined
671
- }
672
- content={<LayoutContent padding={0}>{messageStream}</LayoutContent>}
673
- />
674
- </div>
675
- );
676
- }
@@ -1,12 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('../../../../core/src/docs-types').TemplateDoc} */
4
- export const doc = {
5
- type: 'page',
6
- name: 'Messaging Shell',
7
- displayName: 'Messaging Shell',
8
- description:
9
- 'Slack-style messaging shell with a four-column frame (workspace rail, channel sidebar, message stream, thread panel) built on the Chat component family: dense rows, zero cards.',
10
- isReady: false,
11
- category: 'Shell - Messaging',
12
- };