@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.
- package/.devcontainer/Dockerfile +13 -0
- package/.devcontainer/devcontainer.json +52 -0
- package/.github/dependabot.yml +10 -0
- package/.github/workflows/pages.yml +42 -0
- package/.github/workflows/publish.yml +41 -0
- package/.vscode/settings.json +7 -0
- package/LICENSE +9 -0
- package/README.md +9 -0
- package/app/_component/editor.tsx +383 -0
- package/app/layout.tsx +46 -0
- package/app/page.tsx +11 -0
- package/app/r/registry.json/route.ts +22 -0
- package/components/editorx/editor.tsx +1794 -0
- package/components/editorx/extensions/floating-menu.tsx +376 -0
- package/components/editorx/extensions/floating-toolbar.tsx +97 -0
- package/components/editorx/extensions/image-placeholder.tsx +316 -0
- package/components/editorx/extensions/image.tsx +462 -0
- package/components/editorx/extensions/search-and-replace.tsx +438 -0
- package/components/editorx/rich-text-editor.tsx +383 -0
- package/components/editorx/tiptap.css +421 -0
- package/components/editorx/toolbars/alignment.tsx +126 -0
- package/components/editorx/toolbars/blockquote.tsx +47 -0
- package/components/editorx/toolbars/bold.tsx +48 -0
- package/components/editorx/toolbars/bullet-list.tsx +48 -0
- package/components/editorx/toolbars/code-block.tsx +47 -0
- package/components/editorx/toolbars/code.tsx +43 -0
- package/components/editorx/toolbars/color-and-highlight.tsx +215 -0
- package/components/editorx/toolbars/editor-toolbar.tsx +77 -0
- package/components/editorx/toolbars/hard-break.tsx +46 -0
- package/components/editorx/toolbars/headings.tsx +97 -0
- package/components/editorx/toolbars/horizontal-rule.tsx +42 -0
- package/components/editorx/toolbars/image-placeholder-toolbar.tsx +47 -0
- package/components/editorx/toolbars/italic.tsx +48 -0
- package/components/editorx/toolbars/link.tsx +130 -0
- package/components/editorx/toolbars/mobile-toolbar-group.tsx +76 -0
- package/components/editorx/toolbars/ordered-list.tsx +47 -0
- package/components/editorx/toolbars/redo.tsx +44 -0
- package/components/editorx/toolbars/strikethrough.tsx +48 -0
- package/components/editorx/toolbars/toolbar-provider.tsx +29 -0
- package/components/editorx/toolbars/underline.tsx +48 -0
- package/components/editorx/toolbars/undo.tsx +43 -0
- package/components/layout/theme-switcher.tsx +26 -0
- package/components/main-nav.tsx +24 -0
- package/components/mobile-nav.tsx +46 -0
- package/components/open-in-v0-button.tsx +38 -0
- package/components/page-header.tsx +30 -0
- package/components/site-footer.tsx +41 -0
- package/components/site-header.tsx +32 -0
- package/components/theme-provider.tsx +8 -0
- package/components/ui/button.tsx +57 -0
- package/components/ui/checkbox.tsx +30 -0
- package/components/ui/collapsible.tsx +11 -0
- package/components/ui/command.tsx +148 -0
- package/components/ui/dialog.tsx +122 -0
- package/components/ui/drawer.tsx +118 -0
- package/components/ui/dropdown-menu.tsx +201 -0
- package/components/ui/input.tsx +22 -0
- package/components/ui/label.tsx +26 -0
- package/components/ui/popover.tsx +33 -0
- package/components/ui/resizable.tsx +40 -0
- package/components/ui/scroll-area.tsx +42 -0
- package/components/ui/separator.tsx +31 -0
- package/components/ui/sheet.tsx +140 -0
- package/components/ui/sidebar.tsx +763 -0
- package/components/ui/skeleton.tsx +15 -0
- package/components/ui/spinner.tsx +29 -0
- package/components/ui/tabs.tsx +55 -0
- package/components/ui/toggle-group.tsx +61 -0
- package/components/ui/toggle.tsx +45 -0
- package/components/ui/tooltip.tsx +32 -0
- package/components.json +21 -0
- package/config/site.ts +15 -0
- package/eslint.config.mjs +20 -0
- package/hooks/use-character-limit.ts +28 -0
- package/hooks/use-copy-to-clipboard.ts +16 -0
- package/hooks/use-debounce.ts +17 -0
- package/hooks/use-image-upload.ts +97 -0
- package/hooks/use-media-querry.ts +18 -0
- package/hooks/use-mobile.tsx +19 -0
- package/images/editor.png +0 -0
- package/lib/content.ts +39 -0
- package/lib/cookie-client.ts +19 -0
- package/lib/localstorage-client.ts +19 -0
- package/lib/package.ts +144 -0
- package/lib/preferences-config.ts +72 -0
- package/lib/preferences-storage.ts +20 -0
- package/lib/theme-utils.ts +12 -0
- package/lib/theme.ts +50 -0
- package/lib/tiptap-utils.ts +45 -0
- package/lib/utils.ts +11 -0
- package/next-env.d.ts +6 -0
- package/next.config.mjs +11 -0
- package/package.json +92 -0
- package/postcss.config.mjs +8 -0
- package/prettier.config.mjs +15 -0
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/og.webp +0 -0
- package/public/r/editor-x.json +85 -0
- package/public/r/registry.json +93 -0
- package/public/site.webmanifest +19 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/registry/editor/components/editor.tsx +1794 -0
- package/registry/editor/components/extensions/floating-menu.tsx +376 -0
- package/registry/editor/components/extensions/floating-toolbar.tsx +97 -0
- package/registry/editor/components/extensions/image-placeholder.tsx +316 -0
- package/registry/editor/components/extensions/image.tsx +462 -0
- package/registry/editor/components/extensions/search-and-replace.tsx +438 -0
- package/registry/editor/components/rich-text-editor.tsx +383 -0
- package/registry/editor/components/tiptap.css +421 -0
- package/registry/editor/components/toolbars/alignment.tsx +126 -0
- package/registry/editor/components/toolbars/blockquote.tsx +47 -0
- package/registry/editor/components/toolbars/bold.tsx +48 -0
- package/registry/editor/components/toolbars/bullet-list.tsx +48 -0
- package/registry/editor/components/toolbars/code-block.tsx +47 -0
- package/registry/editor/components/toolbars/code.tsx +43 -0
- package/registry/editor/components/toolbars/color-and-highlight.tsx +215 -0
- package/registry/editor/components/toolbars/editor-toolbar.tsx +77 -0
- package/registry/editor/components/toolbars/hard-break.tsx +46 -0
- package/registry/editor/components/toolbars/headings.tsx +97 -0
- package/registry/editor/components/toolbars/horizontal-rule.tsx +42 -0
- package/registry/editor/components/toolbars/image-placeholder-toolbar.tsx +47 -0
- package/registry/editor/components/toolbars/italic.tsx +48 -0
- package/registry/editor/components/toolbars/link.tsx +130 -0
- package/registry/editor/components/toolbars/mobile-toolbar-group.tsx +76 -0
- package/registry/editor/components/toolbars/ordered-list.tsx +47 -0
- package/registry/editor/components/toolbars/redo.tsx +44 -0
- package/registry/editor/components/toolbars/strikethrough.tsx +48 -0
- package/registry/editor/components/toolbars/toolbar-provider.tsx +29 -0
- package/registry/editor/components/toolbars/underline.tsx +48 -0
- package/registry/editor/components/toolbars/undo.tsx +43 -0
- package/registry/editor/components/ui/button.tsx +57 -0
- package/registry/editor/components/ui/checkbox.tsx +30 -0
- package/registry/editor/components/ui/collapsible.tsx +11 -0
- package/registry/editor/components/ui/command.tsx +148 -0
- package/registry/editor/components/ui/dialog.tsx +122 -0
- package/registry/editor/components/ui/drawer.tsx +118 -0
- package/registry/editor/components/ui/dropdown-menu.tsx +201 -0
- package/registry/editor/components/ui/input.tsx +22 -0
- package/registry/editor/components/ui/label.tsx +26 -0
- package/registry/editor/components/ui/popover.tsx +33 -0
- package/registry/editor/components/ui/resizable.tsx +40 -0
- package/registry/editor/components/ui/scroll-area.tsx +42 -0
- package/registry/editor/components/ui/separator.tsx +31 -0
- package/registry/editor/components/ui/sheet.tsx +140 -0
- package/registry/editor/components/ui/sidebar.tsx +763 -0
- package/registry/editor/components/ui/skeleton.tsx +15 -0
- package/registry/editor/components/ui/spinner.tsx +29 -0
- package/registry/editor/components/ui/tabs.tsx +55 -0
- package/registry/editor/components/ui/toggle-group.tsx +61 -0
- package/registry/editor/components/ui/toggle.tsx +45 -0
- package/registry/editor/components/ui/tooltip.tsx +32 -0
- package/registry/editor/hooks/use-character-limit.ts +28 -0
- package/registry/editor/hooks/use-copy-to-clipboard.ts +16 -0
- package/registry/editor/hooks/use-debounce.ts +17 -0
- package/registry/editor/hooks/use-image-upload.ts +97 -0
- package/registry/editor/hooks/use-media-querry.ts +18 -0
- package/registry/editor/hooks/use-mobile.tsx +19 -0
- package/registry/editor/lib/content.ts +39 -0
- package/registry/editor/lib/cookie-client.ts +19 -0
- package/registry/editor/lib/localstorage-client.ts +19 -0
- package/registry/editor/lib/package.ts +144 -0
- package/registry/editor/lib/preferences-config.ts +72 -0
- package/registry/editor/lib/preferences-storage.ts +20 -0
- package/registry/editor/lib/theme-utils.ts +12 -0
- package/registry/editor/lib/theme.ts +50 -0
- package/registry/editor/lib/tiptap-utils.ts +45 -0
- package/registry/editor/lib/utils.ts +11 -0
- package/registry/editor/page.tsx +9 -0
- package/registry.json +93 -0
- package/reset.d.ts +1 -0
- package/scripts/generate-theme-presets.ts +128 -0
- package/scripts/postCreateCommand.sh +0 -0
- package/scripts/theme-boot.tsx +105 -0
- package/server/server-actions.ts +27 -0
- package/stores/preferences/preferences-provider.tsx +55 -0
- package/stores/preferences/preferences-store.ts +23 -0
- package/styles/globals.css +288 -0
- package/styles/presets/brutalist.css +89 -0
- package/styles/presets/soft-pop.css +89 -0
- package/styles/presets/tangerine.css +89 -0
- 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 }
|