Usage
The EditorMentionMenu component displays a menu of user suggestions when typing a trigger character (defaults to @) in the editor and inserts the selected mention using the @tiptap/extension-mention package. The trigger character is also used as the prefix when rendering the inserted mention.
It uses the
useEditorMenu composable built on top of TipTap's Suggestion utility to filter items as you type and support keyboard navigation (arrow keys, enter to select, escape to close).It must be used inside an Editor component's default slot to have access to the editor instance.
Items
Use the items prop as an array of objects with the following properties:
label: stringavatar?: AvatarPropsicon?: IconComponentdescription?: stringdisabled?: boolean
You can also pass an array of arrays to the
items prop to create separated groups of items.Char
Use the char prop to change the trigger character. Defaults to @. The trigger character is also used as the prefix when rendering the inserted mention (e.g. #channel instead of @channel).
<template>
<B24Editor v-slot="{ editor }">
<B24EditorMentionMenu :editor="editor" :items="channels" char="#" />
</B24Editor>
</template>
You can use multiple
EditorMentionMenu components on the same editor with different char and plugin-key props to support different mention types.<template>
<B24Editor v-slot="{ editor }">
<B24EditorMentionMenu :editor="editor" :items="users" plugin-key="mentionMenu" />
<B24EditorMentionMenu :editor="editor" :items="tags" char="#" plugin-key="tagMenu" />
</B24Editor>
</template>
Options
Use the options prop to customize the positioning behavior using Floating UI options.
<template>
<B24Editor v-slot="{ editor }">
<B24EditorMentionMenu
:editor="editor"
:items="items"
:options="{
placement: 'bottom-start',
offset: 4
}"
/>
</B24Editor>
</template>
API
Props
Theme
app.config.ts
export default defineAppConfig({
b24ui: {
editorMentionMenu: {
slots: {
content: 'base-mode bg-(--ui-color-bg-content-primary) shadow-(--popup-window-box-shadow) rounded-(--ui-border-radius-xl) will-change-[opacity] motion-safe:data-[state=open]:animate-[scale-in_100ms_ease-out] motion-safe:data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-dropdown-menu-content-transform-origin) font-[family-name:var(--ui-font-family-primary)] relative isolate px-0 py-(--menu-popup-padding) pointer-events-auto',
viewport: 'relative w-full max-h-[40vh] min-w-[192px] max-w-[240px] overflow-x-hidden overflow-y-auto scrollbar-thin',
group: 'grid',
label: 'w-full h-(--popup-window-delimiter-section-height) mt-(--menu-item-block-stack-space) flex flex-row rtl:flex-row-reverse items-center select-none outline-none whitespace-nowrap text-start text-(--b24ui-typography-legend-color) font-(--ui-font-weight-normal) after:ms-[10px] after:block after:flex-1 after:min-w-[15px] after:h-px after:bg-(--ui-color-divider-vibrant-default)',
item: 'group w-full mt-(--menu-item-block-stack-space) relative flex flex-row rtl:flex-row-reverse items-center select-none outline-none whitespace-nowrap cursor-pointer data-disabled:cursor-not-allowed data-disabled:opacity-30 text-start text-(--b24ui-typography-legend-color) hover:text-(--b24ui-typography-label-color) data-highlighted:text-(--b24ui-typography-label-color) data-[state=open]:text-(--b24ui-typography-label-color) hover:bg-(--ui-color-divider-optical-1-alt) data-highlighted:bg-(--ui-color-divider-optical-1-alt) data-[state=open]:bg-(--ui-color-divider-optical-1-alt) transition-colors',
itemLeadingIcon: 'shrink-0 text-(--ui-color-design-plain-content-icon-secondary) group-data-highlighted:text-(--ui-color-accent-main-primary) group-data-[state=open]:text-(--ui-color-accent-main-primary) group-data-[state=checked]:text-(--ui-color-accent-main-primary) transition-colors',
itemLeadingAvatar: 'shrink-0',
itemLeadingAvatarSize: '',
itemWrapper: 'ms-[4px] flex-1 flex flex-col text-start min-w-0',
itemLabel: 'max-w-[240px] truncate -mt-px group-data-[state=checked]:text-(--ui-color-accent-main-primary)',
itemDescription: 'max-w-[240px] truncate -mt-[6px] text-(--b24ui-typography-description-color) text-(length:--ui-font-size-sm)',
itemLabelExternalIcon: 'inline-block size-[16px] text-(--ui-color-design-plain-content-icon-secondary)'
},
variants: {
size: {
xss: {
label: 'px-[14px] text-(length:--ui-font-size-4xs)/[normal] gap-[14px]',
item: 'px-[14px] :text-(length:--ui-font-size-4xs)/[normal] gap-[14px]',
itemLeadingIcon: 'size-4 text-(length:--ui-font-size-sm)/[normal]',
itemLeadingAvatarSize: '3xs'
},
xs: {
label: 'px-[14px] text-(length:--ui-font-size-4xs)/[normal] gap-[14px]',
item: 'px-[14px] :text-(length:--ui-font-size-xs)/[normal] gap-[14px]',
itemLeadingIcon: 'size-4 text-(length:--ui-font-size-sm)/[normal]',
itemLeadingAvatarSize: '3xs'
},
sm: {
label: 'px-[18px] text-(length:--ui-font-size-xs)/[normal] gap-[18px]',
item: 'px-[18px] :text-(length:--ui-font-size-xs)/[normal] gap-[18px]',
itemLeadingIcon: 'size-4 text-(length:--ui-font-size-sm)/[normal]',
itemLeadingAvatarSize: '3xs'
},
md: {
label: 'px-[18px] text-(length:--ui-font-size-sm)/[normal] gap-[18px]',
item: 'h-[36px] px-[18px] text-(length:--ui-font-size-sm)/[normal] gap-[18px]',
itemLeadingIcon: 'size-[18px] text-(length:--ui-font-size-lg)/[normal]',
itemLeadingAvatar: 'size-[16px] me-[8px]',
itemLeadingAvatarSize: '2xs'
},
lg: {
label: 'px-[20px] :text-(length:--ui-font-size-sm)/[normal] gap-[20px]',
item: 'px-[20px] text-(length:--ui-font-size-sm)/[normal] gap-[20px]',
itemLeadingIcon: 'size-[18px] text-(length:--ui-font-size-lg)/[normal]',
itemLeadingAvatar: 'size-[16px] me-[8px]',
itemLeadingAvatarSize: '2xs'
},
xl: {
label: 'px-[20px] text-(length:--ui-font-size-sm)/[normal] gap-[20px]',
item: 'px-[20px] text-(length:--ui-font-size-lg)/[normal] gap-[20px]',
itemLeadingIcon: 'size-[20px] text-(length:--ui-font-size-2xl)/[normal]',
itemLeadingAvatar: 'size-[16px] me-[8px]',
itemLeadingAvatarSize: 'xs'
}
},
active: {
true: {
item: 'text-(--ui-color-accent-main-primary) hover:text-(--ui-color-accent-main-primary)',
itemLeadingIcon: 'text-(--ui-color-accent-main-primary) hover:text-(--ui-color-accent-main-primary) group-data-[state=open]:text-(--ui-color-accent-main-primary)'
},
false: {}
}
},
defaultVariants: {
size: 'md'
}
}
}
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import bitrix24UIPluginVite from '@bitrix24/b24ui-nuxt/vite'
export default defineConfig({
plugins: [
vue(),
bitrix24UIPluginVite({
b24ui: {
editorMentionMenu: {
slots: {
content: 'base-mode bg-(--ui-color-bg-content-primary) shadow-(--popup-window-box-shadow) rounded-(--ui-border-radius-xl) will-change-[opacity] motion-safe:data-[state=open]:animate-[scale-in_100ms_ease-out] motion-safe:data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-dropdown-menu-content-transform-origin) font-[family-name:var(--ui-font-family-primary)] relative isolate px-0 py-(--menu-popup-padding) pointer-events-auto',
viewport: 'relative w-full max-h-[40vh] min-w-[192px] max-w-[240px] overflow-x-hidden overflow-y-auto scrollbar-thin',
group: 'grid',
label: 'w-full h-(--popup-window-delimiter-section-height) mt-(--menu-item-block-stack-space) flex flex-row rtl:flex-row-reverse items-center select-none outline-none whitespace-nowrap text-start text-(--b24ui-typography-legend-color) font-(--ui-font-weight-normal) after:ms-[10px] after:block after:flex-1 after:min-w-[15px] after:h-px after:bg-(--ui-color-divider-vibrant-default)',
item: 'group w-full mt-(--menu-item-block-stack-space) relative flex flex-row rtl:flex-row-reverse items-center select-none outline-none whitespace-nowrap cursor-pointer data-disabled:cursor-not-allowed data-disabled:opacity-30 text-start text-(--b24ui-typography-legend-color) hover:text-(--b24ui-typography-label-color) data-highlighted:text-(--b24ui-typography-label-color) data-[state=open]:text-(--b24ui-typography-label-color) hover:bg-(--ui-color-divider-optical-1-alt) data-highlighted:bg-(--ui-color-divider-optical-1-alt) data-[state=open]:bg-(--ui-color-divider-optical-1-alt) transition-colors',
itemLeadingIcon: 'shrink-0 text-(--ui-color-design-plain-content-icon-secondary) group-data-highlighted:text-(--ui-color-accent-main-primary) group-data-[state=open]:text-(--ui-color-accent-main-primary) group-data-[state=checked]:text-(--ui-color-accent-main-primary) transition-colors',
itemLeadingAvatar: 'shrink-0',
itemLeadingAvatarSize: '',
itemWrapper: 'ms-[4px] flex-1 flex flex-col text-start min-w-0',
itemLabel: 'max-w-[240px] truncate -mt-px group-data-[state=checked]:text-(--ui-color-accent-main-primary)',
itemDescription: 'max-w-[240px] truncate -mt-[6px] text-(--b24ui-typography-description-color) text-(length:--ui-font-size-sm)',
itemLabelExternalIcon: 'inline-block size-[16px] text-(--ui-color-design-plain-content-icon-secondary)'
},
variants: {
size: {
xss: {
label: 'px-[14px] text-(length:--ui-font-size-4xs)/[normal] gap-[14px]',
item: 'px-[14px] :text-(length:--ui-font-size-4xs)/[normal] gap-[14px]',
itemLeadingIcon: 'size-4 text-(length:--ui-font-size-sm)/[normal]',
itemLeadingAvatarSize: '3xs'
},
xs: {
label: 'px-[14px] text-(length:--ui-font-size-4xs)/[normal] gap-[14px]',
item: 'px-[14px] :text-(length:--ui-font-size-xs)/[normal] gap-[14px]',
itemLeadingIcon: 'size-4 text-(length:--ui-font-size-sm)/[normal]',
itemLeadingAvatarSize: '3xs'
},
sm: {
label: 'px-[18px] text-(length:--ui-font-size-xs)/[normal] gap-[18px]',
item: 'px-[18px] :text-(length:--ui-font-size-xs)/[normal] gap-[18px]',
itemLeadingIcon: 'size-4 text-(length:--ui-font-size-sm)/[normal]',
itemLeadingAvatarSize: '3xs'
},
md: {
label: 'px-[18px] text-(length:--ui-font-size-sm)/[normal] gap-[18px]',
item: 'h-[36px] px-[18px] text-(length:--ui-font-size-sm)/[normal] gap-[18px]',
itemLeadingIcon: 'size-[18px] text-(length:--ui-font-size-lg)/[normal]',
itemLeadingAvatar: 'size-[16px] me-[8px]',
itemLeadingAvatarSize: '2xs'
},
lg: {
label: 'px-[20px] :text-(length:--ui-font-size-sm)/[normal] gap-[20px]',
item: 'px-[20px] text-(length:--ui-font-size-sm)/[normal] gap-[20px]',
itemLeadingIcon: 'size-[18px] text-(length:--ui-font-size-lg)/[normal]',
itemLeadingAvatar: 'size-[16px] me-[8px]',
itemLeadingAvatarSize: '2xs'
},
xl: {
label: 'px-[20px] text-(length:--ui-font-size-sm)/[normal] gap-[20px]',
item: 'px-[20px] text-(length:--ui-font-size-lg)/[normal] gap-[20px]',
itemLeadingIcon: 'size-[20px] text-(length:--ui-font-size-2xl)/[normal]',
itemLeadingAvatar: 'size-[16px] me-[8px]',
itemLeadingAvatarSize: 'xs'
}
},
active: {
true: {
item: 'text-(--ui-color-accent-main-primary) hover:text-(--ui-color-accent-main-primary)',
itemLeadingIcon: 'text-(--ui-color-accent-main-primary) hover:text-(--ui-color-accent-main-primary) group-data-[state=open]:text-(--ui-color-accent-main-primary)'
},
false: {}
}
},
defaultVariants: {
size: 'md'
}
}
}
})
]
})