@open-cloud-initiative/editor-x 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/.devcontainer/Dockerfile +13 -0
  2. package/.devcontainer/devcontainer.json +52 -0
  3. package/.github/dependabot.yml +10 -0
  4. package/.github/workflows/pages.yml +42 -0
  5. package/.github/workflows/publish.yml +41 -0
  6. package/.vscode/settings.json +7 -0
  7. package/LICENSE +9 -0
  8. package/README.md +9 -0
  9. package/app/_component/editor.tsx +383 -0
  10. package/app/layout.tsx +46 -0
  11. package/app/page.tsx +11 -0
  12. package/app/r/registry.json/route.ts +22 -0
  13. package/components/editorx/editor.tsx +1794 -0
  14. package/components/editorx/extensions/floating-menu.tsx +376 -0
  15. package/components/editorx/extensions/floating-toolbar.tsx +97 -0
  16. package/components/editorx/extensions/image-placeholder.tsx +316 -0
  17. package/components/editorx/extensions/image.tsx +462 -0
  18. package/components/editorx/extensions/search-and-replace.tsx +438 -0
  19. package/components/editorx/rich-text-editor.tsx +383 -0
  20. package/components/editorx/tiptap.css +421 -0
  21. package/components/editorx/toolbars/alignment.tsx +126 -0
  22. package/components/editorx/toolbars/blockquote.tsx +47 -0
  23. package/components/editorx/toolbars/bold.tsx +48 -0
  24. package/components/editorx/toolbars/bullet-list.tsx +48 -0
  25. package/components/editorx/toolbars/code-block.tsx +47 -0
  26. package/components/editorx/toolbars/code.tsx +43 -0
  27. package/components/editorx/toolbars/color-and-highlight.tsx +215 -0
  28. package/components/editorx/toolbars/editor-toolbar.tsx +77 -0
  29. package/components/editorx/toolbars/hard-break.tsx +46 -0
  30. package/components/editorx/toolbars/headings.tsx +97 -0
  31. package/components/editorx/toolbars/horizontal-rule.tsx +42 -0
  32. package/components/editorx/toolbars/image-placeholder-toolbar.tsx +47 -0
  33. package/components/editorx/toolbars/italic.tsx +48 -0
  34. package/components/editorx/toolbars/link.tsx +130 -0
  35. package/components/editorx/toolbars/mobile-toolbar-group.tsx +76 -0
  36. package/components/editorx/toolbars/ordered-list.tsx +47 -0
  37. package/components/editorx/toolbars/redo.tsx +44 -0
  38. package/components/editorx/toolbars/strikethrough.tsx +48 -0
  39. package/components/editorx/toolbars/toolbar-provider.tsx +29 -0
  40. package/components/editorx/toolbars/underline.tsx +48 -0
  41. package/components/editorx/toolbars/undo.tsx +43 -0
  42. package/components/layout/theme-switcher.tsx +26 -0
  43. package/components/main-nav.tsx +24 -0
  44. package/components/mobile-nav.tsx +46 -0
  45. package/components/open-in-v0-button.tsx +38 -0
  46. package/components/page-header.tsx +30 -0
  47. package/components/site-footer.tsx +41 -0
  48. package/components/site-header.tsx +32 -0
  49. package/components/theme-provider.tsx +8 -0
  50. package/components/ui/button.tsx +57 -0
  51. package/components/ui/checkbox.tsx +30 -0
  52. package/components/ui/collapsible.tsx +11 -0
  53. package/components/ui/command.tsx +148 -0
  54. package/components/ui/dialog.tsx +122 -0
  55. package/components/ui/drawer.tsx +118 -0
  56. package/components/ui/dropdown-menu.tsx +201 -0
  57. package/components/ui/input.tsx +22 -0
  58. package/components/ui/label.tsx +26 -0
  59. package/components/ui/popover.tsx +33 -0
  60. package/components/ui/resizable.tsx +40 -0
  61. package/components/ui/scroll-area.tsx +42 -0
  62. package/components/ui/separator.tsx +31 -0
  63. package/components/ui/sheet.tsx +140 -0
  64. package/components/ui/sidebar.tsx +763 -0
  65. package/components/ui/skeleton.tsx +15 -0
  66. package/components/ui/spinner.tsx +29 -0
  67. package/components/ui/tabs.tsx +55 -0
  68. package/components/ui/toggle-group.tsx +61 -0
  69. package/components/ui/toggle.tsx +45 -0
  70. package/components/ui/tooltip.tsx +32 -0
  71. package/components.json +21 -0
  72. package/config/site.ts +15 -0
  73. package/eslint.config.mjs +20 -0
  74. package/hooks/use-character-limit.ts +28 -0
  75. package/hooks/use-copy-to-clipboard.ts +16 -0
  76. package/hooks/use-debounce.ts +17 -0
  77. package/hooks/use-image-upload.ts +97 -0
  78. package/hooks/use-media-querry.ts +18 -0
  79. package/hooks/use-mobile.tsx +19 -0
  80. package/images/editor.png +0 -0
  81. package/lib/content.ts +39 -0
  82. package/lib/cookie-client.ts +19 -0
  83. package/lib/localstorage-client.ts +19 -0
  84. package/lib/package.ts +144 -0
  85. package/lib/preferences-config.ts +72 -0
  86. package/lib/preferences-storage.ts +20 -0
  87. package/lib/theme-utils.ts +12 -0
  88. package/lib/theme.ts +50 -0
  89. package/lib/tiptap-utils.ts +45 -0
  90. package/lib/utils.ts +11 -0
  91. package/next-env.d.ts +6 -0
  92. package/next.config.mjs +11 -0
  93. package/package.json +92 -0
  94. package/postcss.config.mjs +8 -0
  95. package/prettier.config.mjs +15 -0
  96. package/public/android-chrome-192x192.png +0 -0
  97. package/public/android-chrome-512x512.png +0 -0
  98. package/public/apple-touch-icon.png +0 -0
  99. package/public/favicon-16x16.png +0 -0
  100. package/public/favicon-32x32.png +0 -0
  101. package/public/favicon.ico +0 -0
  102. package/public/file.svg +1 -0
  103. package/public/globe.svg +1 -0
  104. package/public/next.svg +1 -0
  105. package/public/og.webp +0 -0
  106. package/public/r/editor-x.json +85 -0
  107. package/public/r/registry.json +93 -0
  108. package/public/site.webmanifest +19 -0
  109. package/public/vercel.svg +1 -0
  110. package/public/window.svg +1 -0
  111. package/registry/editor/components/editor.tsx +1794 -0
  112. package/registry/editor/components/extensions/floating-menu.tsx +376 -0
  113. package/registry/editor/components/extensions/floating-toolbar.tsx +97 -0
  114. package/registry/editor/components/extensions/image-placeholder.tsx +316 -0
  115. package/registry/editor/components/extensions/image.tsx +462 -0
  116. package/registry/editor/components/extensions/search-and-replace.tsx +438 -0
  117. package/registry/editor/components/rich-text-editor.tsx +383 -0
  118. package/registry/editor/components/tiptap.css +421 -0
  119. package/registry/editor/components/toolbars/alignment.tsx +126 -0
  120. package/registry/editor/components/toolbars/blockquote.tsx +47 -0
  121. package/registry/editor/components/toolbars/bold.tsx +48 -0
  122. package/registry/editor/components/toolbars/bullet-list.tsx +48 -0
  123. package/registry/editor/components/toolbars/code-block.tsx +47 -0
  124. package/registry/editor/components/toolbars/code.tsx +43 -0
  125. package/registry/editor/components/toolbars/color-and-highlight.tsx +215 -0
  126. package/registry/editor/components/toolbars/editor-toolbar.tsx +77 -0
  127. package/registry/editor/components/toolbars/hard-break.tsx +46 -0
  128. package/registry/editor/components/toolbars/headings.tsx +97 -0
  129. package/registry/editor/components/toolbars/horizontal-rule.tsx +42 -0
  130. package/registry/editor/components/toolbars/image-placeholder-toolbar.tsx +47 -0
  131. package/registry/editor/components/toolbars/italic.tsx +48 -0
  132. package/registry/editor/components/toolbars/link.tsx +130 -0
  133. package/registry/editor/components/toolbars/mobile-toolbar-group.tsx +76 -0
  134. package/registry/editor/components/toolbars/ordered-list.tsx +47 -0
  135. package/registry/editor/components/toolbars/redo.tsx +44 -0
  136. package/registry/editor/components/toolbars/strikethrough.tsx +48 -0
  137. package/registry/editor/components/toolbars/toolbar-provider.tsx +29 -0
  138. package/registry/editor/components/toolbars/underline.tsx +48 -0
  139. package/registry/editor/components/toolbars/undo.tsx +43 -0
  140. package/registry/editor/components/ui/button.tsx +57 -0
  141. package/registry/editor/components/ui/checkbox.tsx +30 -0
  142. package/registry/editor/components/ui/collapsible.tsx +11 -0
  143. package/registry/editor/components/ui/command.tsx +148 -0
  144. package/registry/editor/components/ui/dialog.tsx +122 -0
  145. package/registry/editor/components/ui/drawer.tsx +118 -0
  146. package/registry/editor/components/ui/dropdown-menu.tsx +201 -0
  147. package/registry/editor/components/ui/input.tsx +22 -0
  148. package/registry/editor/components/ui/label.tsx +26 -0
  149. package/registry/editor/components/ui/popover.tsx +33 -0
  150. package/registry/editor/components/ui/resizable.tsx +40 -0
  151. package/registry/editor/components/ui/scroll-area.tsx +42 -0
  152. package/registry/editor/components/ui/separator.tsx +31 -0
  153. package/registry/editor/components/ui/sheet.tsx +140 -0
  154. package/registry/editor/components/ui/sidebar.tsx +763 -0
  155. package/registry/editor/components/ui/skeleton.tsx +15 -0
  156. package/registry/editor/components/ui/spinner.tsx +29 -0
  157. package/registry/editor/components/ui/tabs.tsx +55 -0
  158. package/registry/editor/components/ui/toggle-group.tsx +61 -0
  159. package/registry/editor/components/ui/toggle.tsx +45 -0
  160. package/registry/editor/components/ui/tooltip.tsx +32 -0
  161. package/registry/editor/hooks/use-character-limit.ts +28 -0
  162. package/registry/editor/hooks/use-copy-to-clipboard.ts +16 -0
  163. package/registry/editor/hooks/use-debounce.ts +17 -0
  164. package/registry/editor/hooks/use-image-upload.ts +97 -0
  165. package/registry/editor/hooks/use-media-querry.ts +18 -0
  166. package/registry/editor/hooks/use-mobile.tsx +19 -0
  167. package/registry/editor/lib/content.ts +39 -0
  168. package/registry/editor/lib/cookie-client.ts +19 -0
  169. package/registry/editor/lib/localstorage-client.ts +19 -0
  170. package/registry/editor/lib/package.ts +144 -0
  171. package/registry/editor/lib/preferences-config.ts +72 -0
  172. package/registry/editor/lib/preferences-storage.ts +20 -0
  173. package/registry/editor/lib/theme-utils.ts +12 -0
  174. package/registry/editor/lib/theme.ts +50 -0
  175. package/registry/editor/lib/tiptap-utils.ts +45 -0
  176. package/registry/editor/lib/utils.ts +11 -0
  177. package/registry/editor/page.tsx +9 -0
  178. package/registry.json +93 -0
  179. package/reset.d.ts +1 -0
  180. package/scripts/generate-theme-presets.ts +128 -0
  181. package/scripts/postCreateCommand.sh +0 -0
  182. package/scripts/theme-boot.tsx +105 -0
  183. package/server/server-actions.ts +27 -0
  184. package/stores/preferences/preferences-provider.tsx +55 -0
  185. package/stores/preferences/preferences-store.ts +23 -0
  186. package/styles/globals.css +288 -0
  187. package/styles/presets/brutalist.css +89 -0
  188. package/styles/presets/soft-pop.css +89 -0
  189. package/styles/presets/tangerine.css +89 -0
  190. package/tsconfig.json +50 -0
