Skip to content

Commit d4ae659

Browse files
feat: add example used page and layouts (#10)
* feat: integrate Tiptap editor with character count, link, and placeholder extensions; add Tiptap layout and toolbar components * feat: update navigation items in Tiptap layout to include GitHub repository link and improve sidebar menu item styling * feat: update homepage to showcase Tiptap editor with examples and contribution links
1 parent 2cf5123 commit d4ae659

File tree

7 files changed

+586
-52
lines changed

7 files changed

+586
-52
lines changed

app/components/tiptap/TiptapToolbar.vue

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,9 @@ import Icon from './TiptapIcon.vue'
1616
const props = defineProps<{
1717
editor?: Editor | null
1818
class?: HTMLAttributes['class']
19-
loadingComponents?: boolean
2019
}>()
2120
22-
const emits = defineEmits<{
23-
'open-component-selector': []
24-
}>()
21+
const emits = defineEmits<{}>()
2522
2623
// Get editor from context if not provided directly
2724
const { editor: contextEditor } = useTiptapContext()
@@ -39,11 +36,6 @@ const isActive = (type: string, attributes = {}) => {
3936
return editor.value!.isActive(type, attributes)
4037
}
4138
42-
// Handle opening the component selector
43-
const handleOpenComponentSelector = () => {
44-
emits('open-component-selector')
45-
}
46-
4739
// Character and word count
4840
const wordCount = computed(() => {
4941
if (!isEditorReady.value) return { characters: 0, words: 0 }
@@ -319,26 +311,6 @@ const wordCount = computed(() => {
319311
</div>
320312

321313
<Separator orientation="vertical" class="h-6" />
322-
323-
<!-- Component Insertion -->
324-
<div class="flex items-center gap-1">
325-
<TooltipProvider>
326-
<Tooltip>
327-
<TooltipTrigger asChild>
328-
<Button
329-
size="icon"
330-
variant="ghost"
331-
@click="handleOpenComponentSelector"
332-
:disabled="!isEditorReady || loadingComponents"
333-
>
334-
<Icon name="mdi:puzzle-outline" class="h-5 w-5" />
335-
<span v-if="loadingComponents" class="ml-2 text-xs animate-spin">...</span>
336-
</Button>
337-
</TooltipTrigger>
338-
<TooltipContent>Insert Component</TooltipContent>
339-
</Tooltip>
340-
</TooltipProvider>
341-
</div>
342314

343315
<!-- Word count -->
344316
<div class="ml-auto flex items-center text-xs text-muted-foreground">

app/layouts/tiptap-layout.vue

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
<script setup lang="ts">
2+
import { useEditor } from '@tiptap/vue-3'
3+
import StarterKit from '@tiptap/starter-kit'
4+
import Placeholder from '@tiptap/extension-placeholder'
5+
import Link from '@tiptap/extension-link'
6+
import CharacterCount from '@tiptap/extension-character-count'
7+
import { TiptapProvider, TiptapTreeStructure } from '@/components/tiptap'
8+
import {
9+
Sidebar,
10+
SidebarContent,
11+
SidebarFooter,
12+
SidebarGroup,
13+
SidebarGroupContent,
14+
SidebarGroupLabel,
15+
SidebarHeader,
16+
SidebarMenu,
17+
SidebarMenuButton,
18+
SidebarMenuItem,
19+
SidebarProvider,
20+
SidebarTrigger,
21+
useSidebar
22+
} from '@/components/ui/sidebar'
23+
24+
// Editor content
25+
const content = ref('')
26+
27+
// Initialize editor
28+
const tiptapEditor = useEditor({
29+
content: content.value,
30+
extensions: [
31+
StarterKit,
32+
Placeholder.configure({
33+
placeholder: 'Write something…',
34+
}),
35+
Link.configure({
36+
openOnClick: false,
37+
}),
38+
CharacterCount.configure({
39+
limit: null,
40+
}),
41+
],
42+
onUpdate: ({ editor }) => {
43+
content.value = editor.getHTML()
44+
},
45+
editable: true,
46+
})
47+
48+
// Clean up editor on component unmount
49+
onBeforeUnmount(() => {
50+
if (tiptapEditor.value) {
51+
tiptapEditor.value.destroy()
52+
}
53+
})
54+
55+
// Navigation items
56+
const navigationItems = [
57+
{ name: 'Github Repo', icon: 'mdi:github', to: 'https://github.com/productdevbook/tiptap-shadcn-vue' },
58+
]
59+
60+
// Computed properties for character and word counts
61+
const characterCount = computed(() => {
62+
if (!tiptapEditor.value) return 0
63+
return tiptapEditor.value.storage.characterCount?.characters() ?? 0
64+
})
65+
66+
const wordCount = computed(() => {
67+
if (!tiptapEditor.value) return 0
68+
return tiptapEditor.value.storage.characterCount?.words() ?? 0
69+
})
70+
71+
// Add this to control keyboard shortcuts dialog
72+
const showKeyboardShortcuts = ref(false)
73+
</script>
74+
75+
<template>
76+
<TiptapProvider :editor="tiptapEditor">
77+
<div class="relative flex min-h-svh h-svh flex-col bg-background">
78+
<SidebarProvider>
79+
<div class="flex h-full w-full">
80+
<!-- Sidebar using shadcn-vue components -->
81+
<Sidebar collapsible="icon">
82+
<!-- Sidebar header -->
83+
<SidebarHeader>
84+
<SidebarMenuButton class="w-fit px-1.5">
85+
<Icon name="mdi:home" class="h-12 w-12" />
86+
<span class="truncate font-semibold">{{ 'Tiptap Editor' }}</span>
87+
</SidebarMenuButton>
88+
89+
</SidebarHeader>
90+
91+
<!-- Sidebar content -->
92+
<SidebarContent>
93+
<!-- Navigation group -->
94+
<SidebarGroup>
95+
<SidebarGroupLabel>
96+
Navigation
97+
</SidebarGroupLabel>
98+
<SidebarGroupContent>
99+
<SidebarMenu>
100+
<SidebarMenuItem v-for="item in navigationItems" :key="item.to">
101+
<SidebarMenuButton asChild class="w-full">
102+
<NuxtLink :to="item.to" target="_blank" class="flex items-center gap-2 w-full px-2 py-1.5">
103+
<Icon :name="item.icon" class="h-4 w-4 flex-shrink-0" />
104+
<span class="truncate">{{ item.name }}</span>
105+
</NuxtLink>
106+
</SidebarMenuButton>
107+
</SidebarMenuItem>
108+
</SidebarMenu>
109+
</SidebarGroupContent>
110+
</SidebarGroup>
111+
112+
<!-- Document structure group -->
113+
<SidebarGroup>
114+
<SidebarGroupLabel>
115+
Document Structure
116+
</SidebarGroupLabel>
117+
<SidebarGroupContent>
118+
<TiptapTreeStructure class="border-0 shadow-none" />
119+
</SidebarGroupContent>
120+
</SidebarGroup>
121+
</SidebarContent>
122+
123+
<SidebarFooter>
124+
<Button variant="ghost" size="sm" class="w-full justify-start">
125+
<Icon name="mdi:help-circle-outline" class="h-4 w-4 mr-2" />
126+
Help
127+
</Button>
128+
</SidebarFooter>
129+
</Sidebar>
130+
131+
<!-- Main content area -->
132+
<div class="flex-1 flex flex-col h-full overflow-hidden">
133+
<header class="h-14 border-b p-2 bg-background flex items-center justify-between">
134+
<!-- Sidebar toggle button -->
135+
<div class="flex items-center gap-2">
136+
<SidebarTrigger />
137+
138+
<h1 class="text-lg font-medium hidden sm:inline-block">
139+
Tiptap Editor
140+
</h1>
141+
</div>
142+
143+
<div class="flex items-center space-x-2">
144+
<div class="text-sm text-muted-foreground">
145+
<Button variant="outline" size="sm" @click="showKeyboardShortcuts = true">
146+
<Icon name="mdi:keyboard-outline" class="h-4 w-4 mr-1" />
147+
Shortcuts
148+
</Button>
149+
<Button variant="outline" size="sm">
150+
<Icon name="mdi:help-circle-outline" class="h-4 w-4 mr-1" />
151+
Help
152+
</Button>
153+
</div>
154+
</div>
155+
</header>
156+
157+
<!-- Main content (page) -->
158+
<main class="flex-1 overflow-auto">
159+
<slot />
160+
</main>
161+
162+
<!-- Status Bar - moved from the page to the layout -->
163+
<div class="border-t p-2 bg-background mt-auto">
164+
<TiptapStatusBar :character-count="characterCount" :word-count="wordCount" />
165+
</div>
166+
</div>
167+
</div>
168+
</SidebarProvider>
169+
</div>
170+
171+
<!-- Keyboard shortcuts dialog -->
172+
<Dialog v-model:open="showKeyboardShortcuts">
173+
<DialogContent>
174+
<DialogHeader>
175+
<DialogTitle>Keyboard Shortcuts</DialogTitle>
176+
<DialogDescription>
177+
These shortcuts will help you use the editor more efficiently.
178+
</DialogDescription>
179+
</DialogHeader>
180+
<TiptapKeyboardShortcuts />
181+
</DialogContent>
182+
</Dialog>
183+
</TiptapProvider>
184+
</template>
185+
186+
<style>
187+
/* Dragging styles that need to be global */
188+
body.is-dragging {
189+
cursor: grabbing !important;
190+
}
191+
192+
body.is-dragging .outline-node {
193+
transition: transform 0.15s ease, opacity 0.15s ease;
194+
}
195+
196+
body.is-dragging .outline-node {
197+
cursor: grabbing !important;
198+
}
199+
</style>

app/pages/index.vue

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,59 @@
44

55
<template>
66
<div class="container mx-auto p-6">
7-
<h1 class="text-4xl font-bold mb-6">Welcome to your Nuxt 4 App</h1>
8-
<p class="mb-4">This is your homepage powered by Nuxt 4 and shadcn-vue components.</p>
7+
<h1 class="text-4xl font-bold mb-6">Tiptap Editor with shadcn-vue</h1>
8+
<p class="mb-4">A modern, extensible rich text editor built with Tiptap, shadcn-vue components, and Nuxt 4.</p>
99

10-
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mt-8">
11-
<div class="border rounded-lg p-6 shadow-sm">
12-
<h2 class="text-xl font-semibold mb-2">UI Components</h2>
13-
<p>Explore the shadcn-vue components in your app.</p>
14-
</div>
15-
16-
<div class="border rounded-lg p-6 shadow-sm">
17-
<h2 class="text-xl font-semibold mb-2">Markdown Content</h2>
18-
<p>Use MDC for rich content with Vue components.</p>
10+
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
11+
<!-- Tiptap Example 1 Card -->
12+
<NuxtLink to="/tiptap" class="border-2 border-primary rounded-lg p-6 shadow-md hover:shadow-lg transition-all duration-300 bg-primary/5">
13+
<div class="flex flex-col items-center text-center">
14+
<div class="text-3xl mb-3">✏️</div>
15+
<h2 class="text-2xl font-bold mb-2">Example 1</h2>
16+
<p class="mb-4">Full-screen WYSIWYG editor with responsive toolbar and mobile support.</p>
17+
<button class="bg-primary text-primary-foreground hover:bg-primary/90 px-4 py-2 rounded-md mt-2">
18+
Try Example
19+
</button>
20+
</div>
21+
</NuxtLink>
22+
23+
<!-- Add Your Example Card -->
24+
<div class="border-2 border-dashed rounded-lg p-6 hover:bg-muted/50 transition-colors duration-300 flex flex-col items-center justify-center text-center">
25+
<div class="text-3xl mb-3">➕</div>
26+
<h2 class="text-2xl font-bold mb-2">Add Your Example</h2>
27+
<p class="mb-4">Contribute your own Tiptap editor example to showcase different configurations and use cases.</p>
28+
<a href="https://github.com/productdevbook/tiptap-shadcn-vue#contributing" target="_blank" rel="noopener noreferrer"
29+
class="bg-muted hover:bg-muted/80 text-muted-foreground px-4 py-2 rounded-md mt-2 inline-block">
30+
How to Contribute
31+
</a>
1932
</div>
20-
21-
<div class="border rounded-lg p-6 shadow-sm">
22-
<h2 class="text-xl font-semibold mb-2">Tailwind CSS</h2>
23-
<p>Style your application with utility-first CSS.</p>
33+
34+
<!-- Contribute Card -->
35+
<div class="border rounded-lg p-6 shadow-sm bg-card">
36+
<h2 class="text-xl font-semibold mb-2">Contribute to the Project</h2>
37+
<p class="mb-4">This project is open source and MIT licensed. We welcome your contributions!</p>
38+
39+
<div class="flex flex-col space-y-3 mt-4">
40+
<a href="https://github.com/productdevbook/tiptap-shadcn-vue" target="_blank" rel="noopener noreferrer"
41+
class="flex items-center space-x-2 text-primary hover:underline">
42+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-github">
43+
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
44+
</svg>
45+
<span>GitHub Repository</span>
46+
</a>
47+
48+
<a href="https://x.com/productdevbook" target="_blank" rel="noopener noreferrer"
49+
class="flex items-center space-x-2 text-primary hover:underline">
50+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
51+
<path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"></path>
52+
</svg>
53+
<span>Follow on X</span>
54+
</a>
55+
</div>
56+
57+
<div class="mt-6 text-sm text-muted-foreground">
58+
<p>This project is available under the MIT License.</p>
59+
</div>
2460
</div>
2561
</div>
2662
</div>

app/pages/tiptap.vue

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script setup lang="ts">
2+
import { useMediaQuery } from '@vueuse/core'
3+
// Set the layout
4+
definePageMeta({
5+
layout: 'tiptap-layout'
6+
})
7+
8+
// Add this to determine if we're on mobile
9+
const isMobile = useMediaQuery('(max-width: 768px)')
10+
</script>
11+
12+
<template>
13+
<div class="flex flex-col h-full">
14+
<!-- Editor Header with Toolbar -->
15+
<div v-if="!isMobile" class="border-b p-2 bg-background">
16+
<div class="flex items-center justify-between flex-wrap gap-y-2">
17+
<!-- Toolbar - Only show on desktop -->
18+
<TiptapToolbar
19+
class="flex-1"
20+
/>
21+
</div>
22+
</div>
23+
24+
<!-- Editor Content Area -->
25+
<div class="flex-1 overflow-auto">
26+
<TiptapContent placeholder="Start writing..." />
27+
</div>
28+
29+
<!-- Mobile toolbar - Only show on mobile -->
30+
<TiptapMobileToolbar
31+
v-if="isMobile"
32+
class="pb-safe"
33+
/>
34+
</div>
35+
</template>
36+
37+
<style>
38+
/* Dragging styles that need to be global */
39+
body.is-dragging {
40+
cursor: grabbing !important;
41+
}
42+
43+
body.is-dragging .outline-node {
44+
transition: transform 0.15s ease, opacity 0.15s ease;
45+
}
46+
47+
body.is-dragging .outline-node {
48+
cursor: grabbing !important;
49+
}
50+
</style>

0 commit comments

Comments
 (0)