@edux-design/comments 0.1.1

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.
package/dist/index.mjs ADDED
@@ -0,0 +1,556 @@
1
+ // src/elements/Comment.jsx
2
+ import React, {
3
+ forwardRef,
4
+ useEffect,
5
+ useMemo,
6
+ useState,
7
+ createContext,
8
+ useContext,
9
+ isValidElement
10
+ } from "react";
11
+ import { cx } from "@edux-design/utils";
12
+ import { Avatar } from "@edux-design/avatars";
13
+ import { jsx, jsxs } from "react/jsx-runtime";
14
+ var VARIANT_STYLES = {
15
+ default: "border-border-minimal bg-bg-base",
16
+ resolved: "border-border-success-base bg-bg-success-subtle"
17
+ };
18
+ var CommentContext = createContext({
19
+ editable: true,
20
+ onResolve: void 0
21
+ });
22
+ var useCommentContext = () => useContext(CommentContext);
23
+ var Comment = forwardRef(function Comment2({
24
+ author = "Author",
25
+ timestamp,
26
+ avatar,
27
+ avatarName,
28
+ avatarProps,
29
+ body,
30
+ replies,
31
+ variant = "default",
32
+ resolved = false,
33
+ onResolve,
34
+ editable = true,
35
+ children,
36
+ className,
37
+ ...props
38
+ }, ref) {
39
+ const childArray = React.Children.toArray(children);
40
+ const actionsSlot = childArray.find(
41
+ (child) => {
42
+ var _a;
43
+ return isValidElement(child) && (child.type === CommentActions || ((_a = child.type) == null ? void 0 : _a.displayName) === "Comment.Actions");
44
+ }
45
+ );
46
+ const bodyNodes = childArray.filter((child) => child !== actionsSlot);
47
+ const bodyContent = body ?? bodyNodes;
48
+ const hasBodyContent = body !== void 0 ? Boolean(body) : bodyNodes.length > 0;
49
+ const isResolved = resolved || variant === "resolved";
50
+ const resolvedVariant = VARIANT_STYLES[isResolved ? "resolved" : variant] ?? VARIANT_STYLES.default;
51
+ const resolvedAvatarName = avatarName ?? (typeof author === "string" ? author : void 0);
52
+ const shouldRenderAvatar = Boolean(avatar || avatarProps || resolvedAvatarName);
53
+ const replyItems = useMemo(
54
+ () => React.Children.toArray(replies).filter(
55
+ (reply) => reply !== null && reply !== void 0 && reply !== false
56
+ ),
57
+ [replies]
58
+ );
59
+ const replyCount = replyItems.length;
60
+ const shouldCollapseReplies = replyCount > 2;
61
+ const [showAllReplies, setShowAllReplies] = useState(!shouldCollapseReplies);
62
+ useEffect(() => {
63
+ setShowAllReplies(!shouldCollapseReplies);
64
+ }, [shouldCollapseReplies]);
65
+ const visibleReplies = shouldCollapseReplies && !showAllReplies ? [replyItems[replyCount - 1]] : replyItems;
66
+ return /* @__PURE__ */ jsx(CommentContext.Provider, { value: { editable, onResolve }, children: /* @__PURE__ */ jsxs(
67
+ "div",
68
+ {
69
+ ref,
70
+ className: cx(
71
+ "flex gap-12 rounded-default border p-12",
72
+ resolvedVariant,
73
+ className
74
+ ),
75
+ "data-variant": variant,
76
+ ...props,
77
+ children: [
78
+ shouldRenderAvatar && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar ?? /* @__PURE__ */ jsx(Avatar, { name: resolvedAvatarName, size: "small", ...avatarProps }) }),
79
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
80
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-6", children: [
81
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-fg-base", children: author }),
82
+ timestamp && /* @__PURE__ */ jsx("span", { className: "text-xs text-fg-subtle", children: timestamp })
83
+ ] }),
84
+ hasBodyContent && !isResolved ? /* @__PURE__ */ jsx("div", { className: "mt-6 text-sm leading-relaxed text-fg-base", children: bodyContent }) : null,
85
+ isResolved ? /* @__PURE__ */ jsx("div", { className: "mt-6 text-xs font-semibold uppercase tracking-wide text-fg-success-base", children: "Resolved" }) : null,
86
+ actionsSlot ? /* @__PURE__ */ jsx("div", { className: "mt-8", children: actionsSlot }) : null,
87
+ replies && !isResolved ? /* @__PURE__ */ jsxs("div", { className: "mt-10 border-l border-border-minimal pl-12", children: [
88
+ shouldCollapseReplies && !showAllReplies && /* @__PURE__ */ jsxs(
89
+ "button",
90
+ {
91
+ type: "button",
92
+ className: "mb-8 text-xs font-semibold text-fg-info-base",
93
+ onClick: () => setShowAllReplies(true),
94
+ "aria-expanded": showAllReplies,
95
+ children: [
96
+ "Show all ",
97
+ replyCount,
98
+ " replies"
99
+ ]
100
+ }
101
+ ),
102
+ visibleReplies
103
+ ] }) : null
104
+ ] })
105
+ ]
106
+ }
107
+ ) });
108
+ });
109
+ var CommentActions = ({ children, className, ...props }) => /* @__PURE__ */ jsx(
110
+ "div",
111
+ {
112
+ className: cx(
113
+ "flex flex-wrap items-center gap-8 text-xs text-fg-subtle",
114
+ className
115
+ ),
116
+ ...props,
117
+ children
118
+ }
119
+ );
120
+ var CommentReply = ({ className, ...props }) => /* @__PURE__ */ jsx(
121
+ "button",
122
+ {
123
+ type: "button",
124
+ className: cx("text-xs text-fg-info-base cursor-pointer", className),
125
+ ...props,
126
+ children: "Reply"
127
+ }
128
+ );
129
+ var CommentEdit = ({ className, ...props }) => /* @__PURE__ */ jsx(
130
+ "button",
131
+ {
132
+ type: "button",
133
+ className: cx("text-xs text-fg-info-base cursor-pointer", className),
134
+ ...props,
135
+ children: "Edit"
136
+ }
137
+ );
138
+ var CommentResolve = ({
139
+ className,
140
+ disabled,
141
+ onClick,
142
+ ...props
143
+ }) => {
144
+ const { editable, onResolve } = useCommentContext();
145
+ const isDisabled = disabled || !editable;
146
+ const handleClick = onClick ?? onResolve;
147
+ return /* @__PURE__ */ jsx(
148
+ "button",
149
+ {
150
+ type: "button",
151
+ className: cx(
152
+ "text-xs font-semibold text-fg-success-base cursor-pointer",
153
+ className
154
+ ),
155
+ onClick: handleClick,
156
+ disabled: isDisabled,
157
+ "aria-disabled": isDisabled,
158
+ ...props,
159
+ children: "Resolve"
160
+ }
161
+ );
162
+ };
163
+ Comment.Actions = CommentActions;
164
+ Comment.Reply = CommentReply;
165
+ Comment.Edit = CommentEdit;
166
+ Comment.Resolve = CommentResolve;
167
+ CommentActions.displayName = "Comment.Actions";
168
+ CommentReply.displayName = "Comment.Reply";
169
+ CommentEdit.displayName = "Comment.Edit";
170
+ CommentResolve.displayName = "Comment.Resolve";
171
+
172
+ // src/elements/CommentComposer.jsx
173
+ import React3, {
174
+ forwardRef as forwardRef2,
175
+ useMemo as useMemo2,
176
+ useRef,
177
+ createContext as createContext2,
178
+ useContext as useContext2,
179
+ useEffect as useEffect2
180
+ } from "react";
181
+ import { cx as cx2 } from "@edux-design/utils";
182
+ import { Avatar as Avatar2 } from "@edux-design/avatars";
183
+ import { Button } from "@edux-design/buttons";
184
+ import { useEditor, EditorContent } from "@tiptap/react";
185
+ import StarterKit from "@tiptap/starter-kit";
186
+ import Placeholder from "@tiptap/extension-placeholder";
187
+
188
+ // src/elements/CommentMentionExtension.jsx
189
+ import React2 from "react";
190
+ import { Node, mergeAttributes } from "@tiptap/core";
191
+ import { ReactNodeViewRenderer, NodeViewWrapper } from "@tiptap/react";
192
+ import { Mention } from "@edux-design/mentions";
193
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
194
+ var MentionNodeView = ({ node }) => {
195
+ var _a, _b;
196
+ const handle = ((_a = node.attrs) == null ? void 0 : _a.handle) ?? "";
197
+ const label = ((_b = node.attrs) == null ? void 0 : _b.label) ?? "";
198
+ return /* @__PURE__ */ jsx2(
199
+ NodeViewWrapper,
200
+ {
201
+ as: "span",
202
+ className: "inline-flex items-center align-middle -translate-y-2",
203
+ children: /* @__PURE__ */ jsxs2(Mention, { handle, size: "small", children: [
204
+ "@",
205
+ handle,
206
+ " \xB7 ",
207
+ label
208
+ ] })
209
+ }
210
+ );
211
+ };
212
+ var CommentMentionExtension = Node.create({
213
+ name: "commentMention",
214
+ group: "inline",
215
+ inline: true,
216
+ atom: true,
217
+ selectable: false,
218
+ addAttributes() {
219
+ return {
220
+ id: { default: null },
221
+ handle: { default: "" },
222
+ label: { default: "" }
223
+ };
224
+ },
225
+ parseHTML() {
226
+ return [{ tag: "span[data-comment-mention]" }];
227
+ },
228
+ renderHTML({ HTMLAttributes }) {
229
+ const handle = HTMLAttributes.handle || "";
230
+ const label = HTMLAttributes.label || `@${handle}`;
231
+ return [
232
+ "span",
233
+ mergeAttributes(HTMLAttributes, {
234
+ "data-comment-mention": "true",
235
+ "data-handle": handle
236
+ }),
237
+ label
238
+ ];
239
+ },
240
+ addNodeView() {
241
+ return ReactNodeViewRenderer(MentionNodeView);
242
+ }
243
+ });
244
+
245
+ // src/elements/CommentComposer.jsx
246
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
247
+ var CommentComposerContext = createContext2(null);
248
+ var useCommentComposer = () => {
249
+ const context = useContext2(CommentComposerContext);
250
+ if (!context) {
251
+ throw new Error(
252
+ "CommentComposer components must be used within a <CommentComposer />."
253
+ );
254
+ }
255
+ return context;
256
+ };
257
+ var CommentComposer = forwardRef2(function CommentComposer2({
258
+ value,
259
+ onChange,
260
+ onSubmit,
261
+ onCancel,
262
+ placeholder = "Write a comment...",
263
+ rows = 4,
264
+ disabled = false,
265
+ isSubmitting = false,
266
+ submitLabel = "Post",
267
+ cancelLabel = "Cancel",
268
+ avatar,
269
+ avatarProps,
270
+ actions,
271
+ className,
272
+ children,
273
+ ...props
274
+ }, ref) {
275
+ const hasValue = value === void 0 ? true : Boolean(value && value.trim().length);
276
+ const isSubmitDisabled = disabled || isSubmitting || !hasValue;
277
+ const editor = useEditor({
278
+ extensions: [
279
+ StarterKit,
280
+ CommentMentionExtension,
281
+ Placeholder.configure({
282
+ placeholder
283
+ })
284
+ ],
285
+ content: value ?? "",
286
+ editable: !disabled,
287
+ editorProps: {
288
+ attributes: {
289
+ class: cx2(
290
+ "w-full bg-white border-2 rounded-md border-border-base",
291
+ "outline-none transition-all duration-300 ease-in-out",
292
+ "pl-5 py-3 font-normal text-base leading-relaxed",
293
+ "h-[120px] overflow-y-auto",
294
+ "focus:shadow-focus focus-visible:shadow-focus",
295
+ disabled && "bg-bg-subtle border-border-subtle text-fg-inactive"
296
+ )
297
+ }
298
+ },
299
+ onUpdate: ({ editor: editor2 }) => {
300
+ onChange == null ? void 0 : onChange({ target: { value: editor2.getText() } });
301
+ }
302
+ });
303
+ const containerRef = useRef(null);
304
+ useEffect2(() => {
305
+ if (typeof ref === "function") {
306
+ ref(containerRef.current);
307
+ } else if (ref) {
308
+ ref.current = containerRef.current;
309
+ }
310
+ }, [ref]);
311
+ useEffect2(() => {
312
+ if (!editor) return;
313
+ if (value === "") {
314
+ editor.commands.clearContent();
315
+ }
316
+ }, [editor, value]);
317
+ const contextValue = useMemo2(
318
+ () => ({
319
+ editor,
320
+ placeholder,
321
+ editorContainerRef: containerRef
322
+ }),
323
+ [editor, placeholder]
324
+ );
325
+ return /* @__PURE__ */ jsx3(CommentComposerContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs3(
326
+ "form",
327
+ {
328
+ className: cx2(
329
+ "relative flex gap-12 rounded-default border border-border-minimal bg-bg-base p-12",
330
+ className
331
+ ),
332
+ onSubmit: (event) => {
333
+ event.preventDefault();
334
+ if (isSubmitDisabled) return;
335
+ onSubmit == null ? void 0 : onSubmit(event);
336
+ },
337
+ ...props,
338
+ children: [
339
+ (avatar || avatarProps) && /* @__PURE__ */ jsx3("div", { className: "shrink-0", children: avatar ?? /* @__PURE__ */ jsx3(Avatar2, { size: "small", ...avatarProps }) }),
340
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
341
+ /* @__PURE__ */ jsx3("div", { ref: containerRef, className: "relative", children: /* @__PURE__ */ jsx3(EditorContent, { editor }) }),
342
+ /* @__PURE__ */ jsxs3("div", { className: "mt-8 flex flex-wrap items-center justify-between gap-8", children: [
343
+ actions ? /* @__PURE__ */ jsx3("div", { className: "flex flex-wrap items-center gap-8 text-xs text-fg-subtle", children: actions }) : /* @__PURE__ */ jsx3("span", {}),
344
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-8", children: [
345
+ onCancel && /* @__PURE__ */ jsx3(
346
+ Button,
347
+ {
348
+ type: "button",
349
+ size: "small",
350
+ isSecondary: true,
351
+ onClick: onCancel,
352
+ disabled: disabled || isSubmitting,
353
+ children: cancelLabel
354
+ }
355
+ ),
356
+ /* @__PURE__ */ jsx3(Button, { type: "submit", size: "small", disabled: isSubmitDisabled, children: isSubmitting ? "Posting..." : submitLabel })
357
+ ] })
358
+ ] })
359
+ ] }),
360
+ children
361
+ ]
362
+ }
363
+ ) });
364
+ });
365
+
366
+ // src/elements/CommentMention.jsx
367
+ import React4, { useMemo as useMemo3, useState as useState2, useCallback, useEffect as useEffect3, useRef as useRef2 } from "react";
368
+ import { Menu, MenuList, MenuItem } from "@edux-design/menus";
369
+ import { Avatar as Avatar3 } from "@edux-design/avatars";
370
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
371
+ var CommentMention = ({
372
+ peopleList = []
373
+ }) => {
374
+ const { editor, editorContainerRef } = useCommentComposer();
375
+ const [menuOpen, setMenuOpen] = useState2(false);
376
+ const [query, setQuery] = useState2("");
377
+ const [range, setRange] = useState2(null);
378
+ const [anchorStyle, setAnchorStyle] = useState2({ left: 0, top: 0 });
379
+ const menuListRef = useRef2(null);
380
+ const rangeRef = useRef2(null);
381
+ const peopleByHandle = useMemo3(
382
+ () => new Map(peopleList.map((person) => [person.handle.toLowerCase(), person])),
383
+ [peopleList]
384
+ );
385
+ const filteredPeople = useMemo3(() => {
386
+ if (!query) return peopleList;
387
+ const lowered = query.toLowerCase();
388
+ return peopleList.filter(
389
+ (person) => person.handle.toLowerCase().startsWith(lowered)
390
+ );
391
+ }, [peopleList, query]);
392
+ const updateMenuState = useCallback(() => {
393
+ var _a, _b;
394
+ if (!editor) return;
395
+ const { from } = editor.state.selection;
396
+ const { parent, parentOffset, pos } = editor.state.selection.$from;
397
+ if (!parent.isTextblock) {
398
+ setMenuOpen(false);
399
+ setQuery("");
400
+ setRange(null);
401
+ return;
402
+ }
403
+ const textBefore = parent.textBetween(0, parentOffset, "\n", "\0");
404
+ const match = textBefore.match(/@([\w-]*)$/);
405
+ if (match) {
406
+ const handleQuery = match[1] ?? "";
407
+ setQuery(handleQuery);
408
+ setMenuOpen(true);
409
+ const fromPos = pos - match[0].length;
410
+ const nextRange = { from: fromPos, to: pos };
411
+ setRange(nextRange);
412
+ rangeRef.current = nextRange;
413
+ const coords = editor.view.coordsAtPos(fromPos);
414
+ const containerRect = (_b = (_a = editorContainerRef == null ? void 0 : editorContainerRef.current) == null ? void 0 : _a.getBoundingClientRect) == null ? void 0 : _b.call(_a);
415
+ if (containerRect) {
416
+ setAnchorStyle({
417
+ left: coords.left - containerRect.left + 12,
418
+ top: coords.bottom - containerRect.top + 4
419
+ });
420
+ }
421
+ return;
422
+ }
423
+ setMenuOpen(false);
424
+ setQuery("");
425
+ setRange(null);
426
+ rangeRef.current = null;
427
+ }, [editor]);
428
+ const handleSelectPerson = useCallback(
429
+ (person) => {
430
+ if (!editor || !rangeRef.current) return;
431
+ const selectionRange = rangeRef.current;
432
+ editor.commands.focus();
433
+ editor.commands.setTextSelection(selectionRange);
434
+ editor.commands.insertContentAt(
435
+ selectionRange,
436
+ [
437
+ {
438
+ type: "commentMention",
439
+ attrs: {
440
+ id: person.id,
441
+ handle: person.handle,
442
+ label: person.name
443
+ }
444
+ },
445
+ { type: "text", text: " " }
446
+ ],
447
+ { updateSelection: false }
448
+ );
449
+ const nextPos = selectionRange.from + 2;
450
+ requestAnimationFrame(() => {
451
+ editor.commands.setTextSelection(nextPos);
452
+ editor.view.focus();
453
+ setTimeout(() => editor.view.focus(), 0);
454
+ });
455
+ setMenuOpen(false);
456
+ setQuery("");
457
+ setRange(null);
458
+ rangeRef.current = null;
459
+ },
460
+ [editor]
461
+ );
462
+ useEffect3(() => {
463
+ if (!editor) return;
464
+ updateMenuState();
465
+ editor.on("transaction", updateMenuState);
466
+ editor.on("selectionUpdate", updateMenuState);
467
+ return () => {
468
+ editor.off("transaction", updateMenuState);
469
+ editor.off("selectionUpdate", updateMenuState);
470
+ };
471
+ }, [editor, updateMenuState]);
472
+ useEffect3(() => {
473
+ var _a;
474
+ if (!editor) return;
475
+ const handleKeyDown = (event) => {
476
+ var _a2;
477
+ if (!menuOpen) return;
478
+ if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
479
+ event.preventDefault();
480
+ const list = menuListRef.current;
481
+ const items = list == null ? void 0 : list.querySelectorAll("[data-menu-item]");
482
+ if (!list || !(items == null ? void 0 : items.length)) return;
483
+ list.focus();
484
+ const direction = event.key === "ArrowDown" ? 1 : -1;
485
+ const targetIndex = direction === 1 ? 0 : items.length - 1;
486
+ (_a2 = items[targetIndex]) == null ? void 0 : _a2.focus();
487
+ };
488
+ const dom = (_a = editor.view) == null ? void 0 : _a.dom;
489
+ dom == null ? void 0 : dom.addEventListener("keydown", handleKeyDown);
490
+ return () => dom == null ? void 0 : dom.removeEventListener("keydown", handleKeyDown);
491
+ }, [editor, menuOpen]);
492
+ const menuContent = useMemo3(
493
+ () => /* @__PURE__ */ jsx4(
494
+ Menu,
495
+ {
496
+ open: menuOpen && filteredPeople.length > 0,
497
+ onOpenChange: setMenuOpen,
498
+ button: /* @__PURE__ */ jsx4(
499
+ "button",
500
+ {
501
+ type: "button",
502
+ "aria-hidden": true,
503
+ className: "absolute h-1 w-1 opacity-0",
504
+ style: {
505
+ left: `${anchorStyle.left}px`,
506
+ top: `${anchorStyle.top}px`,
507
+ pointerEvents: "none"
508
+ }
509
+ }
510
+ ),
511
+ align: "start",
512
+ side: "bottom",
513
+ trapFocus: false,
514
+ disableOutsidePointerEvents: false,
515
+ onOpenAutoFocus: (event) => event.preventDefault(),
516
+ contentProps: {
517
+ sideOffset: 4,
518
+ avoidCollisions: true,
519
+ collisionPadding: 12,
520
+ onCloseAutoFocus: (event) => event.preventDefault()
521
+ },
522
+ children: /* @__PURE__ */ jsx4(MenuList, { ref: menuListRef, className: "min-w-[220px]", children: filteredPeople.map((person) => /* @__PURE__ */ jsx4(
523
+ MenuItem,
524
+ {
525
+ onMouseDown: (event) => {
526
+ event.preventDefault();
527
+ event.stopPropagation();
528
+ handleSelectPerson(person);
529
+ },
530
+ onClick: () => handleSelectPerson(person),
531
+ onPointerDown: (event) => event.preventDefault(),
532
+ children: /* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-8", children: [
533
+ /* @__PURE__ */ jsx4(Avatar3, { size: "small", ...person.avatarProps }),
534
+ /* @__PURE__ */ jsxs4("span", { className: "flex flex-col", children: [
535
+ /* @__PURE__ */ jsx4("span", { className: "text-sm text-fg-base", children: person.name }),
536
+ /* @__PURE__ */ jsxs4("span", { className: "text-xs text-fg-subtle", children: [
537
+ "@",
538
+ person.handle
539
+ ] })
540
+ ] })
541
+ ] })
542
+ },
543
+ person.id
544
+ )) })
545
+ }
546
+ ),
547
+ [filteredPeople, handleSelectPerson, menuOpen]
548
+ );
549
+ if (!editor) return null;
550
+ return menuContent;
551
+ };
552
+ export {
553
+ Comment,
554
+ CommentComposer,
555
+ CommentMention
556
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@edux-design/comments",
3
+ "version": "0.1.1",
4
+ "private": false,
5
+ "sideEffects": [
6
+ "**/*.css"
7
+ ],
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "lint": "eslint . --max-warnings 0",
18
+ "build": "tsup src/index.js --format esm,cjs --dts",
19
+ "dev": "tsup src/index.js --watch --format esm,cjs --dts",
20
+ "generate:component": "turbo gen react-component",
21
+ "check-types": "tsc --noEmit"
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5.9.2",
25
+ "tailwindcss": "^4.1.12",
26
+ "postcss": "^8.5.6",
27
+ "tsup": "^8.5.0",
28
+ "@tailwindcss/cli": "^4.1.12",
29
+ "@tailwindcss/postcss": "^4.1.12",
30
+ "@tiptap/core": "^2.14.0",
31
+ "@tiptap/react": "^2.14.0",
32
+ "@tiptap/starter-kit": "^2.14.0",
33
+ "@tiptap/extension-placeholder": "^2.14.0"
34
+ },
35
+ "dependencies": {
36
+ "@edux-design/avatars": "*",
37
+ "@edux-design/buttons": "*",
38
+ "@edux-design/forms": "*",
39
+ "@edux-design/mentions": "*",
40
+ "@edux-design/menus": "*",
41
+ "@edux-design/utils": "*"
42
+ },
43
+ "peerDependencies": {
44
+ "react": "^19.1.0",
45
+ "react-dom": "^19.1.0",
46
+ "@tiptap/core": "^2.14.0",
47
+ "@tiptap/react": "^2.14.0",
48
+ "@tiptap/starter-kit": "^2.14.0",
49
+ "@tiptap/extension-placeholder": "^2.14.0"
50
+ }
51
+ }