@@ -0,0 +1,47 @@
1
+ 'use client'
2
+
3
+ import { Code } from 'lucide-react'
4
+ import React from 'react'
5
+
6
+ import { cn } from '@/lib/utils'
7
+ import { Button, type ButtonProps } from 'components/ui/button'
8
+ import { Tooltip, TooltipContent, TooltipTrigger } from 'components/ui/tooltip'
9
+ import { useToolbar } from './toolbar-provider'
10
+
11
+ const CodeBlockToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
12
+ ({ className, onClick, children, ...props }, ref) => {
13
+ const { editor } = useToolbar()
14
+ return (
15
+ <Tooltip>
16
+ <TooltipTrigger asChild>
17
+ <Button
18
+ variant="ghost"
19
+ size="icon"
20
+ type="button"
21
+ className={cn(
22
+ 'h-8 w-8 p-0 sm:h-9 sm:w-9',
23
+ editor?.isActive('codeBlock') && 'bg-accent',
24
+ className,
25
+ )}
26
+ onClick={(e) => {
27
+ editor?.chain().focus().toggleCodeBlock().run()
28
+ onClick?.(e)
29
+ }}
30
+ disabled={!editor?.can().chain().focus().toggleCodeBlock().run()}
31
+ ref={ref}
32
+ {...props}
33
+ >
34
+ {children ?? <Code className="h-4 w-4" />}
35
+ </Button>
36
+ </TooltipTrigger>
37
+ <TooltipContent>
38
+ <span>Code Block</span>
39
+ </TooltipContent>
40
+ </Tooltip>
41
+ )
42
+ },
43
+ )
44
+
45
+ CodeBlockToolbar.displayName = 'CodeBlockToolbar'
46
+
47
+ export { CodeBlockToolbar }
@@ -0,0 +1,43 @@
1
+ 'use client'
2
+
3
+ import { Code2 } from 'lucide-react'
4
+ import React from 'react'
5
+
6
+ import { cn } from '@/lib/utils'
7
+ import { Button, type ButtonProps } from 'components/ui/button'
8
+ import { Tooltip, TooltipContent, TooltipTrigger } from 'components/ui/tooltip'
9
+ import { useToolbar } from './toolbar-provider'
10
+
11
+ const CodeToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
12
+ ({ className, onClick, children, ...props }, ref) => {
13
+ const { editor } = useToolbar()
14
+ return (
15
+ <Tooltip>
16
+ <TooltipTrigger asChild>
17
+ <Button
18
+ variant="ghost"
19
+ size="icon"
20
+ type="button"
21
+ className={cn('h-8 w-8 p-0 sm:h-9 sm:w-9', editor?.isActive('code') && 'bg-accent', className)}
22
+ onClick={(e) => {
23
+ editor?.chain().focus().toggleCode().run()
24
+ onClick?.(e)
25
+ }}
26
+ disabled={!editor?.can().chain().focus().toggleCode().run()}
27
+ ref={ref}
28
+ {...props}
29
+ >
30
+ {children ?? <Code2 className="h-4 w-4" />}
31
+ </Button>
32
+ </TooltipTrigger>
33
+ <TooltipContent>
34
+ <span>Code</span>
35
+ </TooltipContent>
36
+ </Tooltip>
37
+ )
38
+ },
39
+ )
40
+
41
+ CodeToolbar.displayName = 'CodeToolbar'
42
+
43
+ export { CodeToolbar }
@@ -0,0 +1,215 @@
1
+ 'use client'
2
+ /* eslint-disable */
3
+ // @ts-nocheck
4
+ import { Button } from "@/components/ui/button";
5
+ import {
6
+ Popover,
7
+ PopoverContent,
8
+ PopoverTrigger,
9
+ } from "@/components/ui/popover";
10
+ import { ScrollArea } from "@/components/ui/scroll-area";
11
+ import { Separator } from "@/components/ui/separator";
12
+ import {
13
+ Tooltip,
14
+ TooltipContent,
15
+ TooltipTrigger,
16
+ } from "@/components/ui/tooltip";
17
+ import { useMediaQuery } from "@/hooks/use-media-querry";
18
+ import { cn } from "@/lib/utils";
19
+ import type { Extension } from "@tiptap/core";
20
+ import type { ColorOptions } from "@tiptap/extension-color";
21
+ import type { HighlightOptions } from "@tiptap/extension-highlight";
22
+ import { CheckIcon, ChevronDownIcon } from "lucide-react";
23
+ import { MobileToolbarGroup, MobileToolbarItem } from "./mobile-toolbar-group";
24
+ import { useToolbar } from "./toolbar-provider";
25
+
26
+ type TextStylingExtensions =
27
+ | Extension<ColorOptions, any>
28
+ | Extension<HighlightOptions, any>;
29
+
30
+ const TEXT_COLORS = [
31
+ { name: "Default", color: "var(--editor-text-default)" },
32
+ { name: "Gray", color: "var(--editor-text-gray)" },
33
+ { name: "Brown", color: "var(--editor-text-brown)" },
34
+ { name: "Orange", color: "var(--editor-text-orange)" },
35
+ { name: "Yellow", color: "var(--editor-text-yellow)" },
36
+ { name: "Green", color: "var(--editor-text-green)" },
37
+ { name: "Blue", color: "var(--editor-text-blue)" },
38
+ { name: "Purple", color: "var(--editor-text-purple)" },
39
+ { name: "Pink", color: "var(--editor-text-pink)" },
40
+ { name: "Red", color: "var(--editor-text-red)" },
41
+ ];
42
+
43
+ const HIGHLIGHT_COLORS = [
44
+ { name: "Default", color: "var(--editor-highlight-default)" },
45
+ { name: "Gray", color: "var(--editor-highlight-gray)" },
46
+ { name: "Brown", color: "var(--editor-highlight-brown)" },
47
+ { name: "Orange", color: "var(--editor-highlight-orange)" },
48
+ { name: "Yellow", color: "var(--editor-highlight-yellow)" },
49
+ { name: "Green", color: "var(--editor-highlight-green)" },
50
+ { name: "Blue", color: "var(--editor-highlight-blue)" },
51
+ { name: "Purple", color: "var(--editor-highlight-purple)" },
52
+ { name: "Pink", color: "var(--editor-highlight-pink)" },
53
+ { name: "Red", color: "var(--editor-highlight-red)" },
54
+ ];
55
+
56
+ interface ColorHighlightButtonProps {
57
+ name: string;
58
+ color: string;
59
+ isActive: boolean;
60
+ onClick: () => void;
61
+ isHighlight?: boolean;
62
+ }
63
+
64
+ const ColorHighlightButton = ({
65
+ name,
66
+ color,
67
+ isActive,
68
+ onClick,
69
+ isHighlight,
70
+ }: ColorHighlightButtonProps) => (
71
+ <button
72
+ onClick={onClick}
73
+ className="flex w-full items-center justify-between rounded-sm px-2 py-1 text-sm hover:bg-gray-3"
74
+ type="button"
75
+ >
76
+ <div className="flex items-center space-x-2">
77
+ <div
78
+ className="rounded-sm border px-1 py-px font-medium"
79
+ style={isHighlight ? { backgroundColor: color } : { color }}
80
+ >
81
+ A
82
+ </div>
83
+ <span>{name}</span>
84
+ </div>
85
+ {isActive && <CheckIcon className="h-4 w-4" />}
86
+ </button>
87
+ );
88
+
89
+ export const ColorHighlightToolbar = () => {
90
+ const { editor } = useToolbar();
91
+ const isMobile = useMediaQuery("(max-width: 640px)");
92
+
93
+ const currentColor = editor?.getAttributes("textStyle").color;
94
+ const currentHighlight = editor?.getAttributes("highlight").color;
95
+
96
+ const handleSetColor = (color: string) => {
97
+ editor
98
+ ?.chain()
99
+ .focus()
100
+ .setColor(color === currentColor ? "" : color)
101
+ .run();
102
+ };
103
+
104
+ const handleSetHighlight = (color: string) => {
105
+ editor
106
+ ?.chain()
107
+ .focus()
108
+ .setHighlight(color === currentHighlight ? { color: "" } : { color })
109
+ .run();
110
+ };
111
+
112
+ const isDisabled =
113
+ !editor?.can().chain().setHighlight().run() ||
114
+ !editor?.can().chain().setColor("").run();
115
+
116
+ if (isMobile) {
117
+ return (
118
+ <div className="flex gap-1">
119
+ <MobileToolbarGroup label="Color">
120
+ {TEXT_COLORS.map(({ name, color }) => (
121
+ <MobileToolbarItem
122
+ key={name}
123
+ onClick={() => handleSetColor(color)}
124
+ active={currentColor === color}
125
+ >
126
+ <div className="flex items-center gap-2">
127
+ <div className="rounded-sm border px-2" style={{ color }}>
128
+ A
129
+ </div>
130
+ <span>{name}</span>
131
+ </div>
132
+ </MobileToolbarItem>
133
+ ))}
134
+ </MobileToolbarGroup>
135
+
136
+ <MobileToolbarGroup label="Highlight">
137
+ {HIGHLIGHT_COLORS.map(({ name, color }) => (
138
+ <MobileToolbarItem
139
+ key={name}
140
+ onClick={() => handleSetHighlight(color)}
141
+ active={currentHighlight === color}
142
+ >
143
+ <div className="flex items-center gap-2">
144
+ <div
145
+ className="rounded-sm border px-2"
146
+ style={{ backgroundColor: color }}
147
+ >
148
+ A
149
+ </div>
150
+ <span>{name}</span>
151
+ </div>
152
+ </MobileToolbarItem>
153
+ ))}
154
+ </MobileToolbarGroup>
155
+ </div>
156
+ );
157
+ }
158
+
159
+ return (
160
+ <Popover>
161
+ <div className="relative h-full">
162
+ <Tooltip>
163
+ <TooltipTrigger asChild>
164
+ <PopoverTrigger disabled={isDisabled} asChild>
165
+ <Button
166
+ variant="ghost"
167
+ size="sm"
168
+ type="button"
169
+ style={{
170
+ color: currentColor,
171
+ }}
172
+ className={cn("h-8 w-14 p-0 font-normal")}
173
+ >
174
+ <span className="text-md">A</span>
175
+ <ChevronDownIcon className="ml-2 h-4 w-4" />
176
+ </Button>
177
+ </PopoverTrigger>
178
+ </TooltipTrigger>
179
+ <TooltipContent>Text Color & Highlight</TooltipContent>
180
+ </Tooltip>
181
+
182
+ <PopoverContent align="start" className="w-56 p-1 dark:bg-gray-2">
183
+ <ScrollArea className="max-h-80 overflow-y-auto pr-2">
184
+ <div className="mb-2.5 mt-2 px-2 text-xs text-gray-11">Color</div>
185
+ {TEXT_COLORS.map(({ name, color }) => (
186
+ <ColorHighlightButton
187
+ key={name}
188
+ name={name}
189
+ color={color}
190
+ isActive={currentColor === color}
191
+ onClick={() => handleSetColor(color)}
192
+ />
193
+ ))}
194
+
195
+ <Separator className="my-3" />
196
+
197
+ <div className="mb-2.5 w-full px-2 pr-3 text-xs text-gray-11">
198
+ Background
199
+ </div>
200
+ {HIGHLIGHT_COLORS.map(({ name, color }) => (
201
+ <ColorHighlightButton
202
+ key={name}
203
+ name={name}
204
+ color={color}
205
+ isActive={currentHighlight === color}
206
+ onClick={() => handleSetHighlight(color)}
207
+ isHighlight
208
+ />
209
+ ))}
210
+ </ScrollArea>
211
+ </PopoverContent>
212
+ </div>
213
+ </Popover>
214
+ );
215
+ };
@@ -0,0 +1,77 @@
1
+ 'use client'
2
+
3
+ import { useCurrentEditor } from '@tiptap/react'
4
+ import { ScrollArea, ScrollBar } from 'components/ui/scroll-area'
5
+ import { Separator } from 'components/ui/separator'
6
+ import { TooltipProvider } from 'components/ui/tooltip'
7
+ import { AlignmentTooolbar } from './alignment'
8
+ import { BlockquoteToolbar } from './blockquote'
9
+ import { BoldToolbar } from './bold'
10
+ import { BulletListToolbar } from './bullet-list'
11
+ import { CodeToolbar } from './code'
12
+ import { CodeBlockToolbar } from './code-block'
13
+ import { ColorHighlightToolbar } from './color-and-highlight'
14
+ import { HeadingsToolbar } from './headings'
15
+ import { HorizontalRuleToolbar } from './horizontal-rule'
16
+ import { ImagePlaceholderToolbar } from './image-placeholder-toolbar'
17
+ import { ItalicToolbar } from './italic'
18
+ import { LinkToolbar } from './link'
19
+ import { OrderedListToolbar } from './ordered-list'
20
+ import { RedoToolbar } from './redo'
21
+ import { StrikeThroughToolbar } from './strikethrough'
22
+ import { ToolbarProvider } from './toolbar-provider'
23
+ import { UnderlineToolbar } from './underline'
24
+ import { UndoToolbar } from './undo'
25
+
26
+ export const EditorToolbar = () => {
27
+ const { editor } = useCurrentEditor()
28
+
29
+ return (
30
+ <div className="sticky top-0 z-20 w-full border-b bg-background hidden sm:block">
31
+ <ToolbarProvider editor={editor}>
32
+ <TooltipProvider>
33
+ <ScrollArea className="h-fit py-0.5">
34
+ <div>
35
+ <div className="flex items-center gap-1 px-2">
36
+ {/* History Group */}
37
+ <UndoToolbar />
38
+ <RedoToolbar />
39
+ <Separator orientation="vertical" className="mx-1 h-7" />
40
+
41
+ {/* Text Structure Group */}
42
+ <HeadingsToolbar />
43
+ <BlockquoteToolbar />
44
+ <CodeToolbar />
45
+ <CodeBlockToolbar />
46
+ <Separator orientation="vertical" className="mx-1 h-7" />
47
+
48
+ {/* Basic Formatting Group */}
49
+ <BoldToolbar />
50
+ <ItalicToolbar />
51
+ <UnderlineToolbar />
52
+ <StrikeThroughToolbar />
53
+ <LinkToolbar />
54
+ <Separator orientation="vertical" className="mx-1 h-7" />
55
+
56
+ {/* Lists & Structure Group */}
57
+ <BulletListToolbar />
58
+ <OrderedListToolbar />
59
+ <HorizontalRuleToolbar />
60
+ <Separator orientation="vertical" className="mx-1 h-7" />
61
+
62
+ {/* Alignment Group */}
63
+ <AlignmentTooolbar />
64
+ <Separator orientation="vertical" className="mx-1 h-7" />
65
+
66
+ {/* Media & Styling Group */}
67
+ <ImagePlaceholderToolbar />
68
+ <ColorHighlightToolbar />
69
+ </div>
70
+ </div>
71
+ <ScrollBar className="hidden" orientation="horizontal" />
72
+ </ScrollArea>
73
+ </TooltipProvider>
74
+ </ToolbarProvider>
75
+ </div>
76
+ )
77
+ }
@@ -0,0 +1,46 @@
1
+ "use client";
2
+
3
+ import { WrapText } from "lucide-react";
4
+ import React from "react";
5
+
6
+ import { cn } from "@/lib/utils";
7
+ import { Button, type ButtonProps } from "components/ui/button";
8
+ import {
9
+ Tooltip,
10
+ TooltipContent,
11
+ TooltipTrigger,
12
+ } from "components/ui/tooltip";
13
+ import { useToolbar } from "./toolbar-provider";
14
+
15
+ const HardBreakToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
16
+ ({ className, onClick, children, ...props }, ref) => {
17
+ const { editor } = useToolbar();
18
+ return (
19
+ <Tooltip>
20
+ <TooltipTrigger asChild>
21
+ <Button
22
+ variant="ghost"
23
+ size="icon"
24
+ type="button"
25
+ className={cn("h-8 w-8 p-0 sm:h-9 sm:w-9", className)}
26
+ onClick={(e) => {
27
+ editor?.chain().focus().setHardBreak().run();
28
+ onClick?.(e);
29
+ }}
30
+ ref={ref}
31
+ {...props}
32
+ >
33
+ {children ?? <WrapText className="h-4 w-4" />}
34
+ </Button>
35
+ </TooltipTrigger>
36
+ <TooltipContent>
37
+ <span>Hard break</span>
38
+ </TooltipContent>
39
+ </Tooltip>
40
+ );
41
+ },
42
+ );
43
+
44
+ HardBreakToolbar.displayName = "HardBreakToolbar";
45
+
46
+ export { HardBreakToolbar };
@@ -0,0 +1,97 @@
1
+ 'use client'
2
+
3
+ import { Button } from '@/components/ui/button'
4
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
5
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
6
+ import { useMediaQuery } from '@/hooks/use-media-querry'
7
+ import { cn } from '@/lib/utils'
8
+ import { ChevronDown } from 'lucide-react'
9
+ import React from 'react'
10
+ import { MobileToolbarGroup, MobileToolbarItem } from './mobile-toolbar-group'
11
+ import { useToolbar } from './toolbar-provider'
12
+
13
+ const levels = [1, 2, 3, 4] as const
14
+
15
+ export const HeadingsToolbar = React.forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
16
+ ({ className, ...props }, ref) => {
17
+ const { editor } = useToolbar()
18
+ const isMobile = useMediaQuery('(max-width: 640px)')
19
+ const activeLevel = levels.find((level) => editor?.isActive('heading', { level }))
20
+
21
+ if (isMobile) {
22
+ return (
23
+ <MobileToolbarGroup label={activeLevel ? `H${activeLevel}` : 'Normal'}>
24
+ <MobileToolbarItem
25
+ onClick={() => editor?.chain().focus().setParagraph().run()}
26
+ active={!editor?.isActive('heading')}
27
+ >
28
+ Normal
29
+ </MobileToolbarItem>
30
+ {levels.map((level) => (
31
+ <MobileToolbarItem
32
+ key={level}
33
+ onClick={() => editor?.chain().focus().toggleHeading({ level }).run()}
34
+ active={editor?.isActive('heading', { level })}
35
+ >
36
+ H{level}
37
+ </MobileToolbarItem>
38
+ ))}
39
+ </MobileToolbarGroup>
40
+ )
41
+ }
42
+
43
+ return (
44
+ <Tooltip>
45
+ <TooltipTrigger asChild>
46
+ <DropdownMenu>
47
+ <DropdownMenuTrigger asChild>
48
+ <Button
49
+ variant="ghost"
50
+ size="sm"
51
+ type="button"
52
+ className={cn(
53
+ 'h-8 w-max gap-1 px-3 font-normal',
54
+ editor?.isActive('heading') && 'bg-accent',
55
+ className,
56
+ )}
57
+ ref={ref}
58
+ {...props}
59
+ >
60
+ {activeLevel ? `H${activeLevel}` : 'Normal'}
61
+ <ChevronDown className="h-4 w-4" />
62
+ </Button>
63
+ </DropdownMenuTrigger>
64
+ <DropdownMenuContent align="start">
65
+ <DropdownMenuItem
66
+ onClick={() => editor?.chain().focus().setParagraph().run()}
67
+ className={cn(
68
+ 'flex items-center gap-2 h-fit',
69
+ !editor?.isActive('heading') && 'bg-accent',
70
+ )}
71
+ >
72
+ Normal
73
+ </DropdownMenuItem>
74
+ {levels.map((level) => (
75
+ <DropdownMenuItem
76
+ key={level}
77
+ onClick={() => editor?.chain().focus().toggleHeading({ level }).run()}
78
+ className={cn(
79
+ 'flex items-center gap-2',
80
+ editor?.isActive('heading', { level }) && 'bg-accent',
81
+ )}
82
+ >
83
+ H{level}
84
+ </DropdownMenuItem>
85
+ ))}
86
+ </DropdownMenuContent>
87
+ </DropdownMenu>
88
+ </TooltipTrigger>
89
+ <TooltipContent>
90
+ <span>Headings</span>
91
+ </TooltipContent>
92
+ </Tooltip>
93
+ )
94
+ },
95
+ )
96
+
97
+ HeadingsToolbar.displayName = 'HeadingsToolbar'
@@ -0,0 +1,42 @@
1
+ 'use client'
2
+
3
+ import { SeparatorHorizontal } from 'lucide-react'
4
+ import React from 'react'
5
+
6
+ import { cn } from '@/lib/utils'
7
+ import { Button, type ButtonProps } from 'components/ui/button'
8
+ import { Tooltip, TooltipContent, TooltipTrigger } from 'components/ui/tooltip'
9
+ import { useToolbar } from './toolbar-provider'
10
+
11
+ const HorizontalRuleToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
12
+ ({ className, onClick, children, ...props }, ref) => {
13
+ const { editor } = useToolbar()
14
+ return (
15
+ <Tooltip>
16
+ <TooltipTrigger asChild>
17
+ <Button
18
+ variant="ghost"
19
+ size="icon"
20
+ type="button"
21
+ className={cn('h-8 w-8 p-0 sm:h-9 sm:w-9', className)}
22
+ onClick={(e) => {
23
+ editor?.chain().focus().setHorizontalRule().run()
24
+ onClick?.(e)
25
+ }}
26
+ ref={ref}
27
+ {...props}
28
+ >
29
+ {children ?? <SeparatorHorizontal className="h-4 w-4" />}
30
+ </Button>
31
+ </TooltipTrigger>
32
+ <TooltipContent>
33
+ <span>Horizontal Rule</span>
34
+ </TooltipContent>
35
+ </Tooltip>
36
+ )
37
+ },
38
+ )
39
+
40
+ HorizontalRuleToolbar.displayName = 'HorizontalRuleToolbar'
41
+
42
+ export { HorizontalRuleToolbar }
@@ -0,0 +1,47 @@
1
+ 'use client'
2
+
3
+ import { Image } from 'lucide-react'
4
+ import React from 'react'
5
+
6
+ import { cn } from '@/lib/utils'
7
+ import { Button, type ButtonProps } from 'components/ui/button'
8
+ import { Tooltip, TooltipContent, TooltipTrigger } from 'components/ui/tooltip'
9
+ import { useToolbar } from './toolbar-provider'
10
+
11
+ const ImagePlaceholderToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
12
+ ({ className, onClick, children, ...props }, ref) => {
13
+ const { editor } = useToolbar()
14
+ return (
15
+ <Tooltip>
16
+ <TooltipTrigger asChild>
17
+ <Button
18
+ variant="ghost"
19
+ size="icon"
20
+ type="button"
21
+ className={cn(
22
+ 'h-8 w-8 p-0 sm:h-9 sm:w-9',
23
+ editor?.isActive('image-placeholder') && 'bg-accent',
24
+ className,
25
+ )}
26
+ onClick={(e) => {
27
+ e.preventDefault()
28
+ editor?.chain().focus().insertImagePlaceholder().run()
29
+ onClick?.(e)
30
+ }}
31
+ ref={ref}
32
+ {...props}
33
+ >
34
+ {children ?? <Image className="h-4 w-4" />}
35
+ </Button>
36
+ </TooltipTrigger>
37
+ <TooltipContent>
38
+ <span>Image</span>
39
+ </TooltipContent>
40
+ </Tooltip>
41
+ )
42
+ },
43
+ )
44
+
45
+ ImagePlaceholderToolbar.displayName = 'ImagePlaceholderToolbar'
46
+
47
+ export { ImagePlaceholderToolbar }
@@ -0,0 +1,48 @@
1
+ 'use client'
2
+
3
+ import { ItalicIcon } from 'lucide-react'
4
+ import React from 'react'
5
+
6
+ import { cn } from '@/lib/utils'
7
+ import { Button, type ButtonProps } from 'components/ui/button'
8
+ import { Tooltip, TooltipContent, TooltipTrigger } from 'components/ui/tooltip'
9
+ import { useToolbar } from './toolbar-provider'
10
+
11
+ const ItalicToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
12
+ ({ className, onClick, children, ...props }, ref) => {
13
+ const { editor } = useToolbar()
14
+ return (
15
+ <Tooltip>
16
+ <TooltipTrigger asChild>
17
+ <Button
18
+ variant="ghost"
19
+ size="icon"
20
+ type="button"
21
+ className={cn(
22
+ 'h-8 w-8 p-0 sm:h-9 sm:w-9',
23
+ editor?.isActive('italic') && 'bg-accent',
24
+ className,
25
+ )}
26
+ onClick={(e) => {
27
+ editor?.chain().focus().toggleItalic().run()
28
+ onClick?.(e)
29
+ }}
30
+ disabled={!editor?.can().chain().focus().toggleItalic().run()}
31
+ ref={ref}
32
+ {...props}
33
+ >
34
+ {children ?? <ItalicIcon className="h-4 w-4" />}
35
+ </Button>
36
+ </TooltipTrigger>
37
+ <TooltipContent>
38
+ <span>Italic</span>
39
+ <span className="ml-1 text-xs text-gray-11">(cmd + i)</span>
40
+ </TooltipContent>
41
+ </Tooltip>
42
+ )
43
+ },
44
+ )
45
+
46
+ ItalicToolbar.displayName = 'ItalicToolbar'
47
+
48
+ export { ItalicToolbar }