@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,130 @@
1
+ 'use client'
2
+ /* eslint-disable */
3
+ // @ts-nocheck
4
+ import { PopoverClose } from "@radix-ui/react-popover";
5
+ import { Trash2, X } from "lucide-react";
6
+
7
+ import React, { type FormEvent } from "react";
8
+
9
+ import { cn } from "@/lib/utils";
10
+ import { Button, type ButtonProps } from "components/ui/button";
11
+ import { Input } from "components/ui/input";
12
+ import { Label } from "components/ui/label";
13
+ import {
14
+ Tooltip,
15
+ TooltipContent,
16
+ TooltipTrigger,
17
+ } from "components/ui/tooltip";
18
+
19
+ import { getUrlFromString } from "@/lib/tiptap-utils";
20
+ import {
21
+ Popover,
22
+ PopoverContent,
23
+ PopoverTrigger,
24
+ } from "components/ui/popover";
25
+ import { useToolbar } from "./toolbar-provider";
26
+
27
+ const LinkToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
28
+ ({ className, ...props }, ref) => {
29
+ const { editor } = useToolbar();
30
+ const [link, setLink] = React.useState("");
31
+
32
+ const handleSubmit = (e: FormEvent) => {
33
+ e.preventDefault();
34
+ const url = getUrlFromString(link);
35
+ url && editor?.chain().focus().setLink({ href: url }).run();
36
+ };
37
+
38
+ React.useEffect(() => {
39
+ setLink(editor?.getAttributes("link").href ?? "");
40
+ }, [editor]);
41
+
42
+ return (
43
+ <Popover>
44
+ <Tooltip>
45
+ <TooltipTrigger asChild>
46
+ <PopoverTrigger
47
+ disabled={!editor?.can().chain().setLink({ href: "" }).run()}
48
+ asChild
49
+ >
50
+ <Button
51
+ variant="ghost"
52
+ size="sm"
53
+ type="button"
54
+ className={cn(
55
+ "h-8 w-max px-3 font-normal",
56
+ editor?.isActive("link") && "bg-accent",
57
+ className,
58
+ )}
59
+ ref={ref}
60
+ {...props}
61
+ >
62
+ <p className="mr-2 text-base">↗</p>
63
+ <p className={"underline decoration-gray-7 underline-offset-4"}>
64
+ Link
65
+ </p>
66
+ </Button>
67
+ </PopoverTrigger>
68
+ </TooltipTrigger>
69
+ <TooltipContent>
70
+ <span>Link</span>
71
+ </TooltipContent>
72
+ </Tooltip>
73
+
74
+ <PopoverContent
75
+ onCloseAutoFocus={(e) => {
76
+ e.preventDefault();
77
+ }}
78
+ asChild
79
+ className="relative px-3 py-2.5"
80
+ >
81
+ <div className="relative">
82
+ <PopoverClose className="absolute right-3 top-3">
83
+ <X className="h-4 w-4" />
84
+ </PopoverClose>
85
+ <form onSubmit={handleSubmit}>
86
+ <Label>Link</Label>
87
+ <p className="text-sm text-gray-11">
88
+ Attach a link to the selected text
89
+ </p>
90
+ <div className="mt-3 flex flex-col items-end justify-end gap-3">
91
+ <Input
92
+ value={link}
93
+ onChange={(e) => {
94
+ setLink(e.target.value);
95
+ }}
96
+ className="w-full"
97
+ placeholder="https://example.com"
98
+ />
99
+ <div className="flex items-center gap-3">
100
+ {editor?.getAttributes("link").href && (
101
+ <Button
102
+ type="reset"
103
+ size="sm"
104
+ className="h-8 text-gray-11"
105
+ variant="ghost"
106
+ onClick={() => {
107
+ editor?.chain().focus().unsetLink().run();
108
+ setLink("");
109
+ }}
110
+ >
111
+ <Trash2 className="mr-2 h-4 w-4" />
112
+ Remove
113
+ </Button>
114
+ )}
115
+ <Button size="sm" className="h-8">
116
+ {editor?.getAttributes("link").href ? "Update" : "Confirm"}
117
+ </Button>
118
+ </div>
119
+ </div>
120
+ </form>
121
+ </div>
122
+ </PopoverContent>
123
+ </Popover>
124
+ );
125
+ },
126
+ );
127
+
128
+ LinkToolbar.displayName = "LinkToolbar";
129
+
130
+ export { LinkToolbar };
@@ -0,0 +1,76 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@/lib/utils'
4
+ import { Button } from 'components/ui/button'
5
+ import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from 'components/ui/drawer'
6
+ import { ChevronDown } from 'lucide-react'
7
+ import React, { useState } from 'react'
8
+
9
+ interface MobileToolbarGroupProps {
10
+ label: string
11
+ children: React.ReactNode
12
+ className?: string
13
+ }
14
+
15
+ export const MobileToolbarGroup = ({ label, children, className }: MobileToolbarGroupProps) => {
16
+ const [isOpen, setIsOpen] = useState(false)
17
+
18
+ const closeDrawer = () => setIsOpen(false)
19
+
20
+ return (
21
+ <Drawer open={isOpen} onOpenChange={setIsOpen}>
22
+ <DrawerTrigger asChild>
23
+ <Button
24
+ variant="ghost"
25
+ size="sm"
26
+ type="button"
27
+ className={cn('h-8 w-max gap-1 px-3 font-normal', className)}
28
+ >
29
+ {label}
30
+ <ChevronDown className="h-4 w-4" />
31
+ </Button>
32
+ </DrawerTrigger>
33
+ <DrawerContent>
34
+ <DrawerHeader>
35
+ <DrawerTitle className="text-start">{label}</DrawerTitle>
36
+ </DrawerHeader>
37
+ <div className="flex flex-col p-4">
38
+ {React.Children.map(children, (child) =>
39
+ React.isValidElement(child)
40
+ ? React.cloneElement(child, { closeDrawer } as {
41
+ closeDrawer: () => void
42
+ })
43
+ : child,
44
+ )}
45
+ </div>
46
+ </DrawerContent>
47
+ </Drawer>
48
+ )
49
+ }
50
+
51
+ export const MobileToolbarItem = ({
52
+ children,
53
+ active,
54
+ onClick,
55
+ closeDrawer,
56
+ ...props
57
+ }: React.ButtonHTMLAttributes<HTMLButtonElement> & {
58
+ active?: boolean
59
+ closeDrawer?: () => void
60
+ }) => (
61
+ <button
62
+ className={cn(
63
+ 'flex w-full items-center rounded-md px-4 py-2 text-sm transition-colors hover:bg-accent',
64
+ active && 'bg-accent',
65
+ )}
66
+ onClick={(e) => {
67
+ onClick?.(e)
68
+ setTimeout(() => {
69
+ closeDrawer?.()
70
+ }, 100)
71
+ }}
72
+ {...props}
73
+ >
74
+ {children}
75
+ </button>
76
+ )
@@ -0,0 +1,47 @@
1
+ 'use client'
2
+
3
+ import { ListOrdered } 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 OrderedListToolbar = 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('orderedList') && 'bg-accent',
24
+ className,
25
+ )}
26
+ onClick={(e) => {
27
+ editor?.chain().focus().toggleOrderedList().run()
28
+ onClick?.(e)
29
+ }}
30
+ disabled={!editor?.can().chain().focus().toggleOrderedList().run()}
31
+ ref={ref}
32
+ {...props}
33
+ >
34
+ {children ?? <ListOrdered className="h-4 w-4" />}
35
+ </Button>
36
+ </TooltipTrigger>
37
+ <TooltipContent>
38
+ <span>Ordered list</span>
39
+ </TooltipContent>
40
+ </Tooltip>
41
+ )
42
+ },
43
+ )
44
+
45
+ OrderedListToolbar.displayName = 'OrderedListToolbar'
46
+
47
+ export { OrderedListToolbar }
@@ -0,0 +1,44 @@
1
+ 'use client'
2
+
3
+ import { Redo2 } 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 RedoToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
12
+ ({ className, onClick, children, ...props }, ref) => {
13
+ const { editor } = useToolbar()
14
+
15
+ return (
16
+ <Tooltip>
17
+ <TooltipTrigger asChild>
18
+ <Button
19
+ variant="ghost"
20
+ size="icon"
21
+ type="button"
22
+ className={cn('h-8 w-8 p-0 sm:h-9 sm:w-9', className)}
23
+ onClick={(e) => {
24
+ editor?.chain().focus().redo().run()
25
+ onClick?.(e)
26
+ }}
27
+ disabled={!editor?.can().chain().focus().redo().run()}
28
+ ref={ref}
29
+ {...props}
30
+ >
31
+ {children ?? <Redo2 className="h-4 w-4" />}
32
+ </Button>
33
+ </TooltipTrigger>
34
+ <TooltipContent>
35
+ <span>Redo</span>
36
+ </TooltipContent>
37
+ </Tooltip>
38
+ )
39
+ },
40
+ )
41
+
42
+ RedoToolbar.displayName = 'RedoToolbar'
43
+
44
+ export { RedoToolbar }
@@ -0,0 +1,48 @@
1
+ 'use client'
2
+
3
+ import { Strikethrough } 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 StrikeThroughToolbar = 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('strike') && 'bg-accent',
24
+ className,
25
+ )}
26
+ onClick={(e) => {
27
+ editor?.chain().focus().toggleStrike().run()
28
+ onClick?.(e)
29
+ }}
30
+ disabled={!editor?.can().chain().focus().toggleStrike().run()}
31
+ ref={ref}
32
+ {...props}
33
+ >
34
+ {children ?? <Strikethrough className="h-4 w-4" />}
35
+ </Button>
36
+ </TooltipTrigger>
37
+ <TooltipContent>
38
+ <span>Strikethrough</span>
39
+ <span className="ml-1 text-xs text-gray-11">(cmd + shift + x)</span>
40
+ </TooltipContent>
41
+ </Tooltip>
42
+ )
43
+ },
44
+ )
45
+
46
+ StrikeThroughToolbar.displayName = 'StrikeThroughToolbar'
47
+
48
+ export { StrikeThroughToolbar }
@@ -0,0 +1,29 @@
1
+ 'use client'
2
+
3
+ import type { Editor } from '@tiptap/react'
4
+ import React from 'react'
5
+
6
+ export interface ToolbarContextProps {
7
+ editor: Editor | null
8
+ }
9
+
10
+ export const ToolbarContext = React.createContext<ToolbarContextProps | null>(null)
11
+
12
+ interface ToolbarProviderProps {
13
+ editor: Editor | null
14
+ children: React.ReactNode
15
+ }
16
+
17
+ export const ToolbarProvider = ({ editor, children }: ToolbarProviderProps) => {
18
+ return <ToolbarContext.Provider value={{ editor }}>{children}</ToolbarContext.Provider>
19
+ }
20
+
21
+ export const useToolbar = () => {
22
+ const context = React.useContext(ToolbarContext)
23
+
24
+ if (!context) {
25
+ throw new Error('useToolbar must be used within a ToolbarProvider')
26
+ }
27
+
28
+ return context
29
+ }
@@ -0,0 +1,48 @@
1
+ 'use client'
2
+
3
+ import { UnderlineIcon } 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 UnderlineToolbar = 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('underline') && 'bg-accent',
24
+ className,
25
+ )}
26
+ onClick={(e) => {
27
+ editor?.chain().focus().toggleUnderline().run()
28
+ onClick?.(e)
29
+ }}
30
+ disabled={!editor?.can().chain().focus().toggleUnderline().run()}
31
+ ref={ref}
32
+ {...props}
33
+ >
34
+ {children ?? <UnderlineIcon className="h-4 w-4" />}
35
+ </Button>
36
+ </TooltipTrigger>
37
+ <TooltipContent>
38
+ <span>Underline</span>
39
+ <span className="ml-1 text-xs text-gray-11">(cmd + u)</span>
40
+ </TooltipContent>
41
+ </Tooltip>
42
+ )
43
+ },
44
+ )
45
+
46
+ UnderlineToolbar.displayName = 'UnderlineToolbar'
47
+
48
+ export { UnderlineToolbar }
@@ -0,0 +1,43 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@/lib/utils'
4
+ import { Button, type ButtonProps } from 'components/ui/button'
5
+ import { Tooltip, TooltipContent, TooltipTrigger } from 'components/ui/tooltip'
6
+ import { Undo2 } from 'lucide-react'
7
+ import React from 'react'
8
+ import { useToolbar } from './toolbar-provider'
9
+
10
+ const UndoToolbar = React.forwardRef<HTMLButtonElement, ButtonProps>(
11
+ ({ className, onClick, children, ...props }, ref) => {
12
+ const { editor } = useToolbar()
13
+
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().undo().run()
24
+ onClick?.(e)
25
+ }}
26
+ disabled={!editor?.can().chain().focus().undo().run()}
27
+ ref={ref}
28
+ {...props}
29
+ >
30
+ {children ?? <Undo2 className="h-4 w-4" />}
31
+ </Button>
32
+ </TooltipTrigger>
33
+ <TooltipContent>
34
+ <span>Undo</span>
35
+ </TooltipContent>
36
+ </Tooltip>
37
+ )
38
+ },
39
+ )
40
+
41
+ UndoToolbar.displayName = 'UndoToolbar'
42
+
43
+ export { UndoToolbar }
@@ -0,0 +1,57 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14
+ destructive:
15
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16
+ outline:
17
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20
+ ghost: "hover:bg-accent hover:text-accent-foreground",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2",
25
+ sm: "h-8 rounded-md px-3 text-xs",
26
+ lg: "h-10 rounded-md px-8",
27
+ icon: "h-9 w-9",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: "default",
32
+ size: "default",
33
+ },
34
+ }
35
+ )
36
+
37
+ export interface ButtonProps
38
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
39
+ VariantProps<typeof buttonVariants> {
40
+ asChild?: boolean
41
+ }
42
+
43
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
44
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
45
+ const Comp = asChild ? Slot : "button"
46
+ return (
47
+ <Comp
48
+ className={cn(buttonVariants({ variant, size, className }))}
49
+ ref={ref}
50
+ {...props}
51
+ />
52
+ )
53
+ }
54
+ )
55
+ Button.displayName = "Button"
56
+
57
+ export { Button, buttonVariants }
@@ -0,0 +1,30 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5
+ import { Check } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Checkbox = React.forwardRef<
10
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
11
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
12
+ >(({ className, ...props }, ref) => (
13
+ <CheckboxPrimitive.Root
14
+ ref={ref}
15
+ className={cn(
16
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
17
+ className
18
+ )}
19
+ {...props}
20
+ >
21
+ <CheckboxPrimitive.Indicator
22
+ className={cn("flex items-center justify-center text-current")}
23
+ >
24
+ <Check className="h-4 w-4" />
25
+ </CheckboxPrimitive.Indicator>
26
+ </CheckboxPrimitive.Root>
27
+ ))
28
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
29
+
30
+ export { Checkbox }
@@ -0,0 +1,11 @@
1
+ "use client"
2
+
3
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4
+
5
+ const Collapsible = CollapsiblePrimitive.Root
6
+
7
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8
+
9
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10
+
11
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }