편집(Edit)도구 모음(Toolbar)
Usage
EditorToolbar 구성 요소는 활성 상태와 편집기 컨텐츠를 자동으로 동기화하는 서식 단추 도구 모음을 표시합니다. @tiptap/vue-3/menus 패키지를 사용하여 다음과 같은 세 가지 레이아웃 모드를 지원합니다.
fixed(항상 표시)bubble(텍스트 선택에 나타남)floating(빈 행에 나타남)
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/ui'
const value = ref(`# Toolbar
Select some text to see the formatting toolbar appear above your selection.`)
const items: 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>
<UEditor v-slot="{ editor }" v-model="value" content-type="markdown" class="w-full min-h-21">
<UEditorToolbar :editor="editor" :items="items" layout="bubble" />
</UEditor>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { EditorToolbarItem } from '@nuxt/ui'
const value = ref(`# Toolbar
Select some text to see the formatting toolbar appear above your selection.`)
const items: 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>
<UEditor v-slot="{ editor }" v-model="value" content-type="markdown" class="w-full min-h-21">
<UEditorToolbar :editor="editor" :items="items" layout="bubble" />
</UEditor>
</template>Items 이미지
items prop을 다음 속성을 가진 오브젝트 배열로 사용합니다.
label?: string-label?: stringicon?: stringcolor?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"Xph037x{lang="ts-type"}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?: boolean/ - {lang="ts-type"} /disabled?: booleanloading?: booleanactive?: booleantooltip?: TooltipProps의 발음을 -tooltip?: TooltipPropsslot?: stringonClick?: (e: MouseEvent) => void의 발음을 -onClick?: (e: MouseEvent) => voiditems?: EditorToolbarItem[] | EditorToolbarItem[][]- {lang="ts-type"}class?: any
Button 구성 요소에서 color, variant, size 등의 속성을 전달할 수 있습니다.
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/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: 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>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
:extensions="[TextAlign.configure({ types: ['heading', 'paragraph'] })]"
class="w-full min-h-37 flex flex-col gap-4"
>
<UEditorToolbar :editor="editor" :items="items" class="sm:px-8 overflow-x-auto" />
</UEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { EditorToolbarItem } from '@nuxt/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: 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>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
:extensions="[TextAlign.configure({ types: ['heading', 'paragraph'] })]"
class="w-full min-h-37 flex flex-col gap-4"
>
<UEditorToolbar :editor="editor" :items="items" class="sm:px-8 overflow-x-auto" />
</UEditor>
</template>
items prop에 전달하여 개별 항목 그룹을 만들 수도 있습니다.layout
layout 소품을 사용하여 도구 모음 표시 방법을 변경합니다. 기본값은 fixed입니다.
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/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: 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>
<UEditor v-slot="{ editor }" v-model="value" content-type="markdown" class="w-full min-h-26 flex flex-col gap-4">
<UEditorToolbar
:key="layout"
:editor="editor"
:items="items"
:layout="layout"
:data-layout="layout"
class="data-[layout=fixed]:sm:px-8"
/>
</UEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { EditorToolbarItem } from '@nuxt/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: 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>
<UEditor v-slot="{ editor }" v-model="value" content-type="markdown" class="w-full min-h-26 flex flex-col gap-4">
<UEditorToolbar
:key="layout"
:editor="editor"
:items="items"
:layout="layout"
:data-layout="layout"
class="data-[layout=fixed]:sm:px-8"
/>
</UEditor>
</template>
Options 선택
bubble 또는 floating 레이아웃을 사용할 때 options Prop을 사용하여 Floating UI 옵션를 사용하여 위치 동작을 사용자 정의합니다.
<template>
<UEditor v-slot="{ editor }">
<UEditorToolbar
:editor="editor"
:items="items"
layout="bubble"
:options="{
placement: 'top',
offset: 8,
flip: { padding: 8 },
shift: { padding: 8 }
}"
/>
</UEditor>
</template>
Should 소개
bubble 또는 floating 레이아웃을 사용하는 경우 should-show Prop을 사용하여 도구 모음이 표시되는 시기를 제어합니다. 이 함수는 편집기 상태에 대한 컨텍스트를 수신하고 부울을 반환합니다.
<template>
<UEditor v-slot="{ editor }">
<UEditorToolbar
: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
}"
/>
</UEditor>
</template>
examples 예제
With 이미지 도구막대
should-show 소품을 사용하여 특정 노드 유형에 대해서만 나타나는 컨텍스트별 도구 모음을 생성합니다. 이 예에서는 이미지를 선택할 때만 나타나는 다운로드 및 삭제 작업이 포함된 bubble 도구 모음을 보여 줍니다.
<script setup lang="ts">
import type { Editor } from '@tiptap/vue-3'
import type { EditorToolbarItem } from '@nuxt/ui'
const value = ref(`Click on the image below to see the image-specific toolbar:
`)
const items = (editor: Editor): 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>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="w-full min-h-113"
>
<UEditorToolbar
:editor="editor"
:items="items(editor)"
layout="bubble"
:should-show="({ editor, view }) => {
return editor.isActive('image') && view.hasFocus()
}"
/>
</UEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { Editor } from '@tiptap/vue-3'
import type { EditorToolbarItem } from '@nuxt/ui'
const value = ref(`Click on the image below to see the image-specific toolbar:
`)
const items = (editor: Editor): 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>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="w-full min-h-113"
>
<UEditorToolbar
:editor="editor"
:items="items(editor)"
layout="bubble"
:should-show="({ editor, view }) => {
return editor.isActive('image') && view.hasFocus()
}"
/>
</UEditor>
</template>
With 링크 포포오버
이 예에서는 도구 모음 항목 및 Popover 구성 요소의 slot 속성을 사용하여 사용자 정의 링크 포포오버를 작성하는 방법을 보여 줍니다.
- xPopover를 링크 편집 기능으로 래핑하는 Vue 구성 요소를 만듭니다.
<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>
<UPopover v-model:open="open" :ui="{ content: 'p-0.5' }">
<UTooltip text="Link">
<UButton
icon="i-lucide-link"
color="neutral"
active-color="primary"
variant="ghost"
active-variant="soft"
size="sm"
:active="active"
:disabled="disabled"
/>
</UTooltip>
<template #content>
<UInput
v-model="url"
autofocus
name="url"
type="url"
variant="none"
placeholder="Paste a link..."
@keydown="handleKeyDown"
>
<div class="flex items-center mr-0.5">
<UButton
icon="i-lucide-corner-down-left"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Apply link"
@click="setLink"
/>
<USeparator orientation="vertical" class="h-6 mx-1" />
<UButton
icon="i-lucide-external-link"
color="neutral"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Open in new window"
@click="openLink"
/>
<UButton
icon="i-lucide-trash"
color="neutral"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Remove link"
@click="removeLink"
/>
</div>
</UInput>
</template>
</UPopover>
</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>
<UPopover v-model:open="open" :ui="{ content: 'p-0.5' }">
<UTooltip text="Link">
<UButton
icon="i-lucide-link"
color="neutral"
active-color="primary"
variant="ghost"
active-variant="soft"
size="sm"
:active="active"
:disabled="disabled"
/>
</UTooltip>
<template #content>
<UInput
v-model="url"
autofocus
name="url"
type="url"
variant="none"
placeholder="Paste a link..."
@keydown="handleKeyDown"
>
<div class="flex items-center mr-0.5">
<UButton
icon="i-lucide-corner-down-left"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Apply link"
@click="setLink"
/>
<USeparator orientation="vertical" class="h-6 mx-1" />
<UButton
icon="i-lucide-external-link"
color="neutral"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Open in new window"
@click="openLink"
/>
<UButton
icon="i-lucide-trash"
color="neutral"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Remove link"
@click="removeLink"
/>
</div>
</UInput>
</template>
</UPopover>
</template>
- 도구 모음의 사용자 정의 구성요소를 명명된 슬롯과 함께 사용합니다.
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/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://ui.nuxt.com).`)
const toolbarItems = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic'
}, {
slot: 'link' as const
}]] satisfies EditorToolbarItem[][]
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="w-full min-h-30 flex flex-col gap-4"
>
<UEditorToolbar :editor="editor" :items="toolbarItems" class="sm:px-8">
<template #link>
<EditorLinkPopover :editor="editor" auto-open />
</template>
</UEditorToolbar>
</UEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { EditorToolbarItem } from '@nuxt/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://ui.nuxt.com).`)
const toolbarItems = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic'
}, {
slot: 'link' as const
}]] satisfies EditorToolbarItem[][]
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="w-full min-h-30 flex flex-col gap-4"
>
<UEditorToolbar :editor="editor" :items="toolbarItems" class="sm:px-8">
<template #link>
<EditorLinkPopover :editor="editor" auto-open />
</template>
</UEditorToolbar>
</UEditor>
</template>
API
Props (### Props)
| Prop | Default | Type |
|---|---|---|
as | 'div' | anyThe element or component this component should render as. |
editor | Editor | |
color | 'neutral' | "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"The color of the toolbar controls. |
variant | 'ghost' | "solid" | "outline" | "soft" | "subtle" | "ghost" | "link"The variant of the toolbar controls. |
activeColor | 'primary' | "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"The color of the active toolbar control. |
activeVariant | 'soft' | "solid" | "outline" | "soft" | "subtle" | "ghost" | "link"The variant of the active toolbar control. |
size | 'sm' | "xs" | "sm" | "md" | "lg" | "xl"The size of the toolbar controls. |
items | T | |
layout | 'fixed' | "fixed" | "floating" | "bubble" |
pluginKey | unknownThe plugin key. The plugin key for the floating menu. | |
updateDelay | unknownThe delay in milliseconds before the menu should be updated. This can be useful to prevent performance issues. | |
resizeDelay | unknownThe delay in milliseconds before the menu position should be updated on window resize. This can be useful to prevent performance issues. | |
shouldShow | unknownA function that determines whether the menu should be shown or not.
If this function returns | |
appendTo | unknownThe DOM element to append your menu to. Default is the editor's parent element. Sometimes the menu needs to be appended to a different DOM context due to accessibility, clipping, or z-index issues. | |
getReferencedVirtualElement | unknownA function that returns the virtual element for the menu. This is useful when the menu needs to be positioned relative to a specific DOM element. | |
options | unknownThe options for the bubble menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, arrow, size, autoPlacement, hide, and inline middlewares. The options for the floating menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, arrow, size, autoPlacement, hide, and inline middlewares. | |
ui | { root?: SlotClass; base?: SlotClass; group?: SlotClass; separator?: SlotClass; } |
Slots
| Slot | Type |
|---|---|
item | { item: NestedItem<T>; } & SlotPropsProps |
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-default border border-default rounded-lg p-1'
},
floating: {
base: 'bg-default border border-default rounded-lg p-1'
},
fixed: {
base: ''
}
}
}
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/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-default border border-default rounded-lg p-1'
},
floating: {
base: 'bg-default border border-default rounded-lg p-1'
},
fixed: {
base: ''
}
}
}
}
}
})
]
})