EditorToolbar
Usage
The EditorToolbar component displays a toolbar of formatting buttons that automatically sync their active state with the editor content. It supports three layout modes using the @tiptap/vue-3/menus package:
fixed(always visible)bubble(appears on text selection)floating(appears on empty lines)
<script setup lang="ts">
import type { EditorToolbarItem } from 'pohon-ui'
const value = ref(`# Toolbar
Select some text to see the formatting toolbar appear above your selection.`)
const items: Array<Array<EditorToolbarItem>> = [
[
{
icon: 'i-lucide-heading',
tooltip: { text: 'Headings' },
content: {
align: 'start'
},
items: [
{
kind: 'heading',
level: 1,
icon: 'i-lucide-heading-1',
label: 'Heading 1'
},
{
kind: 'heading',
level: 2,
icon: 'i-lucide-heading-2',
label: 'Heading 2'
},
{
kind: 'heading',
level: 3,
icon: 'i-lucide-heading-3',
label: 'Heading 3'
},
{
kind: 'heading',
level: 4,
icon: 'i-lucide-heading-4',
label: 'Heading 4'
}
]
}
],
[
{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold',
tooltip: { text: 'Bold' }
},
{
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic',
tooltip: { text: 'Italic' }
},
{
kind: 'mark',
mark: 'underline',
icon: 'i-lucide-underline',
tooltip: { text: 'Underline' }
},
{
kind: 'mark',
mark: 'strike',
icon: 'i-lucide-strikethrough',
tooltip: { text: 'Strikethrough' }
},
{
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code',
tooltip: { text: 'Code' }
}
]
]
</script>
<template>
<PEditor v-slot="{ editor }" v-model="value" content-type="markdown" class="min-h-21 w-full">
<PEditorToolbar :editor="editor" :items="items" layout="bubble" />
</PEditor>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { EditorToolbarItem } from 'pohon-ui'
const value = ref(`# Toolbar
Select some text to see the formatting toolbar appear above your selection.`)
const items: Array<Array<EditorToolbarItem>> = [
[
{
icon: 'i-lucide-heading',
tooltip: { text: 'Headings' },
content: {
align: 'start'
},
items: [
{
kind: 'heading',
level: 1,
icon: 'i-lucide-heading-1',
label: 'Heading 1'
},
{
kind: 'heading',
level: 2,
icon: 'i-lucide-heading-2',
label: 'Heading 2'
},
{
kind: 'heading',
level: 3,
icon: 'i-lucide-heading-3',
label: 'Heading 3'
},
{
kind: 'heading',
level: 4,
icon: 'i-lucide-heading-4',
label: 'Heading 4'
}
]
}
],
[
{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold',
tooltip: { text: 'Bold' }
},
{
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic',
tooltip: { text: 'Italic' }
},
{
kind: 'mark',
mark: 'underline',
icon: 'i-lucide-underline',
tooltip: { text: 'Underline' }
},
{
kind: 'mark',
mark: 'strike',
icon: 'i-lucide-strikethrough',
tooltip: { text: 'Strikethrough' }
},
{
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code',
tooltip: { text: 'Code' }
}
]
]
</script>
<template>
<PEditor v-slot="{ editor }" v-model="value" content-type="markdown" class="min-h-21 w-full">
<PEditorToolbar :editor="editor" :items="items" layout="bubble" />
</PEditor>
</template>Items
Use the items prop as an array of objects with the following properties:
label?: stringicon?: stringcolor?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"activeColor?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"variant?: "solid" | "outline" | "soft" | "ghost" | "link" | "subtle"activeVariant?: "solid" | "outline" | "soft" | "ghost" | "link" | "subtle"size?: "xs" | "sm" | "md" | "lg" | "xl"kind?: "mark" | "textAlign" | "heading" | "link" | "image" | "blockquote" | "bulletList" | "orderedList" | "taskList" | "codeBlock" | "horizontalRule" | "paragraph" | "undo" | "redo" | "clearFormatting" | "duplicate" | "delete" | "moveUp" | "moveDown" | "suggestion" | "mention" | "emoji"disabled?: booleanloading?: booleanactive?: booleantooltip?: TooltipPropsslot?: stringonClick?: (e: MouseEvent) => voiditems?: EditorToolbarItem[] | EditorToolbarItem[][]class?: any
You can pass any property from the Button component such as color, variant, size, etc.
<script setup lang="ts">
import type { EditorToolbarItem } from 'pohon-ui';
import { TextAlign } from '@tiptap/extension-text-align';
const value = ref(`This toolbar showcases **all available formatting options** using built-in handlers. Try the different controls to see them in action!
You can apply **bold**, *italic*, <u>underline</u>, ~~strikethrough~~, and \`inline code\` formatting to your text.
`);
const items: Array<Array<EditorToolbarItem>> = [
// History controls
[{
kind: 'undo',
icon: 'i-lucide-undo',
tooltip: { text: 'Undo' },
}, {
kind: 'redo',
icon: 'i-lucide-redo',
tooltip: { text: 'Redo' },
}],
// Block types
[{
icon: 'i-lucide-heading',
tooltip: { text: 'Headings' },
content: {
align: 'start',
},
items: [{
kind: 'heading',
level: 1,
icon: 'i-lucide-heading-1',
label: 'Heading 1',
}, {
kind: 'heading',
level: 2,
icon: 'i-lucide-heading-2',
label: 'Heading 2',
}, {
kind: 'heading',
level: 3,
icon: 'i-lucide-heading-3',
label: 'Heading 3',
}, {
kind: 'heading',
level: 4,
icon: 'i-lucide-heading-4',
label: 'Heading 4',
}],
}, {
icon: 'i-lucide-list',
tooltip: { text: 'Lists' },
content: {
align: 'start',
},
items: [{
kind: 'bulletList',
icon: 'i-lucide-list',
label: 'Bullet List',
}, {
kind: 'orderedList',
icon: 'i-lucide-list-ordered',
label: 'Ordered List',
}],
}, {
kind: 'blockquote',
icon: 'i-lucide-text-quote',
tooltip: { text: 'Blockquote' },
}, {
kind: 'codeBlock',
icon: 'i-lucide-square-code',
tooltip: { text: 'Code Block' },
}, {
kind: 'horizontalRule',
icon: 'i-lucide-separator-horizontal',
tooltip: { text: 'Horizontal Rule' },
}],
// Text formatting
[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold',
tooltip: { text: 'Bold' },
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic',
tooltip: { text: 'Italic' },
}, {
kind: 'mark',
mark: 'underline',
icon: 'i-lucide-underline',
tooltip: { text: 'Underline' },
}, {
kind: 'mark',
mark: 'strike',
icon: 'i-lucide-strikethrough',
tooltip: { text: 'Strikethrough' },
}, {
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code',
tooltip: { text: 'Code' },
}],
// Link
[{
kind: 'link',
icon: 'i-lucide-link',
tooltip: { text: 'Link' },
}],
// Text alignment
[{
icon: 'i-lucide-align-justify',
tooltip: { text: 'Text Align' },
content: {
align: 'end',
},
items: [{
kind: 'textAlign',
align: 'left',
icon: 'i-lucide-align-left',
label: 'Align Left',
}, {
kind: 'textAlign',
align: 'center',
icon: 'i-lucide-align-center',
label: 'Align Center',
}, {
kind: 'textAlign',
align: 'right',
icon: 'i-lucide-align-right',
label: 'Align Right',
}, {
kind: 'textAlign',
align: 'justify',
icon: 'i-lucide-align-justify',
label: 'Align Justify',
}],
}],
];
</script>
<template>
<PEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
:extensions="[TextAlign.configure({ types: ['heading', 'paragraph'] })]"
class="flex flex-col gap-4 min-h-37 w-full"
>
<PEditorToolbar
:editor="editor"
:items="items"
class="overflow-x-auto sm:px-8"
/>
</PEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { EditorToolbarItem } from 'pohon-ui';
import { TextAlign } from '@tiptap/extension-text-align';
const value = ref(`This toolbar showcases **all available formatting options** using built-in handlers. Try the different controls to see them in action!
You can apply **bold**, *italic*, <u>underline</u>, ~~strikethrough~~, and \`inline code\` formatting to your text.
`);
const items: Array<Array<EditorToolbarItem>> = [
// History controls
[{
kind: 'undo',
icon: 'i-lucide-undo',
tooltip: { text: 'Undo' },
}, {
kind: 'redo',
icon: 'i-lucide-redo',
tooltip: { text: 'Redo' },
}],
// Block types
[{
icon: 'i-lucide-heading',
tooltip: { text: 'Headings' },
content: {
align: 'start',
},
items: [{
kind: 'heading',
level: 1,
icon: 'i-lucide-heading-1',
label: 'Heading 1',
}, {
kind: 'heading',
level: 2,
icon: 'i-lucide-heading-2',
label: 'Heading 2',
}, {
kind: 'heading',
level: 3,
icon: 'i-lucide-heading-3',
label: 'Heading 3',
}, {
kind: 'heading',
level: 4,
icon: 'i-lucide-heading-4',
label: 'Heading 4',
}],
}, {
icon: 'i-lucide-list',
tooltip: { text: 'Lists' },
content: {
align: 'start',
},
items: [{
kind: 'bulletList',
icon: 'i-lucide-list',
label: 'Bullet List',
}, {
kind: 'orderedList',
icon: 'i-lucide-list-ordered',
label: 'Ordered List',
}],
}, {
kind: 'blockquote',
icon: 'i-lucide-text-quote',
tooltip: { text: 'Blockquote' },
}, {
kind: 'codeBlock',
icon: 'i-lucide-square-code',
tooltip: { text: 'Code Block' },
}, {
kind: 'horizontalRule',
icon: 'i-lucide-separator-horizontal',
tooltip: { text: 'Horizontal Rule' },
}],
// Text formatting
[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold',
tooltip: { text: 'Bold' },
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic',
tooltip: { text: 'Italic' },
}, {
kind: 'mark',
mark: 'underline',
icon: 'i-lucide-underline',
tooltip: { text: 'Underline' },
}, {
kind: 'mark',
mark: 'strike',
icon: 'i-lucide-strikethrough',
tooltip: { text: 'Strikethrough' },
}, {
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code',
tooltip: { text: 'Code' },
}],
// Link
[{
kind: 'link',
icon: 'i-lucide-link',
tooltip: { text: 'Link' },
}],
// Text alignment
[{
icon: 'i-lucide-align-justify',
tooltip: { text: 'Text Align' },
content: {
align: 'end',
},
items: [{
kind: 'textAlign',
align: 'left',
icon: 'i-lucide-align-left',
label: 'Align Left',
}, {
kind: 'textAlign',
align: 'center',
icon: 'i-lucide-align-center',
label: 'Align Center',
}, {
kind: 'textAlign',
align: 'right',
icon: 'i-lucide-align-right',
label: 'Align Right',
}, {
kind: 'textAlign',
align: 'justify',
icon: 'i-lucide-align-justify',
label: 'Align Justify',
}],
}],
];
</script>
<template>
<PEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
:extensions="[TextAlign.configure({ types: ['heading', 'paragraph'] })]"
class="flex flex-col gap-4 min-h-37 w-full"
>
<PEditorToolbar
:editor="editor"
:items="items"
class="overflow-x-auto sm:px-8"
/>
</PEditor>
</template>
items prop to create separated groups of items.items array of objects with the same properties as the items prop to create a DropdownMenu.Layout
Use the layout prop to change how the toolbar is displayed. Defaults to fixed.
<script setup lang="ts">
import type { EditorToolbarItem } from 'pohon-ui';
defineProps<{
layout: 'fixed' | 'bubble' | 'floating';
}>();
const value = ref(`Switch between layouts to see the different toolbar modes.
The **fixed** layout displays the toolbar above the editor. The **bubble** layout shows the toolbar when you select text. The **floating** layout appears on empty lines.`);
const items: Array<Array<EditorToolbarItem>> = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold',
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic',
}, {
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code',
}]];
</script>
<template>
<PEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="flex flex-col gap-4 min-h-26 w-full"
>
<PEditorToolbar
:key="layout"
:editor="editor"
:items="items"
:layout="layout"
:data-layout="layout"
class="data-[layout=fixed]:sm:px-8"
/>
</PEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { EditorToolbarItem } from 'pohon-ui';
defineProps<{
layout: 'fixed' | 'bubble' | 'floating';
}>();
const value = ref(`Switch between layouts to see the different toolbar modes.
The **fixed** layout displays the toolbar above the editor. The **bubble** layout shows the toolbar when you select text. The **floating** layout appears on empty lines.`);
const items: Array<Array<EditorToolbarItem>> = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold',
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic',
}, {
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code',
}]];
</script>
<template>
<PEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="flex flex-col gap-4 min-h-26 w-full"
>
<PEditorToolbar
:key="layout"
:editor="editor"
:items="items"
:layout="layout"
:data-layout="layout"
class="data-[layout=fixed]:sm:px-8"
/>
</PEditor>
</template>
Options
When using bubble or floating layouts, use the options prop to customize the positioning behavior using Floating UI options.
<template>
<PEditor v-slot="{ editor }">
<PEditorToolbar
:editor="editor"
:items="items"
layout="bubble"
:options="{
placement: 'top',
offset: 8,
flip: { padding: 8 },
shift: { padding: 8 },
}"
/>
</PEditor>
</template>
Should Show
When using bubble or floating layouts, use the should-show prop to control when the toolbar appears. This function receives context about the editor state and returns a boolean.
<template>
<PEditor v-slot="{ editor }">
<PEditorToolbar
:editor="editor"
:items="items"
layout="bubble"
:should-show="({ view, state }) => {
const { selection } = state
const { from, to } = selection
const text = state.doc.textBetween(from, to)
return view.hasFocus() && !selection.empty && text.length > 10
}"
/>
</PEditor>
</template>
Examples
With image toolbar
Use the should-show prop to create context-specific toolbars that appear only for certain node types. This example shows a bubble toolbar with download and delete actions that only appears when an image is selected.
<script setup lang="ts">
import type { Editor } from '@tiptap/vue-3';
import type { EditorToolbarItem } from 'pohon-ui';
const value = ref(`Click on the image below to see the image-specific toolbar:
`);
function items(editor: Editor): Array<Array<EditorToolbarItem>> {
const node = editor.state.doc.nodeAt(editor.state.selection.from);
return [[{
icon: 'i-lucide-download',
to: node?.attrs?.src,
download: true,
tooltip: { text: 'Download' },
}], [{
icon: 'i-lucide-trash',
tooltip: { text: 'Delete' },
onClick: () => {
const { state } = editor;
const { selection } = state;
const pos = selection.from;
const node = state.doc.nodeAt(pos);
if (node && node.type.name === 'image') {
editor.chain().focus().deleteRange({ from: pos, to: pos + node.nodeSize }).run();
}
},
}]];
}
</script>
<template>
<PEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="min-h-113 w-full"
>
<PEditorToolbar
:editor="editor"
:items="items(editor)"
layout="bubble"
:should-show="({ editor, view }) => {
return editor.isActive('image') && view.hasFocus()
}"
/>
</PEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { Editor } from '@tiptap/vue-3';
import type { EditorToolbarItem } from 'pohon-ui';
const value = ref(`Click on the image below to see the image-specific toolbar:
`);
function items(editor: Editor): Array<Array<EditorToolbarItem>> {
const node = editor.state.doc.nodeAt(editor.state.selection.from);
return [[{
icon: 'i-lucide-download',
to: node?.attrs?.src,
download: true,
tooltip: { text: 'Download' },
}], [{
icon: 'i-lucide-trash',
tooltip: { text: 'Delete' },
onClick: () => {
const { state } = editor;
const { selection } = state;
const pos = selection.from;
const node = state.doc.nodeAt(pos);
if (node && node.type.name === 'image') {
editor.chain().focus().deleteRange({ from: pos, to: pos + node.nodeSize }).run();
}
},
}]];
}
</script>
<template>
<PEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="min-h-113 w-full"
>
<PEditorToolbar
:editor="editor"
:items="items(editor)"
layout="bubble"
:should-show="({ editor, view }) => {
return editor.isActive('image') && view.hasFocus()
}"
/>
</PEditor>
</template>
With link popover
This example demonstrates how to create a custom link popover using the slot property on toolbar items and the Popover component.
- Create a Vue component that wraps a Popover with link editing functionality:
<script setup lang="ts">
import type { Editor } from '@tiptap/vue-3';
const props = defineProps<{
editor: Editor;
autoOpen?: boolean;
}>();
const open = ref(false);
const url = ref('');
const active = computed(() => props.editor.isActive('link'));
const disabled = computed(() => {
if (!props.editor.isEditable) {
return true;
}
const { selection } = props.editor.state;
return selection.empty && !props.editor.isActive('link');
});
watch(() => props.editor, (editor, _, onCleanup) => {
if (!editor) {
return;
}
const updateUrl = () => {
const { href } = editor.getAttributes('link');
url.value = href || '';
};
updateUrl();
editor.on('selectionUpdate', updateUrl);
onCleanup(() => {
editor.off('selectionUpdate', updateUrl);
});
}, { immediate: true });
watch(active, (isActive) => {
if (isActive && props.autoOpen) {
open.value = true;
}
});
function setLink() {
if (!url.value) {
return;
}
const { selection } = props.editor.state;
const isEmpty = selection.empty;
const hasCode = props.editor.isActive('code');
let chain = props.editor.chain().focus();
// When linking code, extend the code mark range first to select the full code
if (hasCode && !isEmpty) {
chain = chain.extendMarkRange('code').setLink({ href: url.value });
} else {
chain = chain.extendMarkRange('link').setLink({ href: url.value });
if (isEmpty) {
chain = chain.insertContent({ type: 'text', text: url.value });
}
}
chain.run();
open.value = false;
}
function removeLink() {
props.editor
.chain()
.focus()
.extendMarkRange('link')
.unsetLink()
.setMeta('preventAutolink', true)
.run();
url.value = '';
open.value = false;
}
function openLink() {
if (!url.value) {
return;
}
window.open(url.value, '_blank', 'noopener,noreferrer');
}
function handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Enter') {
event.preventDefault();
setLink();
}
}
</script>
<template>
<PPopover
v-model:open="open"
:ui="{ content: 'p-0.5' }"
>
<PTooltip text="Link">
<PButton
icon="i-lucide-link"
color="neutral"
active-color="primary"
variant="ghost"
active-variant="soft"
size="sm"
:active="active"
:disabled="disabled"
/>
</PTooltip>
<template #content>
<PInput
v-model="url"
autofocus
name="url"
type="url"
variant="none"
placeholder="Paste a link..."
@keydown="handleKeyDown"
>
<div class="mr-0.5 flex items-center">
<PButton
icon="i-lucide-corner-down-left"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Apply link"
@click="setLink"
/>
<PSeparator
orientation="vertical"
class="mx-1 h-6"
/>
<PButton
icon="i-lucide-external-link"
color="neutral"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Open in new window"
@click="openLink"
/>
<PButton
icon="i-lucide-trash"
color="neutral"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Remove link"
@click="removeLink"
/>
</div>
</PInput>
</template>
</PPopover>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import type { Editor } from '@tiptap/vue-3';
const props = defineProps<{
editor: Editor;
autoOpen?: boolean;
}>();
const open = ref(false);
const url = ref('');
const active = computed(() => props.editor.isActive('link'));
const disabled = computed(() => {
if (!props.editor.isEditable) {
return true;
}
const { selection } = props.editor.state;
return selection.empty && !props.editor.isActive('link');
});
watch(() => props.editor, (editor, _, onCleanup) => {
if (!editor) {
return;
}
const updateUrl = () => {
const { href } = editor.getAttributes('link');
url.value = href || '';
};
updateUrl();
editor.on('selectionUpdate', updateUrl);
onCleanup(() => {
editor.off('selectionUpdate', updateUrl);
});
}, { immediate: true });
watch(active, (isActive) => {
if (isActive && props.autoOpen) {
open.value = true;
}
});
function setLink() {
if (!url.value) {
return;
}
const { selection } = props.editor.state;
const isEmpty = selection.empty;
const hasCode = props.editor.isActive('code');
let chain = props.editor.chain().focus();
// When linking code, extend the code mark range first to select the full code
if (hasCode && !isEmpty) {
chain = chain.extendMarkRange('code').setLink({ href: url.value });
} else {
chain = chain.extendMarkRange('link').setLink({ href: url.value });
if (isEmpty) {
chain = chain.insertContent({ type: 'text', text: url.value });
}
}
chain.run();
open.value = false;
}
function removeLink() {
props.editor
.chain()
.focus()
.extendMarkRange('link')
.unsetLink()
.setMeta('preventAutolink', true)
.run();
url.value = '';
open.value = false;
}
function openLink() {
if (!url.value) {
return;
}
window.open(url.value, '_blank', 'noopener,noreferrer');
}
function handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Enter') {
event.preventDefault();
setLink();
}
}
</script>
<template>
<PPopover
v-model:open="open"
:ui="{ content: 'p-0.5' }"
>
<PTooltip text="Link">
<PButton
icon="i-lucide-link"
color="neutral"
active-color="primary"
variant="ghost"
active-variant="soft"
size="sm"
:active="active"
:disabled="disabled"
/>
</PTooltip>
<template #content>
<PInput
v-model="url"
autofocus
name="url"
type="url"
variant="none"
placeholder="Paste a link..."
@keydown="handleKeyDown"
>
<div class="mr-0.5 flex items-center">
<PButton
icon="i-lucide-corner-down-left"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Apply link"
@click="setLink"
/>
<PSeparator
orientation="vertical"
class="mx-1 h-6"
/>
<PButton
icon="i-lucide-external-link"
color="neutral"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Open in new window"
@click="openLink"
/>
<PButton
icon="i-lucide-trash"
color="neutral"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Remove link"
@click="removeLink"
/>
</div>
</PInput>
</template>
</PPopover>
</template>
- Use the custom component in the toolbar with a named slot:
<script setup lang="ts">
import type { EditorToolbarItem } from 'pohon-ui';
import EditorLinkPopover from './EditorLinkPopover.vue';
const value = ref(`Select text and click the link button to add a link with the custom popover.
You can also edit existing links like [this one](https://pohon.vinicunca.dev).`);
const toolbarItems = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold',
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic',
}, {
slot: 'link' as const,
}]] satisfies Array<Array<EditorToolbarItem>>;
</script>
<template>
<PEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="flex flex-col gap-4 min-h-30 w-full"
>
<PEditorToolbar
:editor="editor"
:items="toolbarItems"
class="sm:px-8"
>
<template #link>
<EditorLinkPopover
:editor="editor"
auto-open
/>
</template>
</PEditorToolbar>
</PEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { EditorToolbarItem } from 'pohon-ui';
import EditorLinkPopover from './EditorLinkPopover.vue';
const value = ref(`Select text and click the link button to add a link with the custom popover.
You can also edit existing links like [this one](https://pohon.vinicunca.dev).`);
const toolbarItems = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold',
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic',
}, {
slot: 'link' as const,
}]] satisfies Array<Array<EditorToolbarItem>>;
</script>
<template>
<PEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="flex flex-col gap-4 min-h-30 w-full"
>
<PEditorToolbar
:editor="editor"
:items="toolbarItems"
class="sm:px-8"
>
<template #link>
<EditorLinkPopover
:editor="editor"
auto-open
/>
</template>
</PEditorToolbar>
</PEditor>
</template>
API
Props
| Prop | Default | Type |
|---|
Slots
| Slot | Type |
|---|
Theme
export default defineAppConfig({
ui: {
editorToolbar: {
slots: {
root: 'focus:outline-none',
base: 'flex items-stretch gap-1.5',
group: 'flex items-center gap-0.5',
separator: 'w-px self-stretch bg-border'
},
variants: {
layout: {
bubble: {
base: 'bg-background border border-border rounded-lg p-1'
},
floating: {
base: 'bg-background border border-border rounded-lg p-1'
},
fixed: {
base: ''
}
}
}
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from 'pohon-ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
editorToolbar: {
slots: {
root: 'focus:outline-none',
base: 'flex items-stretch gap-1.5',
group: 'flex items-center gap-0.5',
separator: 'w-px self-stretch bg-border'
},
variants: {
layout: {
bubble: {
base: 'bg-background border border-border rounded-lg p-1'
},
floating: {
base: 'bg-background border border-border rounded-lg p-1'
},
fixed: {
base: ''
}
}
}
}
}
})
]
})