@tldraw/commenting 0.0.0-bootstrap

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.
Files changed (242) hide show
  1. package/README.md +11 -0
  2. package/commenting.css +832 -0
  3. package/dist-cjs/canvas/cluster-input.js +40 -0
  4. package/dist-cjs/canvas/cluster-input.js.map +7 -0
  5. package/dist-cjs/canvas/comment-body.js +31 -0
  6. package/dist-cjs/canvas/comment-body.js.map +7 -0
  7. package/dist-cjs/canvas/comment-render.js +72 -0
  8. package/dist-cjs/canvas/comment-render.js.map +7 -0
  9. package/dist-cjs/canvas/comment-store.js +48 -0
  10. package/dist-cjs/canvas/comment-store.js.map +7 -0
  11. package/dist-cjs/canvas/comment-tool.js +160 -0
  12. package/dist-cjs/canvas/comment-tool.js.map +7 -0
  13. package/dist-cjs/canvas/comments-filter-menu.js +109 -0
  14. package/dist-cjs/canvas/comments-filter-menu.js.map +7 -0
  15. package/dist-cjs/canvas/comments-menu-item.js +42 -0
  16. package/dist-cjs/canvas/comments-menu-item.js.map +7 -0
  17. package/dist-cjs/canvas/comments-overflow-menu.js +64 -0
  18. package/dist-cjs/canvas/comments-overflow-menu.js.map +7 -0
  19. package/dist-cjs/canvas/comments-overlay.js +1081 -0
  20. package/dist-cjs/canvas/comments-overlay.js.map +7 -0
  21. package/dist-cjs/canvas/comments-sidebar.js +134 -0
  22. package/dist-cjs/canvas/comments-sidebar.js.map +7 -0
  23. package/dist-cjs/canvas/hooks.js +45 -0
  24. package/dist-cjs/canvas/hooks.js.map +7 -0
  25. package/dist-cjs/canvas/license.js +28 -0
  26. package/dist-cjs/canvas/license.js.map +7 -0
  27. package/dist-cjs/canvas/options.js +45 -0
  28. package/dist-cjs/canvas/options.js.map +7 -0
  29. package/dist-cjs/canvas/region-options.js +49 -0
  30. package/dist-cjs/canvas/region-options.js.map +7 -0
  31. package/dist-cjs/canvas/rich-text.js +28 -0
  32. package/dist-cjs/canvas/rich-text.js.map +7 -0
  33. package/dist-cjs/canvas/sidebar-filters.js +30 -0
  34. package/dist-cjs/canvas/sidebar-filters.js.map +7 -0
  35. package/dist-cjs/canvas/state.js +76 -0
  36. package/dist-cjs/canvas/state.js.map +7 -0
  37. package/dist-cjs/canvas/thread-state.js +81 -0
  38. package/dist-cjs/canvas/thread-state.js.map +7 -0
  39. package/dist-cjs/clustering/computeClusterTable.js +83 -0
  40. package/dist-cjs/clustering/computeClusterTable.js.map +7 -0
  41. package/dist-cjs/clustering/mst.js +117 -0
  42. package/dist-cjs/clustering/mst.js.map +7 -0
  43. package/dist-cjs/clustering/replay.js +275 -0
  44. package/dist-cjs/clustering/replay.js.map +7 -0
  45. package/dist-cjs/clustering/runtime.js +134 -0
  46. package/dist-cjs/clustering/runtime.js.map +7 -0
  47. package/dist-cjs/clustering/schedule.js +170 -0
  48. package/dist-cjs/clustering/schedule.js.map +7 -0
  49. package/dist-cjs/clustering/types.js +17 -0
  50. package/dist-cjs/clustering/types.js.map +7 -0
  51. package/dist-cjs/index.d.ts +868 -0
  52. package/dist-cjs/index.js +126 -0
  53. package/dist-cjs/index.js.map +7 -0
  54. package/dist-cjs/ui/avatar.js +43 -0
  55. package/dist-cjs/ui/avatar.js.map +7 -0
  56. package/dist-cjs/ui/byline.js +35 -0
  57. package/dist-cjs/ui/byline.js.map +7 -0
  58. package/dist-cjs/ui/comment-card.js +37 -0
  59. package/dist-cjs/ui/comment-card.js.map +7 -0
  60. package/dist-cjs/ui/comment-composer.js +136 -0
  61. package/dist-cjs/ui/comment-composer.js.map +7 -0
  62. package/dist-cjs/ui/comment-extensions.js +37 -0
  63. package/dist-cjs/ui/comment-extensions.js.map +7 -0
  64. package/dist-cjs/ui/comment-mention.js +42 -0
  65. package/dist-cjs/ui/comment-mention.js.map +7 -0
  66. package/dist-cjs/ui/comment-pin.js +29 -0
  67. package/dist-cjs/ui/comment-pin.js.map +7 -0
  68. package/dist-cjs/ui/comment-text.js +29 -0
  69. package/dist-cjs/ui/comment-text.js.map +7 -0
  70. package/dist-cjs/ui/comment-thread.js +45 -0
  71. package/dist-cjs/ui/comment-thread.js.map +7 -0
  72. package/dist-cjs/ui/comments-list.js +106 -0
  73. package/dist-cjs/ui/comments-list.js.map +7 -0
  74. package/dist-cjs/ui/count-badge.js +28 -0
  75. package/dist-cjs/ui/count-badge.js.map +7 -0
  76. package/dist-cjs/ui/empty-state.js +31 -0
  77. package/dist-cjs/ui/empty-state.js.map +7 -0
  78. package/dist-cjs/ui/format-time.js +46 -0
  79. package/dist-cjs/ui/format-time.js.map +7 -0
  80. package/dist-cjs/ui/mention-list.js +70 -0
  81. package/dist-cjs/ui/mention-list.js.map +7 -0
  82. package/dist-cjs/ui/mention-suggestion.js +206 -0
  83. package/dist-cjs/ui/mention-suggestion.js.map +7 -0
  84. package/dist-cjs/ui/mention.js +31 -0
  85. package/dist-cjs/ui/mention.js.map +7 -0
  86. package/dist-cjs/ui/reaction.js +31 -0
  87. package/dist-cjs/ui/reaction.js.map +7 -0
  88. package/dist-cjs/ui/reactions.js +34 -0
  89. package/dist-cjs/ui/reactions.js.map +7 -0
  90. package/dist-cjs/ui/render-markdown.js +63 -0
  91. package/dist-cjs/ui/render-markdown.js.map +7 -0
  92. package/dist-cjs/ui/send-button.js +28 -0
  93. package/dist-cjs/ui/send-button.js.map +7 -0
  94. package/dist-esm/canvas/cluster-input.mjs +20 -0
  95. package/dist-esm/canvas/cluster-input.mjs.map +7 -0
  96. package/dist-esm/canvas/comment-body.mjs +11 -0
  97. package/dist-esm/canvas/comment-body.mjs.map +7 -0
  98. package/dist-esm/canvas/comment-render.mjs +52 -0
  99. package/dist-esm/canvas/comment-render.mjs.map +7 -0
  100. package/dist-esm/canvas/comment-store.mjs +28 -0
  101. package/dist-esm/canvas/comment-store.mjs.map +7 -0
  102. package/dist-esm/canvas/comment-tool.mjs +142 -0
  103. package/dist-esm/canvas/comment-tool.mjs.map +7 -0
  104. package/dist-esm/canvas/comments-filter-menu.mjs +99 -0
  105. package/dist-esm/canvas/comments-filter-menu.mjs.map +7 -0
  106. package/dist-esm/canvas/comments-menu-item.mjs +22 -0
  107. package/dist-esm/canvas/comments-menu-item.mjs.map +7 -0
  108. package/dist-esm/canvas/comments-overflow-menu.mjs +53 -0
  109. package/dist-esm/canvas/comments-overflow-menu.mjs.map +7 -0
  110. package/dist-esm/canvas/comments-overlay.mjs +1094 -0
  111. package/dist-esm/canvas/comments-overlay.mjs.map +7 -0
  112. package/dist-esm/canvas/comments-sidebar.mjs +120 -0
  113. package/dist-esm/canvas/comments-sidebar.mjs.map +7 -0
  114. package/dist-esm/canvas/hooks.mjs +25 -0
  115. package/dist-esm/canvas/hooks.mjs.map +7 -0
  116. package/dist-esm/canvas/license.mjs +8 -0
  117. package/dist-esm/canvas/license.mjs.map +7 -0
  118. package/dist-esm/canvas/options.mjs +27 -0
  119. package/dist-esm/canvas/options.mjs.map +7 -0
  120. package/dist-esm/canvas/region-options.mjs +29 -0
  121. package/dist-esm/canvas/region-options.mjs.map +7 -0
  122. package/dist-esm/canvas/rich-text.mjs +8 -0
  123. package/dist-esm/canvas/rich-text.mjs.map +7 -0
  124. package/dist-esm/canvas/sidebar-filters.mjs +10 -0
  125. package/dist-esm/canvas/sidebar-filters.mjs.map +7 -0
  126. package/dist-esm/canvas/state.mjs +60 -0
  127. package/dist-esm/canvas/state.mjs.map +7 -0
  128. package/dist-esm/canvas/thread-state.mjs +61 -0
  129. package/dist-esm/canvas/thread-state.mjs.map +7 -0
  130. package/dist-esm/clustering/computeClusterTable.mjs +63 -0
  131. package/dist-esm/clustering/computeClusterTable.mjs.map +7 -0
  132. package/dist-esm/clustering/mst.mjs +97 -0
  133. package/dist-esm/clustering/mst.mjs.map +7 -0
  134. package/dist-esm/clustering/replay.mjs +255 -0
  135. package/dist-esm/clustering/replay.mjs.map +7 -0
  136. package/dist-esm/clustering/runtime.mjs +114 -0
  137. package/dist-esm/clustering/runtime.mjs.map +7 -0
  138. package/dist-esm/clustering/schedule.mjs +150 -0
  139. package/dist-esm/clustering/schedule.mjs.map +7 -0
  140. package/dist-esm/clustering/types.mjs +1 -0
  141. package/dist-esm/clustering/types.mjs.map +7 -0
  142. package/dist-esm/index.d.mts +868 -0
  143. package/dist-esm/index.mjs +139 -0
  144. package/dist-esm/index.mjs.map +7 -0
  145. package/dist-esm/ui/avatar.mjs +23 -0
  146. package/dist-esm/ui/avatar.mjs.map +7 -0
  147. package/dist-esm/ui/byline.mjs +15 -0
  148. package/dist-esm/ui/byline.mjs.map +7 -0
  149. package/dist-esm/ui/comment-card.mjs +17 -0
  150. package/dist-esm/ui/comment-card.mjs.map +7 -0
  151. package/dist-esm/ui/comment-composer.mjs +116 -0
  152. package/dist-esm/ui/comment-composer.mjs.map +7 -0
  153. package/dist-esm/ui/comment-extensions.mjs +17 -0
  154. package/dist-esm/ui/comment-extensions.mjs.map +7 -0
  155. package/dist-esm/ui/comment-mention.mjs +22 -0
  156. package/dist-esm/ui/comment-mention.mjs.map +7 -0
  157. package/dist-esm/ui/comment-pin.mjs +9 -0
  158. package/dist-esm/ui/comment-pin.mjs.map +7 -0
  159. package/dist-esm/ui/comment-text.mjs +9 -0
  160. package/dist-esm/ui/comment-text.mjs.map +7 -0
  161. package/dist-esm/ui/comment-thread.mjs +25 -0
  162. package/dist-esm/ui/comment-thread.mjs.map +7 -0
  163. package/dist-esm/ui/comments-list.mjs +86 -0
  164. package/dist-esm/ui/comments-list.mjs.map +7 -0
  165. package/dist-esm/ui/count-badge.mjs +8 -0
  166. package/dist-esm/ui/count-badge.mjs.map +7 -0
  167. package/dist-esm/ui/empty-state.mjs +11 -0
  168. package/dist-esm/ui/empty-state.mjs.map +7 -0
  169. package/dist-esm/ui/format-time.mjs +26 -0
  170. package/dist-esm/ui/format-time.mjs.map +7 -0
  171. package/dist-esm/ui/mention-list.mjs +50 -0
  172. package/dist-esm/ui/mention-list.mjs.map +7 -0
  173. package/dist-esm/ui/mention-suggestion.mjs +186 -0
  174. package/dist-esm/ui/mention-suggestion.mjs.map +7 -0
  175. package/dist-esm/ui/mention.mjs +11 -0
  176. package/dist-esm/ui/mention.mjs.map +7 -0
  177. package/dist-esm/ui/reaction.mjs +11 -0
  178. package/dist-esm/ui/reaction.mjs.map +7 -0
  179. package/dist-esm/ui/reactions.mjs +14 -0
  180. package/dist-esm/ui/reactions.mjs.map +7 -0
  181. package/dist-esm/ui/render-markdown.mjs +45 -0
  182. package/dist-esm/ui/render-markdown.mjs.map +7 -0
  183. package/dist-esm/ui/send-button.mjs +8 -0
  184. package/dist-esm/ui/send-button.mjs.map +7 -0
  185. package/package.json +71 -0
  186. package/src/canvas/canvas.css +148 -0
  187. package/src/canvas/cluster-input.test.ts +223 -0
  188. package/src/canvas/cluster-input.ts +29 -0
  189. package/src/canvas/comment-body.tsx +21 -0
  190. package/src/canvas/comment-render.test.ts +90 -0
  191. package/src/canvas/comment-render.ts +92 -0
  192. package/src/canvas/comment-store.ts +55 -0
  193. package/src/canvas/comment-tool.tsx +200 -0
  194. package/src/canvas/comments-filter-menu.tsx +104 -0
  195. package/src/canvas/comments-menu-item.tsx +24 -0
  196. package/src/canvas/comments-overflow-menu.tsx +62 -0
  197. package/src/canvas/comments-overlay.tsx +1460 -0
  198. package/src/canvas/comments-sidebar.tsx +184 -0
  199. package/src/canvas/hooks.ts +28 -0
  200. package/src/canvas/license.ts +15 -0
  201. package/src/canvas/options.test.ts +127 -0
  202. package/src/canvas/options.ts +111 -0
  203. package/src/canvas/region-options.ts +57 -0
  204. package/src/canvas/rich-text.ts +15 -0
  205. package/src/canvas/sidebar-filters.ts +25 -0
  206. package/src/canvas/state.ts +102 -0
  207. package/src/canvas/thread-state.ts +91 -0
  208. package/src/clustering/computeClusterTable.test.ts +299 -0
  209. package/src/clustering/computeClusterTable.ts +82 -0
  210. package/src/clustering/locality.test.ts +189 -0
  211. package/src/clustering/mst.test.ts +383 -0
  212. package/src/clustering/mst.ts +134 -0
  213. package/src/clustering/replay.test.ts +547 -0
  214. package/src/clustering/replay.ts +326 -0
  215. package/src/clustering/runtime.test.ts +596 -0
  216. package/src/clustering/runtime.ts +153 -0
  217. package/src/clustering/schedule.test.ts +514 -0
  218. package/src/clustering/schedule.ts +184 -0
  219. package/src/clustering/types.ts +102 -0
  220. package/src/index.ts +96 -0
  221. package/src/ui/avatar.tsx +31 -0
  222. package/src/ui/byline.tsx +23 -0
  223. package/src/ui/comment-card.tsx +32 -0
  224. package/src/ui/comment-composer.tsx +179 -0
  225. package/src/ui/comment-extensions.ts +25 -0
  226. package/src/ui/comment-mention.ts +47 -0
  227. package/src/ui/comment-pin.tsx +21 -0
  228. package/src/ui/comment-text.tsx +12 -0
  229. package/src/ui/comment-thread.tsx +56 -0
  230. package/src/ui/comments-list.tsx +139 -0
  231. package/src/ui/comments.css +678 -0
  232. package/src/ui/count-badge.tsx +9 -0
  233. package/src/ui/empty-state.tsx +16 -0
  234. package/src/ui/format-time.ts +29 -0
  235. package/src/ui/mention-list.tsx +106 -0
  236. package/src/ui/mention-suggestion.test.ts +18 -0
  237. package/src/ui/mention-suggestion.tsx +285 -0
  238. package/src/ui/mention.tsx +9 -0
  239. package/src/ui/reaction.tsx +16 -0
  240. package/src/ui/reactions.tsx +15 -0
  241. package/src/ui/render-markdown.tsx +72 -0
  242. package/src/ui/send-button.tsx +15 -0
