@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.
- package/CHANGELOG.md +1 -2
- package/docs/getting-started.doc.mjs +0 -4
- package/docs/migration.doc.mjs +0 -134
- package/docs/styling.doc.mjs +0 -53
- package/docs/working-with-ai.doc.mjs +1 -1
- package/package.json +10 -15
- package/src/api/component.mjs +1 -2
- package/src/api/doctor.mjs +2 -13
- package/src/api/template.mjs +3 -14
- package/src/codemods/__tests__/registry.test.mjs +0 -1
- package/src/codemods/__tests__/runner.test.mjs +0 -38
- package/src/codemods/registry.mjs +0 -1
- package/src/codemods/runner.mjs +0 -2
- package/src/commands/agent-docs.mjs +6 -9
- package/src/commands/agent-docs.test.mjs +0 -33
- package/src/commands/build-theme.mjs +14 -112
- package/src/commands/component-resolution.test.mjs +4 -4
- package/src/commands/doctor.test.mjs +0 -34
- package/src/commands/init.mjs +1 -1
- package/src/commands/swizzle.mjs +0 -34
- package/src/commands/swizzle.routing.test.mjs +0 -67
- package/src/lib/component-discovery.mjs +4 -8
- package/templates/blocks/components/AspectRatio/AspectRatioCircleImage.tsx +6 -1
- package/templates/blocks/components/AspectRatio/AspectRatioImageGallery.tsx +7 -2
- package/templates/blocks/components/AspectRatio/AspectRatioShowcase.tsx +5 -2
- package/templates/blocks/components/AspectRatio/AspectRatioSquareImage.tsx +6 -1
- package/templates/blocks/components/AspectRatio/AspectRatioWidescreen.tsx +6 -1
- package/templates/blocks/components/Collapsible/CollapsibleWithoutCard.tsx +23 -25
- package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupShowcase.tsx +48 -27
- package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemShowcase.tsx +54 -31
- package/templates/pages/classic-gallery/page.tsx +1 -1
- package/templates/pages/dashboard/page.tsx +1 -1
- package/templates/pages/documentation/page.tsx +1 -1
- package/templates/pages/kanban-board/page.tsx +13 -37
- package/templates/pages/mixed-gallery/page.tsx +1 -1
- package/templates/pages/payment-form/page.tsx +6 -2
- package/templates/pages/product-detail/page.tsx +11 -3
- package/templates/pages/product-gallery/page.tsx +1 -1
- package/templates/pages/settings/page.tsx +1 -1
- package/templates/pages/side-gallery/page.tsx +1 -1
- package/templates/pages/table-page/page.tsx +1 -1
- package/templates/pages/table-page-chart/page.tsx +14 -4
- package/templates/pages/table-page-heatmap-status/page.tsx +1 -1
- package/templates/pages/table-page-shoe-store-heatmap/page.tsx +19 -5
- package/src/codemods/transforms/v0.1.5/__tests__/rename-switch-label-spacing-default-to-hug.test.mjs +0 -93
- package/src/codemods/transforms/v0.1.5/index.mjs +0 -19
- package/src/codemods/transforms/v0.1.5/rename-switch-label-spacing-default-to-hug.mjs +0 -140
- package/src/commands/build-theme.color-scheme.test.mjs +0 -153
- package/src/commands/build-theme.variants.test.mjs +0 -193
- package/templates/blocks/components/BaseTypeahead/BaseTypeaheadCustomSearch.doc.mjs +0 -14
- package/templates/blocks/components/BaseTypeahead/BaseTypeaheadCustomSearch.tsx +0 -58
- package/templates/blocks/components/CodeBlock/CodeBlockTerminal.doc.mjs +0 -14
- package/templates/blocks/components/CodeBlock/CodeBlockTerminal.tsx +0 -24
- package/templates/blocks/components/Collapsible/CollapsibleDividedAccordion.doc.mjs +0 -13
- package/templates/blocks/components/Collapsible/CollapsibleDividedAccordion.tsx +0 -33
- package/templates/blocks/components/CommandPaletteInput/CommandPaletteInputBasic.doc.mjs +0 -15
- package/templates/blocks/components/CommandPaletteInput/CommandPaletteInputBasic.tsx +0 -39
- package/templates/blocks/components/CommandPaletteInput/CommandPaletteInputShowcase.doc.mjs +0 -15
- package/templates/blocks/components/CommandPaletteInput/CommandPaletteInputShowcase.tsx +0 -39
- package/templates/blocks/components/CommandPaletteList/CommandPaletteListBasic.doc.mjs +0 -15
- package/templates/blocks/components/CommandPaletteList/CommandPaletteListBasic.tsx +0 -27
- package/templates/blocks/components/CommandPaletteList/CommandPaletteListShowcase.doc.mjs +0 -15
- package/templates/blocks/components/CommandPaletteList/CommandPaletteListShowcase.tsx +0 -38
- package/templates/blocks/components/ContextMenuItem/ContextMenuItemBasic.doc.mjs +0 -14
- package/templates/blocks/components/ContextMenuItem/ContextMenuItemBasic.tsx +0 -44
- package/templates/blocks/components/ContextMenuItem/ContextMenuItemShowcase.doc.mjs +0 -15
- package/templates/blocks/components/ContextMenuItem/ContextMenuItemShowcase.tsx +0 -107
- package/templates/blocks/components/NavHeadingMenu/NavHeadingMenuShowcase.doc.mjs +0 -15
- package/templates/blocks/components/NavHeadingMenu/NavHeadingMenuShowcase.tsx +0 -47
- package/templates/pages/incident-console/page.tsx +0 -580
- package/templates/pages/incident-console/template.doc.mjs +0 -12
- package/templates/pages/messaging-shell/page.tsx +0 -676
- 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
|
-
};
|