EditorEmojiMenu
当在编辑器中输入 : 字符时,显示表情符号建议的菜单。
用法
EditorEmojiMenu 组件在编辑器中输入 : 字符时显示表情符号建议菜单,并插入选定的表情符号。它与 @tiptap/extension-emoji 包协同工作,提供表情符号支持。
它使用了基于 TipTap 的Suggestion实用工具构建的
useEditorMenu Composable,用于在输入时过滤项目,并支持键盘导航(箭头键、回车键选择、Escape 键关闭)。它必须在 Editor 组件的默认插槽内使用,以便能够访问编辑器实例。
<script setup lang="ts">
import type { EditorEmojiMenuItem } from '@nuxt/ui'
import { Emoji, gitHubEmojis } from '@tiptap/extension-emoji'
const value = ref(`# Emoji Menu
Type : to insert emojis and select from the list of available emojis.`)
const items: EditorEmojiMenuItem[] = gitHubEmojis.filter(
(emoji) => !emoji.name.startsWith('regional_indicator_')
)
// 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"
:extensions="[Emoji]"
content-type="markdown"
placeholder="Type : to add emojis..."
class="w-full min-h-21"
>
<UEditorEmojiMenu :editor="editor" :items="items" :append-to="appendToBody" />
</UEditor>
</template>
@tiptap/extension-emoji 包默认未安装,您需要单独安装它。项
将 items 属性用作具有以下属性的对象数组
name: stringemoji: stringshortcodes?: string[]tags?: string[]group?: stringfallbackImage?: string
<script setup lang="ts">
import type { EditorEmojiMenuItem } from '@nuxt/ui'
import { Emoji } from '@tiptap/extension-emoji'
const value = ref(`Type : to see a custom emoji set.
You can also install the \`@tiptap/extension-emoji\` extension to use a comprehensive set with over 1800 emojis.`)
const items: EditorEmojiMenuItem[] = [{
name: 'smile',
emoji: '😄',
shortcodes: ['smile'],
tags: ['happy', 'joy', 'pleased']
}, {
name: 'heart',
emoji: '❤️',
shortcodes: ['heart'],
tags: ['love', 'like']
}, {
name: 'thumbsup',
emoji: '👍',
shortcodes: ['thumbsup', '+1'],
tags: ['approve', 'ok']
}, {
name: 'fire',
emoji: '🔥',
shortcodes: ['fire'],
tags: ['hot', 'burn']
}, {
name: 'rocket',
emoji: '🚀',
shortcodes: ['rocket'],
tags: ['ship', 'launch']
}, {
name: 'eyes',
emoji: '👀',
shortcodes: ['eyes'],
tags: ['look', 'watch']
}, {
name: 'tada',
emoji: '🎉',
shortcodes: ['tada'],
tags: ['party', 'celebration']
}, {
name: 'thinking',
emoji: '🤔',
shortcodes: ['thinking'],
tags: ['hmm', 'think', 'consider']
}]
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
:extensions="[Emoji]"
content-type="markdown"
placeholder="Type : to add emojis..."
class="w-full min-h-26"
>
<UEditorEmojiMenu :editor="editor" :items="items" />
</UEditor>
</template>
您还可以将数组的数组传递给
items prop,以创建分隔的项目组。字符
使用 char 属性更改触发字符。默认为 :。
<template>
<UEditor v-slot="{ editor }">
<UEditorEmojiMenu :editor="editor" :items="items" char=";" />
</UEditor>
</template>
选项
使用 options prop 来自定义定位行为,使用Floating UI options.
<template>
<UEditor v-slot="{ editor }">
<UEditorEmojiMenu
:editor="editor"
:items="items"
:options="{
placement: 'bottom-start',
offset: 4
}"
/>
</UEditor>
</template>
API
属性
| 属性 | 默认值 | 类型 |
|---|---|---|
尺寸 | 'md' | "xs" | "md" | "sm" | "lg" | "xl" |
items | T[] | T[][] | |
editor | 编辑器 | |
char | ':' | string触发字符(例如:'/'、'@'、':') |
pluginKey | 'emojiMenu' | string用于识别此菜单的插件密钥 |
filterFields | ["name", "shortcodes", "tags"] | string[]用于过滤项目的字段。 |
limit | 42 | number要显示的最多项目数 |
options | { strategy: 'absolute', placement: 'bottom-start', offset: 8, shift: { padding: 8 } } | FloatingUIOptions用于定位菜单的选项。这些选项会传递给 Floating UI,并包含 placement、offset、flip、shift、size、autoPlacement、hide 和 inline 中间件的选项。
|
appendTo | HTMLElement | (): HTMLElement要追加菜单的 DOM 元素。默认为编辑器的父元素。 有时由于可访问性、剪辑或 z-index 问题,菜单需要追加到不同的 DOM 上下文。
| |
ui | { content?: ClassNameValue; viewport?: ClassNameValue; group?: ClassNameValue; label?: ClassNameValue; separator?: ClassNameValue; item?: ClassNameValue; itemLeadingIcon?: ClassNameValue; itemLeadingAvatar?: ClassNameValue; itemLeadingAvatarSize?: ClassNameValue; itemWrapper?: ClassNameValue; itemLabel?: ClassNameValue; itemDescription?: ClassNameValue; itemLabelExternalIcon?: ClassNameValue; } |
主题
app.config.ts
export default defineAppConfig({
ui: {
editorEmojiMenu: {
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_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] 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'
}
}
}
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
editorEmojiMenu: {
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_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] 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'
}
}
}
})
]
})