CommentsPanel
Threaded record comments with markdown bodies, @ mentions, file attachments, a pending placeholder and author resolution against a users list.
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-comments-panelpnpm add @shopify/flash-list expo-document-picker (optional) expo-image-picker (optional)The attach button offers Files when expo-document-picker is installed and Photo library / Camera when expo-image-picker is installed. With neither, the button is hidden.
Usage
import { CommentsPanel } from '@/components/docyrus-native/comments-panel';
<CommentsPanel
comments={comments}
users={users}
currentUser={currentUser}
maxHeight={420}
isCreatePending={createMutation.isPending}
isDeletePending={deleteMutation.isPending}
onCreateComment={({ message, parentId, attachments, mentions }) =>
createMutation.mutateAsync({ message, parentId, attachments, mentions })}
onUpdateComment={(id, message, mentions) => updateComment(id, message, mentions)}
onDeleteComment={id => deleteMutation.mutateAsync(id)} />useDocyrusLogActivity and useDocyrusContactActivity return comments in exactly this shape (created_by ids + a users list), so their output can be spread straight into the panel.
Mentions
Typing @ opens a list of matching users (max 8) above the input. Picking one inserts @Name into the text; on submit it is stored as the same markdown token web uses, [@Name](mention://<userId>), and the ids are passed as mentions. Stored tokens render as highlighted chips. A native TextInput cannot show inline chips, so while editing they appear as plain @Name text.
Actions
Tap Reply under a comment to answer it. Long-press a comment, or tap its ⋯ button, to open an action sheet with Reply, Edit and Delete. Only the author sees Edit and Delete. Delete asks for confirmation through DeleteConfirmDialog.
API Reference
CommentsPanelProps
| Prop | Type | Default | Description |
|---|---|---|---|
comments | DocyrusComment[] | - | Comments of the record. Top-level comments and their replies (one level) are sorted by created_on |
currentUser | CommentUser | - | Signed-in user. Used for ownership (edit / delete) and the composer avatar |
users | CommentUser[] | - | Users used to resolve created_by and to offer @ mentions |
title | string | 'Comments' | Header title |
editable | boolean | true | Show the composer and the Reply affordance |
isLoading | boolean | false | Show the loading skeleton |
maxHeight | number | - | Max list height in dp. When set, the list is a virtualised, scrollable FlashList that auto-scrolls to the newest comment. When omitted, the panel grows with its content |
onCreateComment | (payload: CommentCreatePayload) => void | Promise<void> | - | Create a comment or reply. The composer and Reply affordance are hidden without it |
onUpdateComment | (commentId: string, message: string, mentions?: string[]) => void | Promise<void> | - | Save an edited comment. Enables the Edit action on own comments |
onDeleteComment | (commentId: string) => void | Promise<void> | - | Delete a comment after confirmation. Enables the Delete action on own comments |
onUploadFile | (file: NativeFile) => Promise<CommentAttachment> | - | Accepted for API parity with web. The panel passes picked files through onCreateComment (attachments) |
isCreatePending | boolean | false | Disables the composer and shows a pending-comment placeholder row |
isDeletePending | boolean | false | Pending state of the delete confirmation |
enableRichFormatting | boolean | true | Offer the formatting toolbar toggle (bold, italic, bulleted list inserted as markdown) |
defaultRichFormatting | boolean | false | Initial toolbar visibility when uncontrolled |
richFormatting | boolean | - | Controlled toolbar visibility |
onRichFormattingChange | (rich: boolean) => void | - | Toolbar toggle callback |
enableEmoji | boolean | true | Accepted for API parity. Native uses the system emoji keyboard |
enableMentions | boolean | true | Enable @ mention autocomplete (needs users) |
enableAttachments | boolean | true | Show the attach button (needs an installed picker) |
className | string | - | Container classes |
CommentCreatePayload
| Field | Type | Description |
|---|---|---|
message | string | Markdown body. Mentions are stored as [@Name](mention://<userId>) |
parentId | string | Parent comment id for replies |
attachments | NativeFile[] | Picked files ({ uri, name, type, size? }). Upload them with RN FormData |
mentions | string[] | Unique mentioned user ids |
DocyrusComment
| Field | Type | Description |
|---|---|---|
id | string | Comment id |
message | string | Markdown body |
attachments | CommentAttachment[] | null | Attached files |
created_on | string | ISO timestamp |
created_by | string | Author user id, resolved against users |
last_modified_on | string | null | Shows "(edited)" when different from created_on |
record_id | string | null | Owning record |
parent_id | string | null | Parent comment for replies (null for top level) |
CommentUser
| Field | Type | Description |
|---|---|---|
id | string | User id |
firstname | string | null | First name |
lastname | string | null | Last name |
photo | string | null | Avatar URL |
CommentAttachment
| Field | Type | Description |
|---|---|---|
id | string | Attachment id |
file_name | string | File name |
file_type | string | null | MIME type. image/* with a signed_url renders a thumbnail |
file_size | number | null | Size in bytes |
signed_url | string | null | Tapping the attachment opens it with Linking.openURL |
Components
| Component | Description |
|---|---|
CommentsPanel | Header, threaded list, composer, action sheet and delete confirmation |
Type Exports
| Type | Description |
|---|---|
CommentsPanelProps | Props for CommentsPanel |
DocyrusComment | Comment row (Docyrus API shape) |
CommentUser | User used for authors and mentions |
CommentAttachment | File attached to a comment |
CommentThread | { comment, replies } thread built from comments |
CommentCreatePayload | Payload of onCreateComment |
Breaking changes
The panel now uses the web / Docyrus API data model.
Commentwas renamed toDocyrusComment. The embeddeduser: CommentUserwas replaced bycreated_by: string, which is resolved against the newusersprop.CommentUserchanged from{ id, name, avatarUrl? }to{ id, firstname?, lastname?, photo? }.- Replies are one level deep, like web: the recursive tree is gone, and a comment whose parent is itself a reply is not rendered. Top-level comments and replies are sorted by
created_on. onCreateCommentreceives{ message, parentId?, attachments?, mentions? }, andonUpdateCommentreceives a thirdmentionsargument.- Delete uses
DeleteConfirmDialoginstead ofAlert.alert. - The panel no longer draws its own card (
rounded-[28px] bg-card). Wrap it in aCardor passclassName.