crucıble

Gloss

Threaded comments block for a doc, issue or PR page: an auto-growing composer with bold / code / link toolbar, caret-anchored @mention autocomplete painted as tokens right in the textarea and ⌘/Ctrl+Enter to send; optimistic posting that dims with a spinner, flashes the accent on success and offers Retry / Discard on failure; six-emoji reactions whose chips spring in and roll their counts on Tally drums (yours accent-outlined, hover lists who reacted); one-level replies that collapse to "Show N more replies"; resolvable threads that fold into a receipt line; inline edit, confirm-to-delete and copy-link actions on a roving toolbar; an "N new replies" pill for comments that arrive via ref.receive() while you're scrolled away; Oldest/Newest sort and a polite live region.

motionpro

Installation

CLI

npx shadcn@latest add @crucible/gloss

Manual — install dependencies, then copy the source

npm install motion

Props

PropTypeDefaultDescription
commentsGlossComment[]a seeded sample discussionInitial comment tree (the thread owns edits/reactions/posts afterwards and reports them through the callbacks). Passing a new array resets it.
peopleGlossPerson[]a seeded sample teamEveryone who can appear in the thread and in @ autocomplete.
currentUserGlossPersonthe first sample personWho "you" are — your comments get Edit / Delete and your reactions are accent-outlined.
onPost(draft: GlossDraft) => void | Promise<Partial<GlossComment> | void>New comment or reply. The comment appears immediately (optimistic, dimmed with a spinner). Resolve to confirm — optionally with server fields such as { id, createdAt } — or reject to show "Failed to send · Retry · Discard".
onEdit(comment: GlossComment, body: string) => void | Promise<unknown>Comment edited. Return a Promise to hold the editor in a saving state; reject keeps it open with an error.
onDelete(comment: GlossComment) => void | Promise<unknown>Comment deleted (after the confirm popover). Return a Promise to hold the confirm in a pending state.
onReact(comment: GlossComment, emoji: string, active: boolean) => void | Promise<unknown>Reaction toggled (active = you now reacted). Applied optimistically; reject reverts it.
onResolve(comment: GlossComment, resolved: boolean) => void | Promise<unknown>Thread resolved (true) or reopened (false). Applied optimistically; reject reverts it.
onCommentsChange(comments: GlossComment[]) => voidFired after every change with the full tree.
linkFor(comment: GlossComment) => stringcurrent page + #comment-<id>URL copied by "Copy link".
composerPosition"top" | "bottom""top"Where the main composer sits.
defaultSort"oldest" | "newest""oldest"Initial sort of top-level threads.
collapseAfternumber2Replies shown under a long thread before it collapses to "Show N more replies".
titlestring"Discussion"Header title.
placeholderstring"Add a comment…"Composer placeholder.
accentColorstring"#fdba74"The single accent: mentions, your reactions, focus rings, send button, new-comment flash. Any CSS color; hex gets auto-contrast ink.
refReact.Ref<GlossHandle>Imperative handle — receive(), focusComposer(), replay(), getComments().
classNamestringClasses for the root panel — give it a height (e.g. h-[560px]) to make the thread scroll inside.

Honors prefers-reduced-motion with a designed static fallback, and passes className through.