Skip to content

@anchor-sdk/ui

Pre-built UI components with theming support. See Theming Guide.

Components

<AnchorDiscussion>

All-in-one discussion component. Integrates Anchor + CommentButton + ThreadPopover with full feature support.

vue
<AnchorDiscussion
  anchor-id="order-123"
  :mention-users="teamMembers"
  :virtualize="true"
  :virtualize-threshold="50"
>
  <div>Order #123</div>
</AnchorDiscussion>
PropTypeRequiredDescription
anchorIdstringYesUnique anchor identifier
mentionUsersUser[] | () => User[] | Promise<User[]>NoUsers available for @mention autocomplete
virtualizebooleanNoEnable virtual scrolling (default: true)
virtualizeThresholdnumberNoMinimum row count before virtualization kicks in (default: 50)

Slot: Default slot for anchor content.

Features:

  • Comment button with message count and unread indicator
  • Thread resolve/reopen
  • Message edit/delete (own messages only)
  • Emoji reactions
  • Markdown editor with write/preview tabs and keyboard shortcuts (⌘B / ⌘I / ⌘K)
  • File & image attachments (when the adapter implements uploadAttachment)
  • @mention autocomplete with keyboard navigation
  • Virtual scrolling for long threads
  • Keyboard navigation (Esc to close, Enter to send)
  • Real-time updates (when adapter supports it)
  • Loading and error states
  • Optimistic updates with rollback
  • Accessible (ARIA roles, labels, keyboard support)

<CommentButton>

Floating comment button with optional count badge and unread indicator.

vue
<CommentButton :count="3" :unread="1" @click="handleClick" />
PropTypeRequiredDescription
countnumberNoComment count
unreadnumberNoUnread count (shows dot indicator)
EventDescription
clickButton clicked

Accessibility: The button includes a dynamic aria-label that announces the comment count and unread count to screen readers.

<ThreadPopover>

Full-featured discussion popover with error handling and loading states.

vue
<ThreadPopover
  :threads="threads"
  :reference-el="anchorEl"
  :current-user-id="userId"
  :loading="loading"
  :error="error?.message"
  @send="handleSend"
  @close="handleClose"
  @resolve="resolveThread"
  @reopen="reopenThread"
  @delete-thread="deleteThread"
  @edit-message="editMessage"
  @delete-message="deleteMessage"
  @add-reaction="addReaction"
  @remove-reaction="removeReaction"
/>
PropTypeRequiredDescription
threadsThread[]YesThreads to display
referenceElHTMLElement | nullYesPositioning reference
currentUserIdstringNoCurrent user ID (enables edit/delete)
loadingbooleanNoShow loading indicator
errorstringNoError message to display
EventPayloadDescription
sendcontent, options?New message (options may include attachments)
closeClose popover
resolvethreadIdResolve thread
reopenthreadIdReopen thread
deleteThreadthreadIdDelete thread
editMessagemessageId, contentEdit message
deleteMessagethreadId, messageIdDelete message
addReactionmessageId, emojiAdd reaction
removeReactionmessageId, emojiRemove reaction

Additional props for the low-level <ThreadPopover>:

PropTypeDescription
mentionUsersUser[] | () => User[] | Promise<User[]>Users available for @mention autocomplete
uploadAttachment(file: File) => Promise<Attachment>Pass through from useThreads to enable attachments
virtualizebooleanEnable virtual scrolling (default: true)
virtualizeThresholdnumberMinimum row count before virtualization (default: 50)

Accessibility: The popover uses role="dialog", all buttons have aria-label, the reaction picker uses role="listbox", and error/loading states use role="alert" and aria-live="polite".

Utilities

renderMarkdown(text)

Lightweight inline markdown renderer used by ThreadPopover. Supports bold, italic, inline code, strikethrough, links, and code blocks. All output is HTML-sanitized to prevent XSS.

ts
import { renderMarkdown } from '@anchor-sdk/ui'

renderMarkdown('**hello** *world*')
// → '<strong>hello</strong> <em>world</em>'

Supported syntax:

SyntaxOutput
**bold**<strong>bold</strong>
*italic*<em>italic</em>
`code`<code>code</code>
~~strike~~<del>strike</del>
[text](https://…)<a href="https://…" target="_blank" rel="noopener">text</a>
```block```<pre><code>block</code></pre>

Only http:// and https:// links are rendered — javascript: and other protocols are rejected.

<MarkdownEditor>

Textarea with write/preview tabs, a small formatting toolbar, and keyboard shortcuts. Used internally by ThreadPopover and exported for custom compositions.

vue
<MarkdownEditor v-model="text" placeholder="Comment..." :disabled="loading" @keydown="handleKey" />
PropTypeRequiredDescription
modelValuestringYesCurrent value
placeholderstringNoTextarea placeholder
disabledbooleanNoDisable all input
rowsnumberNoInitial textarea rows (default: 3)
EventPayloadDescription
update:modelValuestringTwo-way binding
inputInputEventRaw input event (with cursor info)
keydownKeyboardEventForwarded keydown (e.g. Enter/Escape)

Keyboard shortcuts: ⌘B / Ctrl+B wraps selection in **bold**, ⌘I in *italic*, ⌘K inserts a link.

Exposes: focus() and a textarea getter returning the underlying element for cursor-aware features like mentions.

<VirtualList>

Generic virtual-scrolling list with dynamic row heights (measured via ResizeObserver).

vue
<VirtualList :items="rows" :estimated-height="60" :overscan="6" :get-key="(row) => row.id">
  <template #default="{ item, index }">
    <MessageRow :row="item" :index="index" />
  </template>
</VirtualList>
PropTypeRequiredDescription
itemsT[]YesItems to render
estimatedHeightnumberNoDefault row height estimate (default: 80)
overscannumberNoExtra rows rendered above/below (default: 4)
getKey(item: T, index) => string | numberNoStable key per item (defaults to index)

Exposes: scrollToBottom(), scrollToIndex(index).