@astryxdesign/lab 0.7.2-canary.4284442 → 0.7.2-canary.4d200b2
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/blocks/CommentComposerShowcase.doc.mjs +15 -0
- package/blocks/CommentComposerShowcase.tsx +45 -0
- package/blocks/CommentThreadInPopover.doc.mjs +21 -0
- package/blocks/CommentThreadInPopover.tsx +89 -0
- package/blocks/CommentThreadShowcase.doc.mjs +15 -0
- package/blocks/CommentThreadShowcase.tsx +67 -0
- package/blocks/CommentsInCard.doc.mjs +14 -0
- package/blocks/CommentsInCard.tsx +78 -0
- package/blocks/CommentsInline.doc.mjs +14 -0
- package/blocks/CommentsInline.tsx +297 -0
- package/blocks/CommentsShowcase.doc.mjs +15 -0
- package/blocks/CommentsShowcase.tsx +341 -0
- package/dist/Chat/ChatTypingIndicator.d.ts.map +1 -1
- package/dist/Chat/ChatTypingIndicator.js +1 -1
- package/dist/Comments/CommentActions.d.ts +55 -0
- package/dist/Comments/CommentActions.d.ts.map +1 -0
- package/dist/Comments/CommentActions.js +472 -0
- package/dist/Comments/CommentComposer.d.ts +108 -0
- package/dist/Comments/CommentComposer.d.ts.map +1 -0
- package/dist/Comments/CommentComposer.js +507 -0
- package/dist/Comments/CommentMarkdown.d.ts +15 -0
- package/dist/Comments/CommentMarkdown.d.ts.map +1 -0
- package/dist/Comments/CommentMarkdown.js +136 -0
- package/dist/Comments/CommentParts.d.ts +238 -0
- package/dist/Comments/CommentParts.d.ts.map +1 -0
- package/dist/Comments/CommentParts.js +823 -0
- package/dist/Comments/CommentRow.d.ts +20 -0
- package/dist/Comments/CommentRow.d.ts.map +1 -0
- package/dist/Comments/CommentRow.js +693 -0
- package/dist/Comments/CommentThread.d.ts +66 -0
- package/dist/Comments/CommentThread.d.ts.map +1 -0
- package/dist/Comments/CommentThread.js +169 -0
- package/dist/Comments/Comments.d.ts +105 -0
- package/dist/Comments/Comments.d.ts.map +1 -0
- package/dist/Comments/Comments.js +426 -0
- package/dist/Comments/CommentsContext.d.ts +117 -0
- package/dist/Comments/CommentsContext.d.ts.map +1 -0
- package/dist/Comments/CommentsContext.js +515 -0
- package/dist/Comments/i18n.d.ts +18 -0
- package/dist/Comments/i18n.d.ts.map +1 -0
- package/dist/Comments/i18n.js +34 -0
- package/dist/Comments/icons.d.ts +24 -0
- package/dist/Comments/icons.d.ts.map +1 -0
- package/dist/Comments/icons.js +93 -0
- package/dist/Comments/index.d.ts +16 -0
- package/dist/Comments/index.d.ts.map +1 -0
- package/dist/Comments/index.js +15 -0
- package/dist/Comments/types.d.ts +341 -0
- package/dist/Comments/types.d.ts.map +1 -0
- package/dist/Comments/types.js +100 -0
- package/dist/Comments/useCommentThreadDismissal.d.ts +88 -0
- package/dist/Comments/useCommentThreadDismissal.d.ts.map +1 -0
- package/dist/Comments/useCommentThreadDismissal.js +344 -0
- package/dist/Comments/utils.d.ts +44 -0
- package/dist/Comments/utils.d.ts.map +1 -0
- package/dist/Comments/utils.js +84 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/lab.css +70 -46
- package/package.json +4 -4
- package/src/Chat/ChatTypingIndicator.tsx +3 -0
- package/src/Comments/CommentActions.tsx +542 -0
- package/src/Comments/CommentComposer.doc.mjs +177 -0
- package/src/Comments/CommentComposer.test.tsx +255 -0
- package/src/Comments/CommentComposer.tsx +680 -0
- package/src/Comments/CommentMarkdown.tsx +184 -0
- package/src/Comments/CommentParts.tsx +937 -0
- package/src/Comments/CommentRow.tsx +918 -0
- package/src/Comments/CommentThread.doc.mjs +580 -0
- package/src/Comments/CommentThread.test.tsx +634 -0
- package/src/Comments/CommentThread.tsx +226 -0
- package/src/Comments/Comments.doc.mjs +648 -0
- package/src/Comments/Comments.test.tsx +1335 -0
- package/src/Comments/Comments.tsx +643 -0
- package/src/Comments/CommentsContext.tsx +755 -0
- package/src/Comments/i18n.ts +40 -0
- package/src/Comments/icons.tsx +129 -0
- package/src/Comments/index.ts +39 -0
- package/src/Comments/types.ts +392 -0
- package/src/Comments/useCommentThreadDismissal.ts +426 -0
- package/src/Comments/utils.ts +114 -0
- package/src/index.ts +30 -8
- package/blocks/DrawerFullWidthMobile.doc.mjs +0 -21
- package/blocks/DrawerFullWidthMobile.tsx +0 -42
- package/blocks/DrawerHeaderShowcase.doc.mjs +0 -15
- package/blocks/DrawerHeaderShowcase.tsx +0 -73
- package/blocks/DrawerHeaderSubtitleEndContent.doc.mjs +0 -22
- package/blocks/DrawerHeaderSubtitleEndContent.tsx +0 -49
- package/blocks/DrawerHeaderWithCloseButton.doc.mjs +0 -21
- package/blocks/DrawerHeaderWithCloseButton.tsx +0 -42
- package/blocks/DrawerMobileReveal.doc.mjs +0 -21
- package/blocks/DrawerMobileReveal.tsx +0 -43
- package/blocks/DrawerScrim.doc.mjs +0 -22
- package/blocks/DrawerScrim.tsx +0 -68
- package/blocks/DrawerShowcase.doc.mjs +0 -22
- package/blocks/DrawerShowcase.tsx +0 -41
- package/blocks/DrawerStart.doc.mjs +0 -21
- package/blocks/DrawerStart.tsx +0 -41
- package/blocks/DrawerWidths.doc.mjs +0 -22
- package/blocks/DrawerWidths.tsx +0 -54
- package/dist/Drawer/Drawer.d.ts +0 -181
- package/dist/Drawer/Drawer.d.ts.map +0 -1
- package/dist/Drawer/Drawer.js +0 -388
- package/dist/Drawer/DrawerHeader.d.ts +0 -82
- package/dist/Drawer/DrawerHeader.d.ts.map +0 -1
- package/dist/Drawer/DrawerHeader.js +0 -131
- package/dist/Drawer/index.d.ts +0 -11
- package/dist/Drawer/index.d.ts.map +0 -1
- package/dist/Drawer/index.js +0 -11
- package/dist/Drawer/useDrawerDialogPresence.d.ts +0 -42
- package/dist/Drawer/useDrawerDialogPresence.d.ts.map +0 -1
- package/dist/Drawer/useDrawerDialogPresence.js +0 -235
- package/src/Drawer/Drawer.doc.mjs +0 -470
- package/src/Drawer/Drawer.spec.md +0 -343
- package/src/Drawer/Drawer.test.tsx +0 -1039
- package/src/Drawer/Drawer.tsx +0 -618
- package/src/Drawer/DrawerHeader.doc.mjs +0 -291
- package/src/Drawer/DrawerHeader.test.tsx +0 -89
- package/src/Drawer/DrawerHeader.tsx +0 -237
- package/src/Drawer/index.ts +0 -13
- package/src/Drawer/useDrawerDialogPresence.ts +0 -283
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export default {
|
|
5
|
+
type: 'block',
|
|
6
|
+
name: 'New comment',
|
|
7
|
+
displayName: 'New comment',
|
|
8
|
+
description:
|
|
9
|
+
'A comment composer quoting the passage it comments on: type, add an emoji, and post with Enter or Post.',
|
|
10
|
+
exampleFor: 'CommentComposer',
|
|
11
|
+
isReady: true,
|
|
12
|
+
isShowcase: true,
|
|
13
|
+
aspectRatio: 16 / 9,
|
|
14
|
+
componentsUsed: ['CommentComposer', 'VStack', 'Text'],
|
|
15
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @input A controlled draft and a list of posted comments
|
|
7
|
+
* @output A comment composer that posts into a short list above it
|
|
8
|
+
* @position Lab CommentComposer's docsite showcase and copyable CLI block
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {useState} from 'react';
|
|
12
|
+
import {VStack} from '@astryxdesign/core/Layout';
|
|
13
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
14
|
+
import {CommentComposer} from '@astryxdesign/lab';
|
|
15
|
+
|
|
16
|
+
export default function CommentComposerShowcase() {
|
|
17
|
+
const [draft, setDraft] = useState('');
|
|
18
|
+
const [posted, setPosted] = useState<string[]>([]);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<VStack gap={3} width={420}>
|
|
22
|
+
{posted.length === 0 ? (
|
|
23
|
+
<Text type="supporting" color="secondary">
|
|
24
|
+
Nothing posted yet.
|
|
25
|
+
</Text>
|
|
26
|
+
) : (
|
|
27
|
+
posted.map((body, index) => (
|
|
28
|
+
<Text key={index} type="body">
|
|
29
|
+
{body}
|
|
30
|
+
</Text>
|
|
31
|
+
))
|
|
32
|
+
)}
|
|
33
|
+
<CommentComposer
|
|
34
|
+
value={draft}
|
|
35
|
+
onChange={setDraft}
|
|
36
|
+
onSubmit={body => {
|
|
37
|
+
setPosted(current => [...current, body]);
|
|
38
|
+
setDraft('');
|
|
39
|
+
}}
|
|
40
|
+
anchor={{quote: 'Guardrail alerts page the release oncall.'}}
|
|
41
|
+
onAnchorDismiss={() => {}}
|
|
42
|
+
/>
|
|
43
|
+
</VStack>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export default {
|
|
5
|
+
type: 'block',
|
|
6
|
+
name: 'In a popover',
|
|
7
|
+
displayName: 'In a popover',
|
|
8
|
+
description:
|
|
9
|
+
'Open one thread from a passage in a Popover with no padding. The thread owns the Popover’s dismissal through `onDismiss`, so turn the Popover’s light and Escape dismissal off: a press outside or Escape closes the thread, but the first one over an unsaved reply shakes it and keeps the draft.',
|
|
10
|
+
exampleFor: 'CommentThread',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: [
|
|
14
|
+
'CommentThread',
|
|
15
|
+
'Popover',
|
|
16
|
+
'Button',
|
|
17
|
+
'Card',
|
|
18
|
+
'Text',
|
|
19
|
+
'VStack',
|
|
20
|
+
],
|
|
21
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @input Controlled popover state, a controlled comment thread, and its reply
|
|
7
|
+
* callback
|
|
8
|
+
* @output A passage whose comment button opens one thread in a Popover; the
|
|
9
|
+
* thread owns the Popover's dismissal and keeps an unsaved reply on the
|
|
10
|
+
* first press outside or Escape
|
|
11
|
+
* @position Copyable Lab CommentThread example
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {useState} from 'react';
|
|
15
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
16
|
+
import {Card} from '@astryxdesign/core/Card';
|
|
17
|
+
import {VStack} from '@astryxdesign/core/Layout';
|
|
18
|
+
import {Popover} from '@astryxdesign/core/Popover';
|
|
19
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
20
|
+
import {
|
|
21
|
+
CommentThread,
|
|
22
|
+
type CommentThreadData,
|
|
23
|
+
type CommentUser,
|
|
24
|
+
} from '@astryxdesign/lab';
|
|
25
|
+
|
|
26
|
+
const VIEWER: CommentUser = {id: 'avery', name: 'Avery Kim'};
|
|
27
|
+
|
|
28
|
+
export default function CommentThreadInPopover() {
|
|
29
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
30
|
+
const [thread, setThread] = useState<CommentThreadData>({
|
|
31
|
+
id: 'passage',
|
|
32
|
+
author: {id: 'maya', name: 'Maya Chen'},
|
|
33
|
+
body: 'Should this say three days? The dry run adds one.',
|
|
34
|
+
createdAt: '2026-09-30T19:00:00Z',
|
|
35
|
+
anchor: {quote: 'The final cohort completes rollout in two days.'},
|
|
36
|
+
});
|
|
37
|
+
const replyCount = thread.replies?.length ?? 0;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Card padding={4} width={420}>
|
|
41
|
+
<VStack gap={3} align="start">
|
|
42
|
+
<Text>
|
|
43
|
+
Every region moves to the new build in three cohorts. The final cohort
|
|
44
|
+
completes rollout in two days.
|
|
45
|
+
</Text>
|
|
46
|
+
<Popover
|
|
47
|
+
isOpen={isOpen}
|
|
48
|
+
onOpenChange={setIsOpen}
|
|
49
|
+
// The thread owns dismissal: it keeps an unsaved reply on the first
|
|
50
|
+
// press outside or Escape, which native light dismiss cannot.
|
|
51
|
+
hasLightDismiss={false}
|
|
52
|
+
hasEscapeDismiss={false}
|
|
53
|
+
label="Comment thread"
|
|
54
|
+
padding={0}
|
|
55
|
+
width={340}
|
|
56
|
+
content={
|
|
57
|
+
<CommentThread
|
|
58
|
+
comment={thread}
|
|
59
|
+
currentUser={VIEWER}
|
|
60
|
+
container="card"
|
|
61
|
+
onDismiss={() => setIsOpen(false)}
|
|
62
|
+
onSubmit={body =>
|
|
63
|
+
setThread(current => ({
|
|
64
|
+
...current,
|
|
65
|
+
replies: [
|
|
66
|
+
...(current.replies ?? []),
|
|
67
|
+
{
|
|
68
|
+
id: `reply-${Date.now()}`,
|
|
69
|
+
author: VIEWER,
|
|
70
|
+
body,
|
|
71
|
+
createdAt: new Date().toISOString(),
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
}))
|
|
75
|
+
}
|
|
76
|
+
/>
|
|
77
|
+
}>
|
|
78
|
+
<Button
|
|
79
|
+
label={
|
|
80
|
+
replyCount === 0 ? '1 comment' : `${replyCount + 1} comments`
|
|
81
|
+
}
|
|
82
|
+
variant="secondary"
|
|
83
|
+
size="sm"
|
|
84
|
+
/>
|
|
85
|
+
</Popover>
|
|
86
|
+
</VStack>
|
|
87
|
+
</Card>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export default {
|
|
5
|
+
type: 'block',
|
|
6
|
+
name: 'Margin comment',
|
|
7
|
+
displayName: 'Margin comment',
|
|
8
|
+
description:
|
|
9
|
+
'One comment thread in a Card beside a document: it quotes its passage, opens on a press into its replies and reply composer, and resolves.',
|
|
10
|
+
exampleFor: 'CommentThread',
|
|
11
|
+
isReady: true,
|
|
12
|
+
isShowcase: true,
|
|
13
|
+
aspectRatio: 4 / 3,
|
|
14
|
+
componentsUsed: ['CommentThread', 'Card'],
|
|
15
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @input A controlled inline comment thread and its reply callback
|
|
7
|
+
* @output A margin comment: one thread quoting its passage, inside a Card
|
|
8
|
+
* @position Lab CommentThread's docsite showcase and copyable CLI block
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {useState} from 'react';
|
|
12
|
+
import {Card} from '@astryxdesign/core/Card';
|
|
13
|
+
import {
|
|
14
|
+
CommentThread,
|
|
15
|
+
type CommentThreadData,
|
|
16
|
+
type CommentUser,
|
|
17
|
+
} from '@astryxdesign/lab';
|
|
18
|
+
|
|
19
|
+
const VIEWER: CommentUser = {id: 'avery', name: 'Avery Kim'};
|
|
20
|
+
|
|
21
|
+
export default function CommentThreadShowcase() {
|
|
22
|
+
const [thread, setThread] = useState<CommentThreadData>({
|
|
23
|
+
id: 'margin',
|
|
24
|
+
author: {id: 'maya', name: 'Maya Chen'},
|
|
25
|
+
body: 'Should this say three days? The dry run adds one.',
|
|
26
|
+
createdAt: '2026-09-30T19:00:00Z',
|
|
27
|
+
anchor: {quote: 'The final cohort completes rollout in two days.'},
|
|
28
|
+
replies: [
|
|
29
|
+
{
|
|
30
|
+
id: 'margin-reply',
|
|
31
|
+
author: VIEWER,
|
|
32
|
+
body: 'Good catch, updating it.',
|
|
33
|
+
createdAt: '2026-09-30T19:20:00Z',
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Card padding={0} width={340}>
|
|
40
|
+
<CommentThread
|
|
41
|
+
comment={thread}
|
|
42
|
+
currentUser={VIEWER}
|
|
43
|
+
container="card"
|
|
44
|
+
onSubmit={body =>
|
|
45
|
+
setThread(current => ({
|
|
46
|
+
...current,
|
|
47
|
+
replies: [
|
|
48
|
+
...(current.replies ?? []),
|
|
49
|
+
{
|
|
50
|
+
id: `reply-${Date.now()}`,
|
|
51
|
+
author: VIEWER,
|
|
52
|
+
body,
|
|
53
|
+
createdAt: new Date().toISOString(),
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
}))
|
|
57
|
+
}
|
|
58
|
+
onResolveThread={() =>
|
|
59
|
+
setThread(current => ({...current, isResolved: true}))
|
|
60
|
+
}
|
|
61
|
+
onReopenThread={() =>
|
|
62
|
+
setThread(current => ({...current, isResolved: false}))
|
|
63
|
+
}
|
|
64
|
+
/>
|
|
65
|
+
</Card>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export default {
|
|
5
|
+
type: 'block',
|
|
6
|
+
name: 'Inside a card',
|
|
7
|
+
displayName: 'Inside a card',
|
|
8
|
+
description:
|
|
9
|
+
'Set container="card" on a card parent: threads become divider-bounded Sections, so cards never nest, and the composer docks at the bottom of the card.',
|
|
10
|
+
exampleFor: 'Comments',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 3 / 4,
|
|
13
|
+
componentsUsed: ['Comments', 'Card'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @input Controlled comments and a post callback
|
|
7
|
+
* @output A compact Comments panel filling a Card, its threads divider-bounded
|
|
8
|
+
* Sections so cards never nest
|
|
9
|
+
* @position Copyable Lab Comments example
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {useState} from 'react';
|
|
13
|
+
import {Card} from '@astryxdesign/core/Card';
|
|
14
|
+
import {
|
|
15
|
+
Comments,
|
|
16
|
+
type CommentThreadData,
|
|
17
|
+
type CommentUser,
|
|
18
|
+
} from '@astryxdesign/lab';
|
|
19
|
+
|
|
20
|
+
const VIEWER: CommentUser = {id: 'avery', name: 'Avery Kim'};
|
|
21
|
+
const MAYA: CommentUser = {id: 'maya', name: 'Maya Chen'};
|
|
22
|
+
|
|
23
|
+
const INITIAL_COMMENTS: readonly CommentThreadData[] = [
|
|
24
|
+
{
|
|
25
|
+
id: 'kickoff',
|
|
26
|
+
author: MAYA,
|
|
27
|
+
body: 'Can you review the rollout plan before Friday?',
|
|
28
|
+
createdAt: '2026-09-30T19:00:00Z',
|
|
29
|
+
replies: [
|
|
30
|
+
{
|
|
31
|
+
id: 'kickoff-reply',
|
|
32
|
+
author: VIEWER,
|
|
33
|
+
body: 'On it. I will leave notes on the final cohort.',
|
|
34
|
+
createdAt: '2026-09-30T19:12:00Z',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'dashboard',
|
|
40
|
+
author: VIEWER,
|
|
41
|
+
body: 'I added the guardrail dashboard to the summary.',
|
|
42
|
+
createdAt: '2026-09-30T19:20:00Z',
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
export default function CommentsInCard() {
|
|
47
|
+
const [comments, setComments] = useState(INITIAL_COMMENTS);
|
|
48
|
+
const post = (body: string, parentId?: string) => {
|
|
49
|
+
const comment = {
|
|
50
|
+
id: `comment-${Date.now()}`,
|
|
51
|
+
author: VIEWER,
|
|
52
|
+
body,
|
|
53
|
+
createdAt: new Date().toISOString(),
|
|
54
|
+
};
|
|
55
|
+
setComments(current =>
|
|
56
|
+
parentId == null
|
|
57
|
+
? [...current, comment]
|
|
58
|
+
: current.map(root =>
|
|
59
|
+
root.id === parentId
|
|
60
|
+
? {...root, replies: [...(root.replies ?? []), comment]}
|
|
61
|
+
: root,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Card padding={0} width={380} height={460}>
|
|
68
|
+
<Comments
|
|
69
|
+
label="Rollout plan"
|
|
70
|
+
comments={comments}
|
|
71
|
+
currentUser={VIEWER}
|
|
72
|
+
container="card"
|
|
73
|
+
density="compact"
|
|
74
|
+
onSubmit={post}
|
|
75
|
+
/>
|
|
76
|
+
</Card>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export default {
|
|
5
|
+
type: 'block',
|
|
6
|
+
name: 'Inline comments',
|
|
7
|
+
displayName: 'Inline comments',
|
|
8
|
+
description:
|
|
9
|
+
'A realistic Markdown document review with highlighted passages synchronized to a Comments side panel. Selecting an unannotated passage starts a quoted thread; replies and resolution update host-owned data.',
|
|
10
|
+
exampleFor: 'Comments',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 3 / 2,
|
|
13
|
+
componentsUsed: ['Button', 'Card', 'Comments', 'HStack', 'Text', 'VStack'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @input A Markdown-like document, its anchored threads, and controlled
|
|
7
|
+
* document/thread selection
|
|
8
|
+
* @output A realistic document review: highlighted passages stay synchronized
|
|
9
|
+
* with a Comments side panel, and a selected passage starts a quoted thread
|
|
10
|
+
* @position Copyable Lab Comments inline-comments example
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {useRef, useState} from 'react';
|
|
14
|
+
import * as stylex from '@stylexjs/stylex';
|
|
15
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
16
|
+
import {Card} from '@astryxdesign/core/Card';
|
|
17
|
+
import {HStack, VStack} from '@astryxdesign/core/Layout';
|
|
18
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
19
|
+
import {
|
|
20
|
+
borderVars,
|
|
21
|
+
colorVars,
|
|
22
|
+
focusVars,
|
|
23
|
+
radiusVars,
|
|
24
|
+
spacingVars,
|
|
25
|
+
} from '@astryxdesign/core/theme/tokens.stylex';
|
|
26
|
+
import {
|
|
27
|
+
Comments,
|
|
28
|
+
type CommentAnchor,
|
|
29
|
+
type CommentData,
|
|
30
|
+
type CommentThreadData,
|
|
31
|
+
type CommentUser,
|
|
32
|
+
} from '@astryxdesign/lab';
|
|
33
|
+
|
|
34
|
+
const VIEWER: CommentUser = {id: 'avery', name: 'Avery Kim'};
|
|
35
|
+
const MAYA: CommentUser = {id: 'maya', name: 'Maya Chen'};
|
|
36
|
+
const JORDAN: CommentUser = {id: 'jordan', name: 'Jordan Lee'};
|
|
37
|
+
|
|
38
|
+
const ROLLOUT_PASSAGE =
|
|
39
|
+
'The final cohort completes rollout in three days after the dry run.';
|
|
40
|
+
const GUARDRAIL_PASSAGE =
|
|
41
|
+
'Pause if the 95th percentile exceeds 420 ms for two consecutive checks.';
|
|
42
|
+
const REVIEW_PASSAGE =
|
|
43
|
+
'Publish the review note after the final cohort is complete.';
|
|
44
|
+
|
|
45
|
+
const INITIAL_COMMENTS: readonly CommentThreadData[] = [
|
|
46
|
+
{
|
|
47
|
+
id: 'rollout-window',
|
|
48
|
+
author: MAYA,
|
|
49
|
+
body: 'Can we make the dry-run dependency explicit? It explains the extra day.',
|
|
50
|
+
createdAt: '2026-10-14T17:18:00Z',
|
|
51
|
+
anchor: {quote: ROLLOUT_PASSAGE},
|
|
52
|
+
replies: [
|
|
53
|
+
{
|
|
54
|
+
id: 'rollout-reply',
|
|
55
|
+
author: VIEWER,
|
|
56
|
+
body: 'Updated. The window now starts after the dry run finishes.',
|
|
57
|
+
createdAt: '2026-10-14T17:31:00Z',
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'latency-threshold',
|
|
63
|
+
author: JORDAN,
|
|
64
|
+
body: 'This matches the alert. I would keep the two-check window to avoid pausing on a single spike.',
|
|
65
|
+
createdAt: '2026-10-14T18:04:00Z',
|
|
66
|
+
anchor: {quote: GUARDRAIL_PASSAGE},
|
|
67
|
+
isUnread: true,
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const styles = stylex.create({
|
|
72
|
+
passage: {
|
|
73
|
+
appearance: 'none',
|
|
74
|
+
backgroundColor: colorVars['--color-background-yellow'],
|
|
75
|
+
borderStyle: 'solid',
|
|
76
|
+
borderWidth: 0,
|
|
77
|
+
borderBlockEndWidth: borderVars['--border-width'],
|
|
78
|
+
borderBlockEndColor: 'transparent',
|
|
79
|
+
borderRadius: radiusVars['--radius-inner'],
|
|
80
|
+
color: 'inherit',
|
|
81
|
+
cursor: 'pointer',
|
|
82
|
+
font: 'inherit',
|
|
83
|
+
lineHeight: 'inherit',
|
|
84
|
+
marginInline: spacingVars['--spacing-0-5'],
|
|
85
|
+
paddingBlock: 0,
|
|
86
|
+
paddingInline: spacingVars['--spacing-0-5'],
|
|
87
|
+
textAlign: 'start',
|
|
88
|
+
outlineStyle: {
|
|
89
|
+
default: 'none',
|
|
90
|
+
':focus-visible': focusVars['--focus-outline-style'],
|
|
91
|
+
},
|
|
92
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
93
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
94
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
95
|
+
},
|
|
96
|
+
selectedPassage: {
|
|
97
|
+
borderBlockEndColor: colorVars['--color-border-yellow'],
|
|
98
|
+
},
|
|
99
|
+
selectablePassage: {
|
|
100
|
+
backgroundColor: {
|
|
101
|
+
default: 'transparent',
|
|
102
|
+
':hover': colorVars['--color-background-yellow'],
|
|
103
|
+
},
|
|
104
|
+
borderBlockEndStyle: 'dashed',
|
|
105
|
+
borderBlockEndColor: colorVars['--color-border-emphasized'],
|
|
106
|
+
},
|
|
107
|
+
document: {
|
|
108
|
+
overflowY: 'auto',
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
function updateComment(
|
|
113
|
+
comments: readonly CommentThreadData[],
|
|
114
|
+
id: string,
|
|
115
|
+
update: (comment: CommentData) => CommentData,
|
|
116
|
+
): CommentThreadData[] {
|
|
117
|
+
return comments.map(comment =>
|
|
118
|
+
comment.id === id
|
|
119
|
+
? {...comment, ...update(comment)}
|
|
120
|
+
: {
|
|
121
|
+
...comment,
|
|
122
|
+
replies: comment.replies?.map(reply =>
|
|
123
|
+
reply.id === id ? update(reply) : reply,
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default function CommentsInline() {
|
|
130
|
+
const [comments, setComments] = useState<CommentThreadData[]>(() => [
|
|
131
|
+
...INITIAL_COMMENTS,
|
|
132
|
+
]);
|
|
133
|
+
const [selectedThreadId, setSelectedThreadId] = useState<string | null>(
|
|
134
|
+
'rollout-window',
|
|
135
|
+
);
|
|
136
|
+
const [selectedPassage, setSelectedPassage] = useState<string | null>(null);
|
|
137
|
+
const [composerAnchor, setComposerAnchor] = useState<CommentAnchor>();
|
|
138
|
+
const nextId = useRef(1);
|
|
139
|
+
|
|
140
|
+
const post = (body: string, parentCommentId?: string) => {
|
|
141
|
+
const id = `review-comment-${nextId.current++}`;
|
|
142
|
+
const comment: CommentData = {
|
|
143
|
+
id,
|
|
144
|
+
author: VIEWER,
|
|
145
|
+
body,
|
|
146
|
+
createdAt: new Date().toISOString(),
|
|
147
|
+
};
|
|
148
|
+
if (parentCommentId != null) {
|
|
149
|
+
setComments(current =>
|
|
150
|
+
current.map(thread =>
|
|
151
|
+
thread.id === parentCommentId
|
|
152
|
+
? {...thread, replies: [...(thread.replies ?? []), comment]}
|
|
153
|
+
: thread,
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
setComments(current => [...current, {...comment, anchor: composerAnchor}]);
|
|
159
|
+
setComposerAnchor(undefined);
|
|
160
|
+
setSelectedPassage(null);
|
|
161
|
+
setSelectedThreadId(id);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<HStack gap={3} align="start">
|
|
166
|
+
<Card width={520} height={620} padding={4}>
|
|
167
|
+
<VStack gap={4} width="100%" xstyle={styles.document}>
|
|
168
|
+
<VStack gap={1}>
|
|
169
|
+
<Text as="h1" type="display-3" weight="bold">
|
|
170
|
+
Launch plan
|
|
171
|
+
</Text>
|
|
172
|
+
<Text type="supporting" color="secondary">
|
|
173
|
+
Draft · updated today
|
|
174
|
+
</Text>
|
|
175
|
+
</VStack>
|
|
176
|
+
|
|
177
|
+
<VStack gap={2}>
|
|
178
|
+
<Text as="h2" type="body" weight="bold">
|
|
179
|
+
Rollout
|
|
180
|
+
</Text>
|
|
181
|
+
<Text type="body">
|
|
182
|
+
Begin with the internal cohort, then expand by region.
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
{...stylex.props(
|
|
186
|
+
styles.passage,
|
|
187
|
+
selectedThreadId === 'rollout-window' &&
|
|
188
|
+
styles.selectedPassage,
|
|
189
|
+
)}
|
|
190
|
+
aria-label={`Open comments on “${ROLLOUT_PASSAGE}”`}
|
|
191
|
+
onClick={() => {
|
|
192
|
+
setSelectedPassage(null);
|
|
193
|
+
setComposerAnchor(undefined);
|
|
194
|
+
setSelectedThreadId('rollout-window');
|
|
195
|
+
}}>
|
|
196
|
+
{ROLLOUT_PASSAGE}
|
|
197
|
+
</button>
|
|
198
|
+
Owners confirm each step before the next begins.
|
|
199
|
+
</Text>
|
|
200
|
+
</VStack>
|
|
201
|
+
|
|
202
|
+
<VStack gap={2}>
|
|
203
|
+
<Text as="h2" type="body" weight="bold">
|
|
204
|
+
Guardrails
|
|
205
|
+
</Text>
|
|
206
|
+
<Text type="body">
|
|
207
|
+
Watch latency and error rate throughout the rollout.
|
|
208
|
+
<button
|
|
209
|
+
type="button"
|
|
210
|
+
{...stylex.props(
|
|
211
|
+
styles.passage,
|
|
212
|
+
selectedThreadId === 'latency-threshold' &&
|
|
213
|
+
styles.selectedPassage,
|
|
214
|
+
)}
|
|
215
|
+
aria-label={`Open comments on “${GUARDRAIL_PASSAGE}”`}
|
|
216
|
+
onClick={() => {
|
|
217
|
+
setSelectedPassage(null);
|
|
218
|
+
setComposerAnchor(undefined);
|
|
219
|
+
setSelectedThreadId('latency-threshold');
|
|
220
|
+
}}>
|
|
221
|
+
{GUARDRAIL_PASSAGE}
|
|
222
|
+
</button>
|
|
223
|
+
Resume only after the metric returns to baseline.
|
|
224
|
+
</Text>
|
|
225
|
+
</VStack>
|
|
226
|
+
|
|
227
|
+
<VStack gap={2} align="start">
|
|
228
|
+
<Text as="h2" type="body" weight="bold">
|
|
229
|
+
Review
|
|
230
|
+
</Text>
|
|
231
|
+
<Text type="body">
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
{...stylex.props(
|
|
235
|
+
styles.passage,
|
|
236
|
+
styles.selectablePassage,
|
|
237
|
+
selectedPassage === REVIEW_PASSAGE && styles.selectedPassage,
|
|
238
|
+
)}
|
|
239
|
+
aria-label={`Select “${REVIEW_PASSAGE}” for a comment`}
|
|
240
|
+
onClick={() => {
|
|
241
|
+
setSelectedThreadId(null);
|
|
242
|
+
setComposerAnchor(undefined);
|
|
243
|
+
setSelectedPassage(REVIEW_PASSAGE);
|
|
244
|
+
}}>
|
|
245
|
+
{REVIEW_PASSAGE}
|
|
246
|
+
</button>
|
|
247
|
+
</Text>
|
|
248
|
+
{selectedPassage === REVIEW_PASSAGE && (
|
|
249
|
+
<Button
|
|
250
|
+
label="Comment on selection"
|
|
251
|
+
variant="secondary"
|
|
252
|
+
size="sm"
|
|
253
|
+
onClick={() => {
|
|
254
|
+
setComposerAnchor({quote: REVIEW_PASSAGE});
|
|
255
|
+
setSelectedPassage(null);
|
|
256
|
+
}}
|
|
257
|
+
/>
|
|
258
|
+
)}
|
|
259
|
+
</VStack>
|
|
260
|
+
</VStack>
|
|
261
|
+
</Card>
|
|
262
|
+
|
|
263
|
+
<Card width={360} height={620} padding={0}>
|
|
264
|
+
<Comments
|
|
265
|
+
label="Comments on Launch plan"
|
|
266
|
+
comments={comments}
|
|
267
|
+
currentUser={VIEWER}
|
|
268
|
+
container="card"
|
|
269
|
+
density="compact"
|
|
270
|
+
selectedThreadId={selectedThreadId}
|
|
271
|
+
onSelectedThreadChange={setSelectedThreadId}
|
|
272
|
+
composerAnchor={composerAnchor}
|
|
273
|
+
onComposerAnchorDismiss={() => setComposerAnchor(undefined)}
|
|
274
|
+
onSubmit={post}
|
|
275
|
+
onResolveThread={commentId =>
|
|
276
|
+
setComments(current =>
|
|
277
|
+
updateComment(current, commentId, comment => ({
|
|
278
|
+
...comment,
|
|
279
|
+
isResolved: true,
|
|
280
|
+
resolvedBy: VIEWER,
|
|
281
|
+
})),
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
onReopenThread={commentId =>
|
|
285
|
+
setComments(current =>
|
|
286
|
+
updateComment(current, commentId, comment => ({
|
|
287
|
+
...comment,
|
|
288
|
+
isResolved: false,
|
|
289
|
+
resolvedBy: undefined,
|
|
290
|
+
})),
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
/>
|
|
294
|
+
</Card>
|
|
295
|
+
</HStack>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export default {
|
|
5
|
+
type: 'block',
|
|
6
|
+
name: 'Comments panel',
|
|
7
|
+
displayName: 'Comments panel',
|
|
8
|
+
description:
|
|
9
|
+
'A working Comments panel: threads with replies, reactions, edit history, controlled typing with hidden seen metadata, inline comments, a non-interrupting unread-reply reveal, an agent reply still being written, a resolved thread, a comment that failed to send, activity events, and earlier comments to load. Post, reply, react, edit, delete, and resolve; the host updates the controlled comments.',
|
|
10
|
+
exampleFor: 'Comments',
|
|
11
|
+
isReady: true,
|
|
12
|
+
isShowcase: true,
|
|
13
|
+
aspectRatio: 3 / 4,
|
|
14
|
+
componentsUsed: ['Comments', 'VStack'],
|
|
15
|
+
};
|