EditorialSugerenciaMenú
xph0000xUso
El componente EditorSuggestionMenu muestra un menú de sugerencias de formato y acción cuando se escribe un carácter de activación en el editor y ejecuta el correspondiente handler cuando se selecciona un elemento.
useEditorMenu construido sobre la utilidad Suggestion de TipTap para filtrar elementos a medida que escribe y admite la navegación con el teclado (teclas de flecha, ingresar para seleccionar, escapar para cerrar).<script setup lang="ts">
import type { EditorSuggestionMenuItem } from '@nuxt/ui'
const value = ref(`# Suggestion Menu
Type / to open the suggestion menu and browse available formatting commands.`)
const items: EditorSuggestionMenuItem[][] = [
[
{
type: 'label',
label: 'Text'
},
{
kind: 'paragraph',
label: 'Paragraph',
icon: 'i-lucide-type'
},
{
kind: 'heading',
level: 1,
label: 'Heading 1',
icon: 'i-lucide-heading-1'
},
{
kind: 'heading',
level: 2,
label: 'Heading 2',
icon: 'i-lucide-heading-2'
},
{
kind: 'heading',
level: 3,
label: 'Heading 3',
icon: 'i-lucide-heading-3'
}
],
[
{
type: 'label',
label: 'Lists'
},
{
kind: 'bulletList',
label: 'Bullet List',
icon: 'i-lucide-list'
},
{
kind: 'orderedList',
label: 'Numbered List',
icon: 'i-lucide-list-ordered'
}
],
[
{
type: 'label',
label: 'Insert'
},
{
kind: 'blockquote',
label: 'Blockquote',
icon: 'i-lucide-text-quote'
},
{
kind: 'codeBlock',
label: 'Code Block',
icon: 'i-lucide-square-code'
},
{
kind: 'horizontalRule',
label: 'Divider',
icon: 'i-lucide-separator-horizontal'
}
]
]
// SSR-safe function to append menus to body (avoids z-index issues in docs)
const appendToBody = false ? () => document.body : undefined
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
placeholder="Type / for commands..."
class="w-full min-h-21"
>
<UEditorSuggestionMenu :editor="editor" :items="items" :append-to="appendToBody" />
</UEditor>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { EditorSuggestionMenuItem } from '@nuxt/ui'
const value = ref(`# Suggestion Menu
Type / to open the suggestion menu and browse available formatting commands.`)
const items: EditorSuggestionMenuItem[][] = [
[
{
type: 'label',
label: 'Text'
},
{
kind: 'paragraph',
label: 'Paragraph',
icon: 'i-lucide-type'
},
{
kind: 'heading',
level: 1,
label: 'Heading 1',
icon: 'i-lucide-heading-1'
},
{
kind: 'heading',
level: 2,
label: 'Heading 2',
icon: 'i-lucide-heading-2'
},
{
kind: 'heading',
level: 3,
label: 'Heading 3',
icon: 'i-lucide-heading-3'
}
],
[
{
type: 'label',
label: 'Lists'
},
{
kind: 'bulletList',
label: 'Bullet List',
icon: 'i-lucide-list'
},
{
kind: 'orderedList',
label: 'Numbered List',
icon: 'i-lucide-list-ordered'
}
],
[
{
type: 'label',
label: 'Insert'
},
{
kind: 'blockquote',
label: 'Blockquote',
icon: 'i-lucide-text-quote'
},
{
kind: 'codeBlock',
label: 'Code Block',
icon: 'i-lucide-square-code'
},
{
kind: 'horizontalRule',
label: 'Divider',
icon: 'i-lucide-separator-horizontal'
}
]
]
// SSR-safe function to append menus to body (avoids z-index issues in docs)
const appendToBody = false ? () => document.body : undefined
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
placeholder="Type / for commands..."
class="w-full min-h-21"
>
<UEditorSuggestionMenu :editor="editor" :items="items" :append-to="appendToBody" />
</UEditor>
</template>Artículos
Utilice el prop items como una matriz de objetos con las siguientes propiedades:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- xx
label?: stringxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - xxx
description?: stringxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - xx
icon?: stringxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - xx
disabled?: booleanxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
<script setup lang="ts">
import type { EditorSuggestionMenuItem } from '@nuxt/ui'
const value = ref(`Type / to see a list of commands.
You can customize the items with icons, labels, and descriptions.`)
const items: EditorSuggestionMenuItem[][] = [[{
type: 'label',
label: 'Text Styles'
}, {
kind: 'paragraph',
label: 'Paragraph',
icon: 'i-lucide-type'
}, {
kind: 'heading',
level: 1,
label: 'Heading 1',
icon: 'i-lucide-heading-1'
}, {
kind: 'heading',
level: 2,
label: 'Heading 2',
icon: 'i-lucide-heading-2'
}, {
kind: 'heading',
level: 3,
label: 'Heading 3',
icon: 'i-lucide-heading-3'
}], [{
type: 'label',
label: 'Lists'
}, {
kind: 'bulletList',
label: 'Bullet List',
icon: 'i-lucide-list'
}, {
kind: 'orderedList',
label: 'Numbered List',
icon: 'i-lucide-list-ordered'
}], [{
type: 'label',
label: 'Blocks'
}, {
kind: 'blockquote',
label: 'Blockquote',
icon: 'i-lucide-text-quote'
}, {
kind: 'codeBlock',
label: 'Code Block',
icon: 'i-lucide-square-code'
}, {
kind: 'horizontalRule',
label: 'Divider',
icon: 'i-lucide-separator-horizontal'
}]]
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
placeholder="Type / for commands..."
class="w-full min-h-19"
>
<UEditorSuggestionMenu :editor="editor" :items="items" />
</UEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { EditorSuggestionMenuItem } from '@nuxt/ui'
const value = ref(`Type / to see a list of commands.
You can customize the items with icons, labels, and descriptions.`)
const items: EditorSuggestionMenuItem[][] = [[{
type: 'label',
label: 'Text Styles'
}, {
kind: 'paragraph',
label: 'Paragraph',
icon: 'i-lucide-type'
}, {
kind: 'heading',
level: 1,
label: 'Heading 1',
icon: 'i-lucide-heading-1'
}, {
kind: 'heading',
level: 2,
label: 'Heading 2',
icon: 'i-lucide-heading-2'
}, {
kind: 'heading',
level: 3,
label: 'Heading 3',
icon: 'i-lucide-heading-3'
}], [{
type: 'label',
label: 'Lists'
}, {
kind: 'bulletList',
label: 'Bullet List',
icon: 'i-lucide-list'
}, {
kind: 'orderedList',
label: 'Numbered List',
icon: 'i-lucide-list-ordered'
}], [{
type: 'label',
label: 'Blocks'
}, {
kind: 'blockquote',
label: 'Blockquote',
icon: 'i-lucide-text-quote'
}, {
kind: 'codeBlock',
label: 'Code Block',
icon: 'i-lucide-square-code'
}, {
kind: 'horizontalRule',
label: 'Divider',
icon: 'i-lucide-separator-horizontal'
}]]
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
placeholder="Type / for commands..."
class="w-full min-h-19"
>
<UEditorSuggestionMenu :editor="editor" :items="items" />
</UEditor>
</template>
items para crear grupos separados de elementos.type: 'label' para encabezados de sección y type: 'separator' para divisores visuales para organizar comandos en grupos lógicos para una mejor visibilidad.Char (Edición española)
Utilice el prop char para cambiar el carácter de activación. Predeterminados a /.
<template>
<UEditor v-slot="{ editor }">
<UEditorSuggestionMenu :editor="editor" :items="items" char=">" />
</UEditor>
</template>
Sugerencia: badge{label="4.7+" class="align-text-top"}
Utilice el prop suggestion para personalizar el comportamiento de coincidencia de sugerencias x.
Esto es útil cuando el carácter desencadenante debe abrirse directamente después de otros caracteres en lugar de requerir el prefijo de espacio en blanco predeterminado.
<template>
<UEditor v-slot="{ editor }">
<UEditorSuggestionMenu
:editor="editor"
:items="items"
char=":"
:suggestion="{
allowedPrefixes: null
}"
/>
</UEditor>
</template>
Opciones
Utilice el prop options para personalizar el comportamiento de posicionamiento utilizando las opciones de interfaz de usuario flotante ](https://floating-ui.com/docs/computeposition#options).
<template>
<UEditor v-slot="{ editor }">
<UEditorSuggestionMenu
:editor="editor"
:items="items"
:options="{
placement: 'bottom-start',
offset: 4
}"
/>
</UEditor>
</template>
API (Edición española)
Props (Edición española)
| Prop | Default | Type |
|---|---|---|
size | 'md' | "xs" | "md" | "sm" | "lg" | "xl" |
items | T[] | T[][] | |
editor | Editor | |
char | '/' | stringThe trigger character (e.g., '/', '@', ':') |
pluginKey | 'suggestionMenu' | stringPlugin key to identify this menu |
filterFields | ['label'] | string[]Fields to filter items by. |
limit | 42 | numberMaximum number of items to display |
options | { strategy: 'absolute', placement: 'bottom-start', offset: 8, shift: { padding: 8 } } | FloatingUIOptionsThe options for positioning the menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, size, autoPlacement, hide, and inline middleware. |
suggestion | Omit<Partial<SuggestionOptions<any, any>>, "editor" | "char" | "pluginKey" | "items" | "command" | "render">Optional TipTap Suggestion matching options. | |
appendTo | HTMLElement | (): HTMLElementThe DOM element to append the 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. | |
ui | { content?: SlotClass; viewport?: SlotClass; group?: SlotClass; label?: SlotClass; separator?: SlotClass; item?: SlotClass; itemLeadingIcon?: SlotClass; itemLeadingAvatar?: SlotClass; itemLeadingAvatarSize?: SlotClass; itemWrapper?: SlotClass; itemLabel?: SlotClass; itemDescription?: SlotClass; itemLabelExternalIcon?: SlotClass; } |
Temas
export default defineAppConfig({
ui: {
editorSuggestionMenu: {
slots: {
content: 'min-w-48 max-w-60 max-h-96 bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_var(--ease-out)] data-[state=closed]:animate-[scale-out_100ms_var(--ease-out)] origin-(--reka-dropdown-menu-content-transform-origin) flex flex-col',
viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1',
group: 'p-1 isolate',
label: 'w-full flex items-center font-semibold text-highlighted',
separator: '-mx-1 my-1 h-px bg-border',
item: 'group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75',
itemLeadingIcon: 'shrink-0 flex items-center justify-center',
itemLeadingAvatar: 'shrink-0',
itemLeadingAvatarSize: '',
itemWrapper: 'flex-1 flex flex-col text-start min-w-0',
itemLabel: 'truncate',
itemDescription: 'truncate text-muted',
itemLabelExternalIcon: 'inline-block size-3 align-top text-dimmed'
},
variants: {
size: {
xs: {
label: 'p-1 text-[10px]/3 gap-1',
item: 'p-1 text-xs gap-1',
itemLeadingIcon: 'size-4 text-sm',
itemLeadingAvatarSize: '3xs'
},
sm: {
label: 'p-1.5 text-[10px]/3 gap-1.5',
item: 'p-1.5 text-xs gap-1.5',
itemLeadingIcon: 'size-4 text-sm',
itemLeadingAvatarSize: '3xs'
},
md: {
label: 'p-1.5 text-xs gap-1.5',
item: 'p-1.5 text-sm gap-1.5',
itemLeadingIcon: 'size-5 text-base',
itemLeadingAvatarSize: '2xs'
},
lg: {
label: 'p-2 text-xs gap-2',
item: 'p-2 text-sm gap-2',
itemLeadingIcon: 'size-5 text-base',
itemLeadingAvatarSize: '2xs'
},
xl: {
label: 'p-2 text-sm gap-2',
item: 'p-2 text-base gap-2',
itemLeadingIcon: 'size-6 text-xl',
itemLeadingAvatarSize: 'xs'
}
},
active: {
true: {
item: 'text-highlighted before:bg-elevated/75',
itemLeadingIcon: 'text-default'
},
false: {
item: [
'text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50',
'transition-colors before:transition-colors'
],
itemLeadingIcon: [
'text-dimmed group-data-highlighted:not-group-data-disabled:text-default',
'transition-colors'
]
}
}
},
defaultVariants: {
size: 'md'
}
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
editorSuggestionMenu: {
slots: {
content: 'min-w-48 max-w-60 max-h-96 bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_var(--ease-out)] data-[state=closed]:animate-[scale-out_100ms_var(--ease-out)] origin-(--reka-dropdown-menu-content-transform-origin) flex flex-col',
viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1',
group: 'p-1 isolate',
label: 'w-full flex items-center font-semibold text-highlighted',
separator: '-mx-1 my-1 h-px bg-border',
item: 'group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75',
itemLeadingIcon: 'shrink-0 flex items-center justify-center',
itemLeadingAvatar: 'shrink-0',
itemLeadingAvatarSize: '',
itemWrapper: 'flex-1 flex flex-col text-start min-w-0',
itemLabel: 'truncate',
itemDescription: 'truncate text-muted',
itemLabelExternalIcon: 'inline-block size-3 align-top text-dimmed'
},
variants: {
size: {
xs: {
label: 'p-1 text-[10px]/3 gap-1',
item: 'p-1 text-xs gap-1',
itemLeadingIcon: 'size-4 text-sm',
itemLeadingAvatarSize: '3xs'
},
sm: {
label: 'p-1.5 text-[10px]/3 gap-1.5',
item: 'p-1.5 text-xs gap-1.5',
itemLeadingIcon: 'size-4 text-sm',
itemLeadingAvatarSize: '3xs'
},
md: {
label: 'p-1.5 text-xs gap-1.5',
item: 'p-1.5 text-sm gap-1.5',
itemLeadingIcon: 'size-5 text-base',
itemLeadingAvatarSize: '2xs'
},
lg: {
label: 'p-2 text-xs gap-2',
item: 'p-2 text-sm gap-2',
itemLeadingIcon: 'size-5 text-base',
itemLeadingAvatarSize: '2xs'
},
xl: {
label: 'p-2 text-sm gap-2',
item: 'p-2 text-base gap-2',
itemLeadingIcon: 'size-6 text-xl',
itemLeadingAvatarSize: 'xs'
}
},
active: {
true: {
item: 'text-highlighted before:bg-elevated/75',
itemLeadingIcon: 'text-default'
},
false: {
item: [
'text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50',
'transition-colors before:transition-colors'
],
itemLeadingIcon: [
'text-dimmed group-data-highlighted:not-group-data-disabled:text-default',
'transition-colors'
]
}
}
},
defaultVariants: {
size: 'md'
}
}
}
})
]
})