mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat(editor): Main navigation redesign (#4144)
* refactor(editor): N8N-4540 Main navigation layout rework (#4060) * ✨ Implemented new editor layout using css grid * ✨ Reworking main navigation layout, migrating some styling to css modules * ✨ Reworking main sidebar layout and responsiveness * 💄 Minor type update * ✨ Updated editor grid layout so empty cells are collapsed (`fit-content`), fixed updates menu items styling * ✨ Implemented new user area look & feel in main sidebar * 💄 Adjusting sidebar bottom padding when user area is not shown * 💄 CSS cleanup/refactor + minor vue refactoring * ✨ Fixing overscoll issue in chrome and scrolling behaviour of the content view * 👌 Addressing review feedback * ✨ Added collapsed and expanded versions of n8n logo * ✨ Updating infinite scrolling in templates view to work with the new layout * 💄 Updating main sidebar expanded width and templates view left margin * 💄 Updating main content height * 💄 Adding global styles for scrollable views with centered content, minor updates to user area * ✨ Updating zoomToFit logic, lasso select box position and new nodes positioning * ✨ Fixing new node drop position now that mouse detection has been adjusted * 👌 Updating templates view scroll to top logic and responsive padding, aligning menu items titles * 💄 Moving template layout style from global css class to component level * ✨ Moved 'Workflows' menu to node view header. Added new dropdown component for user area and the new WF menu * 💄 Updating disabled states in new WF menu * 💄 Initial stab at new sidebar styling * ✨ Finished main navigation restyling * ✨ Updating `zoomToFit` and centering logic * ✨ Adding updates menu item to settings sidebar * 💄 Adding updates item to the settings sidebar and final touches on main sidebar style * 💄 Removing old code & refactoring * 💄 Minor CSS tweaks * 💄 Opening credentials modal on sidebar menu item click. Minor CSS updates * 💄 Updating sidebar expand/collapse animation * 💄 Few more refinements of sidebar animation * 👌 Addressing code review comments * ✨ Moved ActionDropdown component to design system * 👌 Fixing bugs reported during code review and testing * 👌 Addressing design review comments for the new sidebar * ✔️ Updating `N8nActionDropdown` component tests * ✨ Remembering scroll position when going back to templates list * ✨ Updating zoomToFit logic to account for footer content * 👌 Addressing latest sidebar review comments * 👌 Addressing main sidebar product review comments * 💄 Updating css variable names after vite merge * ✔️ Fixing linting errors in the design system * ✔️ Fixing `element-ui` type import * 👌 Addressing the code review comments. * ✨ Adding link to new credentials view, removed old modal * 💄 Updating credentials view responsiveness and route highlight handling * 💄 Adding highlight to workflows submenu when on new workflow page * 💄 Updated active submenu text color
This commit is contained in:
committed by
GitHub
parent
e6e4f297c6
commit
3db53a1934
@@ -0,0 +1,72 @@
|
||||
import N8nActionDropdown from "./ActionDropdown.vue";
|
||||
import { StoryFn } from '@storybook/vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/ActionDropdown',
|
||||
component: N8nActionDropdown,
|
||||
argTypes: {
|
||||
placement: {
|
||||
control: {
|
||||
type: 'select',
|
||||
options: ['top', 'top-end', 'top-start', 'bottom', 'bottom-end', 'bottom-start'],
|
||||
},
|
||||
},
|
||||
activatorIcon: {
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
trigger: {
|
||||
control: {
|
||||
type: 'select',
|
||||
options: ['click', 'hover'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const template: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nActionDropdown,
|
||||
},
|
||||
template: `<n8n-action-dropdown v-bind="$props" />`,
|
||||
});
|
||||
|
||||
export const defaultActionDropdown = template.bind({});
|
||||
defaultActionDropdown.args = {
|
||||
items: [
|
||||
{
|
||||
id: 'item1',
|
||||
label: 'Action 1',
|
||||
},
|
||||
{
|
||||
id: 'item2',
|
||||
label: 'Action 2',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const customStyling = template.bind({});
|
||||
customStyling.args = {
|
||||
activatorIcon: 'bars',
|
||||
items: [
|
||||
{
|
||||
id: 'item1',
|
||||
label: 'Action 1',
|
||||
icon: 'thumbs-up',
|
||||
},
|
||||
{
|
||||
id: 'item2',
|
||||
label: 'Action 2',
|
||||
icon: 'thumbs-down',
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
id: 'item3',
|
||||
label: 'Action 3',
|
||||
icon: 'heart',
|
||||
divided: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div :class="['action-dropdown-container', $style.actionDropdownContainer]">
|
||||
<el-dropdown :placement="placement" :trigger="trigger" @command="onSelect">
|
||||
<div :class="$style.activator">
|
||||
<n8n-icon :icon="activatorIcon"/>
|
||||
</div>
|
||||
<el-dropdown-menu slot="dropdown" :class="$style.userActionsMenu">
|
||||
<el-dropdown-item
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
:command="item.id"
|
||||
:disabled="item.disabled"
|
||||
:divided="item.divided"
|
||||
>
|
||||
<div :class="{
|
||||
[$style.itemContainer]: true,
|
||||
[$style.hasCustomStyling]: item.customClass !== undefined,
|
||||
[item.customClass]: item.customClass !== undefined,
|
||||
}">
|
||||
<span v-if="item.icon" :class="$style.icon">
|
||||
<n8n-icon :icon="item.icon"/>
|
||||
</span>
|
||||
<span :class="$style.label">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, { PropType } from "vue";
|
||||
import ElDropdown from 'element-ui/lib/dropdown';
|
||||
import ElDropdownMenu from 'element-ui/lib/dropdown-menu';
|
||||
import ElDropdownItem from 'element-ui/lib/dropdown-item';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
|
||||
interface IActionDropdownItem {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
divided?: boolean;
|
||||
disabled?: boolean;
|
||||
customClass?: string;
|
||||
}
|
||||
|
||||
// This component is visually similar to the ActionToggle component
|
||||
// but it offers more options when it comes to dropdown items styling
|
||||
// (supports icons, separators, custom styling and all options provided
|
||||
// by Element UI dropdown component).
|
||||
// It can be used in different parts of editor UI while ActionToggle
|
||||
// is designed to be used in card components.
|
||||
export default Vue.extend({
|
||||
name: 'n8n-action-dropdown',
|
||||
components: {
|
||||
ElDropdownMenu, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||
ElDropdown, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||
ElDropdownItem, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||
N8nIcon,
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
type: Array as PropType<IActionDropdownItem[]>,
|
||||
required: true,
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom',
|
||||
validator: (value: string): boolean =>
|
||||
['top', 'top-end', 'top-start', 'bottom', 'bottom-end', 'bottom-start'].includes(value),
|
||||
},
|
||||
activatorIcon: {
|
||||
type: String,
|
||||
default: 'ellipsis-v',
|
||||
},
|
||||
trigger: {
|
||||
type: String,
|
||||
default: 'click',
|
||||
validator: (value: string): boolean =>
|
||||
['click', 'hover'].includes(value),
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onSelect(action: string) : void {
|
||||
this.$emit('select', action);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
||||
.activator {
|
||||
cursor: pointer;
|
||||
padding: var(--spacing-2xs);
|
||||
margin: 0;
|
||||
border-radius: var(--border-radius-base);
|
||||
line-height: normal !important;
|
||||
|
||||
svg {
|
||||
position: static !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-background-base);
|
||||
color: initial !important;
|
||||
}
|
||||
}
|
||||
|
||||
.itemContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.icon {
|
||||
text-align: center;
|
||||
margin-right: var(--spacing-2xs);
|
||||
|
||||
svg { width: 1.2em !important; }
|
||||
}
|
||||
|
||||
:global(li.is-disabled) {
|
||||
.hasCustomStyling {
|
||||
color: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
import { render } from '@testing-library/vue';
|
||||
import N8nActionDropdown from '../ActionDropdown.vue';
|
||||
|
||||
describe('components', () => {
|
||||
describe('N8nActionDropdown', () => {
|
||||
it('should render default styling correctly', () => {
|
||||
const wrapper = render(N8nActionDropdown, {
|
||||
props: {
|
||||
items: [
|
||||
{
|
||||
id: 'item1',
|
||||
label: 'Action 1',
|
||||
},
|
||||
{
|
||||
id: 'item2',
|
||||
label: 'Action 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
stubs: ['n8n-icon', 'el-dropdown', 'el-dropdown-menu', 'el-dropdown-item'],
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
it('should render custom styling correctly', () => {
|
||||
const wrapper = render(N8nActionDropdown, {
|
||||
props: {
|
||||
items: [
|
||||
{
|
||||
id: 'item1',
|
||||
label: 'Action 1',
|
||||
icon: 'thumbs-up',
|
||||
},
|
||||
{
|
||||
id: 'item2',
|
||||
label: 'Action 2',
|
||||
icon: 'thumbs-down',
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
id: 'item3',
|
||||
label: 'Action 3',
|
||||
icon: 'heart',
|
||||
divided: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
stubs: ['n8n-icon', 'el-dropdown', 'el-dropdown-menu', 'el-dropdown-item'],
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`components > N8nActionDropdown > should render custom styling correctly 1`] = `
|
||||
"<div class=\\"action-dropdown-container\\">
|
||||
<el-dropdown-stub trigger=\\"click\\" size=\\"\\" hideonclick=\\"true\\" placement=\\"bottom\\" visiblearrow=\\"true\\" showtimeout=\\"250\\" hidetimeout=\\"150\\" tabindex=\\"0\\">
|
||||
<div class=\\"_activator_lf4ng_1\\">
|
||||
<n8n-icon-stub icon=\\"ellipsis-v\\" size=\\"medium\\"></n8n-icon-stub>
|
||||
</div>
|
||||
<el-dropdown-menu-stub transformorigin=\\"true\\" placement=\\"bottom\\" boundariespadding=\\"5\\" offset=\\"0\\" visiblearrow=\\"true\\" arrowoffset=\\"0\\" appendtobody=\\"true\\" popperoptions=\\"[object Object]\\">
|
||||
<el-dropdown-item-stub command=\\"item1\\">
|
||||
<div class=\\"_itemContainer_lf4ng_16\\"><span class=\\"_icon_lf4ng_20\\"><n8n-icon-stub icon=\\"thumbs-up\\" size=\\"medium\\"></n8n-icon-stub></span><span> Action 1 </span></div>
|
||||
</el-dropdown-item-stub>
|
||||
<el-dropdown-item-stub command=\\"item2\\" disabled=\\"true\\">
|
||||
<div class=\\"_itemContainer_lf4ng_16\\"><span class=\\"_icon_lf4ng_20\\"><n8n-icon-stub icon=\\"thumbs-down\\" size=\\"medium\\"></n8n-icon-stub></span><span> Action 2 </span></div>
|
||||
</el-dropdown-item-stub>
|
||||
<el-dropdown-item-stub command=\\"item3\\" divided=\\"true\\">
|
||||
<div class=\\"_itemContainer_lf4ng_16\\"><span class=\\"_icon_lf4ng_20\\"><n8n-icon-stub icon=\\"heart\\" size=\\"medium\\"></n8n-icon-stub></span><span> Action 3 </span></div>
|
||||
</el-dropdown-item-stub>
|
||||
</el-dropdown-menu-stub>
|
||||
</el-dropdown-stub>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`components > N8nActionDropdown > should render default styling correctly 1`] = `
|
||||
"<div class=\\"action-dropdown-container\\">
|
||||
<el-dropdown-stub trigger=\\"click\\" size=\\"\\" hideonclick=\\"true\\" placement=\\"bottom\\" visiblearrow=\\"true\\" showtimeout=\\"250\\" hidetimeout=\\"150\\" tabindex=\\"0\\">
|
||||
<div class=\\"_activator_lf4ng_1\\">
|
||||
<n8n-icon-stub icon=\\"ellipsis-v\\" size=\\"medium\\"></n8n-icon-stub>
|
||||
</div>
|
||||
<el-dropdown-menu-stub transformorigin=\\"true\\" placement=\\"bottom\\" boundariespadding=\\"5\\" offset=\\"0\\" visiblearrow=\\"true\\" arrowoffset=\\"0\\" appendtobody=\\"true\\" popperoptions=\\"[object Object]\\">
|
||||
<el-dropdown-item-stub command=\\"item1\\">
|
||||
<div class=\\"_itemContainer_lf4ng_16\\">
|
||||
<!----><span> Action 1 </span></div>
|
||||
</el-dropdown-item-stub>
|
||||
<el-dropdown-item-stub command=\\"item2\\">
|
||||
<div class=\\"_itemContainer_lf4ng_16\\">
|
||||
<!----><span> Action 2 </span></div>
|
||||
</el-dropdown-item-stub>
|
||||
</el-dropdown-menu-stub>
|
||||
</el-dropdown-stub>
|
||||
</div>"
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
import N8nActionDropdown from './ActionDropdown.vue';
|
||||
export default N8nActionDropdown;
|
||||
@@ -15,7 +15,7 @@ html {
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overscroll-behavior-x: none;
|
||||
overscroll-behavior: none;
|
||||
line-height: 1;
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-regular);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
import N8nActionBox from '../components/N8nActionBox';
|
||||
import N8nActionDropdown from '../components/N8nActionDropdown';
|
||||
import N8nActionToggle from '../components/N8nActionToggle';
|
||||
import N8nAvatar from '../components/N8nAvatar';
|
||||
import N8nBadge from "../components/N8nBadge";
|
||||
@@ -46,6 +47,7 @@ export default {
|
||||
install: (app: typeof Vue, options?: {}) => {
|
||||
app.component('n8n-info-accordion', N8nInfoAccordion);
|
||||
app.component('n8n-action-box', N8nActionBox);
|
||||
app.component('n8n-action-dropdown', N8nActionDropdown);
|
||||
app.component('n8n-action-toggle', N8nActionToggle);
|
||||
app.component('n8n-avatar', N8nAvatar);
|
||||
app.component('n8n-badge', N8nBadge);
|
||||
|
||||
Reference in New Issue
Block a user