@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,200 @@
1
+ import {
2
+ BoxModel,
3
+ StateNode,
4
+ TLCommentAnchor,
5
+ TLStateNodeConstructor,
6
+ TLUiOverrides,
7
+ VecLike,
8
+ } from 'tldraw'
9
+ import { type CommentingOptions, defaultCommentingOptions } from './options'
10
+ import { getRegionCommentOptions } from './region-options'
11
+ import { pendingComment, regionDraft } from './state'
12
+ import { regionPinPoint, shapeAnchorAt } from './thread-state'
13
+
14
+ /** A comment being placed but not yet posted: where its composer sits and what it will anchor
15
+ * to. Shared between the tool (which sets it on click) and the overlay (which renders the
16
+ * composer). Null when nothing is being placed. The atom itself lives in `./state`
17
+ * ({@link pendingComment}), scoped per editor.
18
+ * @public */
19
+ export interface PendingComment {
20
+ anchor: TLCommentAnchor
21
+ /** Page point where the composer opens (the click location, or a region's pin corner). */
22
+ point: VecLike
23
+ }
24
+
25
+ /**
26
+ * Merge configure options over a base. Shallow for scalars; `components` is merged rather than
27
+ * replaced so chained `configure` calls layer their slots — a later `{ components: { PinContent } }`
28
+ * doesn't drop an earlier `{ components: { CommentBody } }`.
29
+ */
30
+ function mergeCommentingOptions(
31
+ base: CommentingOptions,
32
+ overrides: Partial<CommentingOptions>
33
+ ): CommentingOptions {
34
+ return {
35
+ ...base,
36
+ ...overrides,
37
+ components: { ...base.components, ...overrides.components },
38
+ }
39
+ }
40
+
41
+ /** The page-space rectangle spanned by two points, normalized so w/h are non-negative. Shared by
42
+ * region creation (pointer-down → cursor) and corner resize (fixed corner → cursor). */
43
+ export function regionBetween(a: VecLike, b: VecLike): BoxModel {
44
+ return {
45
+ x: Math.min(a.x, b.x),
46
+ y: Math.min(a.y, b.y),
47
+ w: Math.abs(a.x - b.x),
48
+ h: Math.abs(a.y - b.y),
49
+ }
50
+ }
51
+
52
+ /**
53
+ * The comment tool. A click starts a point/shape thread (the pin tracks the shape it lands on);
54
+ * dragging out a rectangle starts a region thread anchored to that area. Placement only opens a
55
+ * composer (via `pendingComment`); the records are created when the comment is posted.
56
+ * @public
57
+ */
58
+ export class CommentTool extends StateNode {
59
+ static override id = 'comment'
60
+ static override initial = 'idle'
61
+ static override children(): TLStateNodeConstructor[] {
62
+ return [CommentIdle, CommentPointing, CommentDragging]
63
+ }
64
+
65
+ /**
66
+ * Configure this tool's {@link CommentTool.options | `options`}, returning a configured subclass
67
+ * to register via `tools`. Mirrors `ShapeUtil.configure`. Layers over any prior `configure`, so
68
+ * calls can be chained.
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * <Tldraw tools={[CommentTool.configure({ history: 'ignore', enableClustering: false })]} />
73
+ * ```
74
+ */
75
+ static configure<T extends TLStateNodeConstructor>(
76
+ this: T,
77
+ options: T extends new (...args: any[]) => { options: infer Options } ? Partial<Options> : never
78
+ ): T {
79
+ // @ts-expect-error -- mirrors ShapeUtil.configure; extending `this` is sound at runtime.
80
+ return class extends this {
81
+ // @ts-expect-error
82
+ options = mergeCommentingOptions(this.options, options)
83
+ }
84
+ }
85
+
86
+ /**
87
+ * The merged commenting options for this editor. Read from anywhere via
88
+ * {@link getCommentingOptions}. Override with {@link CommentTool.configure}.
89
+ */
90
+ options: CommentingOptions = defaultCommentingOptions
91
+
92
+ override onEnter() {
93
+ this.editor.setCursor({ type: 'cross', rotation: 0 })
94
+ }
95
+
96
+ // Escape leaves the tool, like the built-in tools (the editor dispatches `cancel` on Escape).
97
+ override onCancel() {
98
+ this.editor.setCurrentTool('select')
99
+ }
100
+ }
101
+
102
+ class CommentIdle extends StateNode {
103
+ static override id = 'idle'
104
+
105
+ override onPointerDown() {
106
+ this.parent.transition('pointing')
107
+ }
108
+ }
109
+
110
+ class CommentPointing extends StateNode {
111
+ static override id = 'pointing'
112
+
113
+ // Once the pointer passes the editor's drag threshold this is a region, not a click — but only
114
+ // when region comments are enabled; otherwise a drag is treated as a click (point/shape).
115
+ override onPointerMove() {
116
+ if (getRegionCommentOptions(this.editor).enabled && this.editor.inputs.getIsDragging()) {
117
+ this.parent.transition('dragging')
118
+ }
119
+ }
120
+
121
+ // A pointer up with no drag is a click: anchor to the shape under it, or drop a point.
122
+ override onPointerUp() {
123
+ const { editor } = this
124
+ const point = editor.inputs.getCurrentPagePoint()
125
+ const hit = editor.getShapeAtPoint(point, { hitInside: true })
126
+ const anchor: TLCommentAnchor = hit
127
+ ? shapeAnchorAt(editor, hit.id, point, editor.inputs.getAltKey())
128
+ : { type: 'point', x: point.x, y: point.y }
129
+ pendingComment.set(editor, { anchor, point: { x: point.x, y: point.y } })
130
+ // Hand back to select; the open composer is now the focus.
131
+ editor.setCurrentTool('select')
132
+ }
133
+ }
134
+
135
+ class CommentDragging extends StateNode {
136
+ static override id = 'dragging'
137
+
138
+ override onEnter() {
139
+ this.updateDraft()
140
+ }
141
+
142
+ override onPointerMove() {
143
+ this.updateDraft()
144
+ }
145
+
146
+ // Commit the dragged rectangle as a region anchor; the composer opens at its pin corner.
147
+ override onPointerUp() {
148
+ const { editor } = this
149
+ const region = regionBetween(
150
+ editor.inputs.getOriginPagePoint(),
151
+ editor.inputs.getCurrentPagePoint()
152
+ )
153
+ regionDraft.set(editor, null)
154
+ pendingComment.set(editor, {
155
+ anchor: { type: 'region', ...region },
156
+ point: regionPinPoint(region, getRegionCommentOptions(editor).pinCorner),
157
+ })
158
+ editor.setCurrentTool('select')
159
+ }
160
+
161
+ override onCancel() {
162
+ this.cancel()
163
+ }
164
+
165
+ override onInterrupt() {
166
+ this.cancel()
167
+ }
168
+
169
+ private updateDraft() {
170
+ const { editor } = this
171
+ regionDraft.set(
172
+ editor,
173
+ regionBetween(editor.inputs.getOriginPagePoint(), editor.inputs.getCurrentPagePoint())
174
+ )
175
+ }
176
+
177
+ private cancel() {
178
+ regionDraft.set(this.editor, null)
179
+ this.editor.setCurrentTool('select')
180
+ }
181
+ }
182
+
183
+ /** @public */
184
+ export const commentTools = [CommentTool]
185
+
186
+ /** Registers the comment tool in the UI (icon, label, shortcut). Compose into your overrides.
187
+ * Once registered, tldraw's `DefaultToolbarContent` shows the comment button next to the eraser.
188
+ * @public */
189
+ export const commentToolOverrides: TLUiOverrides = {
190
+ tools(editor, tools) {
191
+ tools.comment = {
192
+ id: 'comment',
193
+ icon: 'comment',
194
+ label: 'Comment',
195
+ kbd: 'c',
196
+ onSelect: () => editor.setCurrentTool('comment'),
197
+ }
198
+ return tools
199
+ },
200
+ }
@@ -0,0 +1,104 @@
1
+ import {
2
+ TldrawUiDropdownMenuContent,
3
+ TldrawUiDropdownMenuRoot,
4
+ TldrawUiDropdownMenuTrigger,
5
+ TldrawUiMenuCheckboxItem,
6
+ TldrawUiMenuContextProvider,
7
+ TldrawUiMenuGroup,
8
+ useEditor,
9
+ useTranslation,
10
+ useValue,
11
+ } from 'tldraw'
12
+ import { SidebarFilters } from './sidebar-filters'
13
+ import { sidebarFilters } from './state'
14
+
15
+ /** @public */
16
+ export interface CommentsFilterMenuProps {
17
+ /** Whether to offer the "only your threads" toggle (needs a known current user). */
18
+ canFilterByAuthor?: boolean
19
+ /** Whether to offer the "only unread" toggle (needs a read-status source). */
20
+ canFilterByUnread?: boolean
21
+ }
22
+
23
+ /** The funnel dropdown in the sidebar header: toggles for which threads the list shows.
24
+ * @public @react */
25
+ export function CommentsFilterMenu({
26
+ canFilterByAuthor,
27
+ canFilterByUnread,
28
+ }: CommentsFilterMenuProps) {
29
+ const editor = useEditor()
30
+ const msg = useTranslation()
31
+ const filters = useValue('sidebar filters', () => sidebarFilters.get(editor), [editor])
32
+ const toggle = (key: keyof SidebarFilters) => {
33
+ sidebarFilters.update(editor, (f) => ({ ...f, [key]: !f[key] }))
34
+ }
35
+
36
+ return (
37
+ <TldrawUiDropdownMenuRoot id="comments-filter">
38
+ <TldrawUiDropdownMenuTrigger>
39
+ <button
40
+ type="button"
41
+ className="cmt-header-btn"
42
+ title={msg('comments.filter')}
43
+ aria-label={msg('comments.filter')}
44
+ >
45
+ <FilterIcon />
46
+ </button>
47
+ </TldrawUiDropdownMenuTrigger>
48
+ <TldrawUiDropdownMenuContent side="bottom" align="start">
49
+ <TldrawUiMenuContextProvider type="menu" sourceId="menu">
50
+ <TldrawUiMenuGroup id="comments-filter">
51
+ <TldrawUiMenuCheckboxItem
52
+ id="show-resolved"
53
+ label="comments.show-resolved"
54
+ checked={filters.showResolved}
55
+ onSelect={() => toggle('showResolved')}
56
+ />
57
+ {canFilterByAuthor && (
58
+ <TldrawUiMenuCheckboxItem
59
+ id="only-mine"
60
+ label="comments.only-mine"
61
+ checked={filters.onlyMine}
62
+ onSelect={() => toggle('onlyMine')}
63
+ />
64
+ )}
65
+ {canFilterByUnread && (
66
+ <TldrawUiMenuCheckboxItem
67
+ id="only-unread"
68
+ label="comments.only-unread"
69
+ checked={filters.onlyUnread}
70
+ onSelect={() => toggle('onlyUnread')}
71
+ />
72
+ )}
73
+ <TldrawUiMenuCheckboxItem
74
+ id="only-current-page"
75
+ label="comments.only-current-page"
76
+ checked={filters.onlyCurrentPage}
77
+ onSelect={() => toggle('onlyCurrentPage')}
78
+ />
79
+ </TldrawUiMenuGroup>
80
+ </TldrawUiMenuContextProvider>
81
+ </TldrawUiDropdownMenuContent>
82
+ </TldrawUiDropdownMenuRoot>
83
+ )
84
+ }
85
+
86
+ function FilterIcon() {
87
+ return (
88
+ <svg
89
+ width="16"
90
+ height="16"
91
+ viewBox="0 0 16 16"
92
+ fill="none"
93
+ stroke="currentColor"
94
+ strokeWidth="1.5"
95
+ strokeLinecap="round"
96
+ aria-hidden="true"
97
+ >
98
+ <line x1="2.5" y1="5" x2="13.5" y2="5" />
99
+ <line x1="2.5" y1="11" x2="13.5" y2="11" />
100
+ <circle cx="6" cy="5" r="1.9" fill="currentColor" stroke="none" />
101
+ <circle cx="10.5" cy="11" r="1.9" fill="currentColor" stroke="none" />
102
+ </svg>
103
+ )
104
+ }
@@ -0,0 +1,24 @@
1
+ import { TldrawUiMenuCheckboxItem, useEditor, useValue } from 'tldraw'
2
+ import { commentsHidden, toggleCommentsHidden } from './state'
3
+
4
+ /**
5
+ * A checkbox menu item for a "View" menu that shows/hides comment pins on the canvas — checked
6
+ * when comments are visible, like tldraw's own grid/snap toggles. Reads and toggles the shared
7
+ * {@link commentsHidden} signal, so it stays in sync with the Shift+C shortcut and the sidebar
8
+ * control. Drop it into whichever menu your app owns.
9
+ * @public @react
10
+ */
11
+ export function CommentsMenuItem() {
12
+ const editor = useEditor()
13
+ const hidden = useValue('comments hidden', () => commentsHidden.get(editor), [editor])
14
+ return (
15
+ <TldrawUiMenuCheckboxItem
16
+ id="comments-visible"
17
+ label="comments.title"
18
+ kbd="!c"
19
+ readonlyOk
20
+ checked={!hidden}
21
+ onSelect={() => toggleCommentsHidden(editor)}
22
+ />
23
+ )
24
+ }
@@ -0,0 +1,62 @@
1
+ import {
2
+ TldrawUiDropdownMenuContent,
3
+ TldrawUiDropdownMenuGroup,
4
+ TldrawUiDropdownMenuItem,
5
+ TldrawUiDropdownMenuRoot,
6
+ TldrawUiDropdownMenuTrigger,
7
+ useEditor,
8
+ useTranslation,
9
+ useValue,
10
+ } from 'tldraw'
11
+ import { commentsHidden, toggleCommentsHidden } from './state'
12
+
13
+ // A keyboard-shortcut glyph, not translatable copy — kept out of JSX as a constant.
14
+ const HIDE_SHORTCUT = '⇧C'
15
+
16
+ /** The overflow (⋯) dropdown in the sidebar header. For now it holds the hide/show-comments
17
+ * toggle; it's the home for later comment-wide controls (notifications, mark all as read).
18
+ * @public @react */
19
+ export function CommentsOverflowMenu() {
20
+ const editor = useEditor()
21
+ const msg = useTranslation()
22
+ const hidden = useValue('comments hidden', () => commentsHidden.get(editor), [editor])
23
+
24
+ return (
25
+ <TldrawUiDropdownMenuRoot id="comments-overflow">
26
+ <TldrawUiDropdownMenuTrigger>
27
+ <button
28
+ type="button"
29
+ className="cmt-header-btn"
30
+ title={msg('comments.more-options')}
31
+ aria-label={msg('comments.more-options')}
32
+ >
33
+ <MoreIcon />
34
+ </button>
35
+ </TldrawUiDropdownMenuTrigger>
36
+ <TldrawUiDropdownMenuContent side="bottom" align="end">
37
+ <TldrawUiDropdownMenuGroup>
38
+ <TldrawUiDropdownMenuItem>
39
+ <button
40
+ type="button"
41
+ className="cmt-menu-item"
42
+ onClick={() => toggleCommentsHidden(editor)}
43
+ >
44
+ <span>{hidden ? msg('comments.show') : msg('comments.hide')}</span>
45
+ <span className="cmt-menu-item__shortcut">{HIDE_SHORTCUT}</span>
46
+ </button>
47
+ </TldrawUiDropdownMenuItem>
48
+ </TldrawUiDropdownMenuGroup>
49
+ </TldrawUiDropdownMenuContent>
50
+ </TldrawUiDropdownMenuRoot>
51
+ )
52
+ }
53
+
54
+ function MoreIcon() {
55
+ return (
56
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
57
+ <circle cx="3" cy="8" r="1.4" />
58
+ <circle cx="8" cy="8" r="1.4" />
59
+ <circle cx="13" cy="8" r="1.4" />
60
+ </svg>
61
+ )
62
+ }