@betterstart/cli 0.1.3 → 0.1.4

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 (234) hide show
  1. package/README.md +133 -0
  2. package/dist/cli.d.ts +1 -9
  3. package/dist/cli.js +13484 -367
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.d.ts +24 -266
  6. package/dist/index.js +4 -11378
  7. package/dist/index.js.map +1 -1
  8. package/package.json +29 -42
  9. package/templates/schema.json +959 -0
  10. package/templates/tiptap/hooks/use-composed-ref.ts +43 -0
  11. package/templates/tiptap/hooks/use-cursor-visibility.ts +68 -0
  12. package/templates/tiptap/hooks/use-element-rect.ts +166 -0
  13. package/templates/tiptap/hooks/use-is-breakpoint.ts +32 -0
  14. package/templates/tiptap/hooks/use-menu-navigation.ts +182 -0
  15. package/templates/tiptap/hooks/use-scrolling.ts +64 -0
  16. package/templates/tiptap/hooks/use-throttled-callback.ts +146 -0
  17. package/templates/tiptap/hooks/use-tiptap-editor.ts +46 -0
  18. package/templates/tiptap/hooks/use-unmount.ts +21 -0
  19. package/templates/tiptap/hooks/use-window-size.ts +87 -0
  20. package/templates/tiptap/lib/tiptap-utils.ts +587 -0
  21. package/templates/tiptap/styles/_keyframe-animations.scss +91 -0
  22. package/templates/tiptap/styles/_variables.scss +296 -0
  23. package/templates/tiptap/tiptap-extension/node-background-extension.ts +138 -0
  24. package/templates/tiptap/tiptap-icons/align-center-icon.tsx +38 -0
  25. package/templates/tiptap/tiptap-icons/align-justify-icon.tsx +38 -0
  26. package/templates/tiptap/tiptap-icons/align-left-icon.tsx +38 -0
  27. package/templates/tiptap/tiptap-icons/align-right-icon.tsx +38 -0
  28. package/templates/tiptap/tiptap-icons/arrow-left-icon.tsx +24 -0
  29. package/templates/tiptap/tiptap-icons/ban-icon.tsx +26 -0
  30. package/templates/tiptap/tiptap-icons/blockquote-icon.tsx +44 -0
  31. package/templates/tiptap/tiptap-icons/bold-icon.tsx +26 -0
  32. package/templates/tiptap/tiptap-icons/chevron-down-icon.tsx +26 -0
  33. package/templates/tiptap/tiptap-icons/close-icon.tsx +24 -0
  34. package/templates/tiptap/tiptap-icons/code-block-icon.tsx +38 -0
  35. package/templates/tiptap/tiptap-icons/code2-icon.tsx +32 -0
  36. package/templates/tiptap/tiptap-icons/corner-down-left-icon.tsx +26 -0
  37. package/templates/tiptap/tiptap-icons/external-link-icon.tsx +28 -0
  38. package/templates/tiptap/tiptap-icons/heading-five-icon.tsx +28 -0
  39. package/templates/tiptap/tiptap-icons/heading-four-icon.tsx +28 -0
  40. package/templates/tiptap/tiptap-icons/heading-icon.tsx +24 -0
  41. package/templates/tiptap/tiptap-icons/heading-one-icon.tsx +28 -0
  42. package/templates/tiptap/tiptap-icons/heading-six-icon.tsx +30 -0
  43. package/templates/tiptap/tiptap-icons/heading-three-icon.tsx +36 -0
  44. package/templates/tiptap/tiptap-icons/heading-two-icon.tsx +28 -0
  45. package/templates/tiptap/tiptap-icons/highlighter-icon.tsx +26 -0
  46. package/templates/tiptap/tiptap-icons/image-plus-icon.tsx +26 -0
  47. package/templates/tiptap/tiptap-icons/italic-icon.tsx +24 -0
  48. package/templates/tiptap/tiptap-icons/link-icon.tsx +28 -0
  49. package/templates/tiptap/tiptap-icons/list-icon.tsx +56 -0
  50. package/templates/tiptap/tiptap-icons/list-ordered-icon.tsx +56 -0
  51. package/templates/tiptap/tiptap-icons/list-todo-icon.tsx +50 -0
  52. package/templates/tiptap/tiptap-icons/moon-star-icon.tsx +30 -0
  53. package/templates/tiptap/tiptap-icons/redo2-icon.tsx +26 -0
  54. package/templates/tiptap/tiptap-icons/strike-icon.tsx +28 -0
  55. package/templates/tiptap/tiptap-icons/subscript-icon.tsx +38 -0
  56. package/templates/tiptap/tiptap-icons/sun-icon.tsx +58 -0
  57. package/templates/tiptap/tiptap-icons/superscript-icon.tsx +38 -0
  58. package/templates/tiptap/tiptap-icons/trash-icon.tsx +26 -0
  59. package/templates/tiptap/tiptap-icons/underline-icon.tsx +26 -0
  60. package/templates/tiptap/tiptap-icons/undo2-icon.tsx +26 -0
  61. package/templates/tiptap/tiptap-node/blockquote-node/blockquote-node.scss +37 -0
  62. package/templates/tiptap/tiptap-node/code-block-node/code-block-node.scss +54 -0
  63. package/templates/tiptap/tiptap-node/heading-node/heading-node.scss +45 -0
  64. package/templates/tiptap/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts +10 -0
  65. package/templates/tiptap/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss +25 -0
  66. package/templates/tiptap/tiptap-node/image-node/image-node.scss +35 -0
  67. package/templates/tiptap/tiptap-node/image-upload-node/image-upload-node-extension.ts +154 -0
  68. package/templates/tiptap/tiptap-node/image-upload-node/image-upload-node.scss +249 -0
  69. package/templates/tiptap/tiptap-node/image-upload-node/image-upload-node.tsx +522 -0
  70. package/templates/tiptap/tiptap-node/image-upload-node/index.tsx +1 -0
  71. package/templates/tiptap/tiptap-node/list-node/list-node.scss +208 -0
  72. package/templates/tiptap/tiptap-node/paragraph-node/paragraph-node.scss +273 -0
  73. package/templates/tiptap/tiptap-ui/blockquote-button/blockquote-button.tsx +104 -0
  74. package/templates/tiptap/tiptap-ui/blockquote-button/index.tsx +2 -0
  75. package/templates/tiptap/tiptap-ui/blockquote-button/use-blockquote.ts +252 -0
  76. package/templates/tiptap/tiptap-ui/code-block-button/code-block-button.tsx +106 -0
  77. package/templates/tiptap/tiptap-ui/code-block-button/index.tsx +2 -0
  78. package/templates/tiptap/tiptap-ui/code-block-button/use-code-block.ts +261 -0
  79. package/templates/tiptap/tiptap-ui/color-highlight-button/color-highlight-button.scss +49 -0
  80. package/templates/tiptap/tiptap-ui/color-highlight-button/color-highlight-button.tsx +153 -0
  81. package/templates/tiptap/tiptap-ui/color-highlight-button/index.tsx +2 -0
  82. package/templates/tiptap/tiptap-ui/color-highlight-button/use-color-highlight.ts +345 -0
  83. package/templates/tiptap/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx +207 -0
  84. package/templates/tiptap/tiptap-ui/color-highlight-popover/index.tsx +1 -0
  85. package/templates/tiptap/tiptap-ui/heading-button/heading-button.tsx +107 -0
  86. package/templates/tiptap/tiptap-ui/heading-button/index.tsx +2 -0
  87. package/templates/tiptap/tiptap-ui/heading-button/use-heading.ts +314 -0
  88. package/templates/tiptap/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +131 -0
  89. package/templates/tiptap/tiptap-ui/heading-dropdown-menu/index.tsx +2 -0
  90. package/templates/tiptap/tiptap-ui/heading-dropdown-menu/use-heading-dropdown-menu.ts +130 -0
  91. package/templates/tiptap/tiptap-ui/image-upload-button/image-upload-button.tsx +114 -0
  92. package/templates/tiptap/tiptap-ui/image-upload-button/index.tsx +2 -0
  93. package/templates/tiptap/tiptap-ui/image-upload-button/use-image-upload.ts +192 -0
  94. package/templates/tiptap/tiptap-ui/link-popover/index.tsx +2 -0
  95. package/templates/tiptap/tiptap-ui/link-popover/link-popover.tsx +285 -0
  96. package/templates/tiptap/tiptap-ui/link-popover/use-link-popover.ts +286 -0
  97. package/templates/tiptap/tiptap-ui/list-button/index.tsx +2 -0
  98. package/templates/tiptap/tiptap-ui/list-button/list-button.tsx +108 -0
  99. package/templates/tiptap/tiptap-ui/list-button/use-list.ts +329 -0
  100. package/templates/tiptap/tiptap-ui/list-dropdown-menu/index.tsx +1 -0
  101. package/templates/tiptap/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +123 -0
  102. package/templates/tiptap/tiptap-ui/list-dropdown-menu/use-list-dropdown-menu.ts +203 -0
  103. package/templates/tiptap/tiptap-ui/mark-button/index.tsx +2 -0
  104. package/templates/tiptap/tiptap-ui/mark-button/mark-button.tsx +107 -0
  105. package/templates/tiptap/tiptap-ui/mark-button/use-mark.ts +206 -0
  106. package/templates/tiptap/tiptap-ui/text-align-button/index.tsx +2 -0
  107. package/templates/tiptap/tiptap-ui/text-align-button/text-align-button.tsx +118 -0
  108. package/templates/tiptap/tiptap-ui/text-align-button/use-text-align.ts +212 -0
  109. package/templates/tiptap/tiptap-ui/undo-redo-button/index.tsx +2 -0
  110. package/templates/tiptap/tiptap-ui/undo-redo-button/undo-redo-button.tsx +105 -0
  111. package/templates/tiptap/tiptap-ui/undo-redo-button/use-undo-redo.ts +173 -0
  112. package/templates/tiptap/tiptap-ui-primitive/badge/badge-colors.scss +395 -0
  113. package/templates/tiptap/tiptap-ui-primitive/badge/badge-group.scss +16 -0
  114. package/templates/tiptap/tiptap-ui-primitive/badge/badge.scss +99 -0
  115. package/templates/tiptap/tiptap-ui-primitive/badge/badge.tsx +46 -0
  116. package/templates/tiptap/tiptap-ui-primitive/badge/index.tsx +1 -0
  117. package/templates/tiptap/tiptap-ui-primitive/button/button-colors.scss +429 -0
  118. package/templates/tiptap/tiptap-ui-primitive/button/button-group.scss +22 -0
  119. package/templates/tiptap/tiptap-ui-primitive/button/button.scss +314 -0
  120. package/templates/tiptap/tiptap-ui-primitive/button/button.tsx +102 -0
  121. package/templates/tiptap/tiptap-ui-primitive/button/index.tsx +1 -0
  122. package/templates/tiptap/tiptap-ui-primitive/card/card.scss +77 -0
  123. package/templates/tiptap/tiptap-ui-primitive/card/card.tsx +59 -0
  124. package/templates/tiptap/tiptap-ui-primitive/card/index.tsx +1 -0
  125. package/templates/tiptap/tiptap-ui-primitive/dropdown-menu/dropdown-menu.scss +63 -0
  126. package/templates/tiptap/tiptap-ui-primitive/dropdown-menu/dropdown-menu.tsx +95 -0
  127. package/templates/tiptap/tiptap-ui-primitive/dropdown-menu/index.tsx +1 -0
  128. package/templates/tiptap/tiptap-ui-primitive/input/index.tsx +1 -0
  129. package/templates/tiptap/tiptap-ui-primitive/input/input.scss +45 -0
  130. package/templates/tiptap/tiptap-ui-primitive/input/input.tsx +18 -0
  131. package/templates/tiptap/tiptap-ui-primitive/popover/index.tsx +1 -0
  132. package/templates/tiptap/tiptap-ui-primitive/popover/popover.scss +63 -0
  133. package/templates/tiptap/tiptap-ui-primitive/popover/popover.tsx +33 -0
  134. package/templates/tiptap/tiptap-ui-primitive/separator/index.tsx +1 -0
  135. package/templates/tiptap/tiptap-ui-primitive/separator/separator.scss +23 -0
  136. package/templates/tiptap/tiptap-ui-primitive/separator/separator.tsx +33 -0
  137. package/templates/tiptap/tiptap-ui-primitive/spacer/index.tsx +1 -0
  138. package/templates/tiptap/tiptap-ui-primitive/spacer/spacer.tsx +21 -0
  139. package/templates/tiptap/tiptap-ui-primitive/toolbar/index.tsx +1 -0
  140. package/templates/tiptap/tiptap-ui-primitive/toolbar/toolbar.scss +98 -0
  141. package/templates/tiptap/tiptap-ui-primitive/toolbar/toolbar.tsx +113 -0
  142. package/templates/tiptap/tiptap-ui-primitive/tooltip/index.tsx +1 -0
  143. package/templates/tiptap/tiptap-ui-primitive/tooltip/tooltip.scss +43 -0
  144. package/templates/tiptap/tiptap-ui-primitive/tooltip/tooltip.tsx +223 -0
  145. package/templates/ui/accordion.tsx +52 -0
  146. package/templates/ui/alert-dialog.tsx +116 -0
  147. package/templates/ui/alert.tsx +48 -0
  148. package/templates/ui/aspect-ratio.tsx +7 -0
  149. package/templates/ui/avatar.tsx +46 -0
  150. package/templates/ui/badge.tsx +32 -0
  151. package/templates/ui/breadcrumb.tsx +98 -0
  152. package/templates/ui/button-group.tsx +77 -0
  153. package/templates/ui/button.tsx +48 -0
  154. package/templates/ui/calendar.tsx +176 -0
  155. package/templates/ui/card.tsx +54 -0
  156. package/templates/ui/carousel.tsx +234 -0
  157. package/templates/ui/chart.tsx +349 -0
  158. package/templates/ui/checkbox.tsx +27 -0
  159. package/templates/ui/collapsible.tsx +11 -0
  160. package/templates/ui/command.tsx +142 -0
  161. package/templates/ui/context-menu.tsx +188 -0
  162. package/templates/ui/curriculum-editor.tsx +601 -0
  163. package/templates/ui/date-picker.tsx +70 -0
  164. package/templates/ui/dialog.tsx +103 -0
  165. package/templates/ui/drawer.tsx +99 -0
  166. package/templates/ui/dropdown-menu.tsx +185 -0
  167. package/templates/ui/dynamic-list-field.tsx +95 -0
  168. package/templates/ui/empty.tsx +90 -0
  169. package/templates/ui/field.tsx +231 -0
  170. package/templates/ui/file-upload-example.tsx +113 -0
  171. package/templates/ui/form.tsx +172 -0
  172. package/templates/ui/hover-card.tsx +28 -0
  173. package/templates/ui/icon-picker.tsx +435 -0
  174. package/templates/ui/icons-data.ts +6 -0
  175. package/templates/ui/image-upload-field.tsx +360 -0
  176. package/templates/ui/input-group.tsx +160 -0
  177. package/templates/ui/input-otp.tsx +70 -0
  178. package/templates/ui/input.tsx +21 -0
  179. package/templates/ui/item.tsx +171 -0
  180. package/templates/ui/kbd.tsx +28 -0
  181. package/templates/ui/label.tsx +20 -0
  182. package/templates/ui/logo.tsx +113 -0
  183. package/templates/ui/markdown-editor.tsx +303 -0
  184. package/templates/ui/markdown-utils.ts +128 -0
  185. package/templates/ui/media-upload-field.tsx +255 -0
  186. package/templates/ui/menubar.tsx +230 -0
  187. package/templates/ui/navigation-menu.tsx +119 -0
  188. package/templates/ui/pagination.tsx +96 -0
  189. package/templates/ui/placeholder.tsx +25 -0
  190. package/templates/ui/popover.tsx +32 -0
  191. package/templates/ui/progress.tsx +24 -0
  192. package/templates/ui/radio-group.tsx +37 -0
  193. package/templates/ui/resizable.tsx +41 -0
  194. package/templates/ui/rich-text-editor.tsx +374 -0
  195. package/templates/ui/scroll-area.tsx +45 -0
  196. package/templates/ui/select.tsx +151 -0
  197. package/templates/ui/separator.tsx +25 -0
  198. package/templates/ui/sheet.tsx +120 -0
  199. package/templates/ui/sidebar.tsx +684 -0
  200. package/templates/ui/skeleton.tsx +7 -0
  201. package/templates/ui/slider.tsx +24 -0
  202. package/templates/ui/sonner.tsx +29 -0
  203. package/templates/ui/spinner.tsx +15 -0
  204. package/templates/ui/switch.tsx +28 -0
  205. package/templates/ui/table.tsx +93 -0
  206. package/templates/ui/tabs.tsx +54 -0
  207. package/templates/ui/textarea.tsx +20 -0
  208. package/templates/ui/toast.tsx +127 -0
  209. package/templates/ui/toggle-group.tsx +56 -0
  210. package/templates/ui/toggle.tsx +43 -0
  211. package/templates/ui/tooltip.tsx +31 -0
  212. package/templates/ui/use-mobile.tsx +19 -0
  213. package/templates/ui/video-upload-field.tsx +368 -0
  214. package/dist/chunk-EIH4RRIJ.js +0 -183
  215. package/dist/chunk-EIH4RRIJ.js.map +0 -1
  216. package/dist/chunk-NKRQYAS6.js +0 -260
  217. package/dist/chunk-NKRQYAS6.js.map +0 -1
  218. package/dist/chunk-QLVSHP7X.js +0 -235
  219. package/dist/chunk-QLVSHP7X.js.map +0 -1
  220. package/dist/chunk-WY6BC55D.js +0 -357
  221. package/dist/chunk-WY6BC55D.js.map +0 -1
  222. package/dist/config/index.d.ts +0 -93
  223. package/dist/config/index.js +0 -58
  224. package/dist/config/index.js.map +0 -1
  225. package/dist/core/index.d.ts +0 -415
  226. package/dist/core/index.js +0 -906
  227. package/dist/core/index.js.map +0 -1
  228. package/dist/import-resolver-BaZ-rzkH.d.ts +0 -123
  229. package/dist/logger-awLb347n.d.ts +0 -81
  230. package/dist/plugins/index.d.ts +0 -213
  231. package/dist/plugins/index.js +0 -365
  232. package/dist/plugins/index.js.map +0 -1
  233. package/dist/types-ByX_gl6y.d.ts +0 -232
  234. package/dist/types-eI549DEG.d.ts +0 -331
