feat(editor): Add universal Create Resource Menu (#11564)

Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
This commit is contained in:
Raúl Gómez Morales
2024-11-19 15:47:10 +01:00
committed by GitHub
parent 1d8fd13d84
commit b38ce14ec9
23 changed files with 700 additions and 346 deletions

View File

@@ -537,13 +537,11 @@ code[class^='language-'] {
}
.inputWrapper {
position: absolute;
display: flex;
bottom: 0;
background-color: var(--color-foreground-xlight);
border: var(--border-base);
width: 100%;
padding-top: 1px;
border-top: 0;
textarea {
border: none;

View File

@@ -132,6 +132,7 @@ const onSelect = (item: IMenuItem): void => {
display: flex;
flex-direction: column;
background-color: var(--menu-background, var(--color-background-xlight));
overflow: hidden;
}
.menuHeader {

View File

@@ -29,9 +29,10 @@ describe('N8nNavigationDropdown', () => {
await router.isReady();
});
it('default slot should trigger first level', async () => {
const { getByTestId, queryByTestId } = render(NavigationDropdown, {
slots: { default: h('button', { ['data-test-id']: 'test-trigger' }) },
slots: { default: h('button', { 'data-test-id': 'test-trigger' }) },
props: { menu: [{ id: 'aaa', title: 'aaa', route: { name: 'projects' } }] },
global: {
plugins: [router],
@@ -40,13 +41,13 @@ describe('N8nNavigationDropdown', () => {
expect(getByTestId('test-trigger')).toBeVisible();
expect(queryByTestId('navigation-menu-item')).not.toBeVisible();
await userEvent.hover(getByTestId('test-trigger'));
await userEvent.click(getByTestId('test-trigger'));
await waitFor(() => expect(queryByTestId('navigation-menu-item')).toBeVisible());
});
it('redirect to route', async () => {
const { getByTestId, queryByTestId } = render(NavigationDropdown, {
slots: { default: h('button', { ['data-test-id']: 'test-trigger' }) },
slots: { default: h('button', { 'data-test-id': 'test-trigger' }) },
props: {
menu: [
{
@@ -64,7 +65,7 @@ describe('N8nNavigationDropdown', () => {
expect(getByTestId('test-trigger')).toBeVisible();
expect(queryByTestId('navigation-submenu')).not.toBeVisible();
await userEvent.hover(getByTestId('test-trigger'));
await userEvent.click(getByTestId('test-trigger'));
await waitFor(() => expect(getByTestId('navigation-submenu')).toBeVisible());
@@ -75,7 +76,7 @@ describe('N8nNavigationDropdown', () => {
it('should render icons in submenu when provided', () => {
const { getByTestId } = render(NavigationDropdown, {
slots: { default: h('button', { ['data-test-id']: 'test-trigger' }) },
slots: { default: h('button', { 'data-test-id': 'test-trigger' }) },
props: {
menu: [
{
@@ -95,7 +96,7 @@ describe('N8nNavigationDropdown', () => {
it('should propagate events', async () => {
const { getByTestId, emitted } = render(NavigationDropdown, {
slots: { default: h('button', { ['data-test-id']: 'test-trigger' }) },
slots: { default: h('button', { 'data-test-id': 'test-trigger' }) },
props: {
menu: [
{

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import { ElMenu, ElSubMenu, ElMenuItem, type MenuItemRegistered } from 'element-plus';
import { ref, defineProps, defineEmits, defineOptions } from 'vue';
import type { RouteLocationRaw } from 'vue-router';
import ConditionalRouterLink from '../ConditionalRouterLink';
@@ -17,28 +18,51 @@ type Item = BaseItem & {
submenu?: BaseItem[];
};
defineOptions({
name: 'N8nNavigationDropdown',
});
defineProps<{
menu: Item[];
disabled?: boolean;
teleport?: boolean;
}>();
const menuRef = ref<typeof ElMenu | null>(null);
const menuIndex = ref('-1');
const emit = defineEmits<{
itemClick: [item: MenuItemRegistered];
select: [id: Item['id']];
}>();
const close = () => {
menuRef.value?.close(menuIndex.value);
};
defineExpose({
close,
});
</script>
<template>
<ElMenu
ref="menuRef"
mode="horizontal"
unique-opened
menu-trigger="click"
:ellipsis="false"
:class="$style.dropdown"
@select="emit('select', $event)"
@keyup.escape="close"
>
<ElSubMenu
index="-1"
:index="menuIndex"
:class="$style.trigger"
:popper-offset="-10"
:popper-class="$style.submenu"
:disabled
:teleported="teleport"
>
<template #title>
<slot />
@@ -49,7 +73,7 @@ const emit = defineEmits<{
<ElSubMenu :index="item.id" :popper-offset="-10" data-test-id="navigation-submenu">
<template #title>{{ item.title }}</template>
<template v-for="subitem in item.submenu" :key="subitem.id">
<ConditionalRouterLink :to="subitem.route">
<ConditionalRouterLink :to="!subitem.disabled && subitem.route">
<ElMenuItem
data-test-id="navigation-submenu-item"
:index="subitem.id"
@@ -63,7 +87,7 @@ const emit = defineEmits<{
</template>
</ElSubMenu>
</template>
<ConditionalRouterLink v-else :to="item.route">
<ConditionalRouterLink v-else :to="!item.disabled && item.route">
<ElMenuItem
:index="item.id"
:disabled="item.disabled"
@@ -81,24 +105,29 @@ const emit = defineEmits<{
:global(.el-menu).dropdown {
border-bottom: 0;
background-color: transparent;
}
:global(.el-sub-menu).trigger {
:global(.el-sub-menu__title) {
height: auto;
line-height: initial;
border-bottom: 0 !important;
:global(.el-sub-menu__icon-arrow) {
display: none;
> :global(.el-sub-menu) {
> :global(.el-sub-menu__title) {
height: auto;
line-height: initial;
border-bottom: 0 !important;
padding: 0;
:global(.el-sub-menu__icon-arrow) {
display: none;
}
}
}
:global(.el-sub-menu__title:hover) {
background-color: transparent;
&:global(.is-active) {
:global(.el-sub-menu__title) {
border: 0;
}
}
}
}
.submenu {
padding: 5px 0 !important;
:global(.el-menu--horizontal .el-menu .el-menu-item),
:global(.el-menu--horizontal .el-menu .el-sub-menu__title) {
color: var(--color-text-dark);
@@ -109,6 +138,15 @@ const emit = defineEmits<{
background-color: var(--color-foreground-base);
}
:global(.el-popper) {
padding: 0 10px !important;
}
:global(.el-menu--popup) {
border: 1px solid var(--color-foreground-base);
border-radius: var(--border-radius-base);
}
:global(.el-menu--horizontal .el-menu .el-menu-item.is-disabled) {
opacity: 1;
cursor: default;