@@ -0,0 +1,47 @@
1
+ import { mergeAttributes } from '@tiptap/core'
2
+ import { Mention, type MentionNodeAttrs, type MentionOptions } from '@tiptap/extension-mention'
3
+
4
+ /**
5
+ * A mention node's display name: the member's current name, resolved live from its id when a
6
+ * resolver is given (so a rename shows through), falling back to the label captured at insert time
7
+ * when no source can name the id — e.g. a deleted account. The stored id is always the source of
8
+ * truth; live resolution only freshens the displayed name over that captured label.
9
+ */
10
+ function mentionName(
11
+ attrs: MentionNodeAttrs,
12
+ resolveName?: (id: string) => string | undefined
13
+ ): string {
14
+ const resolved = attrs.id && resolveName ? resolveName(attrs.id) : undefined
15
+ return resolved ?? attrs.label ?? attrs.id ?? ''
16
+ }
17
+
18
+ export interface CommentMentionOptions {
19
+ /**
20
+ * Resolve a member id to its current display name — for the read-only render paths. Returns
21
+ * `undefined` when the id can't be resolved, so the render falls back to the mention's stored label.
22
+ */
23
+ resolveName?(id: string): string | undefined
24
+ /** The `@`-picker suggestion config — for the composer (omit on the render paths). */
25
+ suggestion?: MentionOptions['suggestion']
26
+ }
27
+
28
+ /**
29
+ * The comment @-mention node — TipTap's `Mention` configured to render as a `.cmt-mention` pill.
30
+ *
31
+ * A factory rather than a shared constant because it's configured differently per context: the
32
+ * read-only render paths pass `resolveName` (so the stored `{ id }` node always shows the member's
33
+ * current name, not a copy frozen at insert time), while the composer passes a `suggestion` (the
34
+ * `@` picker). The node schema is identical either way — only these two levers differ.
35
+ */
36
+ export function commentMention({ resolveName, suggestion }: CommentMentionOptions = {}) {
37
+ return Mention.configure({
38
+ HTMLAttributes: { class: 'cmt-mention' },
39
+ renderText: ({ node }) => `@${mentionName(node.attrs as MentionNodeAttrs, resolveName)}`,
40
+ renderHTML: ({ node, options }) => [
41
+ 'span',
42
+ mergeAttributes(options.HTMLAttributes, { 'data-mention-id': node.attrs.id }),
43
+ `@${mentionName(node.attrs as MentionNodeAttrs, resolveName)}`,
44
+ ],
45
+ ...(suggestion ? { suggestion } : {}),
46
+ })
47
+ }
@@ -0,0 +1,21 @@
1
+ import { ReactNode } from 'react'
2
+
3
+ /** @public */
4
+ export interface CommentPinProps {
5
+ /** What the pin shows when unresolved — a number, an author initial, an <Avatar>, etc.
6
+ * Left as a lever so consumers aren't locked into a count. */
7
+ children?: ReactNode
8
+ resolved?: boolean
9
+ /** The pin's thread is open — shows the active/selected indicator state. */
10
+ open?: boolean
11
+ }
12
+
13
+ /** A canvas comment marker: shows its `children` (or a check when resolved). Purely
14
+ * presentational — it reflects open/resolved state via CSS; wrap it to make it clickable.
15
+ * @public @react */
16
+ export function CommentPin({ children, resolved, open }: CommentPinProps) {
17
+ const className = ['cmt-pin', resolved && 'cmt-pin--resolved', open && 'cmt-pin--open']
18
+ .filter(Boolean)
19
+ .join(' ')
20
+ return <div className={className}>{resolved ? '✓' : children}</div>
21
+ }
@@ -0,0 +1,12 @@
1
+ import { renderMarkdown } from './render-markdown'
2
+
3
+ /** @public */
4
+ export interface CommentTextProps {
5
+ /** Rich text body, authored as markdown. */
6
+ text: string
7
+ }
8
+
9
+ /** A comment's body, rendered as markdown (bold, italic, code, links, lists). @public @react */
10
+ export function CommentText({ text }: CommentTextProps) {
11
+ return <div className="cmt-text">{renderMarkdown(text)}</div>
12
+ }
@@ -0,0 +1,56 @@
1
+ import { ReactNode } from 'react'
2
+ import { CommentCard, CommentCardProps } from './comment-card'
3
+ import { CommentComposer, CommentComposerProps } from './comment-composer'
4
+
5
+ /** @public */
6
+ export interface CommentThreadProps {
7
+ /** The thread's comments, already adapted to card props (oldest first). */
8
+ comments: CommentCardProps[]
9
+ /** Optional header, e.g. "Thread". Omit for no header. */
10
+ header?: ReactNode
11
+ /** Action controls shown at the right of the header (resolve, delete, dismiss…). */
12
+ headerActions?: ReactNode
13
+ /** When set, shows a banner above the comments (e.g. "Resolved by Jess"). */
14
+ resolvedBanner?: ReactNode
15
+ /** Reply composer props. Omit for a read-only thread (no composer). */
16
+ composer?: CommentComposerProps
17
+ /** Override how each comment renders. Defaults to `<CommentCard>`. */
18
+ renderComment?(comment: CommentCardProps, index: number): ReactNode
19
+ }
20
+
21
+ /**
22
+ * A comment thread: an optional header, an optional resolved banner, the comments, and an
23
+ * optional reply composer. Presentational — you supply the comments (as card props) and the
24
+ * composer's handlers; how each comment renders is overridable via `renderComment`.
25
+ * @public @react
26
+ */
27
+ export function CommentThread({
28
+ comments,
29
+ header,
30
+ headerActions,
31
+ resolvedBanner,
32
+ composer,
33
+ renderComment,
34
+ }: CommentThreadProps) {
35
+ return (
36
+ <div className="cmt-thread">
37
+ {(header !== undefined || headerActions !== undefined) && (
38
+ <div className="cmt-thread__header">
39
+ <span className="cmt-thread__title">{header}</span>
40
+ {headerActions !== undefined && (
41
+ <div className="cmt-thread__actions">{headerActions}</div>
42
+ )}
43
+ </div>
44
+ )}
45
+ {resolvedBanner !== undefined && <div className="cmt-thread__resolved">{resolvedBanner}</div>}
46
+ <div className="cmt-thread__list">
47
+ {comments.map((comment, i) => (
48
+ <div key={i}>
49
+ {renderComment ? renderComment(comment, i) : <CommentCard {...comment} />}
50
+ </div>
51
+ ))}
52
+ </div>
53
+ {composer !== undefined && <CommentComposer {...composer} />}
54
+ </div>
55
+ )
56
+ }
@@ -0,0 +1,139 @@
1
+ import { ReactNode } from 'react'
2
+ import { Avatar } from './avatar'
3
+ import { Byline } from './byline'
4
+
5
+ /** @public */
6
+ export interface CommentListItemProps {
7
+ id: string
8
+ author: string
9
+ /** A short preview of the thread — e.g. the first comment's body. */
10
+ preview: ReactNode
11
+ /** ISO datetime of the thread's first comment. */
12
+ date: string
13
+ resolved?: boolean
14
+ /** Name of the page the thread lives on, shown as a small label. Omit to hide. */
15
+ page?: string
16
+ /** Total comments in the thread. */
17
+ count?: number
18
+ /** Whether this thread is the open one. */
19
+ selected?: boolean
20
+ }
21
+
22
+ /** @public */
23
+ export interface CommentsListProps {
24
+ items: CommentListItemProps[]
25
+ /** Called with a thread id when an item is chosen. */
26
+ onSelect?(id: string): void
27
+ /** Shown above the list (e.g. "Comments"). Omit for none. */
28
+ header?: ReactNode
29
+ /** Rendered at the right of the header row — e.g. a filter menu. */
30
+ headerAction?: ReactNode
31
+ /** Shown in place of the list when there are no threads. */
32
+ empty?: ReactNode
33
+ /** Label for a resolved thread's marker on its row. Defaults to "Resolved". */
34
+ resolvedLabel?: string
35
+ /** Override how each item renders. Defaults to `<CommentListItem>`. */
36
+ renderItem?(item: CommentListItemProps): ReactNode
37
+ }
38
+
39
+ /**
40
+ * A scrollable list of comment threads — each an avatar, byline, and a one-line preview.
41
+ * Presentational: you supply the items (already summarised) and an `onSelect` handler; the canvas
42
+ * `CanvasCommentsSidebar` wires it to the store, but a consumer can build their own list from this.
43
+ * @public @react
44
+ */
45
+ export function CommentsList({
46
+ items,
47
+ onSelect,
48
+ header,
49
+ headerAction,
50
+ empty,
51
+ resolvedLabel,
52
+ renderItem,
53
+ }: CommentsListProps) {
54
+ return (
55
+ <div className="cmt-list">
56
+ {(header !== undefined || headerAction) && (
57
+ <div className="cmt-list__header">
58
+ {header !== undefined && <span className="cmt-list__header-title">{header}</span>}
59
+ {headerAction}
60
+ </div>
61
+ )}
62
+ {items.length === 0 ? (
63
+ <div className="cmt-list__empty">{empty}</div>
64
+ ) : (
65
+ <div className="cmt-list__items">
66
+ {items.map((item) =>
67
+ renderItem ? (
68
+ renderItem(item)
69
+ ) : (
70
+ <CommentListItem
71
+ key={item.id}
72
+ {...item}
73
+ resolvedLabel={resolvedLabel}
74
+ onSelect={onSelect}
75
+ />
76
+ )
77
+ )}
78
+ </div>
79
+ )}
80
+ </div>
81
+ )
82
+ }
83
+
84
+ function CommentListItem({
85
+ id,
86
+ author,
87
+ preview,
88
+ date,
89
+ resolved,
90
+ page,
91
+ count,
92
+ selected,
93
+ resolvedLabel = 'Resolved',
94
+ onSelect,
95
+ }: CommentListItemProps & { resolvedLabel?: string; onSelect?(id: string): void }) {
96
+ const handleClick = () => {
97
+ if (onSelect) onSelect(id)
98
+ }
99
+ return (
100
+ <button
101
+ type="button"
102
+ className={selected ? 'cmt-list__item cmt-list__item--selected' : 'cmt-list__item'}
103
+ data-resolved={resolved || undefined}
104
+ onClick={handleClick}
105
+ >
106
+ <Avatar name={author} />
107
+ <div className="cmt-list__item-body">
108
+ <Byline author={author} date={date} />
109
+ <div className="cmt-list__item-preview">{preview}</div>
110
+ {(resolved || page !== undefined) && (
111
+ <div className="cmt-list__item-meta">
112
+ {resolved && (
113
+ <span className="cmt-list__item-resolved">
114
+ <CheckIcon />
115
+ {resolvedLabel}
116
+ </span>
117
+ )}
118
+ {page !== undefined && <span className="cmt-list__item-page">{page}</span>}
119
+ </div>
120
+ )}
121
+ </div>
122
+ {count !== undefined && count > 1 && <span className="cmt-list__item-count">{count}</span>}
123
+ </button>
124
+ )
125
+ }
126
+
127
+ function CheckIcon() {
128
+ return (
129
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
130
+ <path
131
+ d="M2.5 6.2 4.7 8.4 9.5 3.6"
132
+ stroke="currentColor"
133
+ strokeWidth="1.5"
134
+ strokeLinecap="round"
135
+ strokeLinejoin="round"
136
+ />
137
+ </svg>
138
+ )
139
+ }