Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(editor): Universal button snags #11974

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('N8nNavigationDropdown', () => {
it('default slot should trigger first level', async () => {
const { getByTestId, queryByTestId } = render(NavigationDropdown, {
slots: { default: h('button', { 'data-test-id': 'test-trigger' }) },
props: { menu: [{ id: 'aaa', title: 'aaa', route: { name: 'projects' } }] },
props: { menu: [{ id: 'first', title: 'first', route: { name: 'projects' } }] },
global: {
plugins: [router],
},
Expand All @@ -51,9 +51,9 @@ describe('N8nNavigationDropdown', () => {
props: {
menu: [
{
id: 'aaa',
title: 'aaa',
submenu: [{ id: 'bbb', title: 'bbb', route: { name: 'projects' } }],
id: 'first',
title: 'first',
submenu: [{ id: 'nested', title: 'nested', route: { name: 'projects' } }],
},
],
},
Expand All @@ -80,9 +80,9 @@ describe('N8nNavigationDropdown', () => {
props: {
menu: [
{
id: 'aaa',
title: 'aaa',
submenu: [{ id: 'bbb', title: 'bbb', route: { name: 'projects' }, icon: 'user' }],
id: 'first',
title: 'first',
submenu: [{ id: 'nested', title: 'nested', route: { name: 'projects' }, icon: 'user' }],
},
],
},
Expand All @@ -100,9 +100,9 @@ describe('N8nNavigationDropdown', () => {
props: {
menu: [
{
id: 'aaa',
title: 'aaa',
submenu: [{ id: 'bbb', title: 'bbb', route: { name: 'projects' }, icon: 'user' }],
id: 'first',
title: 'first',
submenu: [{ id: 'nested', title: 'nested', route: { name: 'projects' }, icon: 'user' }],
},
],
},
Expand All @@ -114,8 +114,53 @@ describe('N8nNavigationDropdown', () => {
await userEvent.click(getByTestId('navigation-submenu-item'));

expect(emitted('itemClick')).toStrictEqual([
[{ active: true, index: 'bbb', indexPath: ['-1', 'aaa', 'bbb'] }],
[{ active: true, index: 'nested', indexPath: ['-1', 'first', 'nested'] }],
]);
expect(emitted('select')).toStrictEqual([['bbb']]);
expect(emitted('select')).toStrictEqual([['nested']]);
});

it('should open first level on click', async () => {
const { getByTestId, getByText } = render(NavigationDropdown, {
slots: { default: h('button', { 'data-test-id': 'test-trigger' }) },
props: {
menu: [
{
id: 'first',
title: 'first',
},
],
},
});
expect(getByText('first')).not.toBeVisible();
await userEvent.click(getByTestId('test-trigger'));
expect(getByText('first')).toBeVisible();
});

it('should toggle nested level on mouseenter / mouseleave', async () => {
const { getByTestId, getByText } = render(NavigationDropdown, {
slots: { default: h('button', { 'data-test-id': 'test-trigger' }) },
props: {
menu: [
{
id: 'first',
title: 'first',
submenu: [{ id: 'nested', title: 'nested' }],
},
],
},
});
expect(getByText('first')).not.toBeVisible();
await userEvent.click(getByTestId('test-trigger'));
expect(getByText('first')).toBeVisible();

expect(getByText('nested')).not.toBeVisible();
await userEvent.hover(getByTestId('navigation-submenu'));
await waitFor(() => expect(getByText('nested')).toBeVisible());

await userEvent.pointer([
{ target: getByTestId('navigation-submenu') },
{ target: getByTestId('test-trigger') },
]);
await waitFor(() => expect(getByText('nested')).not.toBeVisible());
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,26 @@ defineProps<{
}>();

const menuRef = ref<typeof ElMenu | null>(null);
const menuIndex = ref('-1');
const ROOT_MENU_INDEX = '-1';

const emit = defineEmits<{
itemClick: [item: MenuItemRegistered];
select: [id: Item['id']];
}>();

const close = () => {
menuRef.value?.close(menuIndex.value);
menuRef.value?.close(ROOT_MENU_INDEX);
};

const menuTrigger = ref<'click' | 'hover'>('click');
const onOpen = (index: string) => {
if (index !== ROOT_MENU_INDEX) return;
menuTrigger.value = 'hover';
};

const onClose = (index: string) => {
if (index !== ROOT_MENU_INDEX) return;
menuTrigger.value = 'click';
};

defineExpose({
Expand All @@ -50,14 +61,16 @@ defineExpose({
ref="menuRef"
mode="horizontal"
unique-opened
menu-trigger="click"
:menu-trigger="menuTrigger"
:ellipsis="false"
:class="$style.dropdown"
@select="emit('select', $event)"
@keyup.escape="close"
@open="onOpen"
@close="onClose"
>
<ElSubMenu
:index="menuIndex"
:index="ROOT_MENU_INDEX"
:class="$style.trigger"
:popper-offset="-10"
:popper-class="$style.submenu"
Expand All @@ -70,10 +83,15 @@ defineExpose({

<template v-for="item in menu" :key="item.id">
<template v-if="item.submenu">
<ElSubMenu :index="item.id" :popper-offset="-10" data-test-id="navigation-submenu">
<ElSubMenu
:popper-class="$style.nestedSubmenu"
: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.disabled && subitem.route">
<ConditionalRouterLink :to="(!subitem.disabled && subitem.route) || undefined">
<ElMenuItem
data-test-id="navigation-submenu-item"
:index="subitem.id"
Expand All @@ -82,18 +100,20 @@ defineExpose({
>
<N8nIcon v-if="subitem.icon" :icon="subitem.icon" :class="$style.submenu__icon" />
{{ subitem.title }}
<slot :name="`item.append.${item.id}`" v-bind="{ item }" />
</ElMenuItem>
</ConditionalRouterLink>
</template>
</ElSubMenu>
</template>
<ConditionalRouterLink v-else :to="!item.disabled && item.route">
<ConditionalRouterLink v-else :to="(!item.disabled && item.route) || undefined">
<ElMenuItem
:index="item.id"
:disabled="item.disabled"
data-test-id="navigation-menu-item"
>
{{ item.title }}
<slot :name="`item.append.${item.id}`" v-bind="{ item }" />
</ElMenuItem>
</ConditionalRouterLink>
</template>
Expand Down Expand Up @@ -125,17 +145,25 @@ defineExpose({
}
}

.nestedSubmenu {
:global(.el-menu) {
max-height: 450px;
overflow: auto;
}
}

.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);
background-color: var(--color-menu-background);
}

:global(.el-menu--horizontal .el-menu .el-menu-item:not(.is-disabled):hover),
:global(.el-menu--horizontal .el-menu .el-sub-menu__title:not(.is-disabled):hover) {
background-color: var(--color-foreground-base);
background-color: var(--color-menu-hover-background);
}

:global(.el-popper) {
Expand Down
4 changes: 4 additions & 0 deletions packages/design-system/src/css/_tokens.dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,10 @@
--color-configurable-node-name: var(--color-text-dark);
--color-secondary-link: var(--prim-color-secondary-tint-200);
--color-secondary-link-hover: var(--prim-color-secondary-tint-100);

--color-menu-background: var(--prim-gray-740);
--color-menu-hover-background: var(--prim-gray-670);
--color-menu-active-background: var(--prim-gray-670);
}

body[data-theme='dark'] {
Expand Down
5 changes: 5 additions & 0 deletions packages/design-system/src/css/_tokens.scss
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,11 @@
--color-secondary-link: var(--color-secondary);
--color-secondary-link-hover: var(--color-secondary-shade-1);

// Menu
--color-menu-background: var(--prim-gray-0);
--color-menu-hover-background: var(--prim-gray-120);
--color-menu-active-background: var(--prim-gray-120);

// Generated Color Shades from 50 to 950
// Not yet used in design system
@each $color in ('neutral', 'success', 'warning', 'danger') {
Expand Down
25 changes: 23 additions & 2 deletions packages/editor-ui/src/components/MainSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,12 @@ const checkWidthAndAdjustSidebar = async (width: number) => {
}
};

const { menu, handleSelect: handleMenuSelect } = useGlobalEntityCreation();
const {
menu,
handleSelect: handleMenuSelect,
CREATE_PROJECT_ID,
projectsLimitReachedMessage,
} = useGlobalEntityCreation();
onClickOutside(createBtn as Ref<VueInstance>, () => {
createBtn.value?.close();
});
Expand Down Expand Up @@ -322,7 +327,23 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
:menu="menu"
@select="handleMenuSelect"
>
<N8nIconButton icon="plus" type="secondary" outline />
<n8n-icon-button icon="plus" type="secondary" outline />
r00gm marked this conversation as resolved.
Show resolved Hide resolved
<template #[`item.append.${CREATE_PROJECT_ID}`]="{ item }">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just spotted this
Isn't there a nicer solution?
This looks very weird, could it be just a computed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a dynamic slot and is used for thing like this when you only want your changes to affect specific elements within what's rendered,

i could create a computed inside the composable that return the same string, but it would be just moving it from one place to another

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but I think it's more readable and understandable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you were right it looks much nicer, thank you!

<n8n-tooltip
v-if="item.disabled"
placement="right"
:content="projectsLimitReachedMessage"
>
<N8nButton
:size="'mini'"
style="margin-left: auto"
type="tertiary"
@click="handleMenuSelect(item.id)"
>
Upgrade
</N8nButton>
</n8n-tooltip>
</template>
</N8nNavigationDropdown>
</div>
<n8n-menu :items="mainMenuItems" :collapsed="isCollapsed" @select="handleSelect">
Expand Down
25 changes: 22 additions & 3 deletions packages/editor-ui/src/components/Projects/ProjectHeader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { within } from '@testing-library/dom';
import { createComponentRenderer } from '@/__tests__/render';
import { mockedStore } from '@/__tests__/utils';
import { createTestProject } from '@/__tests__/data/projects';
import { useRoute } from 'vue-router';
import * as router from 'vue-router';
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';
import ProjectHeader from '@/components/Projects/ProjectHeader.vue';
import { useProjectsStore } from '@/stores/projects.store';
import type { Project } from '@/types/projects.types';
import { ProjectTypes } from '@/types/projects.types';
import { VIEWS } from '@/constants';

vi.mock('vue-router', async () => {
const actual = await vi.importActual('vue-router');
Expand Down Expand Up @@ -35,13 +37,13 @@ const renderComponent = createComponentRenderer(ProjectHeader, {
},
});

let route: ReturnType<typeof useRoute>;
let route: ReturnType<typeof router.useRoute>;
let projectsStore: ReturnType<typeof mockedStore<typeof useProjectsStore>>;

describe('ProjectHeader', () => {
beforeEach(() => {
createTestingPinia();
route = useRoute();
route = router.useRoute();
projectsStore = mockedStore(useProjectsStore);

projectsStore.teamProjectsLimit = -1;
Expand Down Expand Up @@ -159,4 +161,21 @@ describe('ProjectHeader', () => {

expect(within(getByTestId('resource-add')).getByRole('button', { name: label })).toBeVisible();
});

it('should not render creation button in setting page', async () => {
projectsStore.currentProject = createTestProject({ type: ProjectTypes.Personal });
vi.spyOn(router, 'useRoute').mockReturnValueOnce({
name: VIEWS.PROJECT_SETTINGS,
} as RouteLocationNormalizedLoadedGeneric);
const { queryByTestId } = renderComponent({
global: {
stubs: {
N8nNavigationDropdown: {
template: '<div><slot></slot></div>',
},
},
},
});
expect(queryByTestId('resource-add')).not.toBeInTheDocument();
});
});
Loading
Loading