@@ -0,0 +1 @@
1
+ export * from './list-dropdown-menu'
@@ -0,0 +1,123 @@
1
+ 'use client'
2
+
3
+ import type { Editor } from '@tiptap/react'
4
+ import { useCallback, useState } from 'react'
5
+
6
+ // --- Hooks ---
7
+ import { useTiptapEditor } from '../../hooks/use-tiptap-editor'
8
+
9
+ // --- Icons ---
10
+ import { ChevronDownIcon } from '../../tiptap-icons/chevron-down-icon'
11
+ // --- UI Primitives ---
12
+ import type { ButtonProps } from '../../tiptap-ui-primitive/button'
13
+ import { Button, ButtonGroup } from '../../tiptap-ui-primitive/button'
14
+ import { Card, CardBody } from '../../tiptap-ui-primitive/card'
15
+ import {
16
+ DropdownMenu,
17
+ DropdownMenuContent,
18
+ DropdownMenuItem,
19
+ DropdownMenuTrigger
20
+ } from '../../tiptap-ui-primitive/dropdown-menu'
21
+ // --- Tiptap UI ---
22
+ import { ListButton, type ListType } from '../list-button'
23
+ import { useListDropdownMenu } from './use-list-dropdown-menu'
24
+
25
+ export interface ListDropdownMenuProps extends Omit<ButtonProps, 'type'> {
26
+ /**
27
+ * The Tiptap editor instance.
28
+ */
29
+ editor?: Editor
30
+ /**
31
+ * The list types to display in the dropdown.
32
+ */
33
+ types?: ListType[]
34
+ /**
35
+ * Whether the dropdown should be hidden when no list types are available
36
+ * @default false
37
+ */
38
+ hideWhenUnavailable?: boolean
39
+ /**
40
+ * Callback for when the dropdown opens or closes
41
+ */
42
+ onOpenChange?: (isOpen: boolean) => void
43
+ /**
44
+ * Whether to render the dropdown menu in a portal
45
+ * @default false
46
+ */
47
+ portal?: boolean
48
+ }
49
+
50
+ export function ListDropdownMenu({
51
+ editor: providedEditor,
52
+ types = ['bulletList', 'orderedList', 'taskList'],
53
+ hideWhenUnavailable = false,
54
+ onOpenChange,
55
+ portal = false,
56
+ ...props
57
+ }: ListDropdownMenuProps) {
58
+ const { editor } = useTiptapEditor(providedEditor)
59
+ const [isOpen, setIsOpen] = useState(false)
60
+
61
+ const { filteredLists, canToggle, isActive, isVisible, Icon } = useListDropdownMenu({
62
+ editor,
63
+ types,
64
+ hideWhenUnavailable
65
+ })
66
+
67
+ const handleOnOpenChange = useCallback(
68
+ (open: boolean) => {
69
+ setIsOpen(open)
70
+ onOpenChange?.(open)
71
+ },
72
+ [onOpenChange]
73
+ )
74
+
75
+ if (!isVisible) {
76
+ return null
77
+ }
78
+
79
+ return (
80
+ <DropdownMenu open={isOpen} onOpenChange={handleOnOpenChange}>
81
+ <DropdownMenuTrigger asChild>
82
+ <Button
83
+ type="button"
84
+ variant="ghost"
85
+ data-active-state={isActive ? 'on' : 'off'}
86
+ role="button"
87
+ tabIndex={-1}
88
+ disabled={!canToggle}
89
+ data-disabled={!canToggle}
90
+ aria-label="List options"
91
+ tooltip="List"
92
+ {...props}
93
+ onPointerDown={(e) => e.preventDefault()}
94
+ onClick={() => handleOnOpenChange(!isOpen)}
95
+ >
96
+ <Icon className="tiptap-button-icon" />
97
+ <ChevronDownIcon className="tiptap-button-dropdown-small" />
98
+ </Button>
99
+ </DropdownMenuTrigger>
100
+
101
+ <DropdownMenuContent align="start" portal={portal}>
102
+ <Card>
103
+ <CardBody>
104
+ <ButtonGroup>
105
+ {filteredLists.map((option) => (
106
+ <DropdownMenuItem key={option.type} asChild>
107
+ <ListButton
108
+ editor={editor}
109
+ type={option.type}
110
+ text={option.label}
111
+ showTooltip={false}
112
+ />
113
+ </DropdownMenuItem>
114
+ ))}
115
+ </ButtonGroup>
116
+ </CardBody>
117
+ </Card>
118
+ </DropdownMenuContent>
119
+ </DropdownMenu>
120
+ )
121
+ }
122
+
123
+ export default ListDropdownMenu
@@ -0,0 +1,203 @@
1
+ 'use client'
2
+
3
+ import type { Editor } from '@tiptap/react'
4
+ import { useEffect, useMemo, useState } from 'react'
5
+
6
+ // --- Hooks ---
7
+ import { useTiptapEditor } from '../../hooks/use-tiptap-editor'
8
+ // --- Lib ---
9
+ import { isNodeInSchema } from '../../lib/tiptap-utils'
10
+ // --- Icons ---
11
+ import { ListIcon } from '../../tiptap-icons/list-icon'
12
+ import { ListOrderedIcon } from '../../tiptap-icons/list-ordered-icon'
13
+ import { ListTodoIcon } from '../../tiptap-icons/list-todo-icon'
14
+
15
+ // --- Tiptap UI ---
16
+ import { canToggleList, isListActive, type ListType, listIcons } from '../list-button'
17
+
18
+ /**
19
+ * Configuration for the list dropdown menu functionality
20
+ */
21
+ export interface UseListDropdownMenuConfig {
22
+ /**
23
+ * The Tiptap editor instance.
24
+ */
25
+ editor?: Editor | null
26
+ /**
27
+ * The list types to display in the dropdown.
28
+ * @default ["bulletList", "orderedList", "taskList"]
29
+ */
30
+ types?: ListType[]
31
+ /**
32
+ * Whether the dropdown should be hidden when no list types are available
33
+ * @default false
34
+ */
35
+ hideWhenUnavailable?: boolean
36
+ }
37
+
38
+ export interface ListOption {
39
+ label: string
40
+ type: ListType
41
+ icon: React.ElementType
42
+ }
43
+
44
+ export const listOptions: ListOption[] = [
45
+ {
46
+ label: 'Bullet List',
47
+ type: 'bulletList',
48
+ icon: ListIcon
49
+ },
50
+ {
51
+ label: 'Ordered List',
52
+ type: 'orderedList',
53
+ icon: ListOrderedIcon
54
+ },
55
+ {
56
+ label: 'Task List',
57
+ type: 'taskList',
58
+ icon: ListTodoIcon
59
+ }
60
+ ]
61
+
62
+ export function canToggleAnyList(editor: Editor | null, listTypes: ListType[]): boolean {
63
+ if (!editor || !editor.isEditable) return false
64
+ return listTypes.some((type) => canToggleList(editor, type))
65
+ }
66
+
67
+ export function isAnyListActive(editor: Editor | null, listTypes: ListType[]): boolean {
68
+ if (!editor || !editor.isEditable) return false
69
+ return listTypes.some((type) => isListActive(editor, type))
70
+ }
71
+
72
+ export function getFilteredListOptions(availableTypes: ListType[]): typeof listOptions {
73
+ return listOptions.filter((option) => !option.type || availableTypes.includes(option.type))
74
+ }
75
+
76
+ export function shouldShowListDropdown(params: {
77
+ editor: Editor | null
78
+ listTypes: ListType[]
79
+ hideWhenUnavailable: boolean
80
+ listInSchema: boolean
81
+ canToggleAny: boolean
82
+ }): boolean {
83
+ const { editor, hideWhenUnavailable, listInSchema, canToggleAny } = params
84
+
85
+ if (!editor) return false
86
+
87
+ if (!hideWhenUnavailable) {
88
+ return true
89
+ }
90
+
91
+ if (!listInSchema) return false
92
+
93
+ if (!editor.isActive('code')) {
94
+ return canToggleAny
95
+ }
96
+
97
+ return true
98
+ }
99
+
100
+ /**
101
+ * Gets the currently active list type from the available types
102
+ */
103
+ export function getActiveListType(
104
+ editor: Editor | null,
105
+ availableTypes: ListType[]
106
+ ): ListType | undefined {
107
+ if (!editor || !editor.isEditable) return undefined
108
+ return availableTypes.find((type) => isListActive(editor, type))
109
+ }
110
+
111
+ /**
112
+ * Custom hook that provides list dropdown menu functionality for Tiptap editor
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * // Simple usage
117
+ * function MyListDropdown() {
118
+ * const {
119
+ * isVisible,
120
+ * activeType,
121
+ * isAnyActive,
122
+ * canToggleAny,
123
+ * filteredLists,
124
+ * } = useListDropdownMenu()
125
+ *
126
+ * if (!isVisible) return null
127
+ *
128
+ * return (
129
+ * <DropdownMenu>
130
+ * // dropdown content
131
+ * </DropdownMenu>
132
+ * )
133
+ * }
134
+ *
135
+ * // Advanced usage with configuration
136
+ * function MyAdvancedListDropdown() {
137
+ * const {
138
+ * isVisible,
139
+ * activeType,
140
+ * } = useListDropdownMenu({
141
+ * editor: myEditor,
142
+ * types: ["bulletList", "orderedList"],
143
+ * hideWhenUnavailable: true,
144
+ * })
145
+ *
146
+ * // component implementation
147
+ * }
148
+ * ```
149
+ */
150
+ export function useListDropdownMenu(config?: UseListDropdownMenuConfig) {
151
+ const {
152
+ editor: providedEditor,
153
+ types = ['bulletList', 'orderedList', 'taskList'],
154
+ hideWhenUnavailable = false
155
+ } = config || {}
156
+
157
+ const { editor } = useTiptapEditor(providedEditor)
158
+ const [isVisible, setIsVisible] = useState(true)
159
+
160
+ const listInSchema = types.some((type) => isNodeInSchema(type, editor))
161
+
162
+ const filteredLists = useMemo(() => getFilteredListOptions(types), [types])
163
+
164
+ const canToggleAny = canToggleAnyList(editor, types)
165
+ const isAnyActive = isAnyListActive(editor, types)
166
+ const activeType = getActiveListType(editor, types)
167
+ const activeList = filteredLists.find((option) => option.type === activeType)
168
+
169
+ useEffect(() => {
170
+ if (!editor) return
171
+
172
+ const handleSelectionUpdate = () => {
173
+ setIsVisible(
174
+ shouldShowListDropdown({
175
+ editor,
176
+ listTypes: types,
177
+ hideWhenUnavailable,
178
+ listInSchema,
179
+ canToggleAny
180
+ })
181
+ )
182
+ }
183
+
184
+ handleSelectionUpdate()
185
+
186
+ editor.on('selectionUpdate', handleSelectionUpdate)
187
+
188
+ return () => {
189
+ editor.off('selectionUpdate', handleSelectionUpdate)
190
+ }
191
+ }, [canToggleAny, editor, hideWhenUnavailable, listInSchema, types])
192
+
193
+ return {
194
+ isVisible,
195
+ activeType,
196
+ isActive: isAnyActive,
197
+ canToggle: canToggleAny,
198
+ types,
199
+ filteredLists,
200
+ label: 'List',
201
+ Icon: activeList ? listIcons[activeList.type] : ListIcon
202
+ }
203
+ }
@@ -0,0 +1,2 @@
1
+ export * from './mark-button'
2
+ export * from './use-mark'
@@ -0,0 +1,107 @@
1
+ 'use client'
2
+
3
+ import { forwardRef, useCallback } from 'react'
4
+ // --- Hooks ---
5
+ import { useTiptapEditor } from '../../hooks/use-tiptap-editor'
6
+ // --- Lib ---
7
+ import { parseShortcutKeys } from '../../lib/tiptap-utils'
8
+ import { Badge } from '../../tiptap-ui-primitive/badge'
9
+ // --- UI Primitives ---
10
+ import type { ButtonProps } from '../../tiptap-ui-primitive/button'
11
+ import { Button } from '../../tiptap-ui-primitive/button'
12
+ // --- Tiptap UI ---
13
+ import type { Mark, UseMarkConfig } from '.'
14
+ import { MARK_SHORTCUT_KEYS, useMark } from '.'
15
+
16
+ export interface MarkButtonProps extends Omit<ButtonProps, 'type'>, UseMarkConfig {
17
+ /**
18
+ * Optional text to display alongside the icon.
19
+ */
20
+ text?: string
21
+ /**
22
+ * Optional show shortcut keys in the button.
23
+ * @default false
24
+ */
25
+ showShortcut?: boolean
26
+ }
27
+
28
+ export function MarkShortcutBadge({
29
+ type,
30
+ shortcutKeys = MARK_SHORTCUT_KEYS[type]
31
+ }: {
32
+ type: Mark
33
+ shortcutKeys?: string
34
+ }) {
35
+ return <Badge>{parseShortcutKeys({ shortcutKeys })}</Badge>
36
+ }
37
+
38
+ /**
39
+ * Button component for toggling marks in a Tiptap editor.
40
+ *
41
+ * For custom button implementations, use the `useMark` hook instead.
42
+ */
43
+ export const MarkButton = forwardRef<HTMLButtonElement, MarkButtonProps>(
44
+ (
45
+ {
46
+ editor: providedEditor,
47
+ type,
48
+ text,
49
+ hideWhenUnavailable = false,
50
+ onToggled,
51
+ showShortcut = false,
52
+ onClick,
53
+ children,
54
+ ...buttonProps
55
+ },
56
+ ref
57
+ ) => {
58
+ const { editor } = useTiptapEditor(providedEditor)
59
+ const { isVisible, handleMark, label, canToggle, isActive, Icon, shortcutKeys } = useMark({
60
+ editor,
61
+ type,
62
+ hideWhenUnavailable,
63
+ onToggled
64
+ })
65
+
66
+ const handleClick = useCallback(
67
+ (event: React.MouseEvent<HTMLButtonElement>) => {
68
+ onClick?.(event)
69
+ if (event.defaultPrevented) return
70
+ handleMark()
71
+ },
72
+ [handleMark, onClick]
73
+ )
74
+
75
+ if (!isVisible) {
76
+ return null
77
+ }
78
+
79
+ return (
80
+ <Button
81
+ type="button"
82
+ disabled={!canToggle}
83
+ variant="ghost"
84
+ data-active-state={isActive ? 'on' : 'off'}
85
+ data-disabled={!canToggle}
86
+ role="button"
87
+ tabIndex={-1}
88
+ aria-label={label}
89
+ aria-pressed={isActive}
90
+ tooltip={label}
91
+ onClick={handleClick}
92
+ {...buttonProps}
93
+ ref={ref}
94
+ >
95
+ {children ?? (
96
+ <>
97
+ <Icon className="tiptap-button-icon" />
98
+ {text && <span className="tiptap-button-text">{text}</span>}
99
+ {showShortcut && <MarkShortcutBadge type={type} shortcutKeys={shortcutKeys} />}
100
+ </>
101
+ )}
102
+ </Button>
103
+ )
104
+ }
105
+ )
106
+
107
+ MarkButton.displayName = 'MarkButton'
@@ -0,0 +1,206 @@
1
+ 'use client'
2
+
3
+ import type { Editor } from '@tiptap/react'
4
+ import { useCallback, useEffect, useState } from 'react'
5
+
6
+ // --- Hooks ---
7
+ import { useTiptapEditor } from '../../hooks/use-tiptap-editor'
8
+
9
+ // --- Lib ---
10
+ import { isMarkInSchema, isNodeTypeSelected } from '../../lib/tiptap-utils'
11
+
12
+ // --- Icons ---
13
+ import { BoldIcon } from '../../tiptap-icons/bold-icon'
14
+ import { Code2Icon } from '../../tiptap-icons/code2-icon'
15
+ import { ItalicIcon } from '../../tiptap-icons/italic-icon'
16
+ import { StrikeIcon } from '../../tiptap-icons/strike-icon'
17
+ import { SubscriptIcon } from '../../tiptap-icons/subscript-icon'
18
+ import { SuperscriptIcon } from '../../tiptap-icons/superscript-icon'
19
+ import { UnderlineIcon } from '../../tiptap-icons/underline-icon'
20
+
21
+ export type Mark = 'bold' | 'italic' | 'strike' | 'code' | 'underline' | 'superscript' | 'subscript'
22
+
23
+ /**
24
+ * Configuration for the mark functionality
25
+ */
26
+ export interface UseMarkConfig {
27
+ /**
28
+ * The Tiptap editor instance.
29
+ */
30
+ editor?: Editor | null
31
+ /**
32
+ * The type of mark to toggle
33
+ */
34
+ type: Mark
35
+ /**
36
+ * Whether the button should hide when mark is not available.
37
+ * @default false
38
+ */
39
+ hideWhenUnavailable?: boolean
40
+ /**
41
+ * Callback function called after a successful mark toggle.
42
+ */
43
+ onToggled?: () => void
44
+ }
45
+
46
+ export const markIcons = {
47
+ bold: BoldIcon,
48
+ italic: ItalicIcon,
49
+ underline: UnderlineIcon,
50
+ strike: StrikeIcon,
51
+ code: Code2Icon,
52
+ superscript: SuperscriptIcon,
53
+ subscript: SubscriptIcon
54
+ }
55
+
56
+ export const MARK_SHORTCUT_KEYS: Record<Mark, string> = {
57
+ bold: 'mod+b',
58
+ italic: 'mod+i',
59
+ underline: 'mod+u',
60
+ strike: 'mod+shift+s',
61
+ code: 'mod+e',
62
+ superscript: 'mod+.',
63
+ subscript: 'mod+,'
64
+ }
65
+
66
+ /**
67
+ * Checks if a mark can be toggled in the current editor state
68
+ */
69
+ export function canToggleMark(editor: Editor | null, type: Mark): boolean {
70
+ if (!editor || !editor.isEditable) return false
71
+ if (!isMarkInSchema(type, editor) || isNodeTypeSelected(editor, ['image'])) return false
72
+
73
+ return editor.can().toggleMark(type)
74
+ }
75
+
76
+ /**
77
+ * Checks if a mark is currently active
78
+ */
79
+ export function isMarkActive(editor: Editor | null, type: Mark): boolean {
80
+ if (!editor || !editor.isEditable) return false
81
+ return editor.isActive(type)
82
+ }
83
+
84
+ /**
85
+ * Toggles a mark in the editor
86
+ */
87
+ export function toggleMark(editor: Editor | null, type: Mark): boolean {
88
+ if (!editor || !editor.isEditable) return false
89
+ if (!canToggleMark(editor, type)) return false
90
+
91
+ return editor.chain().focus().toggleMark(type).run()
92
+ }
93
+
94
+ /**
95
+ * Determines if the mark button should be shown
96
+ */
97
+ export function shouldShowButton(props: {
98
+ editor: Editor | null
99
+ type: Mark
100
+ hideWhenUnavailable: boolean
101
+ }): boolean {
102
+ const { editor, type, hideWhenUnavailable } = props
103
+
104
+ if (!editor || !editor.isEditable) return false
105
+
106
+ if (!hideWhenUnavailable) {
107
+ return true
108
+ }
109
+
110
+ if (!isMarkInSchema(type, editor)) return false
111
+
112
+ if (!editor.isActive('code')) {
113
+ return canToggleMark(editor, type)
114
+ }
115
+
116
+ return true
117
+ }
118
+
119
+ /**
120
+ * Gets the formatted mark name
121
+ */
122
+ export function getFormattedMarkName(type: Mark): string {
123
+ return type.charAt(0).toUpperCase() + type.slice(1)
124
+ }
125
+
126
+ /**
127
+ * Custom hook that provides mark functionality for Tiptap editor
128
+ *
129
+ * @example
130
+ * ```tsx
131
+ * // Simple usage
132
+ * function MySimpleBoldButton() {
133
+ * const { isVisible, handleMark } = useMark({ type: "bold" })
134
+ *
135
+ * if (!isVisible) return null
136
+ *
137
+ * return <button onClick={handleMark}>Bold</button>
138
+ * }
139
+ *
140
+ * // Advanced usage with configuration
141
+ * function MyAdvancedItalicButton() {
142
+ * const { isVisible, handleMark, label, isActive } = useMark({
143
+ * editor: myEditor,
144
+ * type: "italic",
145
+ * hideWhenUnavailable: true,
146
+ * onToggled: () => console.log('Mark toggled!')
147
+ * })
148
+ *
149
+ * if (!isVisible) return null
150
+ *
151
+ * return (
152
+ * <MyButton
153
+ * onClick={handleMark}
154
+ * aria-pressed={isActive}
155
+ * aria-label={label}
156
+ * >
157
+ * Italic
158
+ * </MyButton>
159
+ * )
160
+ * }
161
+ * ```
162
+ */
163
+ export function useMark(config: UseMarkConfig) {
164
+ const { editor: providedEditor, type, hideWhenUnavailable = false, onToggled } = config
165
+
166
+ const { editor } = useTiptapEditor(providedEditor)
167
+ const [isVisible, setIsVisible] = useState<boolean>(true)
168
+ const canToggle = canToggleMark(editor, type)
169
+ const isActive = isMarkActive(editor, type)
170
+
171
+ useEffect(() => {
172
+ if (!editor) return
173
+
174
+ const handleSelectionUpdate = () => {
175
+ setIsVisible(shouldShowButton({ editor, type, hideWhenUnavailable }))
176
+ }
177
+
178
+ handleSelectionUpdate()
179
+
180
+ editor.on('selectionUpdate', handleSelectionUpdate)
181
+
182
+ return () => {
183
+ editor.off('selectionUpdate', handleSelectionUpdate)
184
+ }
185
+ }, [editor, type, hideWhenUnavailable])
186
+
187
+ const handleMark = useCallback(() => {
188
+ if (!editor) return false
189
+
190
+ const success = toggleMark(editor, type)
191
+ if (success) {
192
+ onToggled?.()
193
+ }
194
+ return success
195
+ }, [editor, type, onToggled])
196
+
197
+ return {
198
+ isVisible,
199
+ isActive,
200
+ handleMark,
201
+ canToggle,
202
+ label: getFormattedMarkName(type),
203
+ shortcutKeys: MARK_SHORTCUT_KEYS[type],
204
+ Icon: markIcons[type]
205
+ }
206
+ }
@@ -0,0 +1,2 @@
1
+ export * from './text-align-button'
2
+ export * from './use-text-align'