@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,26 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Moon, Sun } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
import { persistPreference } from '@/lib/preferences-storage'
|
|
7
|
+
import { applyThemeMode } from '@/lib/theme-utils'
|
|
8
|
+
import { usePreferencesStore } from '@/stores/preferences/preferences-provider'
|
|
9
|
+
|
|
10
|
+
export function ThemeSwitcher() {
|
|
11
|
+
const themeMode = usePreferencesStore((s) => s.themeMode)
|
|
12
|
+
const setThemeMode = usePreferencesStore((s) => s.setThemeMode)
|
|
13
|
+
|
|
14
|
+
const handleValueChange = async () => {
|
|
15
|
+
const newTheme = themeMode === 'dark' ? 'light' : 'dark'
|
|
16
|
+
applyThemeMode(newTheme)
|
|
17
|
+
setThemeMode(newTheme)
|
|
18
|
+
persistPreference('theme_mode', newTheme)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Button variant="ghost" onClick={handleValueChange}>
|
|
23
|
+
{themeMode === 'dark' ? <Sun /> : <Moon />}
|
|
24
|
+
</Button>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
4
|
+
import { navConfig } from '@/config/site'
|
|
5
|
+
import Link from 'next/link'
|
|
6
|
+
import { usePathname } from 'next/navigation'
|
|
7
|
+
|
|
8
|
+
export function MainNav() {
|
|
9
|
+
const pathname = usePathname()
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="mr-4 hidden items-center space-x-6 md:flex">
|
|
13
|
+
<nav className="flex items-center gap-4 text-sm xl:gap-6">
|
|
14
|
+
{navConfig.mainNav.map((item, idx) => (
|
|
15
|
+
<Button key={idx} variant="ghost">
|
|
16
|
+
<Link key={item.path} href={item.path}>
|
|
17
|
+
{item.title}
|
|
18
|
+
</Link>
|
|
19
|
+
</Button>
|
|
20
|
+
))}
|
|
21
|
+
</nav>
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { navConfig } from '@/config/site'
|
|
4
|
+
import { Button } from 'components/ui/button'
|
|
5
|
+
import { Drawer, DrawerContent, DrawerTrigger } from 'components/ui/drawer'
|
|
6
|
+
import Link from 'next/link'
|
|
7
|
+
import * as React from 'react'
|
|
8
|
+
|
|
9
|
+
export default function MobileNav() {
|
|
10
|
+
const [open, setOpen] = React.useState(false)
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Drawer open={open} onOpenChange={setOpen}>
|
|
14
|
+
<DrawerTrigger asChild>
|
|
15
|
+
<Button
|
|
16
|
+
variant="ghost"
|
|
17
|
+
className="mr-2 px-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden"
|
|
18
|
+
>
|
|
19
|
+
<svg
|
|
20
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
21
|
+
fill="none"
|
|
22
|
+
viewBox="0 0 24 24"
|
|
23
|
+
strokeWidth="1.5"
|
|
24
|
+
stroke="currentColor"
|
|
25
|
+
className="h-6 w-6"
|
|
26
|
+
>
|
|
27
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 9h16.5m-16.5 6.75h16.5" />
|
|
28
|
+
</svg>
|
|
29
|
+
<span className="sr-only">Toggle Menu</span>
|
|
30
|
+
</Button>
|
|
31
|
+
</DrawerTrigger>
|
|
32
|
+
<DrawerContent className="h-[50%]">
|
|
33
|
+
<nav className="flex flex-col space-y-3 p-4">
|
|
34
|
+
<Link href="/" onClick={() => setOpen(false)}>
|
|
35
|
+
Home
|
|
36
|
+
</Link>
|
|
37
|
+
{navConfig.mainNav.map((item) => (
|
|
38
|
+
<Link key={item.path} href={item.path} onClick={() => setOpen(false)}>
|
|
39
|
+
{item.title}
|
|
40
|
+
</Link>
|
|
41
|
+
))}
|
|
42
|
+
</nav>
|
|
43
|
+
</DrawerContent>
|
|
44
|
+
</Drawer>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cn } from '@/lib/utils'
|
|
2
|
+
import { Button } from 'components/ui/button'
|
|
3
|
+
|
|
4
|
+
export function OpenInV0Button({ name, className }: { name: string } & React.ComponentProps<typeof Button>) {
|
|
5
|
+
return (
|
|
6
|
+
<Button
|
|
7
|
+
aria-label="Open in v0"
|
|
8
|
+
className={cn(
|
|
9
|
+
'h-7 gap-1 rounded-lg shadow-none bg-black px-3 text-xs text-white hover:bg-black hover:text-white dark:bg-white dark:text-black',
|
|
10
|
+
className,
|
|
11
|
+
)}
|
|
12
|
+
asChild
|
|
13
|
+
>
|
|
14
|
+
<a
|
|
15
|
+
href={`https://v0.dev/chat/api/open?url=${process.env.NEXT_PUBLIC_BASE_URL}/r/${name}.json`}
|
|
16
|
+
target="_blank"
|
|
17
|
+
rel="noreferrer"
|
|
18
|
+
>
|
|
19
|
+
Open in{' '}
|
|
20
|
+
<svg
|
|
21
|
+
viewBox="0 0 40 20"
|
|
22
|
+
fill="none"
|
|
23
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
24
|
+
className="h-5 w-5 text-current"
|
|
25
|
+
>
|
|
26
|
+
<path
|
|
27
|
+
d="M23.3919 0H32.9188C36.7819 0 39.9136 3.13165 39.9136 6.99475V16.0805H36.0006V6.99475C36.0006 6.90167 35.9969 6.80925 35.9898 6.71766L26.4628 16.079C26.4949 16.08 26.5272 16.0805 26.5595 16.0805H36.0006V19.7762H26.5595C22.6964 19.7762 19.4788 16.6139 19.4788 12.7508V3.68923H23.3919V12.7508C23.3919 12.9253 23.4054 13.0977 23.4316 13.2668L33.1682 3.6995C33.0861 3.6927 33.003 3.68923 32.9188 3.68923H23.3919V0Z"
|
|
28
|
+
fill="currentColor"
|
|
29
|
+
></path>
|
|
30
|
+
<path
|
|
31
|
+
d="M13.7688 19.0956L0 3.68759H5.53933L13.6231 12.7337V3.68759H17.7535V17.5746C17.7535 19.6705 15.1654 20.6584 13.7688 19.0956Z"
|
|
32
|
+
fill="currentColor"
|
|
33
|
+
></path>
|
|
34
|
+
</svg>
|
|
35
|
+
</a>
|
|
36
|
+
</Button>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { cn } from '@/lib/utils'
|
|
2
|
+
|
|
3
|
+
function PageHeader({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
4
|
+
return (
|
|
5
|
+
<section className={cn('border-grid border-b', className)} {...props}>
|
|
6
|
+
<div className="container-wrapper">
|
|
7
|
+
<div className="container flex flex-col items-start gap-1 py-8 md:py-10 lg:py-12">{children}</div>
|
|
8
|
+
</div>
|
|
9
|
+
</section>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function PageHeaderHeading({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
|
|
14
|
+
return (
|
|
15
|
+
<h1
|
|
16
|
+
className={cn('text-3xl font-bold leading-tight tracking-tighter md:text-4xl lg:leading-[1.1]', className)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function PageHeaderDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
|
|
23
|
+
return <p className={cn('max-w-2xl text-balance text-lg font-light text-foreground', className)} {...props} />
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function PageActions({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
27
|
+
return <div className={cn('flex w-full items-center justify-start gap-2 pt-2', className)} {...props} />
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { PageActions, PageHeader, PageHeaderDescription, PageHeaderHeading }
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { siteConfig } from '@/config/site'
|
|
2
|
+
import Link from 'next/link'
|
|
3
|
+
|
|
4
|
+
export function SiteFooter() {
|
|
5
|
+
return (
|
|
6
|
+
<footer className="border-t border-border/40 py-6 dark:border-border md:px-8 md:py-0">
|
|
7
|
+
<div className="container flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">
|
|
8
|
+
<p className="text-balance text-center text-sm leading-loose text-muted-foreground md:text-left">
|
|
9
|
+
From{' '}
|
|
10
|
+
<a
|
|
11
|
+
href={siteConfig.authorUrl}
|
|
12
|
+
className="font-medium underline underline-offset-4"
|
|
13
|
+
rel="noreferrer"
|
|
14
|
+
target="_blank"
|
|
15
|
+
>
|
|
16
|
+
{siteConfig.author}
|
|
17
|
+
</a>{' '}
|
|
18
|
+
Inspired by{' '}
|
|
19
|
+
<a
|
|
20
|
+
className="font-medium underline underline-offset-4"
|
|
21
|
+
href="https://ui.shadcn.com"
|
|
22
|
+
rel="noreferrer"
|
|
23
|
+
target="_blank"
|
|
24
|
+
>
|
|
25
|
+
ShadCN UI Website
|
|
26
|
+
</a>{' '}
|
|
27
|
+
. The source code is available on{' '}
|
|
28
|
+
<Link
|
|
29
|
+
href={siteConfig.links.github}
|
|
30
|
+
rel="noreferrer"
|
|
31
|
+
target="_blank"
|
|
32
|
+
className="font-medium underline underline-offset-4"
|
|
33
|
+
>
|
|
34
|
+
GitHub
|
|
35
|
+
</Link>
|
|
36
|
+
.
|
|
37
|
+
</p>
|
|
38
|
+
</div>
|
|
39
|
+
</footer>
|
|
40
|
+
)
|
|
41
|
+
}
|