@multicomma/arkcn 0.1.2 → 0.1.3

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 (148) hide show
  1. package/package.json +1 -1
  2. package/registry/index.json +1 -1
  3. package/registry/items/accordion.json +302 -0
  4. package/registry/items/activity-feed.json +408 -0
  5. package/registry/items/alert-dialog.json +676 -0
  6. package/registry/items/alert.json +113 -0
  7. package/registry/items/angle-slider.json +378 -0
  8. package/registry/items/app-shell.json +2654 -1
  9. package/registry/items/aspect-ratio.json +36 -0
  10. package/registry/items/attachment.json +299 -1
  11. package/registry/items/avatar.json +213 -0
  12. package/registry/items/badge.json +44 -0
  13. package/registry/items/breadcrumb.json +191 -0
  14. package/registry/items/bubble.json +146 -0
  15. package/registry/items/button-group.json +110 -0
  16. package/registry/items/button.json +52 -0
  17. package/registry/items/calendar.json +499 -1
  18. package/registry/items/canvas.json +446 -1
  19. package/registry/items/card.json +192 -0
  20. package/registry/items/carousel.json +208 -1
  21. package/registry/items/cascader.json +797 -1
  22. package/registry/items/chart.json +1764 -1
  23. package/registry/items/chat.json +993 -0
  24. package/registry/items/checkbox.json +503 -0
  25. package/registry/items/clipboard.json +308 -0
  26. package/registry/items/collapsible.json +226 -0
  27. package/registry/items/color-picker.json +1261 -0
  28. package/registry/items/combobox.json +1123 -1
  29. package/registry/items/command.json +837 -1
  30. package/registry/items/comment-thread.json +952 -1
  31. package/registry/items/context-menu.json +1344 -1
  32. package/registry/items/data-grid.json +449 -1
  33. package/registry/items/data-table.json +1310 -1
  34. package/registry/items/date-input.json +476 -0
  35. package/registry/items/dialog.json +592 -0
  36. package/registry/items/direction.json +29 -1
  37. package/registry/items/drawer.json +797 -0
  38. package/registry/items/dropdown-menu.json +1311 -1
  39. package/registry/items/editable.json +503 -0
  40. package/registry/items/empty.json +166 -0
  41. package/registry/items/field.json +286 -1
  42. package/registry/items/file-upload.json +638 -0
  43. package/registry/items/floating-panel.json +720 -0
  44. package/registry/items/floating-toolbar.json +43 -0
  45. package/registry/items/format.json +109 -0
  46. package/registry/items/gantt.json +773 -1
  47. package/registry/items/hooks/use-mobile.json +1 -0
  48. package/registry/items/hotkeys.json +99 -1
  49. package/registry/items/hover-card.json +357 -0
  50. package/registry/items/image-cropper.json +431 -0
  51. package/registry/items/input-group.json +189 -0
  52. package/registry/items/input-otp.json +181 -0
  53. package/registry/items/input.json +28 -0
  54. package/registry/items/item.json +308 -0
  55. package/registry/items/json-tree-view.json +336 -0
  56. package/registry/items/kanban.json +624 -1
  57. package/registry/items/kbd.json +54 -0
  58. package/registry/items/label.json +28 -0
  59. package/registry/items/lib/controllable.json +1 -0
  60. package/registry/items/lib/data-table-adapter.json +1 -0
  61. package/registry/items/lib/drag-preview.json +1 -0
  62. package/registry/items/lib/form.json +1 -0
  63. package/registry/items/lib/history.json +1 -0
  64. package/registry/items/lib/row-layout.json +1 -0
  65. package/registry/items/lib/table.json +1 -0
  66. package/registry/items/lib/time.json +1 -0
  67. package/registry/items/lib/utils.json +1 -0
  68. package/registry/items/live-region.json +43 -0
  69. package/registry/items/marker.json +96 -0
  70. package/registry/items/marquee.json +300 -0
  71. package/registry/items/menubar.json +1282 -1
  72. package/registry/items/message.json +166 -0
  73. package/registry/items/navigation-menu.json +561 -1
  74. package/registry/items/node-graph.json +1037 -1
  75. package/registry/items/number-input.json +519 -0
  76. package/registry/items/pagination.json +244 -1
  77. package/registry/items/password-input.json +311 -0
  78. package/registry/items/popconfirm.json +566 -0
  79. package/registry/items/popover.json +615 -0
  80. package/registry/items/progress.json +516 -0
  81. package/registry/items/qr-code.json +249 -0
  82. package/registry/items/query-builder.json +1368 -1
  83. package/registry/items/radio-group.json +362 -0
  84. package/registry/items/rating-group.json +304 -0
  85. package/registry/items/resizable.json +242 -1
  86. package/registry/items/rich-text-editor.json +934 -1
  87. package/registry/items/scheduler.json +1013 -1
  88. package/registry/items/scroll-area.json +227 -0
  89. package/registry/items/segment-group.json +363 -0
  90. package/registry/items/select.json +956 -0
  91. package/registry/items/separator.json +45 -1
  92. package/registry/items/sheet.json +592 -0
  93. package/registry/items/sidebar.json +785 -1
  94. package/registry/items/signature-pad.json +330 -0
  95. package/registry/items/skeleton.json +28 -0
  96. package/registry/items/slider.json +799 -0
  97. package/registry/items/sonner.json +135 -0
  98. package/registry/items/spinner.json +28 -0
  99. package/registry/items/steps.json +483 -0
  100. package/registry/items/swap.json +107 -0
  101. package/registry/items/switch.json +362 -0
  102. package/registry/items/table.json +210 -0
  103. package/registry/items/tabs.json +326 -0
  104. package/registry/items/tags-input.json +640 -0
  105. package/registry/items/textarea.json +28 -0
  106. package/registry/items/timer.json +289 -0
  107. package/registry/items/toc.json +372 -0
  108. package/registry/items/toggle-group.json +214 -1
  109. package/registry/items/toggle.json +124 -0
  110. package/registry/items/tooltip.json +417 -0
  111. package/registry/items/tour.json +498 -0
  112. package/registry/items/transfer-list.json +609 -1
  113. package/registry/items/tree-select.json +742 -1
  114. package/registry/items/tree-view.json +794 -0
  115. package/registry/items/virtual-list.json +214 -1
  116. package/src/components/ui/app-shell.tsx +11 -0
  117. package/src/components/ui/attachment.tsx +1 -0
  118. package/src/components/ui/calendar.tsx +1 -0
  119. package/src/components/ui/canvas.tsx +8 -0
  120. package/src/components/ui/carousel.tsx +3 -0
  121. package/src/components/ui/cascader.tsx +1 -0
  122. package/src/components/ui/chart.tsx +10 -0
  123. package/src/components/ui/combobox.tsx +3 -0
  124. package/src/components/ui/command.tsx +1 -0
  125. package/src/components/ui/comment-thread.tsx +11 -0
  126. package/src/components/ui/context-menu.tsx +6 -0
  127. package/src/components/ui/data-grid.tsx +8 -0
  128. package/src/components/ui/data-table.tsx +6 -0
  129. package/src/components/ui/direction.tsx +2 -0
  130. package/src/components/ui/dropdown-menu.tsx +6 -0
  131. package/src/components/ui/field.tsx +1 -0
  132. package/src/components/ui/gantt.tsx +15 -0
  133. package/src/components/ui/hotkeys.tsx +3 -0
  134. package/src/components/ui/kanban.tsx +3 -0
  135. package/src/components/ui/menubar.tsx +6 -0
  136. package/src/components/ui/navigation-menu.tsx +1 -0
  137. package/src/components/ui/node-graph.tsx +23 -0
  138. package/src/components/ui/pagination.tsx +1 -0
  139. package/src/components/ui/query-builder.tsx +7 -0
  140. package/src/components/ui/resizable.tsx +1 -0
  141. package/src/components/ui/rich-text-editor.tsx +54 -0
  142. package/src/components/ui/scheduler.tsx +26 -0
  143. package/src/components/ui/separator.tsx +1 -0
  144. package/src/components/ui/sidebar.tsx +5 -0
  145. package/src/components/ui/toggle-group.tsx +3 -0
  146. package/src/components/ui/transfer-list.tsx +11 -0
  147. package/src/components/ui/tree-select.tsx +5 -0
  148. package/src/components/ui/virtual-list.tsx +7 -0
@@ -19,6 +19,939 @@
19
19
  "useRichTextEditor",
20
20
  "richTextActions"
21
21
  ],
