Activity & conversations
What people said and did: threads, doc comments, feeds, inboxes, versions, chat, calls and feedback.
Installation & usage
npx @youj/cli add activity-comment-threadAlso installs alert-dialog, avatar, avatar-stack, badge, button, card, collapse, counter, dropdown-menu, empty, icon-at-sign, icon-bell, icon-bell-off, icon-bold, icon-check, icon-chevron-down, icon-circle-alert, icon-code, icon-engine, icon-italic, icon-link, icon-loader-dots, icon-message, icon-more-horizontal, icon-pencil, icon-reply, icon-smile-plus, icon-trash, input-group, kbd, popover, presence-list, skeleton, toggle, tooltip, use-announcer, use-controllable-state, use-now, use-visible, youj-core, youj-presets, youj-ui-core.
The CLI copies the block to blocks/activity/comment-thread.tsx with every component it uses, so it’s yours to edit.
Every prop is optional and falls back to the demo, so it renders as you see it until you pass your own. Give it your data, rewrite or translate its strings through copy, and connect its actions:
'use client'
import { CommentThread, type CommentThreadProps } from '@/blocks/activity/comment-thread'
export function CommentThreadView({ threads, people, user, typing }: Pick<CommentThreadProps, 'threads' | 'people' | 'user' | 'typing'>) {
return (
<CommentThread
threads={threads}
people={people}
user={user}
typing={typing}
copy={{ title: 'Discussion' }}
onSubscribedChange={(subscribed) => {
// your code
}}
onRetry={() => {
// your code
}}
onPost={async (draft) => {
// await your API here; the block shows progress until it settles
}}
/>
)
}Description
The discussion on a record: threads of a comment and its replies on a rail, written in markdown-lite with @mentions that autocomplete at the caret, emoji reactions that pop, edit in place, delete with a confirmation, and resolve, which folds a thread to one line. A comment shows the moment it's sent and settles when onPost resolves; one that fails stays with Retry and Discard. In the demo, Mara starts typing a few seconds in, then replies.
A document with comments pinned to its words, the way Google Docs does it: highlights in the text and a rail of threads beside them, each level with its words, the one you open set right against them and the rest pushed clear. Select a few words and press Comment to start a thread on them. Below 48rem of its own width, the rail becomes a list under the document.
A project's activity by day: one sentence a row, with what it carried under it (a comment's words, a change as from → to, labels, a deploy's outcome, the commits). Repeats fold together, filters pick a kind and people, and new events arrive live, waiting behind an "N new" pill while you're reading so nothing moves under your eyes.
The notifications triage panel, for a popover or a side panel. One row per thread with its latest words; approve, decline or reply in place; snooze to later today, tomorrow or next week; mark done with Undo. A thread with news is unread again. Every move is a key.
A document's versions by day beside the document as it was, with what changed marked word by word and changed properties as before → after. Compare with the version before, the current one or a named one, step through the changes, name a version, or restore one: the restore lands on top as a new version, so nothing is lost.
A support widget: a launcher with an unread count opens a panel with your team, recent conversations and a help search. Conversations start with an assistant that streams replies, takes attachments and hands off to a person.
A call with a voice agent: its name and what it is doing, the call clock, a Voice Orb, the caption line, and the controls a call needs. Mute grays the orb and stops it hearing, the speaker volume sits beside a meter of the voice, and End call leaves the call ended with how long it lasted and Call again. Its stories show the call muted and ended.
An in-product NPS survey: a card that slides into the corner without taking focus, with a 0 to 10 scale, a follow-up question that changes with the score and Not now or dismiss with Undo. A floating Feedback widget is also in the block.
Code
Comment Thread, the Comment thread story: the files youj add writes.
blocks/activity
Reference
Props29 props, 5 types
Props
| Prop | Type | |
|---|---|---|
theme | ThemeInputWraps the block in this theme. Default: the theme around it. | – |
embedded | booleanInside another app: a default true | true |
fill | booleanTake the whole width it's given instead of stopping at its natural width (640px at 100% scale). default false | false |
classNames | { card?: string }Classes for its parts: | – |
headingLevel | 1 | 2 | 3The title's heading level. Default 1, or 2 when | – |
locale | stringFormats its times. Default: | – |
copy | Partial<CommentThreadCopy>Any of the block's strings, to rewrite or translate. | – |
subject | stringWhat the discussion is about, under the title: "Q4 launch plan". | – |
threads | CommentThreadItem[]The threads, oldest first. Default: a launch plan's discussion, with a reply arriving while it's open (a demo, marked). | – |
people | CommentThreadPerson[]Who can be default demo | demo |
user | CommentThreadPersonThe signed-in person: new comments are theirs, and they can edit and delete their own. default the demo's Jordan | the demo's Jordan |
typing | CommentThreadPerson[]People typing right now, from your realtime layer. default [] | [] |
reactions | string[]The emoji a comment can get. default 👍 👎 😄 🎉 😕 ❤️ 🚀 👀 | 👍 👎 😄 🎉 😕 ❤️ 🚀 👀 |
subscribed | booleanWhether the signed-in person gets notified, when your app owns it; pair with | – |
defaultSubscribed | booleanSubscribed at first, when the block owns it. default true | true |
onSubscribedChange | (subscribed: boolean) => void | – |
now | Date | numberThe time the first render shows, which the server and the browser agree on; it ticks on once mounted. Default: the demo's, or the browser's clock with your | – |
loading | booleanShows the threads' shapes while they load. default false | false |
error | ReactNodeWhy the threads didn't load; shows with Retry when | – |
onRetry | () => void | – |
empty | ReactNodeShown when there are no threads. Default: a quiet "No comments yet". | – |
demoMarker | booleanShows a dashed "Demo data" badge while the block runs on its demo threads. default true | true |
onPost | (draft: CommentThreadDraft) => Promise<Partial<CommentThreadComment> | void> | Partial<CommentThreadComment> | voidA new comment ( | – |
onEdit | (commentId: string, body: string) => Promise<unknown> | unknown | – |
onDelete | (commentId: string) => Promise<unknown> | unknown | – |
onReact | (commentId: string, emoji: string, reacted: boolean) => Promise<unknown> | unknown | – |
onResolve | (threadId: string, resolved: boolean) => Promise<unknown> | unknown | – |
onCopyLink | (commentId: string) => voidA comment's Copy link. Default: this page's address with the comment's anchor. | – |
actions | ReactNodeMore controls at the end of the header, after Subscribe: the host's own actions on the discussion (Resolve, Delete). | – |
CommentThreadItem
| Prop | Type | |
|---|---|---|
idRequired | string | – |
commentsRequired | CommentThreadComment[]The first is the thread's own comment; the rest are its replies, oldest first. | – |
resolved | { by: CommentThreadPerson; at: Date | number | string } | nullWho resolved it and when. A resolved thread folds to one line. | – |
CommentThreadComment
| Prop | Type | |
|---|---|---|
idRequired | string | – |
authorRequired | CommentThreadPerson | – |
bodyRequired | stringThe text, in markdown-lite: **bold**, _italic_, ~~strike~~, | – |
createdAtRequired | Date | number | string | – |
editedAt | Date | number | stringWhen it was last edited; shows "edited". | – |
reactions | CommentThreadReaction[] | – |
deleted | booleanDeleted while it had replies: shown as a placeholder so the replies keep their context. | – |
CommentThreadPerson
| Prop | Type | |
|---|---|---|
idRequired | string | – |
nameRequired | string | – |
handle | stringWhat follows @ when they're mentioned: "mara". Default: their id. | – |
avatar | stringAn image URL for their avatar. Default: their initials. | – |
CommentThreadReaction
| Prop | Type | |
|---|---|---|
emojiRequired | string | – |
byRequired | string[] | – |
CommentThreadDraft
| Prop | Type | |
|---|---|---|
threadIdRequired | string | nullThe thread it replies to; null for a new thread. | – |
bodyRequired | string | – |
mentionsRequired | string[]The ids of the people it | – |
Strings71 keys for copy
Strings
copy takes any of these keys; the rest keep their defaults. A key whose text holds a value (a count, a name) takes a function.
titledemoDatacountparticipantssubscribesubscribedsubscribeHintjustNoweditededitedAtsendingsendFailedretrydiscardfaileddeletednewLabelnewPlaceholderreplyLabelreplyPlaceholdereditLabelsendsendReplysavecancelcomposerHintcomposerHintOtherformattingbolditaliccodementionmentionListmentionResultsactionsreactreplymoreeditcopyLinkdeleteresolvereopendeleteTitledeleteDescriptiondeleteConfirmresolvedByshowThreadhideThreadmoreRepliesrepliesemojiNamesreactionsyouothersreactedWithreactionLabeltypingemptyTitleemptyDescriptionloadingerrorTitleannounceSentannounceReplyannounceCommentannounceDeletedannounceEditedannounceResolvedannounceReopenedannounceLinkCopiedlocaleBuilt from17 components
Keyboardcomposer, mentions, threads, feed, versions, triage
Keyboard
| Where | Keys | What they do |
|---|---|---|
| Any composer | ⌘/Ctrl + Enter | Send |
| Esc | Cancel an edit or a reply | |
| ↑ in an empty composer | Edit your last comment | |
| ⌘/Ctrl + B, I, E | Bold, italic, code | |
| @ | Mention someone: ↑ ↓ to choose, Enter or Tab to insert, Esc to close | |
| Doc comments | ⌘⌥M / Ctrl+Alt+M | Comment on the selected words |
| Alt + ↑ / ↓ | Previous or next thread | |
| Enter on a highlight | Open its thread and go to it | |
| Activity feed | PageUp / PageDown | Previous or next row |
| Ctrl + Home / End | Leave the feed before its start or after its end | |
| Version history | ↑ ↓, Home, End in the list | Pick a version |
| Alt + ↑ / ↓ | Previous or next change | |
| Inbox | ↑ ↓ or J K, Home, End | Move between notifications |
| Enter | Open (and mark read) | |
| E, S, R, U | Done, snooze, reply, read or unread |
Accessibilityarticles, feeds, live regions, motion
Accessibility
Comments are articles named by their author and described by their time, in lists. The composer is a labelled textarea whose shortcuts are its description; the mention list is a listbox the field drives with aria-activedescendant, so focus never leaves the text, and the number of matches is announced. Reactions are toggle buttons (aria-pressed) named with the emoji's name, the count and who reacted. Sent, edited, deleted and resolved comments, new replies and failures are announced; failures use role="alert".
In Doc comments each highlight is a button in the text, named by its words and described by its comments, one tab stop per thread; the rail is a list in document order, each thread a group named by its quote.
The activity feed is a WAI-ARIA feed of articles with aria-posinset and aria-setsize (−1 while older events can load), busy while they load, under a heading per day. Arrivals are announced once, whether they come in at once or wait behind the pill.
The inbox's rows are one tab stop between them (the current row's tools join it), named by their sentence with "Unread" first when they are; its keys are listed in each tool's aria-keyshortcuts.
The version list is a listbox with groups per day; changes are real ins and del elements that say "Added" and "Removed" to a screen reader, each a stop for Previous and Next change.
The support chat is an aside named for the product. On desktop the panel is a popover, on phones a full-screen sheet; Escape closes it and focus returns to the launcher. A polite role="status" line reports the header's status.
The voice call's orb is an image named by the agent and its state, and state changes are announced politely; the clock is a role="timer", Mute is a toggle with aria-pressed, the volume is a labeled slider and the voice level a meter, End call and Call again are named buttons, and the caption line is a polite live region. Muting grays the orb and empties the meter while the agent listens.
The survey card is a labelled region that does not steal focus when it appears, and focus returns to the page when it leaves. The 0 to 10 scale is one radio group: each score is named by its number, the arrow keys move through it. The feedback widget's type is a labelled segmented control, the message sends with Cmd or Ctrl and Enter.
Under reduced motion new rows and comments appear in place, threads fold instantly, reactions don't pop and the rail's cards jump to their places.
Under reduced motion cards appear in place and replies arrive whole.