Skip to content

Commit

Permalink
fix(editor): Universal button snags (#11974)
Browse files Browse the repository at this point in the history
Co-authored-by: Csaba Tuncsik <[email protected]>
  • Loading branch information
r00gm and cstuncsik authored Dec 6, 2024
1 parent b1f8663 commit 956b11a
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 56 deletions.
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
55 changes: 36 additions & 19 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,
createProjectAppendSlotName,
projectsLimitReachedMessage,
} = useGlobalEntityCreation();
onClickOutside(createBtn as Ref<VueInstance>, () => {
createBtn.value?.close();
});
Expand All @@ -311,8 +316,8 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
:class="['clickable', $style.sideMenuCollapseButton]"
@click="toggleCollapse"
>
<n8n-icon v-if="isCollapsed" icon="chevron-right" size="xsmall" class="ml-5xs" />
<n8n-icon v-else icon="chevron-left" size="xsmall" class="mr-5xs" />
<N8nIcon v-if="isCollapsed" icon="chevron-right" size="xsmall" class="ml-5xs" />
<N8nIcon v-else icon="chevron-left" size="xsmall" class="mr-5xs" />
</div>
<div :class="$style.logo">
<img :src="logoPath" data-test-id="n8n-logo" :class="$style.icon" alt="n8n" />
Expand All @@ -323,9 +328,21 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
@select="handleMenuSelect"
>
<N8nIconButton icon="plus" type="secondary" outline />
<template #[createProjectAppendSlotName]="{ item }">
<N8nTooltip v-if="item.disabled" placement="right" :content="projectsLimitReachedMessage">
<N8nButton
:size="'mini'"
style="margin-left: auto"
type="tertiary"
@click="handleMenuSelect(item.id)"
>
{{ i18n.baseText('generic.upgrade') }}
</N8nButton>
</N8nTooltip>
</template>
</N8nNavigationDropdown>
</div>
<n8n-menu :items="mainMenuItems" :collapsed="isCollapsed" @select="handleSelect">
<N8nMenu :items="mainMenuItems" :collapsed="isCollapsed" @select="handleSelect">
<template #header>
<ProjectNavigation
:collapsed="isCollapsed"
Expand All @@ -347,14 +364,14 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
<div :class="$style.giftContainer">
<GiftNotificationIcon />
</div>
<n8n-text
<N8nText
:class="{ ['ml-xs']: true, [$style.expanded]: fullyExpanded }"
color="text-base"
>
{{ nextVersions.length > 99 ? '99+' : nextVersions.length }} update{{
nextVersions.length > 1 ? 's' : ''
}}
</n8n-text>
</N8nText>
</div>
<MainSidebarSourceControl :is-collapsed="isCollapsed" />
</div>
Expand All @@ -363,35 +380,35 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
<div ref="user" :class="$style.userArea">
<div class="ml-3xs" data-test-id="main-sidebar-user-menu">
<!-- This dropdown is only enabled when sidebar is collapsed -->
<el-dropdown placement="right-end" trigger="click" @command="onUserActionToggle">
<ElDropdown placement="right-end" trigger="click" @command="onUserActionToggle">
<div :class="{ [$style.avatar]: true, ['clickable']: isCollapsed }">
<n8n-avatar
<N8nAvatar
:first-name="usersStore.currentUser?.firstName"
:last-name="usersStore.currentUser?.lastName"
size="small"
/>
</div>
<template v-if="isCollapsed" #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="settings">
<ElDropdownMenu>
<ElDropdownItem command="settings">
{{ i18n.baseText('settings') }}
</el-dropdown-item>
<el-dropdown-item command="logout">
</ElDropdownItem>
<ElDropdownItem command="logout">
{{ i18n.baseText('auth.signout') }}
</el-dropdown-item>
</el-dropdown-menu>
</ElDropdownItem>
</ElDropdownMenu>
</template>
</el-dropdown>
</ElDropdown>
</div>
<div
:class="{ ['ml-2xs']: true, [$style.userName]: true, [$style.expanded]: fullyExpanded }"
>
<n8n-text size="small" :bold="true" color="text-dark">{{
<N8nText size="small" :bold="true" color="text-dark">{{
usersStore.currentUser?.fullName
}}</n8n-text>
}}</N8nText>
</div>
<div :class="{ [$style.userActions]: true, [$style.expanded]: fullyExpanded }">
<n8n-action-dropdown
<N8nActionDropdown
:items="userMenuItems"
placement="top-start"
data-test-id="user-menu"
Expand All @@ -400,7 +417,7 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
</div>
</div>
</template>
</n8n-menu>
</N8nMenu>
</div>
</template>

Expand Down
Loading

0 comments on commit 956b11a

Please sign in to comment.