22
+ "parts": [
23
+ {
24
+ "name": "RichTextEditor",
25
+ "props": [
26
+ {
27
+ "name": "autofocus",
28
+ "type": "boolean",
29
+ "required": false,
30
+ "default": "false",
31
+ "description": "Focus the editor on mount.",
32
+ "from": "arkcn"
33
+ },
34
+ {
35
+ "name": "characterLimit",
36
+ "type": "number",
37
+ "required": false,
38
+ "description": "Maximum characters; the count shows `data-over` past it.",
39
+ "from": "arkcn"
40
+ },
41
+ {
42
+ "name": "content",
43
+ "type": "string",
44
+ "required": false,
45
+ "description": "Controlled HTML. Prefer `defaultContent` + `onChange` unless you need to replace the document.",
46
+ "from": "arkcn"
47
+ },
48
+ {
49
+ "name": "defaultContent",
50
+ "type": "string",
51
+ "required": false,
52
+ "default": "\"\"",
53
+ "description": "Initial HTML when uncontrolled.",
54
+ "from": "arkcn"
55
+ },
56
+ {
57
+ "name": "editable",
58
+ "type": "boolean",
59
+ "required": false,
60
+ "default": "true",
61
+ "description": "Allow editing.",
62
+ "from": "arkcn"
63
+ },
64
+ {
65
+ "name": "editorOptions",
66
+ "type": "Partial<UseEditorOptions>",
67
+ "required": false,
68
+ "description": "Escape hatch for any other `useEditor` option.",
69
+ "from": "arkcn"
70
+ },
71
+ {
72
+ "name": "extensions",
73
+ "type": "Extensions",
74
+ "required": false,
75
+ "default": "[]",
76
+ "description": "Extra tiptap extensions appended to the built-in set.",
77
+ "from": "arkcn"
78
+ },
79
+ {
80
+ "name": "onChange",
81
+ "type": "((change: RichTextChange) => void)",
82
+ "required": false,
83
+ "description": "Change handler.",
84
+ "from": "arkcn"
85
+ },
86
+ {
87
+ "name": "placeholder",
88
+ "type": "string",
89
+ "required": false,
90
+ "default": "\"Write something…\"",
91
+ "description": "Text shown while there is no value.",
92
+ "from": "arkcn"
93
+ },
94
+ {
95
+ "name": "children",
96
+ "type": "ReactNode",
97
+ "required": false,
98
+ "description": "Content rendered inside the part.",
99
+ "from": "@types/react"
100
+ },
101
+ {
102
+ "name": "className",
103
+ "type": "string",
104
+ "required": false,
105
+ "description": "Extra classes merged into the part's own classes with `cn`.",
106
+ "from": "@types/react"
107
+ },
108
+ {
109
+ "name": "style",
110
+ "type": "CSSProperties",
111
+ "required": false,
112
+ "description": "Inline styles applied to the rendered element.",
113
+ "from": "@types/react"
114
+ }
115
+ ]
116
+ },
117
+ {
118
+ "name": "RichTextEditorToolbar",
119
+ "props": [
120
+ {
121
+ "name": "children",
122
+ "type": "ReactNode",
123
+ "required": false,
124
+ "description": "Content rendered inside the part.",
125
+ "from": "@types/react"
126
+ },
127
+ {
128
+ "name": "className",
129
+ "type": "string",
130
+ "required": false,
131
+ "description": "Extra classes merged into the part's own classes with `cn`.",
132
+ "from": "@types/react"
133
+ },
134
+ {
135
+ "name": "style",
136
+ "type": "CSSProperties",
137
+ "required": false,
138
+ "description": "Inline styles applied to the rendered element.",
139
+ "from": "@types/react"
140
+ }
141
+ ]
142
+ },
143
+ {
144
+ "name": "RichTextEditorToolbarGroup",
145
+ "props": [
146
+ {
147
+ "name": "children",
148
+ "type": "ReactNode",
149
+ "required": false,
150
+ "description": "Content rendered inside the part.",
151
+ "from": "@types/react"
152
+ },
153
+ {
154
+ "name": "className",
155
+ "type": "string",
156
+ "required": false,
157
+ "description": "Extra classes merged into the part's own classes with `cn`.",
158
+ "from": "@types/react"
159
+ },
160
+ {
161
+ "name": "style",
162
+ "type": "CSSProperties",
163
+ "required": false,
164
+ "description": "Inline styles applied to the rendered element.",
165
+ "from": "@types/react"
166
+ }
167
+ ]
168
+ },
169
+ {
170
+ "name": "RichTextEditorToolbarSeparator",
171
+ "props": [
172
+ {
173
+ "name": "decorative",
174
+ "type": "boolean",
175
+ "required": false,
176
+ "description": "Hide the separator from assistive technology.",
177
+ "from": "arkcn"
178
+ },
179
+ {
180
+ "name": "orientation",
181
+ "type": "\"horizontal\" | \"vertical\"",
182
+ "required": false,
183
+ "description": "Layout direction of the part.",
184
+ "from": "arkcn"
185
+ },
186
+ {
187
+ "name": "children",
188
+ "type": "ReactNode",
189
+ "required": false,
190
+ "description": "Content rendered inside the part.",
191
+ "from": "@types/react"
192
+ },
193
+ {
194
+ "name": "className",
195
+ "type": "string",
196
+ "required": false,
197
+ "description": "Extra classes merged into the part's own classes with `cn`.",
198
+ "from": "@types/react"
199
+ },
200
+ {
201
+ "name": "style",
202
+ "type": "CSSProperties",
203
+ "required": false,
204
+ "description": "Inline styles applied to the rendered element.",
205
+ "from": "@types/react"
206
+ }
207
+ ]
208
+ },
209
+ {
210
+ "name": "RichTextEditorToggle",
211
+ "props": [
212
+ {
213
+ "name": "canRun",
214
+ "type": "((editor: Editor) => boolean)",
215
+ "required": false,
216
+ "description": "Whether the action can run right now; disables the control when false.",
217
+ "from": "arkcn"
218
+ },
219
+ {
220
+ "name": "isActive",
221
+ "type": "((editor: Editor) => boolean)",
222
+ "required": false,
223
+ "description": "Whether the toggle is pressed.",
224
+ "from": "arkcn"
225
+ },
226
+ {
227
+ "name": "label",
228
+ "type": "string",
229
+ "required": false,
230
+ "description": "Accessible label.",
231
+ "from": "arkcn"
232
+ },
233
+ {
234
+ "name": "shortcut",
235
+ "type": "string",
236
+ "required": false,
237
+ "description": "Keyboard shortcut shown in the tooltip.",
238
+ "from": "arkcn"
239
+ },
240
+ {
241
+ "name": "size",
242
+ "type": "\"default\" | \"sm\" | \"lg\" | null",
243
+ "required": false,
244
+ "description": "Size variant; sets a `data-size` attribute.",
245
+ "from": "arkcn"
246
+ },
247
+ {
248
+ "name": "tooltip",
249
+ "type": "boolean",
250
+ "required": false,
251
+ "default": "true",
252
+ "description": "Tooltip content shown on hover, or `false` to disable it.",
253
+ "from": "arkcn"
254
+ },
255
+ {
256
+ "name": "variant",
257
+ "type": "\"default\" | \"outline\" | null",
258
+ "required": false,
259
+ "description": "Visual variant, styled with `cva`; sets a `data-variant` attribute.",
260
+ "from": "arkcn"
261
+ },
262
+ {
263
+ "name": "asChild",
264
+ "type": "boolean",
265
+ "required": false,
266
+ "description": "Use the provided child element as the default rendered element, combining their props and behavior.",
267
+ "from": "@ark-ui/react"
268
+ },
269
+ {
270
+ "name": "children",
271
+ "type": "ReactNode",
272
+ "required": false,
273
+ "description": "Content rendered inside the part.",
274
+ "from": "@types/react"
275
+ },
276
+ {
277
+ "name": "className",
278
+ "type": "string",
279
+ "required": false,
280
+ "description": "Extra classes merged into the part's own classes with `cn`.",
281
+ "from": "@types/react"
282
+ },
283
+ {
284
+ "name": "defaultPressed",
285
+ "type": "boolean",
286
+ "required": false,
287
+ "description": "The default pressed state of the toggle.",
288
+ "from": "@zag-js/toggle"
289
+ },
290
+ {
291
+ "name": "style",
292
+ "type": "CSSProperties",
293
+ "required": false,
294
+ "description": "Inline styles applied to the rendered element.",
295
+ "from": "@types/react"
296
+ }
297
+ ]
298
+ },
299
+ {
300
+ "name": "RichTextEditorTrigger",
301
+ "props": [
302
+ {
303
+ "name": "asChild",
304
+ "type": "boolean",
305
+ "required": false,
306
+ "description": "Render the child element instead of the default one, merging props and behaviour onto it.",
307
+ "from": "arkcn"
308
+ },
309
+ {
310
+ "name": "canRun",
311
+ "type": "((editor: Editor) => boolean)",
312
+ "required": false,
313
+ "description": "Whether the action can run right now; disables the control when false.",
314
+ "from": "arkcn"
315
+ },
316
+ {
317
+ "name": "label",
318
+ "type": "string",
319
+ "required": false,
320
+ "description": "Accessible label.",
321
+ "from": "arkcn"
322
+ },
323
+ {
324
+ "name": "shortcut",
325
+ "type": "string",
326
+ "required": false,
327
+ "description": "Keyboard shortcut shown in the tooltip.",
328
+ "from": "arkcn"
329
+ },
330
+ {
331
+ "name": "size",
332
+ "type": "\"default\" | \"xs\" | \"sm\" | \"lg\" | \"icon\" | \"icon-xs\" | \"icon-sm\" | \"icon-lg\" | null",
333
+ "required": false,
334
+ "description": "Size variant; sets a `data-size` attribute.",
335
+ "from": "arkcn"
336
+ },
337
+ {
338
+ "name": "tooltip",
339
+ "type": "boolean",
340
+ "required": false,
341
+ "default": "true",
342
+ "description": "Tooltip content shown on hover, or `false` to disable it.",
343
+ "from": "arkcn"
344
+ },
345
+ {
346
+ "name": "variant",
347
+ "type": "\"link\" | \"default\" | \"outline\" | \"secondary\" | \"ghost\" | \"destructive\" | null",
348
+ "required": false,
349
+ "description": "Visual variant, styled with `cva`; sets a `data-variant` attribute.",
350
+ "from": "arkcn"
351
+ },
352
+ {
353
+ "name": "children",
354
+ "type": "ReactNode",
355
+ "required": false,
356
+ "description": "Content rendered inside the part.",
357
+ "from": "@types/react"
358
+ },
359
+ {
360
+ "name": "className",
361
+ "type": "string",
362
+ "required": false,
363
+ "description": "Extra classes merged into the part's own classes with `cn`.",
364
+ "from": "@types/react"
365
+ },
366
+ {
367
+ "name": "style",
368
+ "type": "CSSProperties",
369
+ "required": false,
370
+ "description": "Inline styles applied to the rendered element.",
371
+ "from": "@types/react"
372
+ }
373
+ ]
374
+ },
375
+ {
376
+ "name": "RichTextEditorBlockSelect",
377
+ "props": [
378
+ {
379
+ "name": "asChild",
380
+ "type": "boolean",
381
+ "required": false,
382
+ "description": "Use the provided child element as the default rendered element, combining their props and behavior.",
383
+ "from": "@ark-ui/react"
384
+ },
385
+ {
386
+ "name": "autoComplete",
387
+ "type": "string",
388
+ "required": false,
389
+ "description": "The autocomplete attribute for the hidden select. Enables browser autofill (e.g. \"address-level1\" for state).",
390
+ "from": "@zag-js/select"
391
+ },
392
+ {
393
+ "name": "children",
394
+ "type": "ReactNode",
395
+ "required": false,
396
+ "description": "Content rendered inside the part.",
397
+ "from": "@types/react"
398
+ },
399
+ {
400
+ "name": "className",
401
+ "type": "string",
402
+ "required": false,
403
+ "description": "Extra classes merged into the part's own classes with `cn`.",
404
+ "from": "@types/react"
405
+ },
406
+ {
407
+ "name": "closeOnSelect",
408
+ "type": "boolean",
409
+ "required": false,
410
+ "default": "true",
411
+ "description": "Whether the select should close after an item is selected",
412
+ "from": "@zag-js/select"
413
+ },
414
+ {
415
+ "name": "composite",
416
+ "type": "boolean",
417
+ "required": false,
418
+ "default": "true",
419
+ "description": "Whether the select is a composed with other composite widgets like tabs or combobox",
420
+ "from": "@zag-js/select"
421
+ },
422
+ {
423
+ "name": "defaultHighlightedValue",
424
+ "type": "string | null",
425
+ "required": false,
426
+ "description": "The initial value of the highlighted item when opened.\nUse when you don't need to control the highlighted value of the select.",
427
+ "from": "@zag-js/select"
428
+ },
429
+ {
430
+ "name": "defaultOpen",
431
+ "type": "boolean",
432
+ "required": false,
433
+ "description": "Whether the select's open state is controlled by the user",
434
+ "from": "@zag-js/select"
435
+ },
436
+ {
437
+ "name": "defaultValue",
438
+ "type": "string[]",
439
+ "required": false,
440
+ "description": "The initial default value of the select when rendered.\nUse when you don't need to control the value of the select.",
441
+ "from": "@zag-js/select"
442
+ },
443
+ {
444
+ "name": "deselectable",
445
+ "type": "boolean",
446
+ "required": false,
447
+ "description": "Whether the value can be cleared by clicking the selected item.\n\n**Note:** this is only applicable for single selection",
448
+ "from": "@zag-js/select"
449
+ },
450
+ {
451
+ "name": "disabled",
452
+ "type": "boolean",
453
+ "required": false,
454
+ "description": "Whether the select is disabled",
455
+ "from": "@zag-js/select"
456
+ },
457
+ {
458
+ "name": "form",
459
+ "type": "string",
460
+ "required": false,
461
+ "description": "The associate form of the underlying select.",
462
+ "from": "@zag-js/select"
463
+ },
464
+ {
465
+ "name": "hideMode",
466
+ "type": "HideMode",
467
+ "required": false,
468
+ "default": "'display-none'",
469
+ "description": "How to hide content when mounted but not present.\n- `'display-none'`: HTML `hidden` attribute. Effects stay alive.\n- `'activity'`: React 19 `<Activity mode=\"hidden\">`. Effects pause. Requires React 19+.",
470
+ "from": "@ark-ui/react"
471
+ },
472
+ {
473
+ "name": "highlightedValue",
474
+ "type": "string | null",
475
+ "required": false,
476
+ "description": "The controlled key of the highlighted item",
477
+ "from": "@zag-js/select"
478
+ },
479
+ {
480
+ "name": "id",
481
+ "type": "string",
482
+ "required": false,
483
+ "description": "The unique identifier of the machine.",
484
+ "from": "@zag-js/types"
485
+ },
486
+ {
487
+ "name": "ids",
488
+ "type": "ElementIds",
489
+ "required": false,
490
+ "description": "The ids of the elements in the select. Useful for composition.",
491
+ "from": "@zag-js/select"
492
+ },
493
+ {
494
+ "name": "immediate",
495
+ "type": "boolean",
496
+ "required": false,
497
+ "description": "Whether to synchronize the present change immediately or defer it to the next frame",
498
+ "from": "@zag-js/presence"
499
+ },
500
+ {
501
+ "name": "invalid",
502
+ "type": "boolean",
503
+ "required": false,
504
+ "description": "Whether the select is invalid",
505
+ "from": "@zag-js/select"
506
+ },
507
+ {
508
+ "name": "lazyMount",
509
+ "type": "boolean",
510
+ "required": false,
511
+ "default": "false",
512
+ "description": "Whether to enable lazy mounting",
513
+ "from": "@ark-ui/react"
514
+ },
515
+ {
516
+ "name": "loopFocus",
517
+ "type": "boolean",
518
+ "required": false,
519
+ "default": "false",
520
+ "description": "Whether to loop the keyboard navigation through the options",
521
+ "from": "@zag-js/select"
522
+ },
523
+ {
524
+ "name": "multiple",
525
+ "type": "boolean",
526
+ "required": false,
527
+ "description": "Whether to allow multiple selection",
528
+ "from": "@zag-js/select"
529
+ },
530
+ {
531
+ "name": "name",
532
+ "type": "string",
533
+ "required": false,
534
+ "description": "The `name` attribute of the underlying select.",
535
+ "from": "@zag-js/select"
536
+ },
537
+ {
538
+ "name": "onEnterComplete",
539
+ "type": "VoidFunction",
540
+ "required": false,
541
+ "description": "Function called when the animation ends in the open state",
542
+ "from": "@zag-js/presence"
543
+ },
544
+ {
545
+ "name": "onExitComplete",
546
+ "type": "VoidFunction",
547
+ "required": false,
548
+ "description": "Function called when the animation ends in the closed state",
549
+ "from": "@zag-js/presence"
550
+ },
551
+ {
552
+ "name": "onFocusOutside",
553
+ "type": "((event: FocusOutsideEvent) => void)",
554
+ "required": false,
555
+ "description": "Function called when the focus is moved outside the component",
556
+ "from": "@zag-js/interact-outside"
557
+ },
558
+ {
559
+ "name": "onHighlightChange",
560
+ "type": "((details: HighlightChangeDetails<unknown>) => void)",
561
+ "required": false,
562
+ "description": "The callback fired when the highlighted item changes.",
563
+ "from": "@zag-js/select"
564
+ },
565
+ {
566
+ "name": "onInteractOutside",
567
+ "type": "((event: InteractOutsideEvent) => void)",
568
+ "required": false,
569
+ "description": "Function called when an interaction happens outside the component",
570
+ "from": "@zag-js/interact-outside"
571
+ },
572
+ {
573
+ "name": "onOpenChange",
574
+ "type": "((details: OpenChangeDetails) => void)",
575
+ "required": false,
576
+ "description": "Function called when the popup is opened",
577
+ "from": "@zag-js/select"
578
+ },
579
+ {
580
+ "name": "onPointerDownOutside",
581
+ "type": "((event: PointerDownOutsideEvent) => void)",
582
+ "required": false,
583
+ "description": "Function called when the pointer is pressed down outside the component",
584
+ "from": "@zag-js/interact-outside"
585
+ },
586
+ {
587
+ "name": "onSelect",
588
+ "type": "((details: SelectionDetails) => void)",
589
+ "required": false,
590
+ "description": "Function called when an item is selected",
591
+ "from": "@zag-js/select"
592
+ },
593
+ {
594
+ "name": "open",
595
+ "type": "boolean",
596
+ "required": false,
597
+ "description": "Whether the select menu is open",
598
+ "from": "@zag-js/select"
599
+ },
600
+ {
601
+ "name": "positioning",
602
+ "type": "PositioningOptions",
603
+ "required": false,
604
+ "description": "The positioning options of the menu.",
605
+ "from": "@zag-js/select"
606
+ },
607
+ {
608
+ "name": "present",
609
+ "type": "boolean",
610
+ "required": false,
611
+ "description": "Whether the node is present (controlled by the user)",
612
+ "from": "@zag-js/presence"
613
+ },
614
+ {
615
+ "name": "readOnly",
616
+ "type": "boolean",
617
+ "required": false,
618
+ "description": "Whether the select is read-only",
619
+ "from": "@zag-js/select"
620
+ },
621
+ {
622
+ "name": "required",
623
+ "type": "boolean",
624
+ "required": false,
625
+ "description": "Whether the select is required",
626
+ "from": "@zag-js/select"
627
+ },
628
+ {
629
+ "name": "scrollToIndexFn",
630
+ "type": "((details: ScrollToIndexDetails) => void)",
631
+ "required": false,
632
+ "description": "Function to scroll to a specific index",
633
+ "from": "@zag-js/select"
634
+ },
635
+ {
636
+ "name": "skipAnimationOnMount",
637
+ "type": "boolean",
638
+ "required": false,
639
+ "default": "false",
640
+ "description": "Whether to allow the initial presence animation.",
641
+ "from": "@ark-ui/react"
642
+ },
643
+ {
644
+ "name": "style",
645
+ "type": "CSSProperties",
646
+ "required": false,
647
+ "description": "Inline styles applied to the rendered element.",
648
+ "from": "@types/react"
649
+ },
650
+ {
651
+ "name": "translations",
652
+ "type": "IntlTranslations",
653
+ "required": false,
654
+ "description": "Specifies the localized strings that identifies the accessibility elements and their states",
655
+ "from": "@zag-js/select"
656
+ },
657
+ {
658
+ "name": "unmountOnExit",
659
+ "type": "boolean",
660
+ "required": false,
661
+ "default": "false",
662
+ "description": "Whether to unmount on exit.",
663
+ "from": "@ark-ui/react"
664
+ }
665
+ ]
666
+ },
667
+ {
668
+ "name": "RichTextEditorLinkTrigger",
669
+ "props": [
670
+ {
671
+ "name": "size",
672
+ "type": "\"default\" | \"sm\" | \"lg\" | null",
673
+ "required": false,
674
+ "description": "Size variant; sets a `data-size` attribute.",
675
+ "from": "arkcn"
676
+ },
677
+ {
678
+ "name": "variant",
679
+ "type": "\"default\" | \"outline\" | null",
680
+ "required": false,
681
+ "description": "Visual variant, styled with `cva`; sets a `data-variant` attribute.",
682
+ "from": "arkcn"
683
+ },
684
+ {
685
+ "name": "asChild",
686
+ "type": "boolean",
687
+ "required": false,
688
+ "description": "Use the provided child element as the default rendered element, combining their props and behavior.",
689
+ "from": "@ark-ui/react"
690
+ },
691
+ {
692
+ "name": "children",
693
+ "type": "ReactNode",
694
+ "required": false,
695
+ "description": "Content rendered inside the part.",
696
+ "from": "@types/react"
697
+ },
698
+ {
699
+ "name": "className",
700
+ "type": "string",
701
+ "required": false,
702
+ "description": "Extra classes merged into the part's own classes with `cn`.",
703
+ "from": "@types/react"
704
+ },
705
+ {
706
+ "name": "defaultPressed",
707
+ "type": "boolean",
708
+ "required": false,
709
+ "description": "The default pressed state of the toggle.",
710
+ "from": "@zag-js/toggle"
711
+ },
712
+ {
713
+ "name": "onPressedChange",
714
+ "type": "((pressed: boolean) => void)",
715
+ "required": false,
716
+ "description": "Event handler called when the pressed state of the toggle changes.",
717
+ "from": "@zag-js/toggle"
718
+ },
719
+ {
720
+ "name": "pressed",
721
+ "type": "boolean",
722
+ "required": false,
723
+ "description": "The pressed state of the toggle.",
724
+ "from": "@zag-js/toggle"
725
+ },
726
+ {
727
+ "name": "style",
728
+ "type": "CSSProperties",
729
+ "required": false,
730
+ "description": "Inline styles applied to the rendered element.",
731
+ "from": "@types/react"
732
+ }
733
+ ]
734
+ },
735
+ {
736
+ "name": "RichTextEditorImageTrigger",
737
+ "props": [
738
+ {
739
+ "name": "asChild",
740
+ "type": "boolean",
741
+ "required": false,
742
+ "description": "Render the child element instead of the default one, merging props and behaviour onto it.",
743
+ "from": "arkcn"
744
+ },
745
+ {
746
+ "name": "size",
747
+ "type": "\"default\" | \"xs\" | \"sm\" | \"lg\" | \"icon\" | \"icon-xs\" | \"icon-sm\" | \"icon-lg\" | null",
748
+ "required": false,
749
+ "description": "Size variant; sets a `data-size` attribute.",
750
+ "from": "arkcn"
751
+ },
752
+ {
753
+ "name": "variant",
754
+ "type": "\"link\" | \"default\" | \"outline\" | \"secondary\" | \"ghost\" | \"destructive\" | null",
755
+ "required": false,
756
+ "description": "Visual variant, styled with `cva`; sets a `data-variant` attribute.",
757
+ "from": "arkcn"
758
+ },
759
+ {
760
+ "name": "children",
761
+ "type": "ReactNode",
762
+ "required": false,
763
+ "description": "Content rendered inside the part.",
764
+ "from": "@types/react"
765
+ },
766
+ {
767
+ "name": "className",
768
+ "type": "string",
769
+ "required": false,
770
+ "description": "Extra classes merged into the part's own classes with `cn`.",
771
+ "from": "@types/react"
772
+ },
773
+ {
774
+ "name": "style",
775
+ "type": "CSSProperties",
776
+ "required": false,
777
+ "description": "Inline styles applied to the rendered element.",
778
+ "from": "@types/react"
779
+ }
780
+ ]
781
+ },
782
+ {
783
+ "name": "RichTextEditorContent",
784
+ "props": [
785
+ {
786
+ "name": "children",
787
+ "type": "ReactNode",
788
+ "required": false,
789
+ "description": "Content rendered inside the part.",
790
+ "from": "@types/react"
791
+ },
792
+ {
793
+ "name": "className",
794
+ "type": "string",
795
+ "required": false,
796
+ "description": "Extra classes merged into the part's own classes with `cn`.",
797
+ "from": "@types/react"
798
+ },
799
+ {
800
+ "name": "innerRef",
801
+ "type": "ForwardedRef<HTMLDivElement | null>",
802
+ "required": false,
803
+ "from": "@tiptap/react"
804
+ },
805
+ {
806
+ "name": "style",
807
+ "type": "CSSProperties",
808
+ "required": false,
809
+ "description": "Inline styles applied to the rendered element.",
810
+ "from": "@types/react"
811
+ }
812
+ ]
813
+ },
814
+ {
815
+ "name": "RichTextEditorBubbleMenu",
816
+ "props": [
817
+ {
818
+ "name": "appendTo",
819
+ "type": "HTMLElement | (() => HTMLElement)",
820
+ "required": false,
821
+ "default": "null",
822
+ "description": "The DOM element to append your menu to. Default is the editor's parent element.\n\nSometimes the menu needs to be appended to a different DOM context due to accessibility, clipping, or z-index issues.",
823
+ "from": "@tiptap/extension-bubble-menu"
824
+ },
825
+ {
826
+ "name": "children",
827
+ "type": "ReactNode",
828
+ "required": false,
829
+ "description": "Content rendered inside the part.",
830
+ "from": "@types/react"
831
+ },
832
+ {
833
+ "name": "className",
834
+ "type": "string",
835
+ "required": false,
836
+ "description": "Extra classes merged into the part's own classes with `cn`.",
837
+ "from": "@types/react"
838
+ },
839
+ {
840
+ "name": "getReferencedVirtualElement",
841
+ "type": "(() => VirtualElement | null)",
842
+ "required": false,
843
+ "default": "Position based on the selection.",
844
+ "description": "A function that returns the virtual element for the menu.\nThis is useful when the menu needs to be positioned relative to a specific DOM element.",
845
+ "from": "@tiptap/extension-bubble-menu"
846
+ },
847
+ {
848
+ "name": "options",
849
+ "type": "{ strategy?: \"absolute\" | \"fixed\" | undefined; placement?: \"left\" | \"right\" | \"top\" | \"bottom\" | \"top-start\" | \"top-end\" | \"right-start\" | \"right-end\" | \"bot…",
850
+ "required": false,
851
+ "default": "{}",
852
+ "description": "The options for the bubble menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, arrow, size, autoPlacement,\nhide, and inline middlewares.",
853
+ "from": "@tiptap/extension-bubble-menu"
854
+ },
855
+ {
856
+ "name": "pluginKey",
857
+ "type": "string | PluginKey<any>",
858
+ "required": false,
859
+ "default": "'bubbleMenu'",
860
+ "description": "The plugin key.",
861
+ "from": "@tiptap/extension-bubble-menu"
862
+ },
863
+ {
864
+ "name": "resizeDelay",
865
+ "type": "number",
866
+ "required": false,
867
+ "default": "60",
868
+ "description": "The delay in milliseconds before the menu position should be updated on window resize.\nThis can be useful to prevent performance issues.",
869
+ "from": "@tiptap/extension-bubble-menu"
870
+ },
871
+ {
872
+ "name": "shouldShow",
873
+ "type": "((props: { editor: Editor; element: HTMLElement; view: EditorView; state: EditorState; oldState?: EditorState | undefined; from: number; to: number; }) => bo…",
874
+ "required": false,
875
+ "description": "A function that determines whether the menu should be shown or not.\nIf this function returns `false`, the menu will be hidden, otherwise it will be shown.",
876
+ "from": "@tiptap/extension-bubble-menu"
877
+ },
878
+ {
879
+ "name": "style",
880
+ "type": "CSSProperties",
881
+ "required": false,
882
+ "description": "Inline styles applied to the rendered element.",
883
+ "from": "@types/react"
884
+ },
885
+ {
886
+ "name": "updateDelay",
887
+ "type": "number",
888
+ "required": false,
889
+ "default": "250",
890
+ "description": "The delay in milliseconds before the menu should be updated.\nThis can be useful to prevent performance issues.",
891
+ "from": "@tiptap/extension-bubble-menu"
892
+ }
893
+ ]
894
+ },
895
+ {
896
+ "name": "RichTextEditorFooter",
897
+ "props": [
898
+ {
899
+ "name": "children",
900
+ "type": "ReactNode",
901
+ "required": false,
902
+ "description": "Content rendered inside the part.",
903
+ "from": "@types/react"
904
+ },
905
+ {
906
+ "name": "className",
907
+ "type": "string",
908
+ "required": false,
909
+ "description": "Extra classes merged into the part's own classes with `cn`.",
910
+ "from": "@types/react"
911
+ },
912
+ {
913
+ "name": "style",
914
+ "type": "CSSProperties",
915
+ "required": false,
916
+ "description": "Inline styles applied to the rendered element.",
917
+ "from": "@types/react"
918
+ }
919
+ ]
920
+ },
921
+ {
922
+ "name": "RichTextEditorCharacterCount",
923
+ "props": [
924
+ {
925
+ "name": "asChild",
926
+ "type": "boolean",
927
+ "required": false,
928
+ "description": "Use the provided child element as the default rendered element, combining their props and behavior.",
929
+ "from": "@ark-ui/react"
930
+ },
931
+ {
932
+ "name": "children",
933
+ "type": "ReactNode",
934
+ "required": false,
935
+ "description": "Content rendered inside the part.",
936
+ "from": "@types/react"
937
+ },
938
+ {
939
+ "name": "className",
940
+ "type": "string",
941
+ "required": false,
942
+ "description": "Extra classes merged into the part's own classes with `cn`.",
943
+ "from": "@types/react"
944
+ },
945
+ {
946
+ "name": "style",
947
+ "type": "CSSProperties",
948
+ "required": false,
949
+ "description": "Inline styles applied to the rendered element.",
950
+ "from": "@types/react"
951
+ }
952
+ ]
953
+ }
954
+ ],
22
955
  "dependencies": {
23
956
  "@ark-ui/react": "^5.39.1",
24
957
  "@tiptap/extension-highlight": "^3.31.3",
@@ -46,7 +979,7 @@
46
979
  "files": [
47
980
  {
48
981
  "path": "components/ui/rich-text-editor.tsx",
49
- "content": "import * as React from \"react\"\nimport { ark } from \"@ark-ui/react\"\nimport { createListCollection } from \"@ark-ui/react/collection\"\nimport { Highlight } from \"@tiptap/extension-highlight\"\nimport { Image } from \"@tiptap/extension-image\"\nimport { TableKit } from \"@tiptap/extension-table\"\nimport { TaskItem } from \"@tiptap/extension-task-item\"\nimport { TaskList } from \"@tiptap/extension-task-list\"\nimport { TextAlign } from \"@tiptap/extension-text-align\"\nimport { CharacterCount, Placeholder } from \"@tiptap/extensions\"\nimport {\n EditorContent,\n useEditor,\n useEditorState,\n type Editor,\n type Extensions,\n type JSONContent,\n type UseEditorOptions,\n} from \"@tiptap/react\"\nimport { BubbleMenu, type BubbleMenuProps } from \"@tiptap/react/menus\"\nimport { StarterKit } from \"@tiptap/starter-kit\"\nimport {\n AlignCenterIcon,\n AlignJustifyIcon,\n AlignLeftIcon,\n AlignRightIcon,\n BoldIcon,\n CodeIcon,\n Heading1Icon,\n Heading2Icon,\n Heading3Icon,\n HighlighterIcon,\n ImageIcon,\n ItalicIcon,\n LinkIcon,\n ListChecksIcon,\n ListIcon,\n ListOrderedIcon,\n MinusIcon,\n PilcrowIcon,\n QuoteIcon,\n Redo2Icon,\n RemoveFormattingIcon,\n SquareCodeIcon,\n StrikethroughIcon,\n TableIcon,\n UnderlineIcon,\n Undo2Icon,\n UnlinkIcon,\n} from \"lucide-react\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Kbd } from \"@/components/ui/kbd\"\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\"\nimport {\n Select,\n SelectContent,\n SelectControl,\n SelectItem,\n SelectItemIndicator,\n SelectItemText,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { Toggle } from \"@/components/ui/toggle\"\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\n\n/**\n * RichTextEditor — compositional editor parts on tiptap. The root owns the\n * tiptap `Editor` (content is controllable as HTML) and reports changes as\n * `onChange({ html, json, text })`. Every toolbar control is its own part and\n * reads the editor from context, so a consumer composes exactly the toolbar\n * it wants; `RichTextEditorToggle name=\"bold\"` covers the common actions and\n * `isActive`/`onToggle` take over for custom ones.\n *\n * Anatomy:\n * RichTextEditor { content, onChange, placeholder, editable }\n * RichTextEditorToolbar > ToolbarGroup > Toggle name=… / Trigger name=… / BlockSelect / LinkTrigger / ImageTrigger\n * RichTextEditorContent the prose area\n * RichTextEditorBubbleMenu selection toolbar (same toggles)\n * RichTextEditorFooter > RichTextEditorCharacterCount\n */\n\ntype RichTextChange = { html: string; json: JSONContent; text: string }\n\ntype RichTextEditorContextValue = { editor: Editor | null; editable: boolean }\nconst RichTextEditorContext = React.createContext<RichTextEditorContextValue | null>(null)\n\nfunction useRichTextEditor() {\n const ctx = React.useContext(RichTextEditorContext)\n if (!ctx) throw new Error(\"RichTextEditor parts must be used within <RichTextEditor>\")\n return ctx\n}\n\n/* ---------------------------------------------------------------------------\n * Root\n * ------------------------------------------------------------------------- */\n\nfunction RichTextEditor({\n content,\n defaultContent = \"\",\n onChange,\n placeholder = \"Write something…\",\n editable = true,\n autofocus = false,\n extensions = [],\n characterLimit,\n editorOptions,\n className,\n children,\n ...props\n}: Omit<React.ComponentProps<\"div\">, \"content\" | \"onChange\"> & {\n /** Controlled HTML. Prefer `defaultContent` + `onChange` unless you need to replace the document. */\n content?: string\n defaultContent?: string\n onChange?: (change: RichTextChange) => void\n placeholder?: string\n editable?: boolean\n autofocus?: boolean\n /** Extra tiptap extensions appended to the built-in set. */\n extensions?: Extensions\n characterLimit?: number\n /** Escape hatch for any other `useEditor` option. */\n editorOptions?: Partial<UseEditorOptions>\n}) {\n const onChangeRef = React.useRef(onChange)\n React.useEffect(() => {\n onChangeRef.current = onChange\n }, [onChange])\n\n const editor = useEditor({\n extensions: [\n StarterKit.configure({\n heading: { levels: [1, 2, 3] },\n link: { openOnClick: false, autolink: true, defaultProtocol: \"https\" },\n }),\n Placeholder.configure({ placeholder }),\n CharacterCount.configure({ limit: characterLimit }),\n TaskList,\n TaskItem.configure({ nested: true }),\n Highlight,\n TextAlign.configure({ types: [\"heading\", \"paragraph\"] }),\n Image.configure({ inline: false }),\n TableKit.configure({ table: { resizable: false } }),\n ...extensions,\n ],\n content: content ?? defaultContent,\n editable,\n autofocus,\n editorProps: {\n attributes: { class: \"outline-none\" },\n ...editorOptions?.editorProps,\n },\n onUpdate: ({ editor }) => {\n onChangeRef.current?.({ html: editor.getHTML(), json: editor.getJSON(), text: editor.getText() })\n editorOptions?.onUpdate?.({ editor, transaction: editor.state.tr, appendedTransactions: [] })\n },\n ...editorOptions,\n })\n\n // Controlled content: replace the document only when the outside value actually differs.\n React.useEffect(() => {\n if (!editor || content === undefined) return\n if (content !== editor.getHTML()) editor.commands.setContent(content, { emitUpdate: false })\n }, [editor, content])\n\n React.useEffect(() => {\n if (editor && editor.isEditable !== editable) editor.setEditable(editable)\n }, [editor, editable])\n\n const ctx = React.useMemo(() => ({ editor, editable }), [editor, editable])\n\n return (\n <RichTextEditorContext.Provider value={ctx}>\n <div\n data-slot=\"rich-text-editor\"\n data-editable={editable ? \"\" : undefined}\n className={cn(\n \"flex min-h-0 flex-col rounded-xl border bg-background text-sm transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-focus-visible:border-ring\",\n className\n )}\n {...props}\n >\n {children}\n </div>\n </RichTextEditorContext.Provider>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Actions registry\n * ------------------------------------------------------------------------- */\n\ntype ActionDef = {\n label: string\n icon: React.ComponentType<{ className?: string }>\n shortcut?: string\n isActive?: (editor: Editor) => boolean\n canRun?: (editor: Editor) => boolean\n run: (editor: Editor) => void\n}\n\nconst chain = (editor: Editor) => editor.chain().focus()\n\nconst actions = {\n bold: {\n label: \"Bold\",\n icon: BoldIcon,\n shortcut: \"⌘B\",\n isActive: (e) => e.isActive(\"bold\"),\n run: (e) => chain(e).toggleBold().run(),\n },\n italic: {\n label: \"Italic\",\n icon: ItalicIcon,\n shortcut: \"⌘I\",\n isActive: (e) => e.isActive(\"italic\"),\n run: (e) => chain(e).toggleItalic().run(),\n },\n underline: {\n label: \"Underline\",\n icon: UnderlineIcon,\n shortcut: \"⌘U\",\n isActive: (e) => e.isActive(\"underline\"),\n run: (e) => chain(e).toggleUnderline().run(),\n },\n strike: {\n label: \"Strikethrough\",\n icon: StrikethroughIcon,\n shortcut: \"⌘⇧S\",\n isActive: (e) => e.isActive(\"strike\"),\n run: (e) => chain(e).toggleStrike().run(),\n },\n code: {\n label: \"Inline code\",\n icon: CodeIcon,\n shortcut: \"⌘E\",\n isActive: (e) => e.isActive(\"code\"),\n run: (e) => chain(e).toggleCode().run(),\n },\n highlight: {\n label: \"Highlight\",\n icon: HighlighterIcon,\n shortcut: \"⌘⇧H\",\n isActive: (e) => e.isActive(\"highlight\"),\n run: (e) => chain(e).toggleHighlight().run(),\n },\n paragraph: {\n label: \"Paragraph\",\n icon: PilcrowIcon,\n shortcut: \"⌘⌥0\",\n isActive: (e) => e.isActive(\"paragraph\"),\n run: (e) => chain(e).setParagraph().run(),\n },\n heading1: {\n label: \"Heading 1\",\n icon: Heading1Icon,\n shortcut: \"⌘⌥1\",\n isActive: (e) => e.isActive(\"heading\", { level: 1 }),\n run: (e) => chain(e).toggleHeading({ level: 1 }).run(),\n },\n heading2: {\n label: \"Heading 2\",\n icon: Heading2Icon,\n shortcut: \"⌘⌥2\",\n isActive: (e) => e.isActive(\"heading\", { level: 2 }),\n run: (e) => chain(e).toggleHeading({ level: 2 }).run(),\n },\n heading3: {\n label: \"Heading 3\",\n icon: Heading3Icon,\n shortcut: \"⌘⌥3\",\n isActive: (e) => e.isActive(\"heading\", { level: 3 }),\n run: (e) => chain(e).toggleHeading({ level: 3 }).run(),\n },\n bulletList: {\n label: \"Bullet list\",\n icon: ListIcon,\n shortcut: \"⌘⇧8\",\n isActive: (e) => e.isActive(\"bulletList\"),\n run: (e) => chain(e).toggleBulletList().run(),\n },\n orderedList: {\n label: \"Numbered list\",\n icon: ListOrderedIcon,\n shortcut: \"⌘⇧7\",\n isActive: (e) => e.isActive(\"orderedList\"),\n run: (e) => chain(e).toggleOrderedList().run(),\n },\n taskList: {\n label: \"Task list\",\n icon: ListChecksIcon,\n shortcut: \"⌘⇧9\",\n isActive: (e) => e.isActive(\"taskList\"),\n run: (e) => chain(e).toggleTaskList().run(),\n },\n blockquote: {\n label: \"Quote\",\n icon: QuoteIcon,\n shortcut: \"⌘⇧B\",\n isActive: (e) => e.isActive(\"blockquote\"),\n run: (e) => chain(e).toggleBlockquote().run(),\n },\n codeBlock: {\n label: \"Code block\",\n icon: SquareCodeIcon,\n shortcut: \"⌘⌥C\",\n isActive: (e) => e.isActive(\"codeBlock\"),\n run: (e) => chain(e).toggleCodeBlock().run(),\n },\n alignLeft: {\n label: \"Align left\",\n icon: AlignLeftIcon,\n shortcut: \"⌘⇧L\",\n isActive: (e) => e.isActive({ textAlign: \"left\" }),\n run: (e) => chain(e).setTextAlign(\"left\").run(),\n },\n alignCenter: {\n label: \"Align center\",\n icon: AlignCenterIcon,\n shortcut: \"⌘⇧E\",\n isActive: (e) => e.isActive({ textAlign: \"center\" }),\n run: (e) => chain(e).setTextAlign(\"center\").run(),\n },\n alignRight: {\n label: \"Align right\",\n icon: AlignRightIcon,\n shortcut: \"⌘⇧R\",\n isActive: (e) => e.isActive({ textAlign: \"right\" }),\n run: (e) => chain(e).setTextAlign(\"right\").run(),\n },\n alignJustify: {\n label: \"Justify\",\n icon: AlignJustifyIcon,\n shortcut: \"⌘⇧J\",\n isActive: (e) => e.isActive({ textAlign: \"justify\" }),\n run: (e) => chain(e).setTextAlign(\"justify\").run(),\n },\n horizontalRule: { label: \"Divider\", icon: MinusIcon, run: (e) => chain(e).setHorizontalRule().run() },\n clearFormatting: {\n label: \"Clear formatting\",\n icon: RemoveFormattingIcon,\n run: (e) => chain(e).unsetAllMarks().clearNodes().run(),\n },\n undo: {\n label: \"Undo\",\n icon: Undo2Icon,\n shortcut: \"⌘Z\",\n canRun: (e) => e.can().undo(),\n run: (e) => chain(e).undo().run(),\n },\n redo: {\n label: \"Redo\",\n icon: Redo2Icon,\n shortcut: \"⌘⇧Z\",\n canRun: (e) => e.can().redo(),\n run: (e) => chain(e).redo().run(),\n },\n unsetLink: {\n label: \"Remove link\",\n icon: UnlinkIcon,\n isActive: (e) => e.isActive(\"link\"),\n canRun: (e) => e.isActive(\"link\"),\n run: (e) => chain(e).unsetLink().run(),\n },\n insertTable: {\n label: \"Insert table\",\n icon: TableIcon,\n run: (e) => chain(e).insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),\n },\n} satisfies Record<string, ActionDef>\n\ntype ActionName = keyof typeof actions\n\n/** Subscribe to the bits of editor state a control needs; re-renders on selection and doc changes. */\nfunction useActionState(isActive?: (e: Editor) => boolean, canRun?: (e: Editor) => boolean) {\n const { editor, editable } = useRichTextEditor()\n const state = useEditorState({\n editor,\n selector: ({ editor }) => ({\n active: !!editor && !!isActive?.(editor),\n enabled: !!editor && editable && (canRun ? canRun(editor) : true),\n }),\n })\n return state ?? { active: false, enabled: false }\n}\n\n/* ---------------------------------------------------------------------------\n * Toolbar\n * ------------------------------------------------------------------------- */\n\nfunction RichTextEditorToolbar({ className, onKeyDown, ...props }: React.ComponentProps<\"div\">) {\n const ref = React.useRef<HTMLDivElement>(null)\n return (\n <div\n ref={ref}\n data-slot=\"rich-text-editor-toolbar\"\n role=\"toolbar\"\n aria-label=\"Formatting\"\n className={cn(\"flex flex-wrap items-center gap-1 border-b p-1.5\", className)}\n onKeyDown={(event) => {\n onKeyDown?.(event)\n if (event.defaultPrevented) return\n if (event.key !== \"ArrowRight\" && event.key !== \"ArrowLeft\") return\n const buttons = Array.from(ref.current?.querySelectorAll<HTMLElement>(\"button:not([disabled])\") ?? [])\n const index = buttons.indexOf(document.activeElement as HTMLElement)\n if (index === -1) return\n event.preventDefault()\n const next =\n event.key === \"ArrowRight\" ? (index + 1) % buttons.length : (index - 1 + buttons.length) % buttons.length\n buttons[next]?.focus()\n }}\n {...props}\n />\n )\n}\n\nfunction RichTextEditorToolbarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div data-slot=\"rich-text-editor-toolbar-group\" className={cn(\"flex items-center gap-0.5\", className)} {...props} />\n )\n}\n\nfunction RichTextEditorToolbarSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) {\n return (\n <Separator\n data-slot=\"rich-text-editor-toolbar-separator\"\n orientation=\"vertical\"\n className={cn(\"mx-1 h-5!\", className)}\n {...props}\n />\n )\n}\n\nfunction ControlTooltip({\n label,\n shortcut,\n children,\n}: {\n label: React.ReactNode\n shortcut?: string\n children: React.ReactElement\n}) {\n return (\n <Tooltip>\n <TooltipTrigger asChild>{children}</TooltipTrigger>\n <TooltipContent>\n {label}\n {shortcut && <Kbd className=\"ms-1.5\">{shortcut}</Kbd>}\n </TooltipContent>\n </Tooltip>\n )\n}\n\ntype ControlProps = {\n /** A built-in action; supplies icon, label, shortcut and behaviour. */\n name?: ActionName\n label?: string\n shortcut?: string\n isActive?: (editor: Editor) => boolean\n canRun?: (editor: Editor) => boolean\n onToggle?: (editor: Editor) => void\n tooltip?: boolean\n}\n\n/** A pressed/unpressed formatting control (marks, lists, alignment, block types). */\nfunction RichTextEditorToggle({\n name,\n label,\n shortcut,\n isActive,\n canRun,\n onToggle,\n tooltip = true,\n className,\n children,\n asChild,\n ...props\n}: Omit<React.ComponentProps<typeof Toggle>, \"pressed\" | \"onPressedChange\"> & ControlProps) {\n const { editor } = useRichTextEditor()\n const def: ActionDef | undefined = name ? actions[name] : undefined\n const activeFn = isActive ?? def?.isActive\n const canFn = canRun ?? def?.canRun\n const { active, enabled } = useActionState(activeFn, canFn)\n const text = label ?? def?.label ?? name ?? \"\"\n const Icon = def?.icon\n const control = (\n <Toggle\n data-slot=\"rich-text-editor-toggle\"\n data-action={name}\n size=\"sm\"\n aria-label={text}\n pressed={active}\n disabled={!enabled}\n asChild={asChild}\n className={cn(\"size-7 px-0\", className)}\n onPressedChange={() => {\n if (!editor) return\n if (onToggle) onToggle(editor)\n else def?.run(editor)\n }}\n {...props}\n >\n {asChild ? children : (children ?? (Icon ? <Icon /> : text))}\n </Toggle>\n )\n return tooltip ? (\n <ControlTooltip label={text} shortcut={shortcut ?? def?.shortcut}>\n {control}\n </ControlTooltip>\n ) : (\n control\n )\n}\n\n/** A one-shot action (undo, redo, divider, clear formatting, insert table). */\nfunction RichTextEditorTrigger({\n name,\n label,\n shortcut,\n canRun,\n onToggle,\n tooltip = true,\n className,\n children,\n asChild,\n onClick,\n ...props\n}: React.ComponentProps<typeof Button> & Omit<ControlProps, \"isActive\">) {\n const { editor } = useRichTextEditor()\n const def: ActionDef | undefined = name ? actions[name] : undefined\n const { enabled } = useActionState(undefined, canRun ?? def?.canRun)\n const text = label ?? def?.label ?? name ?? \"\"\n const Icon = def?.icon\n const control = (\n <Button\n data-slot=\"rich-text-editor-trigger\"\n data-action={name}\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={text}\n disabled={!enabled}\n asChild={asChild}\n className={className}\n onClick={(event) => {\n onClick?.(event)\n if (event.defaultPrevented || !editor) return\n if (onToggle) onToggle(editor)\n else def?.run(editor)\n }}\n {...props}\n >\n {asChild ? children : (children ?? (Icon ? <Icon /> : text))}\n </Button>\n )\n return tooltip ? (\n <ControlTooltip label={text} shortcut={shortcut ?? def?.shortcut}>\n {control}\n </ControlTooltip>\n ) : (\n control\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Block type select, link, image\n * ------------------------------------------------------------------------- */\n\nconst blockOptions: { value: ActionName; label: string }[] = [\n { value: \"paragraph\", label: \"Text\" },\n { value: \"heading1\", label: \"Heading 1\" },\n { value: \"heading2\", label: \"Heading 2\" },\n { value: \"heading3\", label: \"Heading 3\" },\n { value: \"blockquote\", label: \"Quote\" },\n { value: \"codeBlock\", label: \"Code block\" },\n]\nconst blockCollection = createListCollection({ items: blockOptions, itemToValue: (o) => o.value })\n\nfunction RichTextEditorBlockSelect({\n className,\n ...props\n}: Omit<React.ComponentProps<typeof Select>, \"collection\" | \"value\" | \"onValueChange\">) {\n const { editor, editable } = useRichTextEditor()\n const current = useEditorState({\n editor,\n selector: ({ editor }) =>\n editor\n ? (blockOptions.find((o) => (actions[o.value] as ActionDef).isActive?.(editor))?.value ?? \"paragraph\")\n : \"paragraph\",\n })\n const label = blockOptions.find((o) => o.value === current)?.label\n return (\n <Select\n collection={blockCollection}\n value={[current ?? \"paragraph\"]}\n onValueChange={({ value }) => {\n const name = value[0] as ActionName | undefined\n if (editor && name) actions[name].run(editor)\n }}\n disabled={!editable}\n positioning={{ sameWidth: false }}\n {...props}\n >\n <SelectControl>\n <SelectTrigger\n size=\"sm\"\n className={cn(\"w-32\", className)}\n data-slot=\"rich-text-editor-block-select\"\n aria-label=\"Block type\"\n >\n <SelectValue>{label}</SelectValue>\n </SelectTrigger>\n </SelectControl>\n <SelectContent>\n {blockOptions.map((o) => {\n const Icon = actions[o.value].icon\n return (\n <SelectItem key={o.value} item={o}>\n <Icon className=\"size-4 text-muted-foreground\" />\n <SelectItemText>{o.label}</SelectItemText>\n <SelectItemIndicator />\n </SelectItem>\n )\n })}\n </SelectContent>\n </Select>\n )\n}\n\n/** Set or edit a link on the selection through a small popover form. */\nfunction RichTextEditorLinkTrigger({ className, ...props }: React.ComponentProps<typeof Toggle>) {\n const { editor, editable } = useRichTextEditor()\n const { active } = useActionState((e) => e.isActive(\"link\"))\n const [open, setOpen] = React.useState(false)\n const [href, setHref] = React.useState(\"\")\n const apply = () => {\n if (!editor) return\n const url = href.trim()\n if (!url) editor.chain().focus().extendMarkRange(\"link\").unsetLink().run()\n else editor.chain().focus().extendMarkRange(\"link\").setLink({ href: url }).run()\n setOpen(false)\n }\n return (\n <Popover\n open={open}\n onOpenChange={({ open }) => {\n if (open) setHref((editor?.getAttributes(\"link\").href as string | undefined) ?? \"\")\n setOpen(open)\n }}\n positioning={{ placement: \"bottom-start\" }}\n >\n <ControlTooltip label=\"Link\" shortcut=\"⌘K\">\n <PopoverTrigger asChild>\n <Toggle\n data-slot=\"rich-text-editor-link-trigger\"\n size=\"sm\"\n aria-label=\"Link\"\n pressed={active}\n disabled={!editable}\n className={cn(\"size-7 px-0\", className)}\n {...props}\n >\n <LinkIcon />\n </Toggle>\n </PopoverTrigger>\n </ControlTooltip>\n <PopoverContent className=\"w-72\">\n <form\n className=\"flex items-center gap-2\"\n onSubmit={(event) => {\n event.preventDefault()\n apply()\n }}\n >\n <Input\n value={href}\n onChange={(e) => setHref(e.target.value)}\n placeholder=\"https://example.com\"\n aria-label=\"Link URL\"\n autoFocus\n className=\"h-8\"\n />\n <Button type=\"submit\" size=\"sm\">\n {href.trim() ? \"Apply\" : \"Remove\"}\n </Button>\n </form>\n </PopoverContent>\n </Popover>\n )\n}\n\n/** Insert an image by URL. */\nfunction RichTextEditorImageTrigger({ className, ...props }: React.ComponentProps<typeof Button>) {\n const { editor, editable } = useRichTextEditor()\n const [open, setOpen] = React.useState(false)\n const [src, setSrc] = React.useState(\"\")\n return (\n <Popover open={open} onOpenChange={({ open }) => setOpen(open)} positioning={{ placement: \"bottom-start\" }}>\n <ControlTooltip label=\"Image\">\n <PopoverTrigger asChild>\n <Button\n data-slot=\"rich-text-editor-image-trigger\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Image\"\n disabled={!editable}\n className={className}\n {...props}\n >\n <ImageIcon />\n </Button>\n </PopoverTrigger>\n </ControlTooltip>\n <PopoverContent className=\"w-72\">\n <form\n className=\"flex items-center gap-2\"\n onSubmit={(event) => {\n event.preventDefault()\n if (editor && src.trim()) editor.chain().focus().setImage({ src: src.trim() }).run()\n setSrc(\"\")\n setOpen(false)\n }}\n >\n <Input\n value={src}\n onChange={(e) => setSrc(e.target.value)}\n placeholder=\"https://…/image.png\"\n aria-label=\"Image URL\"\n autoFocus\n className=\"h-8\"\n />\n <Button type=\"submit\" size=\"sm\" disabled={!src.trim()}>\n Insert\n </Button>\n </form>\n </PopoverContent>\n </Popover>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Content, bubble menu, footer\n * ------------------------------------------------------------------------- */\n\nfunction RichTextEditorContent({ className, ...props }: Omit<React.ComponentProps<typeof EditorContent>, \"editor\">) {\n const { editor } = useRichTextEditor()\n return (\n <EditorContent\n editor={editor}\n data-slot=\"rich-text-editor-content\"\n className={cn(\n \"prose prose-sm min-h-0 max-w-none flex-1 overflow-y-auto px-4 py-3 prose-neutral dark:prose-invert\",\n \"[&_.tiptap]:min-h-40 [&_.tiptap]:outline-none\",\n className\n )}\n {...props}\n />\n )\n}\n\n/** A floating toolbar over the current text selection. Compose it from the same toggles. */\nfunction RichTextEditorBubbleMenu({ className, children, ...props }: Omit<BubbleMenuProps, \"editor\">) {\n const { editor, editable } = useRichTextEditor()\n if (!editor || !editable) return null\n return (\n <BubbleMenu\n editor={editor}\n options={{ placement: \"top\", offset: 8 }}\n shouldShow={({ editor, from, to }) =>\n editable && from !== to && !editor.isActive(\"image\") && !editor.isActive(\"codeBlock\")\n }\n className={cn(\n \"z-50 flex items-center gap-0.5 rounded-lg border bg-popover p-1 text-popover-foreground shadow-md\",\n className\n )}\n {...props}\n >\n <div\n data-slot=\"rich-text-editor-bubble-menu\"\n role=\"toolbar\"\n aria-label=\"Selection formatting\"\n className=\"contents\"\n >\n {children}\n </div>\n </BubbleMenu>\n )\n}\n\nfunction RichTextEditorFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"rich-text-editor-footer\"\n className={cn(\"flex items-center gap-3 border-t px-3 py-1.5 text-xs text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nfunction RichTextEditorCharacterCount({ className, ...props }: React.ComponentProps<typeof ark.span>) {\n const { editor } = useRichTextEditor()\n const stats = useEditorState({\n editor,\n selector: ({ editor }) => {\n const storage = editor?.storage.characterCount as { characters?: () => number; words?: () => number } | undefined\n return { characters: storage?.characters?.() ?? 0, words: storage?.words?.() ?? 0 }\n },\n })\n const limit = (\n editor?.extensionManager.extensions.find((e) => e.name === \"characterCount\")?.options as { limit?: number }\n )?.limit\n return (\n <ark.span\n data-slot=\"rich-text-editor-character-count\"\n data-over={limit && stats && stats.characters >= limit ? \"\" : undefined}\n className={cn(\"tabular-nums data-over:text-destructive\", className)}\n {...props}\n >\n {stats?.words ?? 0} words · {stats?.characters ?? 0}\n {limit ? ` / ${limit}` : \"\"} characters\n </ark.span>\n )\n}\n\nexport {\n RichTextEditor,\n RichTextEditorToolbar,\n RichTextEditorToolbarGroup,\n RichTextEditorToolbarSeparator,\n RichTextEditorToggle,\n RichTextEditorTrigger,\n RichTextEditorBlockSelect,\n RichTextEditorLinkTrigger,\n RichTextEditorImageTrigger,\n RichTextEditorContent,\n RichTextEditorBubbleMenu,\n RichTextEditorFooter,\n RichTextEditorCharacterCount,\n useRichTextEditor,\n actions as richTextActions,\n type RichTextChange,\n type ActionName as RichTextActionName,\n}\n"
982
+ "content": "import * as React from \"react\"\nimport { ark } from \"@ark-ui/react\"\nimport { createListCollection } from \"@ark-ui/react/collection\"\nimport { Highlight } from \"@tiptap/extension-highlight\"\nimport { Image } from \"@tiptap/extension-image\"\nimport { TableKit } from \"@tiptap/extension-table\"\nimport { TaskItem } from \"@tiptap/extension-task-item\"\nimport { TaskList } from \"@tiptap/extension-task-list\"\nimport { TextAlign } from \"@tiptap/extension-text-align\"\nimport { CharacterCount, Placeholder } from \"@tiptap/extensions\"\nimport {\n EditorContent,\n useEditor,\n useEditorState,\n type Editor,\n type Extensions,\n type JSONContent,\n type UseEditorOptions,\n} from \"@tiptap/react\"\nimport { BubbleMenu, type BubbleMenuProps } from \"@tiptap/react/menus\"\nimport { StarterKit } from \"@tiptap/starter-kit\"\nimport {\n AlignCenterIcon,\n AlignJustifyIcon,\n AlignLeftIcon,\n AlignRightIcon,\n BoldIcon,\n CodeIcon,\n Heading1Icon,\n Heading2Icon,\n Heading3Icon,\n HighlighterIcon,\n ImageIcon,\n ItalicIcon,\n LinkIcon,\n ListChecksIcon,\n ListIcon,\n ListOrderedIcon,\n MinusIcon,\n PilcrowIcon,\n QuoteIcon,\n Redo2Icon,\n RemoveFormattingIcon,\n SquareCodeIcon,\n StrikethroughIcon,\n TableIcon,\n UnderlineIcon,\n Undo2Icon,\n UnlinkIcon,\n} from \"lucide-react\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Kbd } from \"@/components/ui/kbd\"\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\"\nimport {\n Select,\n SelectContent,\n SelectControl,\n SelectItem,\n SelectItemIndicator,\n SelectItemText,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { Toggle } from \"@/components/ui/toggle\"\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\n\n/**\n * RichTextEditor — compositional editor parts on tiptap. The root owns the\n * tiptap `Editor` (content is controllable as HTML) and reports changes as\n * `onChange({ html, json, text })`. Every toolbar control is its own part and\n * reads the editor from context, so a consumer composes exactly the toolbar\n * it wants; `RichTextEditorToggle name=\"bold\"` covers the common actions and\n * `isActive`/`onToggle` take over for custom ones.\n *\n * Anatomy:\n * RichTextEditor { content, onChange, placeholder, editable }\n * RichTextEditorToolbar > ToolbarGroup > Toggle name=… / Trigger name=… / BlockSelect / LinkTrigger / ImageTrigger\n * RichTextEditorContent the prose area\n * RichTextEditorBubbleMenu selection toolbar (same toggles)\n * RichTextEditorFooter > RichTextEditorCharacterCount\n */\n\ntype RichTextChange = { html: string; json: JSONContent; text: string }\n\ntype RichTextEditorContextValue = { editor: Editor | null; editable: boolean }\nconst RichTextEditorContext = React.createContext<RichTextEditorContextValue | null>(null)\n\nfunction useRichTextEditor() {\n const ctx = React.useContext(RichTextEditorContext)\n if (!ctx) throw new Error(\"RichTextEditor parts must be used within <RichTextEditor>\")\n return ctx\n}\n\n/* ---------------------------------------------------------------------------\n * Root\n * ------------------------------------------------------------------------- */\n\nfunction RichTextEditor({\n content,\n defaultContent = \"\",\n onChange,\n placeholder = \"Write something…\",\n editable = true,\n autofocus = false,\n extensions = [],\n characterLimit,\n editorOptions,\n className,\n children,\n ...props\n}: Omit<React.ComponentProps<\"div\">, \"content\" | \"onChange\"> & {\n /** Controlled HTML. Prefer `defaultContent` + `onChange` unless you need to replace the document. */\n content?: string\n /** Initial HTML when uncontrolled. */\n defaultContent?: string\n onChange?: (change: RichTextChange) => void\n placeholder?: string\n editable?: boolean\n /** Focus the editor on mount. */\n autofocus?: boolean\n /** Extra tiptap extensions appended to the built-in set. */\n extensions?: Extensions\n /** Maximum characters; the count shows `data-over` past it. */\n characterLimit?: number\n /** Escape hatch for any other `useEditor` option. */\n editorOptions?: Partial<UseEditorOptions>\n}) {\n const onChangeRef = React.useRef(onChange)\n React.useEffect(() => {\n onChangeRef.current = onChange\n }, [onChange])\n\n const editor = useEditor({\n extensions: [\n StarterKit.configure({\n heading: { levels: [1, 2, 3] },\n link: { openOnClick: false, autolink: true, defaultProtocol: \"https\" },\n }),\n Placeholder.configure({ placeholder }),\n CharacterCount.configure({ limit: characterLimit }),\n TaskList,\n TaskItem.configure({ nested: true }),\n Highlight,\n TextAlign.configure({ types: [\"heading\", \"paragraph\"] }),\n Image.configure({ inline: false }),\n TableKit.configure({ table: { resizable: false } }),\n ...extensions,\n ],\n content: content ?? defaultContent,\n editable,\n autofocus,\n editorProps: {\n attributes: { class: \"outline-none\" },\n ...editorOptions?.editorProps,\n },\n onUpdate: ({ editor }) => {\n onChangeRef.current?.({ html: editor.getHTML(), json: editor.getJSON(), text: editor.getText() })\n editorOptions?.onUpdate?.({ editor, transaction: editor.state.tr, appendedTransactions: [] })\n },\n ...editorOptions,\n })\n\n // Controlled content: replace the document only when the outside value actually differs.\n React.useEffect(() => {\n if (!editor || content === undefined) return\n if (content !== editor.getHTML()) editor.commands.setContent(content, { emitUpdate: false })\n }, [editor, content])\n\n React.useEffect(() => {\n if (editor && editor.isEditable !== editable) editor.setEditable(editable)\n }, [editor, editable])\n\n const ctx = React.useMemo(() => ({ editor, editable }), [editor, editable])\n\n return (\n <RichTextEditorContext.Provider value={ctx}>\n <div\n data-slot=\"rich-text-editor\"\n data-editable={editable ? \"\" : undefined}\n className={cn(\n \"flex min-h-0 flex-col rounded-xl border bg-background text-sm transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-focus-visible:border-ring\",\n className\n )}\n {...props}\n >\n {children}\n </div>\n </RichTextEditorContext.Provider>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Actions registry\n * ------------------------------------------------------------------------- */\n\ntype ActionDef = {\n label: string\n icon: React.ComponentType<{ className?: string }>\n /** Keyboard shortcut shown in the tooltip. */\n shortcut?: string\n /** Whether the toggle is pressed. */\n isActive?: (editor: Editor) => boolean\n /** Whether the action can run right now; disables the control when false. */\n canRun?: (editor: Editor) => boolean\n run: (editor: Editor) => void\n}\n\nconst chain = (editor: Editor) => editor.chain().focus()\n\nconst actions = {\n bold: {\n label: \"Bold\",\n icon: BoldIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘B\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"bold\"),\n run: (e) => chain(e).toggleBold().run(),\n },\n italic: {\n label: \"Italic\",\n icon: ItalicIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘I\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"italic\"),\n run: (e) => chain(e).toggleItalic().run(),\n },\n underline: {\n label: \"Underline\",\n icon: UnderlineIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘U\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"underline\"),\n run: (e) => chain(e).toggleUnderline().run(),\n },\n strike: {\n label: \"Strikethrough\",\n icon: StrikethroughIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⇧S\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"strike\"),\n run: (e) => chain(e).toggleStrike().run(),\n },\n code: {\n label: \"Inline code\",\n icon: CodeIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘E\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"code\"),\n run: (e) => chain(e).toggleCode().run(),\n },\n highlight: {\n label: \"Highlight\",\n icon: HighlighterIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⇧H\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"highlight\"),\n run: (e) => chain(e).toggleHighlight().run(),\n },\n paragraph: {\n label: \"Paragraph\",\n icon: PilcrowIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⌥0\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"paragraph\"),\n run: (e) => chain(e).setParagraph().run(),\n },\n heading1: {\n label: \"Heading 1\",\n icon: Heading1Icon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⌥1\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"heading\", { level: 1 }),\n run: (e) => chain(e).toggleHeading({ level: 1 }).run(),\n },\n heading2: {\n label: \"Heading 2\",\n icon: Heading2Icon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⌥2\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"heading\", { level: 2 }),\n run: (e) => chain(e).toggleHeading({ level: 2 }).run(),\n },\n heading3: {\n label: \"Heading 3\",\n icon: Heading3Icon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⌥3\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"heading\", { level: 3 }),\n run: (e) => chain(e).toggleHeading({ level: 3 }).run(),\n },\n bulletList: {\n label: \"Bullet list\",\n icon: ListIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⇧8\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"bulletList\"),\n run: (e) => chain(e).toggleBulletList().run(),\n },\n orderedList: {\n label: \"Numbered list\",\n icon: ListOrderedIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⇧7\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"orderedList\"),\n run: (e) => chain(e).toggleOrderedList().run(),\n },\n taskList: {\n label: \"Task list\",\n icon: ListChecksIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⇧9\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"taskList\"),\n run: (e) => chain(e).toggleTaskList().run(),\n },\n blockquote: {\n label: \"Quote\",\n icon: QuoteIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⇧B\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"blockquote\"),\n run: (e) => chain(e).toggleBlockquote().run(),\n },\n codeBlock: {\n label: \"Code block\",\n icon: SquareCodeIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⌥C\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"codeBlock\"),\n run: (e) => chain(e).toggleCodeBlock().run(),\n },\n alignLeft: {\n label: \"Align left\",\n icon: AlignLeftIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⇧L\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive({ textAlign: \"left\" }),\n run: (e) => chain(e).setTextAlign(\"left\").run(),\n },\n alignCenter: {\n label: \"Align center\",\n icon: AlignCenterIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⇧E\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive({ textAlign: \"center\" }),\n run: (e) => chain(e).setTextAlign(\"center\").run(),\n },\n alignRight: {\n label: \"Align right\",\n icon: AlignRightIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⇧R\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive({ textAlign: \"right\" }),\n run: (e) => chain(e).setTextAlign(\"right\").run(),\n },\n alignJustify: {\n label: \"Justify\",\n icon: AlignJustifyIcon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⇧J\",\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive({ textAlign: \"justify\" }),\n run: (e) => chain(e).setTextAlign(\"justify\").run(),\n },\n horizontalRule: { label: \"Divider\", icon: MinusIcon, run: (e) => chain(e).setHorizontalRule().run() },\n clearFormatting: {\n label: \"Clear formatting\",\n icon: RemoveFormattingIcon,\n run: (e) => chain(e).unsetAllMarks().clearNodes().run(),\n },\n undo: {\n label: \"Undo\",\n icon: Undo2Icon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘Z\",\n /** Whether the action can run right now; disables the control when false. */\n canRun: (e) => e.can().undo(),\n run: (e) => chain(e).undo().run(),\n },\n redo: {\n label: \"Redo\",\n icon: Redo2Icon,\n /** Keyboard shortcut shown in the tooltip. */\n shortcut: \"⌘⇧Z\",\n /** Whether the action can run right now; disables the control when false. */\n canRun: (e) => e.can().redo(),\n run: (e) => chain(e).redo().run(),\n },\n unsetLink: {\n label: \"Remove link\",\n icon: UnlinkIcon,\n /** Whether the toggle is pressed. */\n isActive: (e) => e.isActive(\"link\"),\n /** Whether the action can run right now; disables the control when false. */\n canRun: (e) => e.isActive(\"link\"),\n run: (e) => chain(e).unsetLink().run(),\n },\n insertTable: {\n label: \"Insert table\",\n icon: TableIcon,\n run: (e) => chain(e).insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),\n },\n} satisfies Record<string, ActionDef>\n\ntype ActionName = keyof typeof actions\n\n/** Subscribe to the bits of editor state a control needs; re-renders on selection and doc changes. */\nfunction useActionState(isActive?: (e: Editor) => boolean, canRun?: (e: Editor) => boolean) {\n const { editor, editable } = useRichTextEditor()\n const state = useEditorState({\n editor,\n selector: ({ editor }) => ({\n active: !!editor && !!isActive?.(editor),\n enabled: !!editor && editable && (canRun ? canRun(editor) : true),\n }),\n })\n return state ?? { active: false, enabled: false }\n}\n\n/* ---------------------------------------------------------------------------\n * Toolbar\n * ------------------------------------------------------------------------- */\n\nfunction RichTextEditorToolbar({ className, onKeyDown, ...props }: React.ComponentProps<\"div\">) {\n const ref = React.useRef<HTMLDivElement>(null)\n return (\n <div\n ref={ref}\n data-slot=\"rich-text-editor-toolbar\"\n role=\"toolbar\"\n aria-label=\"Formatting\"\n className={cn(\"flex flex-wrap items-center gap-1 border-b p-1.5\", className)}\n onKeyDown={(event) => {\n onKeyDown?.(event)\n if (event.defaultPrevented) return\n if (event.key !== \"ArrowRight\" && event.key !== \"ArrowLeft\") return\n const buttons = Array.from(ref.current?.querySelectorAll<HTMLElement>(\"button:not([disabled])\") ?? [])\n const index = buttons.indexOf(document.activeElement as HTMLElement)\n if (index === -1) return\n event.preventDefault()\n const next =\n event.key === \"ArrowRight\" ? (index + 1) % buttons.length : (index - 1 + buttons.length) % buttons.length\n buttons[next]?.focus()\n }}\n {...props}\n />\n )\n}\n\nfunction RichTextEditorToolbarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div data-slot=\"rich-text-editor-toolbar-group\" className={cn(\"flex items-center gap-0.5\", className)} {...props} />\n )\n}\n\nfunction RichTextEditorToolbarSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) {\n return (\n <Separator\n data-slot=\"rich-text-editor-toolbar-separator\"\n orientation=\"vertical\"\n className={cn(\"mx-1 h-5!\", className)}\n {...props}\n />\n )\n}\n\nfunction ControlTooltip({\n label,\n shortcut,\n children,\n}: {\n label: React.ReactNode\n /** Keyboard shortcut shown in the tooltip. */\n shortcut?: string\n children: React.ReactElement\n}) {\n return (\n <Tooltip>\n <TooltipTrigger asChild>{children}</TooltipTrigger>\n <TooltipContent>\n {label}\n {shortcut && <Kbd className=\"ms-1.5\">{shortcut}</Kbd>}\n </TooltipContent>\n </Tooltip>\n )\n}\n\ntype ControlProps = {\n /** A built-in action; supplies icon, label, shortcut and behaviour. */\n name?: ActionName\n label?: string\n /** Keyboard shortcut shown in the tooltip. */\n shortcut?: string\n /** Whether the toggle is pressed. */\n isActive?: (editor: Editor) => boolean\n /** Whether the action can run right now; disables the control when false. */\n canRun?: (editor: Editor) => boolean\n onToggle?: (editor: Editor) => void\n tooltip?: boolean\n}\n\n/** A pressed/unpressed formatting control (marks, lists, alignment, block types). */\nfunction RichTextEditorToggle({\n name,\n label,\n shortcut,\n isActive,\n canRun,\n onToggle,\n tooltip = true,\n className,\n children,\n asChild,\n ...props\n}: Omit<React.ComponentProps<typeof Toggle>, \"pressed\" | \"onPressedChange\"> & ControlProps) {\n const { editor } = useRichTextEditor()\n const def: ActionDef | undefined = name ? actions[name] : undefined\n const activeFn = isActive ?? def?.isActive\n const canFn = canRun ?? def?.canRun\n const { active, enabled } = useActionState(activeFn, canFn)\n const text = label ?? def?.label ?? name ?? \"\"\n const Icon = def?.icon\n const control = (\n <Toggle\n data-slot=\"rich-text-editor-toggle\"\n data-action={name}\n size=\"sm\"\n aria-label={text}\n pressed={active}\n disabled={!enabled}\n asChild={asChild}\n className={cn(\"size-7 px-0\", className)}\n onPressedChange={() => {\n if (!editor) return\n if (onToggle) onToggle(editor)\n else def?.run(editor)\n }}\n {...props}\n >\n {asChild ? children : (children ?? (Icon ? <Icon /> : text))}\n </Toggle>\n )\n return tooltip ? (\n <ControlTooltip label={text} shortcut={shortcut ?? def?.shortcut}>\n {control}\n </ControlTooltip>\n ) : (\n control\n )\n}\n\n/** A one-shot action (undo, redo, divider, clear formatting, insert table). */\nfunction RichTextEditorTrigger({\n name,\n label,\n shortcut,\n canRun,\n onToggle,\n tooltip = true,\n className,\n children,\n asChild,\n onClick,\n ...props\n}: React.ComponentProps<typeof Button> & Omit<ControlProps, \"isActive\">) {\n const { editor } = useRichTextEditor()\n const def: ActionDef | undefined = name ? actions[name] : undefined\n const { enabled } = useActionState(undefined, canRun ?? def?.canRun)\n const text = label ?? def?.label ?? name ?? \"\"\n const Icon = def?.icon\n const control = (\n <Button\n data-slot=\"rich-text-editor-trigger\"\n data-action={name}\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={text}\n disabled={!enabled}\n asChild={asChild}\n className={className}\n onClick={(event) => {\n onClick?.(event)\n if (event.defaultPrevented || !editor) return\n if (onToggle) onToggle(editor)\n else def?.run(editor)\n }}\n {...props}\n >\n {asChild ? children : (children ?? (Icon ? <Icon /> : text))}\n </Button>\n )\n return tooltip ? (\n <ControlTooltip label={text} shortcut={shortcut ?? def?.shortcut}>\n {control}\n </ControlTooltip>\n ) : (\n control\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Block type select, link, image\n * ------------------------------------------------------------------------- */\n\nconst blockOptions: { value: ActionName; label: string }[] = [\n { value: \"paragraph\", label: \"Text\" },\n { value: \"heading1\", label: \"Heading 1\" },\n { value: \"heading2\", label: \"Heading 2\" },\n { value: \"heading3\", label: \"Heading 3\" },\n { value: \"blockquote\", label: \"Quote\" },\n { value: \"codeBlock\", label: \"Code block\" },\n]\nconst blockCollection = createListCollection({ items: blockOptions, itemToValue: (o) => o.value })\n\nfunction RichTextEditorBlockSelect({\n className,\n ...props\n}: Omit<React.ComponentProps<typeof Select>, \"collection\" | \"value\" | \"onValueChange\">) {\n const { editor, editable } = useRichTextEditor()\n const current = useEditorState({\n editor,\n selector: ({ editor }) =>\n editor\n ? (blockOptions.find((o) => (actions[o.value] as ActionDef).isActive?.(editor))?.value ?? \"paragraph\")\n : \"paragraph\",\n })\n const label = blockOptions.find((o) => o.value === current)?.label\n return (\n <Select\n collection={blockCollection}\n value={[current ?? \"paragraph\"]}\n onValueChange={({ value }) => {\n const name = value[0] as ActionName | undefined\n if (editor && name) actions[name].run(editor)\n }}\n disabled={!editable}\n positioning={{ sameWidth: false }}\n {...props}\n >\n <SelectControl>\n <SelectTrigger\n size=\"sm\"\n className={cn(\"w-32\", className)}\n data-slot=\"rich-text-editor-block-select\"\n aria-label=\"Block type\"\n >\n <SelectValue>{label}</SelectValue>\n </SelectTrigger>\n </SelectControl>\n <SelectContent>\n {blockOptions.map((o) => {\n const Icon = actions[o.value].icon\n return (\n <SelectItem key={o.value} item={o}>\n <Icon className=\"size-4 text-muted-foreground\" />\n <SelectItemText>{o.label}</SelectItemText>\n <SelectItemIndicator />\n </SelectItem>\n )\n })}\n </SelectContent>\n </Select>\n )\n}\n\n/** Set or edit a link on the selection through a small popover form. */\nfunction RichTextEditorLinkTrigger({ className, ...props }: React.ComponentProps<typeof Toggle>) {\n const { editor, editable } = useRichTextEditor()\n const { active } = useActionState((e) => e.isActive(\"link\"))\n const [open, setOpen] = React.useState(false)\n const [href, setHref] = React.useState(\"\")\n const apply = () => {\n if (!editor) return\n const url = href.trim()\n if (!url) editor.chain().focus().extendMarkRange(\"link\").unsetLink().run()\n else editor.chain().focus().extendMarkRange(\"link\").setLink({ href: url }).run()\n setOpen(false)\n }\n return (\n <Popover\n open={open}\n onOpenChange={({ open }) => {\n if (open) setHref((editor?.getAttributes(\"link\").href as string | undefined) ?? \"\")\n setOpen(open)\n }}\n positioning={{ placement: \"bottom-start\" }}\n >\n <ControlTooltip label=\"Link\" shortcut=\"⌘K\">\n <PopoverTrigger asChild>\n <Toggle\n data-slot=\"rich-text-editor-link-trigger\"\n size=\"sm\"\n aria-label=\"Link\"\n pressed={active}\n disabled={!editable}\n className={cn(\"size-7 px-0\", className)}\n {...props}\n >\n <LinkIcon />\n </Toggle>\n </PopoverTrigger>\n </ControlTooltip>\n <PopoverContent className=\"w-72\">\n <form\n className=\"flex items-center gap-2\"\n onSubmit={(event) => {\n event.preventDefault()\n apply()\n }}\n >\n <Input\n value={href}\n onChange={(e) => setHref(e.target.value)}\n placeholder=\"https://example.com\"\n aria-label=\"Link URL\"\n autoFocus\n className=\"h-8\"\n />\n <Button type=\"submit\" size=\"sm\">\n {href.trim() ? \"Apply\" : \"Remove\"}\n </Button>\n </form>\n </PopoverContent>\n </Popover>\n )\n}\n\n/** Insert an image by URL. */\nfunction RichTextEditorImageTrigger({ className, ...props }: React.ComponentProps<typeof Button>) {\n const { editor, editable } = useRichTextEditor()\n const [open, setOpen] = React.useState(false)\n const [src, setSrc] = React.useState(\"\")\n return (\n <Popover open={open} onOpenChange={({ open }) => setOpen(open)} positioning={{ placement: \"bottom-start\" }}>\n <ControlTooltip label=\"Image\">\n <PopoverTrigger asChild>\n <Button\n data-slot=\"rich-text-editor-image-trigger\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Image\"\n disabled={!editable}\n className={className}\n {...props}\n >\n <ImageIcon />\n </Button>\n </PopoverTrigger>\n </ControlTooltip>\n <PopoverContent className=\"w-72\">\n <form\n className=\"flex items-center gap-2\"\n onSubmit={(event) => {\n event.preventDefault()\n if (editor && src.trim()) editor.chain().focus().setImage({ src: src.trim() }).run()\n setSrc(\"\")\n setOpen(false)\n }}\n >\n <Input\n value={src}\n onChange={(e) => setSrc(e.target.value)}\n placeholder=\"https://…/image.png\"\n aria-label=\"Image URL\"\n autoFocus\n className=\"h-8\"\n />\n <Button type=\"submit\" size=\"sm\" disabled={!src.trim()}>\n Insert\n </Button>\n </form>\n </PopoverContent>\n </Popover>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Content, bubble menu, footer\n * ------------------------------------------------------------------------- */\n\nfunction RichTextEditorContent({ className, ...props }: Omit<React.ComponentProps<typeof EditorContent>, \"editor\">) {\n const { editor } = useRichTextEditor()\n return (\n <EditorContent\n editor={editor}\n data-slot=\"rich-text-editor-content\"\n className={cn(\n \"prose prose-sm min-h-0 max-w-none flex-1 overflow-y-auto px-4 py-3 prose-neutral dark:prose-invert\",\n \"[&_.tiptap]:min-h-40 [&_.tiptap]:outline-none\",\n className\n )}\n {...props}\n />\n )\n}\n\n/** A floating toolbar over the current text selection. Compose it from the same toggles. */\nfunction RichTextEditorBubbleMenu({ className, children, ...props }: Omit<BubbleMenuProps, \"editor\">) {\n const { editor, editable } = useRichTextEditor()\n if (!editor || !editable) return null\n return (\n <BubbleMenu\n editor={editor}\n options={{ placement: \"top\", offset: 8 }}\n shouldShow={({ editor, from, to }) =>\n editable && from !== to && !editor.isActive(\"image\") && !editor.isActive(\"codeBlock\")\n }\n className={cn(\n \"z-50 flex items-center gap-0.5 rounded-lg border bg-popover p-1 text-popover-foreground shadow-md\",\n className\n )}\n {...props}\n >\n <div\n data-slot=\"rich-text-editor-bubble-menu\"\n role=\"toolbar\"\n aria-label=\"Selection formatting\"\n className=\"contents\"\n >\n {children}\n </div>\n </BubbleMenu>\n )\n}\n\nfunction RichTextEditorFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"rich-text-editor-footer\"\n className={cn(\"flex items-center gap-3 border-t px-3 py-1.5 text-xs text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nfunction RichTextEditorCharacterCount({ className, ...props }: React.ComponentProps<typeof ark.span>) {\n const { editor } = useRichTextEditor()\n const stats = useEditorState({\n editor,\n selector: ({ editor }) => {\n const storage = editor?.storage.characterCount as { characters?: () => number; words?: () => number } | undefined\n return { characters: storage?.characters?.() ?? 0, words: storage?.words?.() ?? 0 }\n },\n })\n const limit = (\n editor?.extensionManager.extensions.find((e) => e.name === \"characterCount\")?.options as { limit?: number }\n )?.limit\n return (\n <ark.span\n data-slot=\"rich-text-editor-character-count\"\n data-over={limit && stats && stats.characters >= limit ? \"\" : undefined}\n className={cn(\"tabular-nums data-over:text-destructive\", className)}\n {...props}\n >\n {stats?.words ?? 0} words · {stats?.characters ?? 0}\n {limit ? ` / ${limit}` : \"\"} characters\n </ark.span>\n )\n}\n\nexport {\n RichTextEditor,\n RichTextEditorToolbar,\n RichTextEditorToolbarGroup,\n RichTextEditorToolbarSeparator,\n RichTextEditorToggle,\n RichTextEditorTrigger,\n RichTextEditorBlockSelect,\n RichTextEditorLinkTrigger,\n RichTextEditorImageTrigger,\n RichTextEditorContent,\n RichTextEditorBubbleMenu,\n RichTextEditorFooter,\n RichTextEditorCharacterCount,\n useRichTextEditor,\n actions as richTextActions,\n type RichTextChange,\n type ActionName as RichTextActionName,\n}\n"
50
983
  }
51
984
  ],
52
985
  "css": "/* Rich text editor (tiptap) */\n[data-slot=\"rich-text-editor-content\"] .tiptap p.is-editor-empty:first-child::before {\n content: attr(data-placeholder);\n float: left;\n height: 0;\n pointer-events: none;\n color: var(--color-muted-foreground);\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap ul[data-type=\"taskList\"] {\n list-style: none;\n padding-inline-start: 0;\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap ul[data-type=\"taskList\"] li {\n display: flex;\n gap: 0.5rem;\n align-items: flex-start;\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap ul[data-type=\"taskList\"] li > label {\n flex: none;\n margin-top: 0.3em;\n user-select: none;\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap ul[data-type=\"taskList\"] li > div {\n flex: 1 1 auto;\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap ul[data-type=\"taskList\"] li > div > p {\n margin: 0;\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap ul[data-type=\"taskList\"] li[data-checked=\"true\"] > div {\n color: var(--color-muted-foreground);\n text-decoration: line-through;\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap ul[data-type=\"taskList\"] input[type=\"checkbox\"] {\n accent-color: var(--color-primary);\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap mark {\n background-color: color-mix(in oklab, var(--color-amber-400) 45%, transparent);\n color: inherit;\n border-radius: 0.2em;\n padding-inline: 0.1em;\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap table {\n table-layout: fixed;\n width: 100%;\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap table td,\n[data-slot=\"rich-text-editor-content\"] .tiptap table th {\n border: 1px solid var(--color-border);\n padding: 0.375rem 0.5rem;\n vertical-align: top;\n position: relative;\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap table th {\n background: color-mix(in oklab, var(--color-muted) 60%, transparent);\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap table .selectedCell::after {\n content: \"\";\n position: absolute;\n inset: 0;\n background: color-mix(in oklab, var(--color-primary) 12%, transparent);\n pointer-events: none;\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap img {\n border-radius: var(--radius-lg);\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap img.ProseMirror-selectednode,\n[data-slot=\"rich-text-editor-content\"] .tiptap .ProseMirror-selectednode {\n outline: 2px solid var(--color-ring);\n outline-offset: 2px;\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap a {\n color: var(--color-primary);\n text-decoration: underline;\n text-underline-offset: 0.2em;\n}\n[data-slot=\"rich-text-editor-content\"] .tiptap :where(h1, h2, h3):first-child {\n margin-top: 0;\n}\n",