feat: Add assignment component with drag and drop to Set node (#8283)

Co-authored-by: Giulio Andreini <andreini@netseven.it>
This commit is contained in:
Elias Meire
2024-02-06 18:34:34 +01:00
committed by GitHub
parent c04f92f7fd
commit 2799de491b
53 changed files with 3296 additions and 1060 deletions

View File

@@ -0,0 +1,163 @@
<script setup lang="ts">
type Props = {
middleWidth?: string;
};
withDefaults(defineProps<Props>(), { middleWidth: '160px' });
</script>
<template>
<n8n-resize-observer
:class="{ [$style.observer]: true }"
:breakpoints="[
{ bp: 'stacked', width: 400 },
{ bp: 'medium', width: 680 },
]"
>
<template #default="{ bp }">
<div :class="$style.background"></div>
<div
:class="{
[$style.triple]: true,
[$style.stacked]: bp === 'stacked',
[$style.medium]: bp === 'medium',
[$style.default]: bp === 'default',
[$style.noRightSlot]: !$slots.right,
[$style.noMiddleSlot]: !$slots.middle,
}"
>
<div v-if="$slots.left" :class="$style.item">
<slot name="left" :breakpoint="bp"></slot>
</div>
<div
v-if="$slots.middle"
:class="[$style.item, $style.middle]"
:style="{ flexBasis: middleWidth }"
>
<slot name="middle" :breakpoint="bp"></slot>
</div>
<div v-if="$slots.right" :class="$style.item">
<slot name="right" :breakpoint="bp"></slot>
</div>
</div>
</template>
</n8n-resize-observer>
</template>
<style lang="scss" module>
.triple {
display: flex;
flex-wrap: nowrap;
align-items: flex-start;
}
.observer {
--parameter-input-options-height: 22px;
width: 100%;
position: relative;
}
.background {
position: absolute;
background-color: var(--color-background-input-triple);
top: var(--parameter-input-options-height);
bottom: 0;
left: 0;
right: 0;
border: 1px solid var(--border-color-base);
border-radius: var(--border-radius-base);
}
.item {
flex-shrink: 0;
flex-basis: 240px;
flex-grow: 1;
--input-border-radius: 0;
}
.default .item:not(:first-child):not(:focus-within + .item) {
margin-left: -1px;
}
.middle {
flex-grow: 0;
flex-basis: 160px;
padding-top: var(--parameter-input-options-height);
}
.item:first-of-type {
--input-border-top-left-radius: var(--border-radius-base);
--input-border-bottom-left-radius: var(--border-radius-base);
--input-border-top-right-radius: 0;
--input-border-bottom-right-radius: 0;
}
.item:last-of-type {
--input-border-top-left-radius: 0;
--input-border-bottom-left-radius: 0;
--input-border-top-right-radius: var(--border-radius-base);
--input-border-bottom-right-radius: var(--border-radius-base);
}
.medium:not(.noRightSlot) {
flex-wrap: wrap;
.middle {
--input-border-top-right-radius: var(--border-radius-base);
--input-border-bottom-right-radius: 0;
&:not(:focus-within + .item) {
margin-left: -1px;
}
}
.item:first-of-type {
--input-border-top-left-radius: var(--border-radius-base);
--input-border-top-right-radius: 0;
--input-border-bottom-left-radius: 0;
}
.item:last-of-type {
flex-basis: 400px;
--input-border-top-left-radius: 0;
--input-border-top-right-radius: 0;
--input-border-bottom-left-radius: var(--border-radius-base);
--input-border-bottom-right-radius: var(--border-radius-base);
&:not(:focus-within ~ .item) {
margin-top: -1px;
}
}
}
.stacked {
display: block;
.middle {
padding-top: 0;
}
.middle:not(.item:last-of-type) {
width: 100%;
--input-border-radius: 0;
}
.item:first-of-type {
--input-border-top-left-radius: var(--border-radius-base);
--input-border-top-right-radius: var(--border-radius-base);
--input-border-bottom-left-radius: 0;
--input-border-bottom-right-radius: 0;
}
.item:not(:first-of-type):not(:focus-within + .item) {
margin-top: -1px;
}
.item:last-of-type {
--input-border-top-left-radius: 0;
--input-border-top-right-radius: 0;
--input-border-bottom-left-radius: var(--border-radius-base);
--input-border-bottom-right-radius: var(--border-radius-base);
}
}
</style>

View File

@@ -0,0 +1,38 @@
import { createComponentRenderer } from '@/__tests__/render';
import InputTriple from '../InputTriple.vue';
const renderComponent = createComponentRenderer(InputTriple);
describe('InputTriple.vue', () => {
afterEach(() => {
vi.clearAllMocks();
});
it('renders layout correctly', async () => {
const { container } = renderComponent({
props: { middleWidth: '200px' },
slots: {
left: '<div>left</div>',
middle: '<div>middle</div>',
right: '<div>right</div>',
},
});
expect(container.querySelector('.triple')).toBeInTheDocument();
expect(container.querySelectorAll('.item')).toHaveLength(3);
expect(container.querySelector('.middle')).toHaveStyle('flex-basis: 200px');
});
it('does not render missing slots', async () => {
const { container } = renderComponent({
props: { middleWidth: '200px' },
slots: {
left: '<div>left</div>',
middle: '<div>middle</div>',
},
});
expect(container.querySelector('.triple')).toBeInTheDocument();
expect(container.querySelectorAll('.item')).toHaveLength(2);
});
});