mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
ci: Ensure that eslint runs on all frontend code (no-changelog) (#4602)
* ensure that eslint runs on all frontend code * remove tslint from `design-system` * enable prettier and eslint-prettier for `design-system` * Delete tslint.json * use a single editorconfig for the repo * enable prettier for all code in `design-system` * more linting fixes on design-system * ignore coverage for git and prettier * lintfix on editor-ui
This commit is contained in:
parent
d96d6f11db
commit
13659d036f
|
@ -15,3 +15,6 @@ indent_size = 2
|
|||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
dist
|
||||
packages/editor-ui
|
||||
packages/design-system
|
||||
package*.json
|
||||
package.json
|
||||
|
||||
!packages/nodes-base/src
|
||||
!packages/nodes-base/test
|
||||
|
|
|
@ -9,11 +9,9 @@ const config = (module.exports = {
|
|||
},
|
||||
|
||||
ignorePatterns: [
|
||||
'.eslintrc.js', // TODO: remove this
|
||||
'node_modules/**',
|
||||
'dist/**',
|
||||
'test/**', // TODO: remove this
|
||||
'jest.config.js', // TODO: remove this
|
||||
],
|
||||
|
||||
plugins: [
|
||||
|
|
|
@ -14,10 +14,15 @@ module.exports = {
|
|||
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parser: {
|
||||
ts: '@typescript-eslint/parser',
|
||||
js: '@typescript-eslint/parser',
|
||||
vue: 'vue-eslint-parser',
|
||||
template: 'vue-eslint-parser',
|
||||
},
|
||||
},
|
||||
|
||||
ignorePatterns: ['**/*.js', '**/*.d.ts', 'vite.config.ts'],
|
||||
ignorePatterns: ['**/*.js', '**/*.d.ts', 'vite.config.ts', '**/*.ts.snap'],
|
||||
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[package.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
|
@ -6,23 +6,50 @@ module.exports = {
|
|||
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
extraFileExtensions: ['.vue'],
|
||||
},
|
||||
|
||||
rules: {
|
||||
// TODO: Remove these
|
||||
'import/no-default-export': 'off',
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'import/order': 'off',
|
||||
'prettier/prettier': 'off',
|
||||
'@typescript-eslint/member-delimiter-style': 'off',
|
||||
'@typescript-eslint/naming-convention': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unsafe-argument': 'off',
|
||||
'@typescript-eslint/no-unsafe-return': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
||||
'@typescript-eslint/prefer-optional-chain': 'off',
|
||||
'@typescript-eslint/restrict-template-expressions': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }],
|
||||
}
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||
'@typescript-eslint/no-unsafe-return': 'warn',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'warn',
|
||||
},
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: ['src/**/*.stories.{js,ts}'],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/**/*.stories.{js,ts}', 'src/**/*.vue', 'src/**/*.spec.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/naming-convention': [
|
||||
'warn',
|
||||
{
|
||||
selector: ['variable', 'property'],
|
||||
format: ['PascalCase', 'camelCase', 'UPPER_CASE'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/components/N8nFormInput/validators.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/naming-convention': [
|
||||
'error',
|
||||
{
|
||||
selector: ['property'],
|
||||
format: ['camelCase', 'UPPER_CASE'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
1
packages/design-system/.gitignore
vendored
1
packages/design-system/.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
coverage
|
||||
storybook-static
|
||||
|
|
3
packages/design-system/.prettierignore
Normal file
3
packages/design-system/.prettierignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
coverage
|
||||
dist
|
||||
package.json
|
|
@ -14,7 +14,7 @@ module.exports = {
|
|||
postcssLoaderOptions: {
|
||||
implementation: require('postcss'),
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
'storybook-addon-designs',
|
||||
'storybook-addon-themes',
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
@use "./fonts.scss";
|
||||
@use './fonts.scss';
|
||||
|
||||
@use "~/src/css/base.scss" with (
|
||||
$font-path: '~element-ui/lib/theme-chalk/fonts',
|
||||
@use '~/src/css/base.scss' with (
|
||||
$font-path: '~element-ui/lib/theme-chalk/fonts'
|
||||
);
|
||||
|
||||
@use "~/src/css/reset.scss";
|
||||
@use "~/src/css/index.scss";
|
||||
@use '~/src/css/reset.scss';
|
||||
@use '~/src/css/index.scss';
|
||||
|
||||
.multi-container > * {
|
||||
margin-bottom: 10px;
|
||||
|
|
|
@ -21,15 +21,22 @@
|
|||
"test:dev": "vitest",
|
||||
"build:storybook": "build-storybook",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"format": "prettier **/**.{ts,vue} --write",
|
||||
"lint": "tslint -p tsconfig.json -c tslint.json && eslint .",
|
||||
"lintfix": "tslint --fix -p tsconfig.json -c tslint.json && eslint . --fix"
|
||||
"format": "prettier **/**.{js,ts,vue,css,scss,mdx,html} --write .",
|
||||
"lint": "eslint --ext .js,.ts,.vue src",
|
||||
"lintfix": "eslint --ext .js,.ts,.vue src --fix"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "1.x",
|
||||
"@fortawesome/free-solid-svg-icons": "5.x",
|
||||
"@fortawesome/vue-fontawesome": "2.x",
|
||||
"core-js": "3.x"
|
||||
"core-js": "3.x",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-emoji": "^2.0.0",
|
||||
"markdown-it-link-attributes": "^4.0.0",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"vue": "^2.7",
|
||||
"vue-typed-mixins": "^0.2.0",
|
||||
"xss": "^1.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||
|
@ -43,14 +50,12 @@
|
|||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/vue": "^5.8.3",
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
"@types/markdown-it-emoji": "^2.0.2",
|
||||
"@types/markdown-it-link-attributes": "^3.0.1",
|
||||
"@types/sanitize-html": "^2.6.2",
|
||||
"c8": "7.11.0",
|
||||
"core-js": "^3.6.5",
|
||||
"jsdom": "19.0.0",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-emoji": "^2.0.0",
|
||||
"markdown-it-link-attributes": "^4.0.0",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"node-notifier": ">=8.0.1",
|
||||
"sass": "^1.55.0",
|
||||
"sass-loader": "^10.1.1",
|
||||
|
@ -60,16 +65,13 @@
|
|||
"vite": "^2.9.5",
|
||||
"@vitejs/plugin-vue2": "^1.1.2",
|
||||
"vitest": "^0.9.3",
|
||||
"vue": "^2.7",
|
||||
"vue-class-component": "^7.2.3",
|
||||
"vue-loader": "^15.9.7",
|
||||
"vue-property-decorator": "^9.1.2",
|
||||
"vue-template-compiler": "^2.7",
|
||||
"vue-tsc": "^0.34.8",
|
||||
"vue-typed-mixins": "^0.2.0",
|
||||
"vue2-boring-avatars": "0.3.4",
|
||||
"webpack": "^4.46.0",
|
||||
"xss": "^1.0.10"
|
||||
"webpack": "^4.46.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"element-ui": "~2.15.7",
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
/* tslint:disable:variable-name */
|
||||
|
||||
import N8nActionBox from './ActionBox.vue';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import {StoryFn} from "@storybook/vue";
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/ActionBox',
|
||||
|
@ -35,8 +33,9 @@ const Template: StoryFn = (args, { argTypes }) => ({
|
|||
|
||||
export const ActionBox = Template.bind({});
|
||||
ActionBox.args = {
|
||||
emoji: "😿",
|
||||
heading: "Headline you need to know",
|
||||
description: "Long description that you should know something is the way it is because of how it is. ",
|
||||
buttonText: "Do something",
|
||||
emoji: '😿',
|
||||
heading: 'Headline you need to know',
|
||||
description:
|
||||
'Long description that you should know something is the way it is because of how it is. ',
|
||||
buttonText: 'Do something',
|
||||
};
|
||||
|
|
|
@ -118,5 +118,4 @@ export default Vue.extend({
|
|||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
import {render} from '@testing-library/vue';
|
||||
import { render } from '@testing-library/vue';
|
||||
import N8NActionBox from '../ActionBox.vue';
|
||||
|
||||
describe('N8NActionBox', () => {
|
||||
it('should render correctly', () => {
|
||||
const wrapper = render(N8NActionBox, {
|
||||
props: {
|
||||
emoji: "😿",
|
||||
heading: "Headline you need to know",
|
||||
description: "Long description that you should know something is the way it is because of how it is. ",
|
||||
buttonText: "Do something",
|
||||
emoji: '😿',
|
||||
heading: 'Headline you need to know',
|
||||
description:
|
||||
'Long description that you should know something is the way it is because of how it is. ',
|
||||
buttonText: 'Do something',
|
||||
},
|
||||
stubs: [
|
||||
'n8n-heading',
|
||||
'n8n-text',
|
||||
'n8n-button',
|
||||
'n8n-callout',
|
||||
],
|
||||
stubs: ['n8n-heading', 'n8n-text', 'n8n-button', 'n8n-callout'],
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import N8nActionDropdown from "./ActionDropdown.vue";
|
||||
import { StoryFn } from '@storybook/vue';
|
||||
import N8nActionDropdown from './ActionDropdown.vue';
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/ActionDropdown',
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
<template>
|
||||
<div :class="['action-dropdown-container', $style.actionDropdownContainer]">
|
||||
<el-dropdown :placement="placement" :trigger="trigger" @command="onSelect" ref="elementDropdown">
|
||||
<el-dropdown
|
||||
:placement="placement"
|
||||
:trigger="trigger"
|
||||
@command="onSelect"
|
||||
ref="elementDropdown"
|
||||
>
|
||||
<div :class="$style.activator" @click.prevent @blur="onButtonBlur">
|
||||
<n8n-icon :icon="activatorIcon"/>
|
||||
<n8n-icon :icon="activatorIcon" />
|
||||
</div>
|
||||
<el-dropdown-menu slot="dropdown" :class="$style.userActionsMenu">
|
||||
<el-dropdown-item
|
||||
|
@ -12,13 +17,15 @@
|
|||
:disabled="item.disabled"
|
||||
:divided="item.divided"
|
||||
>
|
||||
<div :class="{
|
||||
[$style.itemContainer]: true,
|
||||
[$style.hasCustomStyling]: item.customClass !== undefined,
|
||||
[item.customClass]: item.customClass !== undefined,
|
||||
}">
|
||||
<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" :size="item.iconSize"/>
|
||||
<n8n-icon :icon="item.icon" :size="item.iconSize" />
|
||||
</span>
|
||||
<span :class="$style.label">
|
||||
{{ item.label }}
|
||||
|
@ -31,7 +38,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, { PropType } from "vue";
|
||||
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';
|
||||
|
@ -56,8 +63,8 @@ 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
|
||||
ElDropdown, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||
ElDropdownItem, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||
N8nIcon,
|
||||
},
|
||||
props: {
|
||||
|
@ -78,22 +85,22 @@ export default Vue.extend({
|
|||
iconSize: {
|
||||
type: String,
|
||||
default: 'medium',
|
||||
validator: (value: string): boolean =>
|
||||
['small', 'medium', 'large'].includes(value),
|
||||
validator: (value: string): boolean => ['small', 'medium', 'large'].includes(value),
|
||||
},
|
||||
trigger: {
|
||||
type: String,
|
||||
default: 'click',
|
||||
validator: (value: string): boolean =>
|
||||
['click', 'hover'].includes(value),
|
||||
validator: (value: string): boolean => ['click', 'hover'].includes(value),
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onSelect(action: string) : void {
|
||||
onSelect(action: string): void {
|
||||
this.$emit('select', action);
|
||||
},
|
||||
onButtonBlur(event: FocusEvent): void {
|
||||
const elementDropdown = this.$refs.elementDropdown as Vue & { hide: () => void } | undefined;
|
||||
const elementDropdown = this.$refs.elementDropdown as
|
||||
| (Vue & { hide: () => void })
|
||||
| undefined;
|
||||
// Hide dropdown when clicking outside of current document
|
||||
if (elementDropdown && event.relatedTarget === null) {
|
||||
elementDropdown.hide();
|
||||
|
@ -101,11 +108,9 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
||||
.activator {
|
||||
cursor: pointer;
|
||||
padding: var(--spacing-2xs);
|
||||
|
@ -131,7 +136,9 @@ export default Vue.extend({
|
|||
text-align: center;
|
||||
margin-right: var(--spacing-2xs);
|
||||
|
||||
svg { width: 1.2em !important; }
|
||||
svg {
|
||||
width: 1.2em !important;
|
||||
}
|
||||
}
|
||||
|
||||
:global(li.is-disabled) {
|
||||
|
@ -139,5 +146,4 @@ export default Vue.extend({
|
|||
color: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -28,7 +28,8 @@ const Template = (args, { argTypes }) => ({
|
|||
components: {
|
||||
N8nActionToggle,
|
||||
},
|
||||
template: '<div style="height:300px;width:300px;display:flex;align-items:center;justify-content:center"><n8n-action-toggle v-bind="$props" @action="onAction" /></div>',
|
||||
template:
|
||||
'<div style="height:300px;width:300px;display:flex;align-items:center;justify-content:center"><n8n-action-toggle v-bind="$props" @action="onAction" /></div>',
|
||||
methods,
|
||||
});
|
||||
|
||||
|
|
|
@ -8,11 +8,8 @@
|
|||
@command="onCommand"
|
||||
@visible-change="onVisibleChange"
|
||||
>
|
||||
<span :class="{[$style.button]: true, [$style[theme]]: !!theme}">
|
||||
<component :is="$options.components.N8nIcon"
|
||||
icon="ellipsis-v"
|
||||
:size="iconSize"
|
||||
/>
|
||||
<span :class="{ [$style.button]: true, [$style[theme]]: !!theme }">
|
||||
<component :is="$options.components.N8nIcon" icon="ellipsis-v" :size="iconSize" />
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown" data-test-id="action-toggle-dropdown">
|
||||
<el-dropdown-item
|
||||
|
@ -21,7 +18,7 @@
|
|||
:command="action.value"
|
||||
:disabled="action.disabled"
|
||||
>
|
||||
{{action.label}}
|
||||
{{ action.label }}
|
||||
<div :class="$style.iconContainer">
|
||||
<component
|
||||
v-if="action.type === 'external-link'"
|
||||
|
@ -66,8 +63,7 @@ export default Vue.extend({
|
|||
size: {
|
||||
type: String,
|
||||
default: 'medium',
|
||||
validator: (value: string): boolean =>
|
||||
['mini', 'small', 'medium'].includes(value),
|
||||
validator: (value: string): boolean => ['mini', 'small', 'medium'].includes(value),
|
||||
},
|
||||
iconSize: {
|
||||
type: String,
|
||||
|
@ -75,8 +71,7 @@ export default Vue.extend({
|
|||
theme: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
validator: (value: string): boolean =>
|
||||
['default', 'dark'].includes(value),
|
||||
validator: (value: string): boolean => ['default', 'dark'].includes(value),
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<span :class="['n8n-avatar', $style.container]" v-on="$listeners">
|
||||
<span :class="['n8n-avatar', $style.container]" v-on="$listeners">
|
||||
<avatar
|
||||
v-if="firstName"
|
||||
:size="getSize(size)"
|
||||
|
@ -7,19 +7,15 @@
|
|||
variant="marble"
|
||||
:colors="getColors(colors)"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
:class="[$style.empty, $style[size]]"
|
||||
>
|
||||
</div>
|
||||
<span v-if="firstName" :class="$style.initials">{{initials}}</span>
|
||||
<div v-else :class="[$style.empty, $style[size]]"></div>
|
||||
<span v-if="firstName" :class="$style.initials">{{ initials }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Avatar from 'vue2-boring-avatars';
|
||||
|
||||
const sizes: {[size: string]: number} = {
|
||||
const sizes: { [size: string]: number } = {
|
||||
small: 28,
|
||||
large: 48,
|
||||
medium: 40,
|
||||
|
@ -41,7 +37,13 @@ export default Vue.extend({
|
|||
default: 'medium',
|
||||
},
|
||||
colors: {
|
||||
default: () => (['--color-primary', '--color-secondary', '--color-avatar-accent-1', '--color-avatar-accent-2', '--color-primary-tint-1']),
|
||||
default: () => [
|
||||
'--color-primary',
|
||||
'--color-secondary',
|
||||
'--color-avatar-accent-1',
|
||||
'--color-avatar-accent-2',
|
||||
'--color-primary-tint-1',
|
||||
],
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
@ -49,7 +51,10 @@ export default Vue.extend({
|
|||
},
|
||||
computed: {
|
||||
initials() {
|
||||
return (this.firstName ? this.firstName.charAt(0): '') + (this.lastName? this.lastName.charAt(0): '');
|
||||
return (
|
||||
(this.firstName ? this.firstName.charAt(0) : '') +
|
||||
(this.lastName ? this.lastName.charAt(0) : '')
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -75,7 +80,7 @@ export default Vue.extend({
|
|||
.empty {
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-foreground-dark);
|
||||
opacity: .3;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.initials {
|
||||
|
|
|
@ -20,8 +20,7 @@ const Template = (args, { argTypes }) => ({
|
|||
components: {
|
||||
N8nBadge,
|
||||
},
|
||||
template:
|
||||
'<n8n-badge v-bind="$props">Badge</n8n-badge>',
|
||||
template: '<n8n-badge v-bind="$props">Badge</n8n-badge>',
|
||||
});
|
||||
|
||||
export const Badge = Template.bind({});
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<template>
|
||||
<span
|
||||
:class="['n8n-badge', $style[theme]]"
|
||||
>
|
||||
<span :class="['n8n-badge', $style[theme]]">
|
||||
<n8n-text :size="size" :bold="bold" :compact="true">
|
||||
<slot></slot>
|
||||
</n8n-text>
|
||||
|
|
|
@ -16,5 +16,5 @@ const Template = (args, { argTypes }) => ({
|
|||
|
||||
export const BlockUi = Template.bind({});
|
||||
BlockUi.args = {
|
||||
show: false
|
||||
show: false,
|
||||
};
|
||||
|
|
|
@ -1,40 +1,45 @@
|
|||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div v-show="show" :class="['n8n-block-ui', $style.uiBlocker]" role="dialog" :aria-hidden="true" />
|
||||
<div
|
||||
v-show="show"
|
||||
:class="['n8n-block-ui', $style.uiBlocker]"
|
||||
role="dialog"
|
||||
:aria-hidden="true"
|
||||
/>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
type BlockUiProps = {
|
||||
show: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<BlockUiProps>(), {
|
||||
withDefaults(defineProps<BlockUiProps>(), {
|
||||
show: false,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.uiBlocker {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--color-background-dark);
|
||||
z-index: 10;
|
||||
opacity: 0.6;
|
||||
border-radius: var(--border-radius-large);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--color-background-dark);
|
||||
z-index: 10;
|
||||
opacity: 0.6;
|
||||
border-radius: var(--border-radius-large);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 200ms;
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* tslint:disable:variable-name */
|
||||
import N8nButton from './Button.vue';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { StoryFn } from "@storybook/vue";
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Button',
|
||||
|
@ -63,22 +62,6 @@ const AllSizesTemplate: StoryFn = (args, { argTypes }) => ({
|
|||
methods,
|
||||
});
|
||||
|
||||
const AllColorsTemplate: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nButton,
|
||||
},
|
||||
template: `<div>
|
||||
<n8n-button v-bind="$props" type="primary" @click="onClick" />
|
||||
<n8n-button v-bind="$props" type="secondary" @click="onClick" />
|
||||
<n8n-button v-bind="$props" type="tertiary" @click="onClick" />
|
||||
<n8n-button v-bind="$props" type="success" @click="onClick" />
|
||||
<n8n-button v-bind="$props" type="warning" @click="onClick" />
|
||||
<n8n-button v-bind="$props" type="danger" @click="onClick" />
|
||||
</div>`,
|
||||
methods,
|
||||
});
|
||||
|
||||
const AllColorsAndSizesTemplate: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
|
@ -170,4 +153,3 @@ Square.args = {
|
|||
label: '48',
|
||||
square: true,
|
||||
};
|
||||
|
||||
|
|
|
@ -8,15 +8,8 @@
|
|||
v-on="$listeners"
|
||||
>
|
||||
<span :class="$style.icon" v-if="loading || icon">
|
||||
<n8n-spinner
|
||||
v-if="loading"
|
||||
:size="size"
|
||||
/>
|
||||
<n8n-icon
|
||||
v-else-if="icon"
|
||||
:icon="icon"
|
||||
:size="size"
|
||||
/>
|
||||
<n8n-spinner v-if="loading" :size="size" />
|
||||
<n8n-icon v-else-if="icon" :icon="icon" :size="size" />
|
||||
</span>
|
||||
<span v-if="label || $slots.default">
|
||||
<slot>{{ label }}</slot>
|
||||
|
@ -76,13 +69,12 @@ export default Vue.extend({
|
|||
},
|
||||
float: {
|
||||
type: String,
|
||||
validator: (value: string): boolean =>
|
||||
['left', 'right'].includes(value),
|
||||
validator: (value: string): boolean => ['left', 'right'].includes(value),
|
||||
},
|
||||
square: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
square: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
N8nSpinner,
|
||||
|
@ -96,7 +88,8 @@ export default Vue.extend({
|
|||
return this.disabled ? 'true' : 'false';
|
||||
},
|
||||
classes(): string {
|
||||
return `button ${this.$style.button} ${this.$style[this.type]}` +
|
||||
return (
|
||||
`button ${this.$style.button} ${this.$style[this.type]}` +
|
||||
`${this.size ? ` ${this.$style[this.size]}` : ''}` +
|
||||
`${this.outline ? ` ${this.$style.outline}` : ''}` +
|
||||
`${this.loading ? ` ${this.$style.loading}` : ''}` +
|
||||
|
@ -106,7 +99,8 @@ export default Vue.extend({
|
|||
`${this.block ? ` ${this.$style.block}` : ''}` +
|
||||
`${this.active ? ` ${this.$style.active}` : ''}` +
|
||||
`${this.icon || this.loading ? ` ${this.$style.icon}` : ''}` +
|
||||
`${this.square ? ` ${this.$style.square}` : ''}`;
|
||||
`${this.square ? ` ${this.$style.square}` : ''}`
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -150,7 +144,8 @@ export default Vue.extend({
|
|||
outline: $focus-outline-width solid $button-focus-outline-color;
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
&:active,
|
||||
&.active {
|
||||
color: $button-active-color;
|
||||
border-color: $button-active-border-color;
|
||||
background-color: $button-active-background-color;
|
||||
|
@ -213,7 +208,12 @@ $loading-overlay-background-color: rgba(255, 255, 255, 0);
|
|||
--button-hover-color: var(--color-text-dark);
|
||||
--button-hover-border-color: var(--color-neutral-800);
|
||||
|
||||
--button-focus-outline-color: hsla(var(--color-neutral-h), var(--color-neutral-s), var(--color-neutral-l), 0.2);
|
||||
--button-focus-outline-color: hsla(
|
||||
var(--color-neutral-h),
|
||||
var(--color-neutral-s),
|
||||
var(--color-neutral-l),
|
||||
0.2
|
||||
);
|
||||
}
|
||||
|
||||
.success {
|
||||
|
@ -227,7 +227,12 @@ $loading-overlay-background-color: rgba(255, 255, 255, 0);
|
|||
--button-hover-background-color: var(--color-success-450);
|
||||
--button-hover-border-color: var(--color-success-450);
|
||||
|
||||
--button-focus-outline-color: hsla(var(--color-success-h), var(--color-success-s), var(--color-success-l), 0.33);
|
||||
--button-focus-outline-color: hsla(
|
||||
var(--color-success-h),
|
||||
var(--color-success-s),
|
||||
var(--color-success-l),
|
||||
0.33
|
||||
);
|
||||
}
|
||||
|
||||
.warning {
|
||||
|
@ -241,7 +246,12 @@ $loading-overlay-background-color: rgba(255, 255, 255, 0);
|
|||
--button-hover-background-color: var(--color-warning-650);
|
||||
--button-hover-border-color: var(--color-warning-650);
|
||||
|
||||
--button-focus-outline-color: hsla(var(--color-warning-h), var(--color-warning-s), var(--color-warning-l), 0.33);
|
||||
--button-focus-outline-color: hsla(
|
||||
var(--color-warning-h),
|
||||
var(--color-warning-s),
|
||||
var(--color-warning-l),
|
||||
0.33
|
||||
);
|
||||
}
|
||||
|
||||
.danger {
|
||||
|
@ -256,7 +266,12 @@ $loading-overlay-background-color: rgba(255, 255, 255, 0);
|
|||
--button-hover-background-color: var(--color-danger-700);
|
||||
--button-hover-border-color: var(--color-danger-700);
|
||||
|
||||
--button-focus-outline-color: hsla(var(--color-danger-h), var(--color-danger-s), var(--color-danger-l), 0.33);
|
||||
--button-focus-outline-color: hsla(
|
||||
var(--color-danger-h),
|
||||
var(--color-danger-s),
|
||||
var(--color-danger-l),
|
||||
0.33
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -440,7 +455,7 @@ $loading-overlay-background-color: rgba(255, 255, 255, 0);
|
|||
|
||||
.icon {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {render} from '@testing-library/vue';
|
||||
import N8nButton from "../Button.vue";
|
||||
import ElButton from "../overrides/ElButton.vue";
|
||||
import { render } from '@testing-library/vue';
|
||||
import N8nButton from '../Button.vue';
|
||||
import ElButton from '../overrides/ElButton.vue';
|
||||
|
||||
const slots = {
|
||||
default: 'Button',
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`components > N8nButton > overrides > should render correctly 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_1qq65_115 _secondary_1qq65_170 _medium_1qq65_254 _icon_1qq65_384\\" icon=\\"plus-circle\\" type=\\"secondary\\"><span class=\\"_icon_1qq65_384\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
|
||||
exports[`components > N8nButton > overrides > should render correctly 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_10jsj_115 _secondary_10jsj_170 _medium_10jsj_274 _icon_10jsj_404\\" icon=\\"plus-circle\\" type=\\"secondary\\"><span class=\\"_icon_10jsj_404\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
|
||||
|
||||
exports[`components > N8nButton > props > icon > should render icon button 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_1qq65_115 _primary_1qq65_288 _medium_1qq65_254 _icon_1qq65_384\\"><span class=\\"_icon_1qq65_384\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
|
||||
exports[`components > N8nButton > props > icon > should render icon button 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_10jsj_115 _primary_10jsj_308 _medium_10jsj_274 _icon_10jsj_404\\"><span class=\\"_icon_10jsj_404\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
|
||||
|
||||
exports[`components > N8nButton > props > loading > should render loading spinner 1`] = `"<button disabled=\\"disabled\\" aria-disabled=\\"false\\" aria-busy=\\"true\\" aria-live=\\"polite\\" class=\\"button _button_1qq65_115 _primary_1qq65_288 _medium_1qq65_254 _loading_1qq65_355 _icon_1qq65_384\\"><span class=\\"_icon_1qq65_384\\"><n8n-spinner-stub size=\\"medium\\" type=\\"dots\\"></n8n-spinner-stub></span><span>Button</span></button>"`;
|
||||
exports[`components > N8nButton > props > loading > should render loading spinner 1`] = `"<button disabled=\\"disabled\\" aria-disabled=\\"false\\" aria-busy=\\"true\\" aria-live=\\"polite\\" class=\\"button _button_10jsj_115 _primary_10jsj_308 _medium_10jsj_274 _loading_10jsj_375 _icon_10jsj_404\\"><span class=\\"_icon_10jsj_404\\"><n8n-spinner-stub size=\\"medium\\" type=\\"dots\\"></n8n-spinner-stub></span><span>Button</span></button>"`;
|
||||
|
||||
exports[`components > N8nButton > props > square > should render square button 1`] = `
|
||||
"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_1qq65_115 _primary_1qq65_288 _medium_1qq65_254 _square_1qq65_239\\">
|
||||
"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_10jsj_115 _primary_10jsj_308 _medium_10jsj_274 _square_10jsj_259\\">
|
||||
<!----><span>48</span></button>"
|
||||
`;
|
||||
|
||||
exports[`components > N8nButton > should render correctly 1`] = `
|
||||
"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_1qq65_115 _primary_1qq65_288 _medium_1qq65_254\\">
|
||||
"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button _button_10jsj_115 _primary_10jsj_308 _medium_10jsj_274\\">
|
||||
<!----><span>Button</span></button>"
|
||||
`;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import ElButton from "./ElButton.vue";
|
||||
import ElButton from './ElButton.vue';
|
||||
|
||||
export default ElButton;
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
<template>
|
||||
<n8n-button
|
||||
ref="button"
|
||||
v-bind="attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<slot/>
|
||||
<n8n-button ref="button" v-bind="attrs" v-on="$listeners">
|
||||
<slot />
|
||||
</n8n-button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import N8nCallout from './Callout.vue';
|
||||
import N8nLink from '../N8nLink';
|
||||
import N8nText from '../N8nText';
|
||||
import { StoryFn } from '@storybook/vue';
|
||||
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Callout',
|
||||
|
@ -33,7 +32,15 @@ export default {
|
|||
},
|
||||
};
|
||||
|
||||
const template : StoryFn = (args, { argTypes }) => ({
|
||||
interface Args {
|
||||
theme: string;
|
||||
icon: string;
|
||||
default: string;
|
||||
actions: string;
|
||||
trailingContent: string;
|
||||
}
|
||||
|
||||
const template: StoryFn<Args> = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nLink,
|
||||
|
@ -79,7 +86,6 @@ customCallout.args = {
|
|||
`,
|
||||
};
|
||||
|
||||
|
||||
export const secondaryCallout = template.bind({});
|
||||
secondaryCallout.args = {
|
||||
theme: 'secondary',
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
<template>
|
||||
<div :class="classes" role="alert">
|
||||
|
||||
<div :class="$style['message-section']">
|
||||
<div :class="$style.icon">
|
||||
<n8n-icon
|
||||
:icon="getIcon"
|
||||
:size="theme === 'secondary' ? 'medium' : 'large'"
|
||||
/>
|
||||
<n8n-icon :icon="getIcon" :size="theme === 'secondary' ? 'medium' : 'large'" />
|
||||
</div>
|
||||
<slot />
|
||||
<slot name="actions" />
|
||||
|
@ -46,11 +42,7 @@ export default Vue.extend({
|
|||
},
|
||||
computed: {
|
||||
classes(): string[] {
|
||||
return [
|
||||
'n8n-callout',
|
||||
this.$style.callout,
|
||||
this.$style[this.theme],
|
||||
];
|
||||
return ['n8n-callout', this.$style.callout, this.$style[this.theme]];
|
||||
},
|
||||
getIcon(): string {
|
||||
if (Object.keys(CALLOUT_DEFAULT_ICONS).includes(this.theme)) {
|
||||
|
@ -79,7 +71,8 @@ export default Vue.extend({
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.info, .custom {
|
||||
.info,
|
||||
.custom {
|
||||
border-color: var(--color-foreground-base);
|
||||
background-color: var(--color-background-light);
|
||||
color: var(--color-info);
|
||||
|
|
|
@ -8,7 +8,7 @@ describe('components', () => {
|
|||
props: {
|
||||
theme: 'info',
|
||||
},
|
||||
stubs: [ 'n8n-icon', 'n8n-text' ],
|
||||
stubs: ['n8n-icon', 'n8n-text'],
|
||||
slots: {
|
||||
default: '<n8n-text size="small">This is an info callout.</n8n-text>',
|
||||
},
|
||||
|
@ -20,7 +20,7 @@ describe('components', () => {
|
|||
props: {
|
||||
theme: 'success',
|
||||
},
|
||||
stubs: [ 'n8n-icon', 'n8n-text' ],
|
||||
stubs: ['n8n-icon', 'n8n-text'],
|
||||
slots: {
|
||||
default: '<n8n-text size="small">This is a success callout.</n8n-text>',
|
||||
},
|
||||
|
@ -32,7 +32,7 @@ describe('components', () => {
|
|||
props: {
|
||||
theme: 'warning',
|
||||
},
|
||||
stubs: [ 'n8n-icon', 'n8n-text' ],
|
||||
stubs: ['n8n-icon', 'n8n-text'],
|
||||
slots: {
|
||||
default: '<n8n-text size="small">This is a warning callout.</n8n-text>',
|
||||
},
|
||||
|
@ -44,7 +44,7 @@ describe('components', () => {
|
|||
props: {
|
||||
theme: 'danger',
|
||||
},
|
||||
stubs: [ 'n8n-icon', 'n8n-text' ],
|
||||
stubs: ['n8n-icon', 'n8n-text'],
|
||||
slots: {
|
||||
default: '<n8n-text size="small">This is a danger callout.</n8n-text>',
|
||||
},
|
||||
|
@ -56,7 +56,7 @@ describe('components', () => {
|
|||
props: {
|
||||
theme: 'secondary',
|
||||
},
|
||||
stubs: [ 'n8n-icon', 'n8n-text' ],
|
||||
stubs: ['n8n-icon', 'n8n-text'],
|
||||
slots: {
|
||||
default: '<n8n-text size="small">This is a secondary callout.</n8n-text>',
|
||||
},
|
||||
|
@ -69,7 +69,7 @@ describe('components', () => {
|
|||
theme: 'custom',
|
||||
icon: 'code-branch',
|
||||
},
|
||||
stubs: [ 'n8n-icon', 'n8n-text' ],
|
||||
stubs: ['n8n-icon', 'n8n-text'],
|
||||
slots: {
|
||||
default: '<n8n-text size="small">This is a secondary callout.</n8n-text>',
|
||||
},
|
||||
|
@ -82,11 +82,12 @@ describe('components', () => {
|
|||
theme: 'custom',
|
||||
icon: 'code-branch',
|
||||
},
|
||||
stubs: [ 'n8n-icon', 'n8n-text', 'n8n-link' ],
|
||||
stubs: ['n8n-icon', 'n8n-text', 'n8n-link'],
|
||||
slots: {
|
||||
default: '<n8n-text size="small">This is a secondary callout.</n8n-text>',
|
||||
actions: '<n8n-link size="small">Do something!</n8n-link>',
|
||||
trailingContent: '<n8n-link theme="secondary" size="small" :bold="true" :underline="true" to="https://n8n.io">Learn more</n8n-link>',
|
||||
trailingContent:
|
||||
'<n8n-link theme="secondary" size="small" :bold="true" :underline="true" to="https://n8n.io">Learn more</n8n-link>',
|
||||
},
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`components > N8nCallout > should render additional slots correctly 1`] = `
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _custom_p74de_16\\">
|
||||
<div class=\\"_message-section_p74de_12\\">
|
||||
<div class=\\"_icon_p74de_40\\">
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _custom_dfd91_17\\">
|
||||
<div class=\\"_message-section_dfd91_12\\">
|
||||
<div class=\\"_icon_dfd91_41\\">
|
||||
<n8n-icon-stub icon=\\"code-branch\\" size=\\"large\\"></n8n-icon-stub>
|
||||
</div>
|
||||
<n8n-text-stub size=\\"small\\">This is a secondary callout.</n8n-text-stub> <n8n-link-stub size=\\"small\\">Do something!</n8n-link-stub>
|
||||
|
@ -13,9 +13,9 @@ exports[`components > N8nCallout > should render additional slots correctly 1`]
|
|||
`;
|
||||
|
||||
exports[`components > N8nCallout > should render custom theme correctly 1`] = `
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _custom_p74de_16\\">
|
||||
<div class=\\"_message-section_p74de_12\\">
|
||||
<div class=\\"_icon_p74de_40\\">
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _custom_dfd91_17\\">
|
||||
<div class=\\"_message-section_dfd91_12\\">
|
||||
<div class=\\"_icon_dfd91_41\\">
|
||||
<n8n-icon-stub icon=\\"code-branch\\" size=\\"large\\"></n8n-icon-stub>
|
||||
</div>
|
||||
<n8n-text-stub size=\\"small\\">This is a secondary callout.</n8n-text-stub>
|
||||
|
@ -24,9 +24,9 @@ exports[`components > N8nCallout > should render custom theme correctly 1`] = `
|
|||
`;
|
||||
|
||||
exports[`components > N8nCallout > should render danger theme correctly 1`] = `
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _danger_p74de_34\\">
|
||||
<div class=\\"_message-section_p74de_12\\">
|
||||
<div class=\\"_icon_p74de_40\\">
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _danger_dfd91_35\\">
|
||||
<div class=\\"_message-section_dfd91_12\\">
|
||||
<div class=\\"_icon_dfd91_41\\">
|
||||
<n8n-icon-stub icon=\\"times-circle\\" size=\\"large\\"></n8n-icon-stub>
|
||||
</div>
|
||||
<n8n-text-stub size=\\"small\\">This is a danger callout.</n8n-text-stub>
|
||||
|
@ -35,9 +35,9 @@ exports[`components > N8nCallout > should render danger theme correctly 1`] = `
|
|||
`;
|
||||
|
||||
exports[`components > N8nCallout > should render info theme correctly 1`] = `
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _info_p74de_16\\">
|
||||
<div class=\\"_message-section_p74de_12\\">
|
||||
<div class=\\"_icon_p74de_40\\">
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _info_dfd91_16\\">
|
||||
<div class=\\"_message-section_dfd91_12\\">
|
||||
<div class=\\"_icon_dfd91_41\\">
|
||||
<n8n-icon-stub icon=\\"info-circle\\" size=\\"large\\"></n8n-icon-stub>
|
||||
</div>
|
||||
<n8n-text-stub size=\\"small\\">This is an info callout.</n8n-text-stub>
|
||||
|
@ -46,9 +46,9 @@ exports[`components > N8nCallout > should render info theme correctly 1`] = `
|
|||
`;
|
||||
|
||||
exports[`components > N8nCallout > should render secondary theme correctly 1`] = `
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _secondary_p74de_44\\">
|
||||
<div class=\\"_message-section_p74de_12\\">
|
||||
<div class=\\"_icon_p74de_40\\">
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _secondary_dfd91_45\\">
|
||||
<div class=\\"_message-section_dfd91_12\\">
|
||||
<div class=\\"_icon_dfd91_41\\">
|
||||
<n8n-icon-stub icon=\\"info-circle\\" size=\\"medium\\"></n8n-icon-stub>
|
||||
</div>
|
||||
<n8n-text-stub size=\\"small\\">This is a secondary callout.</n8n-text-stub>
|
||||
|
@ -57,9 +57,9 @@ exports[`components > N8nCallout > should render secondary theme correctly 1`] =
|
|||
`;
|
||||
|
||||
exports[`components > N8nCallout > should render success theme correctly 1`] = `
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _success_p74de_28\\">
|
||||
<div class=\\"_message-section_p74de_12\\">
|
||||
<div class=\\"_icon_p74de_40\\">
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _success_dfd91_29\\">
|
||||
<div class=\\"_message-section_dfd91_12\\">
|
||||
<div class=\\"_icon_dfd91_41\\">
|
||||
<n8n-icon-stub icon=\\"check-circle\\" size=\\"large\\"></n8n-icon-stub>
|
||||
</div>
|
||||
<n8n-text-stub size=\\"small\\">This is a success callout.</n8n-text-stub>
|
||||
|
@ -68,9 +68,9 @@ exports[`components > N8nCallout > should render success theme correctly 1`] = `
|
|||
`;
|
||||
|
||||
exports[`components > N8nCallout > should render warning theme correctly 1`] = `
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _warning_p74de_22\\">
|
||||
<div class=\\"_message-section_p74de_12\\">
|
||||
<div class=\\"_icon_p74de_40\\">
|
||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _warning_dfd91_23\\">
|
||||
<div class=\\"_message-section_dfd91_12\\">
|
||||
<div class=\\"_icon_dfd91_41\\">
|
||||
<n8n-icon-stub icon=\\"exclamation-triangle\\" size=\\"large\\"></n8n-icon-stub>
|
||||
</div>
|
||||
<n8n-text-stub size=\\"small\\">This is a warning callout.</n8n-text-stub>
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
/* tslint:disable:variable-name */
|
||||
|
||||
import N8nCard from './Card.vue';
|
||||
import {StoryFn} from "@storybook/vue";
|
||||
import N8nButton from "../N8nButton/Button.vue";
|
||||
import N8nIcon from "../N8nIcon/Icon.vue";
|
||||
import N8nText from "../N8nText/Text.vue";
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
import N8nButton from '../N8nButton/Button.vue';
|
||||
import N8nIcon from '../N8nIcon/Icon.vue';
|
||||
import N8nText from '../N8nText/Text.vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Card',
|
||||
component: N8nCard,
|
||||
};
|
||||
|
||||
export const Default: StoryFn = (args, {argTypes}) => ({
|
||||
export const Default: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nCard,
|
||||
|
@ -19,7 +17,7 @@ export const Default: StoryFn = (args, {argTypes}) => ({
|
|||
template: `<n8n-card v-bind="$props">This is a card.</n8n-card>`,
|
||||
});
|
||||
|
||||
export const Hoverable: StoryFn = (args, {argTypes}) => ({
|
||||
export const Hoverable: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nCard,
|
||||
|
@ -38,8 +36,7 @@ Hoverable.args = {
|
|||
hoverable: true,
|
||||
};
|
||||
|
||||
|
||||
export const WithSlots: StoryFn = (args, {argTypes}) => ({
|
||||
export const WithSlots: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nCard,
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<template>
|
||||
<div :class="classes" v-on="$listeners">
|
||||
<div :class="$style.icon" v-if="$slots.prepend">
|
||||
<slot name="prepend"/>
|
||||
<slot name="prepend" />
|
||||
</div>
|
||||
<div :class="$style.content">
|
||||
<div :class="$style.header" v-if="$slots.header">
|
||||
<slot name="header"/>
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div :class="$style.body" v-if="$slots.default">
|
||||
<slot/>
|
||||
<slot />
|
||||
</div>
|
||||
<div :class="$style.footer" v-if="$slots.footer">
|
||||
<slot name="footer"/>
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.actions" v-if="$slots.append">
|
||||
<slot name="append"/>
|
||||
<slot name="append" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -90,15 +90,15 @@ export default Vue.extend({
|
|||
}
|
||||
|
||||
.hoverable {
|
||||
cursor: pointer;
|
||||
transition-property: border, color;
|
||||
transition-duration: 0.3s;
|
||||
transition-timing-function: ease;
|
||||
cursor: pointer;
|
||||
transition-property: border, color;
|
||||
transition-duration: 0.3s;
|
||||
transition-timing-function: ease;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {render} from '@testing-library/vue';
|
||||
import N8nCard from "../Card.vue";
|
||||
import { render } from '@testing-library/vue';
|
||||
import N8nCard from '../Card.vue';
|
||||
|
||||
describe('components', () => {
|
||||
describe('N8nCard', () => {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* tslint:disable:variable-name */
|
||||
import N8nCheckbox from "./Checkbox.vue";
|
||||
import { StoryFn } from '@storybook/vue';
|
||||
import N8nCheckbox from './Checkbox.vue';
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
export default {
|
||||
|
|
|
@ -51,20 +51,18 @@ export default Vue.extend({
|
|||
labelSize: {
|
||||
type: String,
|
||||
default: 'medium',
|
||||
validator: (value: string): boolean =>
|
||||
['small', 'medium'].includes(value),
|
||||
validator: (value: string): boolean => ['small', 'medium'].includes(value),
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange(event: Event) {
|
||||
this.$emit("input", event);
|
||||
this.$emit('input', event);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
||||
.n8nCheckbox {
|
||||
display: flex !important;
|
||||
white-space: normal !important;
|
||||
|
@ -73,5 +71,4 @@ export default Vue.extend({
|
|||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -4,8 +4,7 @@ import { action } from '@storybook/addon-actions';
|
|||
export default {
|
||||
title: 'Modules/FormBox',
|
||||
component: N8nFormBox,
|
||||
argTypes: {
|
||||
},
|
||||
argTypes: {},
|
||||
parameters: {
|
||||
backgrounds: { default: '--color-background-light' },
|
||||
},
|
||||
|
@ -35,7 +34,7 @@ FormBox.args = {
|
|||
label: 'Your Email',
|
||||
type: 'email',
|
||||
required: true,
|
||||
validationRules: [{name: 'VALID_EMAIL'}],
|
||||
validationRules: [{ name: 'VALID_EMAIL' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -51,7 +50,7 @@ FormBox.args = {
|
|||
label: 'Your Password',
|
||||
type: 'password',
|
||||
required: true,
|
||||
validationRules: [{name: 'DEFAULT_PASSWORD_RULES'}],
|
||||
validationRules: [{ name: 'DEFAULT_PASSWORD_RULES' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -66,4 +65,3 @@ FormBox.args = {
|
|||
redirectText: 'Go somewhere',
|
||||
redirectLink: 'https://n8n.io',
|
||||
};
|
||||
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
<template>
|
||||
<div
|
||||
:class="['n8n-form-box', $style.container]"
|
||||
>
|
||||
<div
|
||||
v-if="title"
|
||||
:class="$style.heading"
|
||||
>
|
||||
<n8n-heading
|
||||
size="xlarge"
|
||||
>
|
||||
{{title}}
|
||||
<div :class="['n8n-form-box', $style.container]">
|
||||
<div v-if="title" :class="$style.heading">
|
||||
<n8n-heading size="xlarge">
|
||||
{{ title }}
|
||||
</n8n-heading>
|
||||
</div>
|
||||
<div
|
||||
:class="$style.inputsContainer"
|
||||
>
|
||||
<div :class="$style.inputsContainer">
|
||||
<n8n-form-inputs
|
||||
:inputs="inputs"
|
||||
:eventBus="formBus"
|
||||
|
@ -24,16 +15,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div :class="$style.buttonsContainer" v-if="secondaryButtonText || buttonText">
|
||||
<span
|
||||
v-if="secondaryButtonText"
|
||||
:class="$style.secondaryButtonContainer"
|
||||
>
|
||||
<n8n-link
|
||||
size="medium"
|
||||
theme="text"
|
||||
@click="onSecondaryButtonClick"
|
||||
>
|
||||
{{secondaryButtonText}}
|
||||
<span v-if="secondaryButtonText" :class="$style.secondaryButtonContainer">
|
||||
<n8n-link size="medium" theme="text" @click="onSecondaryButtonClick">
|
||||
{{ secondaryButtonText }}
|
||||
</n8n-link>
|
||||
</span>
|
||||
<n8n-button
|
||||
|
@ -45,11 +29,8 @@
|
|||
/>
|
||||
</div>
|
||||
<div :class="$style.actionContainer">
|
||||
<n8n-link
|
||||
v-if="redirectText && redirectLink"
|
||||
:to="redirectLink"
|
||||
>
|
||||
{{redirectText}}
|
||||
<n8n-link v-if="redirectText && redirectLink" :to="redirectLink">
|
||||
{{ redirectText }}
|
||||
</n8n-link>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -103,10 +84,10 @@ export default Vue.extend({
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
onInput(e: {name: string, value: string}) {
|
||||
onInput(e: { name: string; value: string }) {
|
||||
this.$emit('input', e);
|
||||
},
|
||||
onSubmit(e: {[key: string]: string}) {
|
||||
onSubmit(e: { [key: string]: string }) {
|
||||
this.$emit('submit', e);
|
||||
},
|
||||
onButtonClick() {
|
||||
|
|
|
@ -4,8 +4,7 @@ import { action } from '@storybook/addon-actions';
|
|||
export default {
|
||||
title: 'Modules/FormInput',
|
||||
component: N8nFormInput,
|
||||
argTypes: {
|
||||
},
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
const methods = {
|
||||
|
|
|
@ -6,7 +6,13 @@
|
|||
@focus="onFocus"
|
||||
ref="inputRef"
|
||||
/>
|
||||
<n8n-input-label v-else :inputName="name" :label="label" :tooltipText="tooltipText" :required="required && showRequiredAsterisk">
|
||||
<n8n-input-label
|
||||
v-else
|
||||
:inputName="name"
|
||||
:label="label"
|
||||
:tooltipText="tooltipText"
|
||||
:required="required && showRequiredAsterisk"
|
||||
>
|
||||
<div :class="showErrors ? $style.errorInput : ''" @keydown.stop @keydown.enter="onEnter">
|
||||
<slot v-if="hasDefaultSlot" />
|
||||
<n8n-select
|
||||
|
@ -21,7 +27,7 @@
|
|||
ref="inputRef"
|
||||
>
|
||||
<n8n-option
|
||||
v-for="option in (options || [])"
|
||||
v-for="option in options || []"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
:label="option.label"
|
||||
|
@ -69,12 +75,12 @@ import N8nInputLabel from '../N8nInputLabel';
|
|||
import N8nCheckbox from '../N8nCheckbox';
|
||||
|
||||
import { getValidationError, VALIDATORS } from './validators';
|
||||
import { Rule, RuleGroup, IValidator } from "../../types";
|
||||
import { Rule, RuleGroup, IValidator } from '../../types';
|
||||
|
||||
import { t } from '../../locale';
|
||||
|
||||
export interface Props {
|
||||
value: any;
|
||||
value: any;
|
||||
label: string;
|
||||
infoText?: string;
|
||||
required?: boolean;
|
||||
|
@ -87,9 +93,9 @@ export interface Props {
|
|||
documentationUrl?: string;
|
||||
documentationText?: string;
|
||||
validationRules?: Array<Rule | RuleGroup>;
|
||||
validators?: {[key: string]: IValidator | RuleGroup};
|
||||
validators?: { [key: string]: IValidator | RuleGroup };
|
||||
maxlength?: number;
|
||||
options?: Array<{value: string | number, label: string}>;
|
||||
options?: Array<{ value: string | number; label: string }>;
|
||||
autocomplete?: string;
|
||||
name?: string;
|
||||
focusInitially?: boolean;
|
||||
|
@ -105,11 +111,11 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'validate', shouldValidate: boolean): void,
|
||||
(event: 'input', value: any): void,
|
||||
(event: 'focus'): void,
|
||||
(event: 'blur'): void,
|
||||
(event: 'enter'): void,
|
||||
(event: 'validate', shouldValidate: boolean): void;
|
||||
(event: 'input', value: any): void;
|
||||
(event: 'focus'): void;
|
||||
(event: 'blur'): void;
|
||||
(event: 'enter'): void;
|
||||
}>();
|
||||
|
||||
const state = reactive({
|
||||
|
@ -121,11 +127,11 @@ const slots = useSlots();
|
|||
|
||||
const inputRef = ref<HTMLTextAreaElement | null>(null);
|
||||
|
||||
function getInputValidationError(): ReturnType<IValidator['validate']> {
|
||||
const rules = props.validationRules || [];
|
||||
function getInputValidationError(): ReturnType<IValidator['validate']> {
|
||||
const rules = props.validationRules ?? [];
|
||||
const validators = {
|
||||
...VALIDATORS,
|
||||
...(props.validators || {}),
|
||||
...(props.validators ?? {}),
|
||||
} as { [key: string]: IValidator | RuleGroup };
|
||||
|
||||
if (props.required) {
|
||||
|
@ -149,11 +155,7 @@ function getInputValidationError(): ReturnType<IValidator['validate']> {
|
|||
|
||||
if (rules[i].hasOwnProperty('rules')) {
|
||||
const rule = rules[i] as RuleGroup;
|
||||
const error = getValidationError(
|
||||
props.value,
|
||||
validators,
|
||||
rule,
|
||||
);
|
||||
const error = getValidationError(props.value, validators, rule);
|
||||
if (error) return error;
|
||||
}
|
||||
}
|
||||
|
@ -190,10 +192,11 @@ const validationError = computed<string | null>(() => {
|
|||
|
||||
const hasDefaultSlot = computed(() => !!slots.default);
|
||||
|
||||
const showErrors = computed(() => (
|
||||
!!validationError.value &&
|
||||
((props.validateOnBlur && state.hasBlurred && !state.isTyping) || props.showValidationWarnings)
|
||||
));
|
||||
const showErrors = computed(
|
||||
() =>
|
||||
!!validationError.value &&
|
||||
((props.validateOnBlur && state.hasBlurred && !state.isTyping) || props.showValidationWarnings),
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
emit('validate', !validationError.value);
|
||||
|
@ -201,7 +204,10 @@ onMounted(() => {
|
|||
if (props.focusInitially && inputRef.value) inputRef.value.focus();
|
||||
});
|
||||
|
||||
watch(() => validationError.value, (error) => emit('validate', !error));
|
||||
watch(
|
||||
() => validationError.value,
|
||||
(error) => emit('validate', !error),
|
||||
);
|
||||
|
||||
defineExpose({ inputRef });
|
||||
</script>
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
|
||||
import { IValidator, RuleGroup } from "../../types";
|
||||
import { IValidator, RuleGroup, Validatable } from '../../types';
|
||||
|
||||
export const emailRegex =
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
|
||||
export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
||||
REQUIRED: {
|
||||
validate: (value: string | number | boolean | null | undefined) => {
|
||||
validate: (value: Validatable) => {
|
||||
if (typeof value === 'string' && !!value.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -21,7 +20,7 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
|||
},
|
||||
},
|
||||
MIN_LENGTH: {
|
||||
validate: (value: string | number | boolean | null | undefined, config: { minimum: number }) => {
|
||||
validate: (value: Validatable, config: { minimum: number }) => {
|
||||
if (typeof value === 'string' && value.length < config.minimum) {
|
||||
return {
|
||||
messageKey: 'formInput.validator.minCharactersRequired',
|
||||
|
@ -33,7 +32,7 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
|||
},
|
||||
},
|
||||
MAX_LENGTH: {
|
||||
validate: (value: string | number | boolean | null | undefined, config: { maximum: number }) => {
|
||||
validate: (value: Validatable, config: { maximum: number }) => {
|
||||
if (typeof value === 'string' && value.length > config.maximum) {
|
||||
return {
|
||||
messageKey: 'formInput.validator.maxCharactersRequired',
|
||||
|
@ -45,12 +44,12 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
|||
},
|
||||
},
|
||||
CONTAINS_NUMBER: {
|
||||
validate: (value: string | number | boolean | null | undefined, config: { minimum: number }) => {
|
||||
validate: (value: Validatable, config: { minimum: number }) => {
|
||||
if (typeof value !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const numberCount = (value.match(/\d/g) || []).length;
|
||||
const numberCount = (value.match(/\d/g) ?? []).length;
|
||||
if (numberCount < config.minimum) {
|
||||
return {
|
||||
messageKey: 'formInput.validator.numbersRequired',
|
||||
|
@ -62,7 +61,7 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
|||
},
|
||||
},
|
||||
VALID_EMAIL: {
|
||||
validate: (value: string | number | boolean | null | undefined) => {
|
||||
validate: (value: Validatable) => {
|
||||
if (!emailRegex.test(String(value).trim().toLowerCase())) {
|
||||
return {
|
||||
messageKey: 'formInput.validator.validEmailRequired',
|
||||
|
@ -73,12 +72,12 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
|||
},
|
||||
},
|
||||
CONTAINS_UPPERCASE: {
|
||||
validate: (value: string | number | boolean | null | undefined, config: { minimum: number }) => {
|
||||
validate: (value: Validatable, config: { minimum: number }) => {
|
||||
if (typeof value !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uppercaseCount = (value.match(/[A-Z]/g) || []).length;
|
||||
const uppercaseCount = (value.match(/[A-Z]/g) ?? []).length;
|
||||
if (uppercaseCount < config.minimum) {
|
||||
return {
|
||||
messageKey: 'formInput.validator.uppercaseCharsRequired',
|
||||
|
@ -101,35 +100,30 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
|||
messageKey: 'formInput.validator.defaultPasswordRequirements',
|
||||
},
|
||||
},
|
||||
{ name: 'MAX_LENGTH', config: {maximum: 64} },
|
||||
{ name: 'MAX_LENGTH', config: { maximum: 64 } },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const getValidationError = (
|
||||
value: any, // tslint:disable-line:no-any
|
||||
export const getValidationError = <T extends Validatable, C>(
|
||||
value: T,
|
||||
validators: { [key: string]: IValidator | RuleGroup },
|
||||
validator: IValidator | RuleGroup,
|
||||
config?: any, // tslint:disable-line:no-any
|
||||
config?: C,
|
||||
): ReturnType<IValidator['validate']> => {
|
||||
if (validator.hasOwnProperty('rules')) {
|
||||
const rules = (validator as RuleGroup).rules;
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
if (rules[i].hasOwnProperty('rules')) {
|
||||
const error = getValidationError(
|
||||
value,
|
||||
validators,
|
||||
rules[i] as RuleGroup,
|
||||
config,
|
||||
);
|
||||
const error = getValidationError(value, validators, rules[i] as RuleGroup, config);
|
||||
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
if (rules[i].hasOwnProperty('name') ) {
|
||||
const rule = rules[i] as {name: string, config?: any}; // tslint:disable-line:no-any
|
||||
if (rules[i].hasOwnProperty('name')) {
|
||||
const rule = rules[i] as { name: string; config?: C };
|
||||
if (!validators[rule.name]) {
|
||||
continue;
|
||||
}
|
||||
|
@ -140,17 +134,14 @@ export const getValidationError = (
|
|||
validators[rule.name] as IValidator,
|
||||
rule.config,
|
||||
);
|
||||
if (error && (validator as RuleGroup).defaultError !== undefined) {
|
||||
// @ts-ignore
|
||||
if (error && 'defaultError' in validator && validator.defaultError) {
|
||||
return validator.defaultError;
|
||||
} else if (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
validator.hasOwnProperty('validate')
|
||||
) {
|
||||
} else if (validator.hasOwnProperty('validate')) {
|
||||
return (validator as IValidator).validate(value, config);
|
||||
}
|
||||
|
||||
|
|
|
@ -71,12 +71,12 @@ FormInputs.args = {
|
|||
{
|
||||
name: 'agree',
|
||||
properties: {
|
||||
type: 'checkbox',
|
||||
label: 'Signup for newsletter and somebody from our marketing team will get in touch with you as soon as possible. You will not spam you, just want to send you some love every now and then ❤️',
|
||||
labelSize: 'small',
|
||||
tooltipText: 'Check this if you agree to be contacted by our marketing team'
|
||||
}
|
||||
}
|
||||
type: 'checkbox',
|
||||
label:
|
||||
'Signup for newsletter and somebody from our marketing team will get in touch with you as soon as possible. You will not spam you, just want to send you some love every now and then ❤️',
|
||||
labelSize: 'small',
|
||||
tooltipText: 'Check this if you agree to be contacted by our marketing team',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<template>
|
||||
<ResizeObserver
|
||||
:breakpoints="[{bp: 'md', width: 500}]"
|
||||
>
|
||||
<ResizeObserver :breakpoints="[{ bp: 'md', width: 500 }]">
|
||||
<template v-slot="{ bp }">
|
||||
<div :class="bp === 'md' || columnView? $style.grid : $style.gridMulti">
|
||||
<div
|
||||
v-for="(input) in filteredInputs"
|
||||
:key="input.name"
|
||||
>
|
||||
<n8n-text color="text-base" v-if="input.properties.type === 'info'" tag="div" align="center">
|
||||
{{input.properties.label}}
|
||||
<div :class="bp === 'md' || columnView ? $style.grid : $style.gridMulti">
|
||||
<div v-for="input in filteredInputs" :key="input.name">
|
||||
<n8n-text
|
||||
color="text-base"
|
||||
v-if="input.properties.type === 'info'"
|
||||
tag="div"
|
||||
align="center"
|
||||
>
|
||||
{{ input.properties.label }}
|
||||
</n8n-text>
|
||||
<n8n-form-input
|
||||
v-else
|
||||
|
@ -57,8 +57,8 @@ export default Vue.extend({
|
|||
data() {
|
||||
return {
|
||||
showValidationWarnings: false,
|
||||
values: {} as {[key: string]: any},
|
||||
validity: {} as {[key: string]: boolean},
|
||||
values: {} as { [key: string]: any },
|
||||
validity: {} as { [key: string]: boolean },
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -74,10 +74,8 @@ export default Vue.extend({
|
|||
},
|
||||
computed: {
|
||||
filteredInputs(): IFormInput[] {
|
||||
return (this.inputs as IFormInput[]).filter(
|
||||
(input) => typeof input.shouldDisplay === 'function'
|
||||
? input.shouldDisplay(this.values)
|
||||
: true,
|
||||
return (this.inputs as IFormInput[]).filter((input) =>
|
||||
typeof input.shouldDisplay === 'function' ? input.shouldDisplay(this.values) : true,
|
||||
);
|
||||
},
|
||||
isReadyToSubmit(): boolean {
|
||||
|
@ -96,7 +94,7 @@ export default Vue.extend({
|
|||
...this.values,
|
||||
[name]: value, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||
};
|
||||
this.$emit('input', {name, value}); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||
this.$emit('input', { name, value }); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||
},
|
||||
onValidate(name: string, valid: boolean) {
|
||||
Vue.set(this.validity, name, valid);
|
||||
|
@ -104,7 +102,7 @@ export default Vue.extend({
|
|||
onSubmit() {
|
||||
this.showValidationWarnings = true;
|
||||
if (this.isReadyToSubmit) {
|
||||
const toSubmit = (this.filteredInputs ).reduce<{ [key: string]: unknown }>((accu, input) => {
|
||||
const toSubmit = this.filteredInputs.reduce<{ [key: string]: unknown }>((accu, input) => {
|
||||
if (this.values[input.name]) {
|
||||
accu[input.name] = this.values[input.name];
|
||||
}
|
||||
|
@ -133,5 +131,4 @@ export default Vue.extend({
|
|||
composes: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -21,11 +21,15 @@ export default Vue.extend({
|
|||
size: {
|
||||
type: String,
|
||||
default: 'medium',
|
||||
validator: (value: string): boolean => ['2xlarge', 'xlarge', 'large', 'medium', 'small'].includes(value),
|
||||
validator: (value: string): boolean =>
|
||||
['2xlarge', 'xlarge', 'large', 'medium', 'small'].includes(value),
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
validator: (value: string): boolean => ['primary', 'text-dark', 'text-base', 'text-light', 'text-xlight', 'danger'].includes(value),
|
||||
validator: (value: string): boolean =>
|
||||
['primary', 'text-dark', 'text-base', 'text-light', 'text-xlight', 'danger'].includes(
|
||||
value,
|
||||
),
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
|
@ -36,15 +40,17 @@ export default Vue.extend({
|
|||
classes() {
|
||||
const applied = [];
|
||||
if (this.align) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
applied.push(`align-${this.align}`);
|
||||
}
|
||||
if (this.color) {
|
||||
applied.push(this.color);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
applied.push(`size-${this.size}`);
|
||||
|
||||
applied.push(this.bold? 'bold': 'regular');
|
||||
applied.push(this.bold ? 'bold' : 'regular');
|
||||
|
||||
return applied.map((c) => (this.$style as { [key: string]: string })[c]);
|
||||
},
|
||||
|
@ -121,5 +127,4 @@ export default Vue.extend({
|
|||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
<template>
|
||||
<n8n-text
|
||||
:size="size"
|
||||
:color="color"
|
||||
:compact="true"
|
||||
class="n8n-icon"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="icon"
|
||||
:spin="spin"
|
||||
:class="$style[size]"
|
||||
/>
|
||||
<n8n-text :size="size" :color="color" :compact="true" class="n8n-icon">
|
||||
<font-awesome-icon :icon="icon" :spin="spin" :class="$style[size]" />
|
||||
</n8n-text>
|
||||
</template>
|
||||
|
||||
|
@ -44,7 +35,6 @@ export default Vue.extend({
|
|||
});
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" module>
|
||||
.xlarge {
|
||||
width: var(--font-size-xl) !important;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* tslint:disable:variable-name */
|
||||
import N8nIconButton from './IconButton.vue';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { StoryFn } from "@storybook/vue";
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Icon Button',
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
<template>
|
||||
<n8n-button
|
||||
square
|
||||
v-bind="$props"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
<n8n-button square v-bind="$props" v-on="$listeners" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -51,8 +47,7 @@ export default Vue.extend({
|
|||
},
|
||||
float: {
|
||||
type: String,
|
||||
validator: (value: string): boolean =>
|
||||
['left', 'right'].includes(value),
|
||||
validator: (value: string): boolean => ['left', 'right'].includes(value),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
/* tslint:disable:variable-name */
|
||||
|
||||
import N8nInfoAccordion from './InfoAccordion.vue';
|
||||
import { StoryFn } from "@storybook/vue";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Info Accordion',
|
||||
component: N8nInfoAccordion,
|
||||
argTypes: {
|
||||
},
|
||||
argTypes: {},
|
||||
parameters: {
|
||||
backgrounds: { default: '--color-background-light' },
|
||||
},
|
||||
|
@ -18,7 +15,7 @@ const methods = {
|
|||
onClick: action('click'),
|
||||
};
|
||||
|
||||
export const Default: StoryFn = (args, {argTypes}) => ({
|
||||
export const Default: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nInfoAccordion,
|
||||
|
|
|
@ -1,25 +1,41 @@
|
|||
<template>
|
||||
<div :class="['accordion', $style.container]" >
|
||||
<div :class="{[$style.header]: true, [$style.expanded]: expanded }" @click="toggle">
|
||||
<n8n-icon v-if="headerIcon" :icon="headerIcon.icon" :color="headerIcon.color" size="small" class="mr-2xs"/>
|
||||
<n8n-text :class="$style.headerText" color="text-base" size="small" align="left" bold>{{ title }}</n8n-text>
|
||||
<n8n-icon :icon="expanded? 'chevron-up' : 'chevron-down'" bold />
|
||||
<div :class="['accordion', $style.container]">
|
||||
<div :class="{ [$style.header]: true, [$style.expanded]: expanded }" @click="toggle">
|
||||
<n8n-icon
|
||||
v-if="headerIcon"
|
||||
:icon="headerIcon.icon"
|
||||
:color="headerIcon.color"
|
||||
size="small"
|
||||
class="mr-2xs"
|
||||
/>
|
||||
<n8n-text :class="$style.headerText" color="text-base" size="small" align="left" bold>{{
|
||||
title
|
||||
}}</n8n-text>
|
||||
<n8n-icon :icon="expanded ? 'chevron-up' : 'chevron-down'" bold />
|
||||
</div>
|
||||
<div v-if="expanded" :class="{[$style.description]: true, [$style.collapsed]: !expanded}" @click="onClick">
|
||||
<div
|
||||
v-if="expanded"
|
||||
:class="{ [$style.description]: true, [$style.collapsed]: !expanded }"
|
||||
@click="onClick"
|
||||
>
|
||||
<!-- Info accordion can display list of items with icons or just a HTML description -->
|
||||
<div v-if="items.length > 0" :class="$style.accordionItems">
|
||||
<div v-for="item in items" :key="item.id" :class="$style.accordionItem">
|
||||
<n8n-tooltip :disabled="!item.tooltip">
|
||||
<div slot="content" v-html="item.tooltip" @click="onTooltipClick(item.id, $event)"></div>
|
||||
<n8n-icon :icon="item.icon" :color="item.iconColor" size="small" class="mr-2xs"/>
|
||||
<div
|
||||
slot="content"
|
||||
v-html="item.tooltip"
|
||||
@click="onTooltipClick(item.id, $event)"
|
||||
></div>
|
||||
<n8n-icon :icon="item.icon" :color="item.iconColor" size="small" class="mr-2xs" />
|
||||
</n8n-tooltip>
|
||||
<n8n-text size="small" color="text-base">{{ item.label }}</n8n-text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<n8n-text color="text-base" size="small" align="left">
|
||||
<span v-html="description"></span>
|
||||
</n8n-text>
|
||||
<slot name="customContent"></slot>
|
||||
<slot name="customContent"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -59,7 +75,7 @@ export default Vue.extend({
|
|||
default: false,
|
||||
},
|
||||
headerIcon: {
|
||||
type: Object as () => { icon: string, color: string },
|
||||
type: Object as PropType<{ icon: string; color: string }>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
|
@ -128,5 +144,4 @@ export default Vue.extend({
|
|||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
/* tslint:disable:variable-name */
|
||||
|
||||
import { StoryFn } from '@storybook/vue';
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
import N8nInfoTip from './InfoTip.vue';
|
||||
|
||||
export default {
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
<template>
|
||||
<div :class="{'n8n-info-tip': true, [$style[theme]]: true, [$style[type]]: true, [$style.bold]: bold}">
|
||||
<div
|
||||
:class="{
|
||||
'n8n-info-tip': true,
|
||||
[$style[theme]]: true,
|
||||
[$style[type]]: true,
|
||||
[$style.bold]: bold,
|
||||
}"
|
||||
>
|
||||
<n8n-tooltip
|
||||
v-if="type === 'tooltip'"
|
||||
:placement="tooltipPlacement"
|
||||
:popper-class="$style.tooltipPopper"
|
||||
:disabled="type !== 'tooltip'"
|
||||
v-if="type === 'tooltip'"
|
||||
:placement="tooltipPlacement"
|
||||
:popper-class="$style.tooltipPopper"
|
||||
:disabled="type !== 'tooltip'"
|
||||
>
|
||||
<span :class="$style.iconText">
|
||||
<n8n-icon :icon="theme.startsWith('info') ? 'info-circle': 'exclamation-triangle'" />
|
||||
<n8n-icon :icon="theme.startsWith('info') ? 'info-circle' : 'exclamation-triangle'" />
|
||||
</span>
|
||||
<span slot="content">
|
||||
<slot />
|
||||
</span>
|
||||
</n8n-tooltip>
|
||||
<span :class="$style.iconText" v-else>
|
||||
<n8n-icon :icon="theme.startsWith('info') ? 'info-circle': 'exclamation-triangle'" />
|
||||
<n8n-icon :icon="theme.startsWith('info') ? 'info-circle' : 'exclamation-triangle'" />
|
||||
<span>
|
||||
<slot />
|
||||
</span>
|
||||
|
@ -44,8 +51,7 @@ export default Vue.extend({
|
|||
type: {
|
||||
type: String,
|
||||
default: 'note',
|
||||
validator: (value: string): boolean =>
|
||||
['note', 'tooltip'].includes(value),
|
||||
validator: (value: string): boolean => ['note', 'tooltip'].includes(value),
|
||||
},
|
||||
bold: {
|
||||
type: Boolean,
|
||||
|
@ -91,7 +97,7 @@ export default Vue.extend({
|
|||
|
||||
.iconText {
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info-light {
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import {render} from '@testing-library/vue';
|
||||
import N8nInfoTip from "../InfoTip.vue";
|
||||
import { render } from '@testing-library/vue';
|
||||
import N8nInfoTip from '../InfoTip.vue';
|
||||
|
||||
const slots = {
|
||||
default: [
|
||||
'Need help doing something?',
|
||||
'<a href="/docs" target="_blank">Open docs</a>',
|
||||
],
|
||||
default: ['Need help doing something?', '<a href="/docs" target="_blank">Open docs</a>'],
|
||||
};
|
||||
const stubs = ['n8n-tooltip'];
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
/* tslint:disable:variable-name */
|
||||
import N8nInput from './Input.vue';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { StoryFn } from '@storybook/vue';
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Input',
|
||||
|
@ -41,7 +40,8 @@ const Template: StoryFn = (args, { argTypes }) => ({
|
|||
components: {
|
||||
N8nInput,
|
||||
},
|
||||
template: '<n8n-input v-bind="$props" v-model="val" @input="onInput" @change="onChange" @focus="onFocus" />',
|
||||
template:
|
||||
'<n8n-input v-bind="$props" v-model="val" @input="onInput" @change="onChange" @focus="onFocus" />',
|
||||
data() {
|
||||
return {
|
||||
val: '',
|
||||
|
@ -82,14 +82,14 @@ TextArea.args = {
|
|||
placeholder: 'placeholder...',
|
||||
};
|
||||
|
||||
|
||||
const WithPrefix: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nIcon,
|
||||
N8nInput,
|
||||
},
|
||||
template: '<n8n-input v-bind="$props" v-model="val" @input="onInput" @change="onChange" @focus="onFocus"><n8n-icon icon="clock" slot="prefix" /></n8n-input>',
|
||||
template:
|
||||
'<n8n-input v-bind="$props" v-model="val" @input="onInput" @change="onChange" @focus="onFocus"><n8n-icon icon="clock" slot="prefix" /></n8n-input>',
|
||||
data() {
|
||||
return {
|
||||
val: '',
|
||||
|
@ -109,7 +109,8 @@ const WithSuffix: StoryFn = (args, { argTypes }) => ({
|
|||
N8nIcon,
|
||||
N8nInput,
|
||||
},
|
||||
template: '<n8n-input v-bind="$props" v-model="val" @input="onInput" @change="onChange" @focus="onFocus"><n8n-icon icon="clock" slot="suffix" /></n8n-input>',
|
||||
template:
|
||||
'<n8n-input v-bind="$props" v-model="val" @input="onInput" @change="onChange" @focus="onFocus"><n8n-icon icon="clock" slot="suffix" /></n8n-input>',
|
||||
data() {
|
||||
return {
|
||||
val: '',
|
||||
|
|
|
@ -33,8 +33,7 @@ export default Vue.extend({
|
|||
ElInput, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
},
|
||||
value: {},
|
||||
type: {
|
||||
type: String,
|
||||
validator: (value: string): boolean =>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {render} from '@testing-library/vue';
|
||||
import N8nInput from "../Input.vue";
|
||||
import { render } from '@testing-library/vue';
|
||||
import N8nInput from '../Input.vue';
|
||||
|
||||
describe('N8nInput', () => {
|
||||
it('should render correctly', () => {
|
||||
|
|
|
@ -17,15 +17,21 @@
|
|||
<n8n-text color="primary" :bold="bold" :size="size" v-if="required">*</n8n-text>
|
||||
</n8n-text>
|
||||
</div>
|
||||
<span :class="[$style.infoIcon, showTooltip ? $style.visible: $style.hidden]" v-if="tooltipText && label">
|
||||
<span
|
||||
:class="[$style.infoIcon, showTooltip ? $style.visible : $style.hidden]"
|
||||
v-if="tooltipText && label"
|
||||
>
|
||||
<n8n-tooltip placement="top" :popper-class="$style.tooltipPopper">
|
||||
<n8n-icon icon="question-circle" size="small" />
|
||||
<div slot="content" v-html="addTargetBlank(tooltipText)" />
|
||||
</n8n-tooltip>
|
||||
</span>
|
||||
<div v-if="$slots.options && label" :class="{[$style.overlay]: true, [$style.visible]: showOptions}" />
|
||||
<div v-if="$slots.options" :class="{[$style.options]: true, [$style.visible]: showOptions}">
|
||||
<slot name="options"/>
|
||||
<div
|
||||
v-if="$slots.options && label"
|
||||
:class="{ [$style.overlay]: true, [$style.visible]: showOptions }"
|
||||
/>
|
||||
<div v-if="$slots.options" :class="{ [$style.options]: true, [$style.visible]: showOptions }">
|
||||
<slot name="options" />
|
||||
</div>
|
||||
</label>
|
||||
<slot />
|
||||
|
@ -71,8 +77,7 @@ export default Vue.extend({
|
|||
size: {
|
||||
type: String,
|
||||
default: 'medium',
|
||||
validator: (value: string): boolean =>
|
||||
['small', 'medium'].includes(value),
|
||||
validator: (value: string): boolean => ['small', 'medium'].includes(value),
|
||||
},
|
||||
underline: {
|
||||
type: Boolean,
|
||||
|
@ -98,7 +103,8 @@ export default Vue.extend({
|
|||
.inputLabel {
|
||||
display: block;
|
||||
}
|
||||
.container:hover,.inputLabel:hover {
|
||||
.container:hover,
|
||||
.inputLabel:hover {
|
||||
.infoIcon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
@ -136,7 +142,7 @@ export default Vue.extend({
|
|||
.options {
|
||||
opacity: 0;
|
||||
background-color: var(--color-background-xlight);
|
||||
transition: opacity 250ms cubic-bezier(.98,-0.06,.49,-0.2); // transition on hover out
|
||||
transition: opacity 250ms cubic-bezier(0.98, -0.06, 0.49, -0.2); // transition on hover out
|
||||
|
||||
> * {
|
||||
float: right;
|
||||
|
@ -147,7 +153,7 @@ export default Vue.extend({
|
|||
position: relative;
|
||||
flex-grow: 1;
|
||||
opacity: 0;
|
||||
transition: opacity 250ms cubic-bezier(.98,-0.06,.49,-0.2); // transition on hover out
|
||||
transition: opacity 250ms cubic-bezier(0.98, -0.06, 0.49, -0.2); // transition on hover out
|
||||
|
||||
> div {
|
||||
position: absolute;
|
||||
|
@ -157,7 +163,11 @@ export default Vue.extend({
|
|||
right: 0;
|
||||
z-index: 0;
|
||||
|
||||
background: linear-gradient(270deg, var(--color-foreground-xlight) 72.19%, rgba(255, 255, 255, 0) 107.45%);
|
||||
background: linear-gradient(
|
||||
270deg,
|
||||
var(--color-foreground-xlight) 72.19%,
|
||||
rgba(255, 255, 255, 0) 107.45%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,5 +207,4 @@ export default Vue.extend({
|
|||
margin-left: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -91,4 +91,3 @@ Sizes.args = {
|
|||
placeholder: 'placeholder...',
|
||||
controls: false,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
<template>
|
||||
<n8n-route :to="to" :newWindow="newWindow"
|
||||
v-on="$listeners"
|
||||
class="n8n-link"
|
||||
>
|
||||
<span
|
||||
:class="$style[`${underline ? `${theme}-underline` : theme}`]"
|
||||
>
|
||||
<n8n-route :to="to" :newWindow="newWindow" v-on="$listeners" class="n8n-link">
|
||||
<span :class="$style[`${underline ? `${theme}-underline` : theme}`]">
|
||||
<n8n-text :size="size" :bold="bold">
|
||||
<slot></slot>
|
||||
</n8n-text>
|
||||
|
@ -54,18 +49,13 @@ export default Vue.extend({
|
|||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
@import "../../utils";
|
||||
@import '../../utils';
|
||||
|
||||
.primary {
|
||||
color: var(--color-primary);
|
||||
|
||||
&:active {
|
||||
color: saturation(
|
||||
--color-primary-h,
|
||||
--color-primary-s,
|
||||
--color-primary-l,
|
||||
-(30%)
|
||||
);
|
||||
color: saturation(--color-primary-h, --color-primary-s, --color-primary-l, -(30%));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,5 +111,4 @@ export default Vue.extend({
|
|||
composes: secondary;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,40 +1,36 @@
|
|||
<template>
|
||||
<el-skeleton :loading="loading" :animated="animated" :class="['n8n-loading', `n8n-loading-${variant}`]">
|
||||
<el-skeleton
|
||||
:loading="loading"
|
||||
:animated="animated"
|
||||
:class="['n8n-loading', `n8n-loading-${variant}`]"
|
||||
>
|
||||
<template slot="template">
|
||||
<div v-if="variant === 'h1'">
|
||||
<div
|
||||
v-for="(item, index) in rows"
|
||||
:key="index"
|
||||
:class="{
|
||||
v-for="(item, index) in rows"
|
||||
:key="index"
|
||||
:class="{
|
||||
[$style.h1Last]: item === rows && rows > 1 && shrinkLast,
|
||||
}"
|
||||
>
|
||||
<el-skeleton-item
|
||||
:variant="variant"
|
||||
/>
|
||||
<el-skeleton-item :variant="variant" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="variant === 'p'">
|
||||
<div
|
||||
v-for="(item, index) in rows"
|
||||
:key="index"
|
||||
:class="{
|
||||
v-for="(item, index) in rows"
|
||||
:key="index"
|
||||
:class="{
|
||||
[$style.pLast]: item === rows && rows > 1 && shrinkLast,
|
||||
}">
|
||||
<el-skeleton-item
|
||||
:variant="variant"
|
||||
/>
|
||||
}"
|
||||
>
|
||||
<el-skeleton-item :variant="variant" />
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.custom" v-else-if="variant === 'custom'">
|
||||
<el-skeleton-item
|
||||
:variant="variant"
|
||||
/>
|
||||
<el-skeleton-item :variant="variant" />
|
||||
</div>
|
||||
<el-skeleton-item
|
||||
v-else
|
||||
:variant="variant"
|
||||
/>
|
||||
<el-skeleton-item v-else :variant="variant" />
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</template>
|
||||
|
@ -71,7 +67,20 @@ export default Vue.extend({
|
|||
variant: {
|
||||
type: String,
|
||||
default: 'p',
|
||||
validator: (value: string): boolean => ['custom', 'p', 'text', 'h1', 'h3', 'text', 'caption', 'button', 'image', 'circle', 'rect'].includes(value),
|
||||
validator: (value: string): boolean =>
|
||||
[
|
||||
'custom',
|
||||
'p',
|
||||
'text',
|
||||
'h1',
|
||||
'h3',
|
||||
'text',
|
||||
'caption',
|
||||
'button',
|
||||
'image',
|
||||
'circle',
|
||||
'rect',
|
||||
].includes(value),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -79,23 +88,23 @@ export default Vue.extend({
|
|||
|
||||
<style lang="scss" module>
|
||||
.h1Last {
|
||||
width: 40%;
|
||||
width: 40%;
|
||||
}
|
||||
.pLast {
|
||||
width: 61%;
|
||||
width: 61%;
|
||||
}
|
||||
.custom {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.n8n-loading-custom .el-skeleton {
|
||||
&,
|
||||
.el-skeleton__item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
&,
|
||||
.el-skeleton__item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -41,8 +41,10 @@ export const Markdown = Template.bind({});
|
|||
Markdown.args = {
|
||||
content: `I wanted a system to monitor website content changes and notify me. So I made it using n8n.\n\nEspecially my competitor blogs. I wanted to know how often they are posting new articles. (I used their sitemap.xml file) (The below workflow may vary)\n\nIn the Below example, I used HackerNews for example.\n\nExplanation:\n\n- First HTTP Request node crawls the webpage and grabs the website source code\n- Then wait for x minutes\n- Again, HTTP Node crawls the webpage\n- If Node compares both results are equal if anything is changed. It’ll go to the false branch and notify me in telegram.\n\n**Workflow:**\n\n\n\n**Sample Response:**\n\n\n`,
|
||||
loading: false,
|
||||
images: [{
|
||||
id: 1,
|
||||
url: 'https://community.n8n.io/uploads/default/optimized/2X/b/b737a95de4dfe0825d50ca098171e9f33a459e74_2_690x288.png',
|
||||
}],
|
||||
images: [
|
||||
{
|
||||
id: 1,
|
||||
url: 'https://community.n8n.io/uploads/default/optimized/2X/b/b737a95de4dfe0825d50ca098171e9f33a459e74_2_690x288.png',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -4,18 +4,13 @@
|
|||
v-if="!loading"
|
||||
ref="editor"
|
||||
class="ph-no-capture"
|
||||
:class="$style[theme]" v-html="htmlContent"
|
||||
:class="$style[theme]"
|
||||
v-html="htmlContent"
|
||||
@click="onClick"
|
||||
/>
|
||||
<div v-else :class="$style.markdown">
|
||||
<div v-for="(block, index) in loadingBlocks"
|
||||
:key="index">
|
||||
<n8n-loading
|
||||
:loading="loading"
|
||||
:rows="loadingRows"
|
||||
animated
|
||||
variant="p"
|
||||
/>
|
||||
<div v-for="(block, index) in loadingBlocks" :key="index">
|
||||
<n8n-loading :loading="loading" :rows="loadingRows" animated variant="p" />
|
||||
<div :class="$style.spacer" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -26,10 +21,9 @@
|
|||
import N8nLoading from '../N8nLoading';
|
||||
import Markdown from 'markdown-it';
|
||||
|
||||
// @ts-ignore
|
||||
import markdownLink from 'markdown-it-link-attributes';
|
||||
// @ts-ignore
|
||||
import markdownEmoji from 'markdown-it-emoji';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import markdownTasklists from 'markdown-it-task-lists';
|
||||
|
||||
|
@ -41,26 +35,32 @@ const DEFAULT_OPTIONS_MARKDOWN = {
|
|||
linkify: true,
|
||||
typographer: true,
|
||||
breaks: true,
|
||||
};
|
||||
} as const;
|
||||
|
||||
const DEFAULT_OPTIONS_LINK_ATTRIBUTES = {
|
||||
attrs: {
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
},
|
||||
};
|
||||
} as const;
|
||||
|
||||
const DEFAULT_OPTIONS_TASKLISTS = {
|
||||
label: true,
|
||||
labelAfter: true,
|
||||
};
|
||||
} as const;
|
||||
|
||||
interface IImage {
|
||||
id: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
import Vue from 'vue';
|
||||
interface Options {
|
||||
markdown: typeof DEFAULT_OPTIONS_MARKDOWN;
|
||||
linkAttributes: typeof DEFAULT_OPTIONS_LINK_ATTRIBUTES;
|
||||
tasklists: typeof DEFAULT_OPTIONS_TASKLISTS;
|
||||
}
|
||||
|
||||
import Vue, { PropType } from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
|
@ -75,7 +75,7 @@ export default Vue.extend({
|
|||
type: Boolean,
|
||||
},
|
||||
images: {
|
||||
type: Array,
|
||||
type: Array<IImage>,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
|
@ -86,16 +86,14 @@ export default Vue.extend({
|
|||
},
|
||||
loadingRows: {
|
||||
type: Number,
|
||||
default: () => {
|
||||
return 3;
|
||||
},
|
||||
default: () => 3,
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'markdown',
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
type: Object as PropType<Options>,
|
||||
default() {
|
||||
return {
|
||||
markdown: DEFAULT_OPTIONS_MARKDOWN,
|
||||
|
@ -108,12 +106,11 @@ export default Vue.extend({
|
|||
computed: {
|
||||
htmlContent(): string {
|
||||
if (!this.content) {
|
||||
return '';
|
||||
return '';
|
||||
}
|
||||
|
||||
const imageUrls: { [key: string]: string } = {};
|
||||
if (this.images) {
|
||||
// @ts-ignore
|
||||
this.images.forEach((image: IImage) => {
|
||||
if (!image) {
|
||||
// Happens if an image got deleted but the workflow
|
||||
|
@ -125,14 +122,13 @@ export default Vue.extend({
|
|||
}
|
||||
|
||||
const fileIdRegex = new RegExp('fileId:([0-9]+)');
|
||||
const imageFilesRegex = /\.(jpeg|jpg|gif|png|webp|bmp|tif|tiff|apng|svg|avif)$/;
|
||||
let contentToRender = this.content;
|
||||
if (this.withMultiBreaks) {
|
||||
contentToRender = contentToRender.replaceAll('\n\n', '\n \n');
|
||||
}
|
||||
const html = this.md.render(escapeMarkdown(contentToRender));
|
||||
const safeHtml = xss(html, {
|
||||
onTagAttr: (tag, name, value, isWhiteAttr) => {
|
||||
onTagAttr: (tag, name, value) => {
|
||||
if (tag === 'img' && name === 'src') {
|
||||
if (value.match(fileIdRegex)) {
|
||||
const id = value.split('fileId:')[1];
|
||||
|
@ -147,7 +143,7 @@ export default Vue.extend({
|
|||
}
|
||||
// Return nothing, means keep the default handling measure
|
||||
},
|
||||
onTag (tag, code, options) {
|
||||
onTag(tag, code) {
|
||||
if (tag === 'img' && code.includes(`alt="workflow-screenshot"`)) {
|
||||
return '';
|
||||
}
|
||||
|
@ -170,13 +166,13 @@ export default Vue.extend({
|
|||
onClick(event: MouseEvent) {
|
||||
let clickedLink = null;
|
||||
|
||||
if(event.target instanceof HTMLAnchorElement) {
|
||||
if (event.target instanceof HTMLAnchorElement) {
|
||||
clickedLink = event.target;
|
||||
}
|
||||
|
||||
if(event.target instanceof HTMLElement && event.target.matches('a *')) {
|
||||
if (event.target instanceof HTMLElement && event.target.matches('a *')) {
|
||||
const parentLink = event.target.closest('a');
|
||||
if(parentLink) {
|
||||
if (parentLink) {
|
||||
clickedLink = parentLink;
|
||||
}
|
||||
}
|
||||
|
@ -195,13 +191,17 @@ export default Vue.extend({
|
|||
line-height: var(--font-line-height-xloose);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
margin-bottom: var(--spacing-s);
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
h3, h4 {
|
||||
h3,
|
||||
h4 {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,8 @@ export default Vue.extend({
|
|||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
ul,
|
||||
ol {
|
||||
margin-bottom: var(--spacing-s);
|
||||
padding-left: var(--spacing-m);
|
||||
|
||||
|
@ -261,7 +262,10 @@ export default Vue.extend({
|
|||
.sticky {
|
||||
color: var(--color-text-dark);
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
margin-bottom: var(--spacing-2xs);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--font-line-height-loose);
|
||||
|
@ -275,7 +279,10 @@ export default Vue.extend({
|
|||
font-size: 24px;
|
||||
}
|
||||
|
||||
h3, h4, h5, h6 {
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: var(--font-size-m);
|
||||
}
|
||||
|
||||
|
@ -286,7 +293,8 @@ export default Vue.extend({
|
|||
line-height: var(--font-line-height-loose);
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
ul,
|
||||
ol {
|
||||
margin-bottom: var(--spacing-2xs);
|
||||
padding-left: var(--spacing-m);
|
||||
|
||||
|
@ -304,7 +312,9 @@ export default Vue.extend({
|
|||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
pre > code,li > code, p > code {
|
||||
pre > code,
|
||||
li > code,
|
||||
p > code {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
|
@ -317,7 +327,7 @@ export default Vue.extend({
|
|||
img {
|
||||
object-fit: contain;
|
||||
|
||||
&[src*="#full-width"] {
|
||||
&[src*='#full-width'] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import N8nMenu from './Menu.vue';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import N8nText from '../N8nText';
|
||||
import { StoryFn } from '@storybook/vue';
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Menu',
|
||||
component: N8nMenu,
|
||||
argTypes: {
|
||||
},
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
const methods = {
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
<template>
|
||||
<div :class="{
|
||||
['menu-container']: true,
|
||||
[$style.container]: true,
|
||||
[$style.menuCollapsed]: collapsed
|
||||
}">
|
||||
<div
|
||||
:class="{
|
||||
['menu-container']: true,
|
||||
[$style.container]: true,
|
||||
[$style.menuCollapsed]: collapsed,
|
||||
}"
|
||||
>
|
||||
<div v-if="$slots.header" :class="$style.menuHeader">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div :class="$style.menuContent">
|
||||
<div :class="{[$style.upperContent]: true, ['pt-xs']: $slots.menuPrefix }">
|
||||
<div :class="{ [$style.upperContent]: true, ['pt-xs']: $slots.menuPrefix }">
|
||||
<div v-if="$slots.menuPrefix" :class="$style.menuPrefix">
|
||||
<slot name="menuPrefix"></slot>
|
||||
</div>
|
||||
<el-menu
|
||||
:defaultActive="defaultActive"
|
||||
:collapse="collapsed"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<el-menu :defaultActive="defaultActive" :collapse="collapsed" v-on="$listeners">
|
||||
<n8n-menu-item
|
||||
v-for="item in upperMenuItems"
|
||||
:key="item.id"
|
||||
|
@ -30,11 +28,7 @@
|
|||
</el-menu>
|
||||
</div>
|
||||
<div :class="[$style.lowerContent, 'pb-2xs']">
|
||||
<el-menu
|
||||
:defaultActive="defaultActive"
|
||||
:collapse="collapsed"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<el-menu :defaultActive="defaultActive" :collapse="collapsed" v-on="$listeners">
|
||||
<n8n-menu-item
|
||||
v-for="item in lowerMenuItems"
|
||||
:key="item.id"
|
||||
|
@ -62,7 +56,6 @@ import ElMenu from 'element-ui/lib/menu';
|
|||
import N8nMenuItem from '../N8nMenuItem';
|
||||
|
||||
import Vue, { PropType } from 'vue';
|
||||
import { Route } from 'vue-router';
|
||||
import { IMenuItem } from '../../types';
|
||||
|
||||
export default Vue.extend({
|
||||
|
@ -108,23 +101,31 @@ export default Vue.extend({
|
|||
},
|
||||
mounted() {
|
||||
if (this.mode === 'router') {
|
||||
const found = this.items.find(item => {
|
||||
return Array.isArray(item.activateOnRouteNames) && item.activateOnRouteNames.includes(this.$route.name || '') ||
|
||||
Array.isArray(item.activateOnRoutePaths) && item.activateOnRoutePaths.includes(this.$route.path);
|
||||
const found = this.items.find((item) => {
|
||||
return (
|
||||
(Array.isArray(item.activateOnRouteNames) &&
|
||||
item.activateOnRouteNames.includes(this.$route.name ?? '')) ||
|
||||
(Array.isArray(item.activateOnRoutePaths) &&
|
||||
item.activateOnRoutePaths.includes(this.$route.path))
|
||||
);
|
||||
});
|
||||
this.activeTab = found ? found.id : '';
|
||||
} else {
|
||||
this.activeTab = this.items.length > 0 ? this.items[0].id : '';
|
||||
this.activeTab = this.items.length > 0 ? this.items[0].id : '';
|
||||
}
|
||||
|
||||
this.$emit('input', this.activeTab);
|
||||
},
|
||||
computed: {
|
||||
upperMenuItems(): IMenuItem[] {
|
||||
return this.items.filter((item: IMenuItem) => item.position === 'top' && item.available !== false);
|
||||
return this.items.filter(
|
||||
(item: IMenuItem) => item.position === 'top' && item.available !== false,
|
||||
);
|
||||
},
|
||||
lowerMenuItems(): IMenuItem[] {
|
||||
return this.items.filter((item: IMenuItem) => item.position === 'bottom' && item.available !== false);
|
||||
return this.items.filter(
|
||||
(item: IMenuItem) => item.position === 'bottom' && item.available !== false,
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -178,11 +179,13 @@ export default Vue.extend({
|
|||
|
||||
.menuCollapsed {
|
||||
transition: width 150ms ease-in-out;
|
||||
:global(.hideme) { display: none !important; }
|
||||
:global(.hideme) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.menuPrefix, .menuSuffix {
|
||||
.menuPrefix,
|
||||
.menuSuffix {
|
||||
padding: var(--spacing-xs) var(--spacing-l);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import N8nMenuItem from ".";
|
||||
import N8nMenuItem from '.';
|
||||
import ElMenu from 'element-ui/lib/menu';
|
||||
import { StoryFn } from '@storybook/vue';
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/MenuItem',
|
||||
|
@ -11,7 +11,7 @@ const template: StoryFn = (args, { argTypes }) => ({
|
|||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
ElMenu, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||
N8nMenuItem ,
|
||||
N8nMenuItem,
|
||||
},
|
||||
template: `
|
||||
<div style="width: 200px">
|
||||
|
|
|
@ -6,14 +6,19 @@
|
|||
:class="{
|
||||
[$style.submenu]: true,
|
||||
[$style.compact]: compact,
|
||||
[$style.active]: mode === 'router' && isItemActive(item)
|
||||
[$style.active]: mode === 'router' && isItemActive(item),
|
||||
}"
|
||||
:index="item.id"
|
||||
popper-append-to-body
|
||||
:popper-class="`${$style.submenuPopper} ${popperClass}`"
|
||||
>
|
||||
<template slot="title">
|
||||
<n8n-icon v-if="item.icon" :class="$style.icon" :icon="item.icon" :size="item.customIconSize || 'large'" />
|
||||
<n8n-icon
|
||||
v-if="item.icon"
|
||||
:class="$style.icon"
|
||||
:icon="item.icon"
|
||||
:size="item.customIconSize || 'large'"
|
||||
/>
|
||||
<span :class="$style.label">{{ item.label }}</span>
|
||||
</template>
|
||||
<el-menu-item
|
||||
|
@ -32,7 +37,13 @@
|
|||
<span :class="$style.label">{{ child.label }}</span>
|
||||
</el-menu-item>
|
||||
</el-submenu>
|
||||
<n8n-tooltip v-else placement="right" :content="item.label" :disabled="!compact" :open-delay="tooltipDelay">
|
||||
<n8n-tooltip
|
||||
v-else
|
||||
placement="right"
|
||||
:content="item.label"
|
||||
:disabled="!compact"
|
||||
:open-delay="tooltipDelay"
|
||||
>
|
||||
<el-menu-item
|
||||
:id="item.id"
|
||||
:class="{
|
||||
|
@ -40,12 +51,17 @@
|
|||
[$style.item]: true,
|
||||
[$style.disableActiveStyle]: !isItemActive(item),
|
||||
[$style.active]: isItemActive(item),
|
||||
[$style.compact]: compact
|
||||
[$style.compact]: compact,
|
||||
}"
|
||||
:index="item.id"
|
||||
@click="onItemClick(item)"
|
||||
>
|
||||
<n8n-icon v-if="item.icon" :class="$style.icon" :icon="item.icon" :size="item.customIconSize || 'large'" />
|
||||
<n8n-icon
|
||||
v-if="item.icon"
|
||||
:class="$style.icon"
|
||||
:icon="item.icon"
|
||||
:size="item.customIconSize || 'large'"
|
||||
/>
|
||||
<span :class="$style.label">{{ item.label }}</span>
|
||||
</el-menu-item>
|
||||
</n8n-tooltip>
|
||||
|
@ -58,8 +74,7 @@ import ElMenuItem from 'element-ui/lib/menu-item';
|
|||
import N8nTooltip from '../N8nTooltip';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import { IMenuItem } from '../../types';
|
||||
import Vue from 'vue';
|
||||
import { Route } from 'vue-router';
|
||||
import Vue, { PropType } from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'n8n-menu-item',
|
||||
|
@ -71,7 +86,7 @@ export default Vue.extend({
|
|||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object as () => IMenuItem,
|
||||
type: Object as PropType<IMenuItem>,
|
||||
required: true,
|
||||
},
|
||||
compact: {
|
||||
|
@ -97,21 +112,30 @@ export default Vue.extend({
|
|||
},
|
||||
computed: {
|
||||
availableChildren(): IMenuItem[] {
|
||||
return Array.isArray(this.item.children) ? this.item.children.filter(child => child.available !== false) : [];
|
||||
return Array.isArray(this.item.children)
|
||||
? this.item.children.filter((child) => child.available !== false)
|
||||
: [];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isItemActive(item: IMenuItem): boolean {
|
||||
const isItemActive = this.isActive(item);
|
||||
const hasActiveChild = Array.isArray(item.children) && item.children.some(child => this.isActive(child));
|
||||
const hasActiveChild =
|
||||
Array.isArray(item.children) && item.children.some((child) => this.isActive(child));
|
||||
return isItemActive || hasActiveChild;
|
||||
},
|
||||
isActive(item: IMenuItem): boolean {
|
||||
if (this.mode === 'router') {
|
||||
if (item.activateOnRoutePaths) {
|
||||
return Array.isArray(item.activateOnRoutePaths) && item.activateOnRoutePaths.includes(this.$route.path);
|
||||
return (
|
||||
Array.isArray(item.activateOnRoutePaths) &&
|
||||
item.activateOnRoutePaths.includes(this.$route.path)
|
||||
);
|
||||
} else if (item.activateOnRouteNames) {
|
||||
return Array.isArray(item.activateOnRouteNames) && item.activateOnRouteNames.includes(this.$route.name || '');
|
||||
return (
|
||||
Array.isArray(item.activateOnRouteNames) &&
|
||||
item.activateOnRouteNames.includes(this.$route.name ?? '')
|
||||
);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
|
@ -127,22 +151,20 @@ export default Vue.extend({
|
|||
|
||||
if (item.properties.newWindow) {
|
||||
window.open(href);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
window.location.assign(item.properties.href);
|
||||
}
|
||||
|
||||
}
|
||||
this.$emit('click', event, item.id);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
// Element menu-item overrides
|
||||
:global(.el-menu-item), :global(.el-submenu__title) {
|
||||
:global(.el-menu-item),
|
||||
:global(.el-submenu__title) {
|
||||
--menu-font-color: var(--color-text-base);
|
||||
--menu-item-active-background-color: var(--color-foreground-base);
|
||||
--menu-item-active-font-color: var(--color-text-dark);
|
||||
|
@ -152,7 +174,6 @@ export default Vue.extend({
|
|||
--submenu-item-height: 27px;
|
||||
}
|
||||
|
||||
|
||||
.submenu {
|
||||
background: none !important;
|
||||
|
||||
|
@ -177,7 +198,9 @@ export default Vue.extend({
|
|||
}
|
||||
|
||||
&:hover {
|
||||
.icon { color: var(--color-text-dark) }
|
||||
.icon {
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,9 +212,11 @@ export default Vue.extend({
|
|||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
.icon { color: var(--color-text-dark) }
|
||||
.icon {
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
.disableActiveStyle {
|
||||
|
@ -214,10 +239,13 @@ export default Vue.extend({
|
|||
}
|
||||
|
||||
.active {
|
||||
&, & :global(.el-submenu__title) {
|
||||
&,
|
||||
& :global(.el-submenu__title) {
|
||||
background-color: var(--color-foreground-base);
|
||||
border-radius: var(--border-radius-base);
|
||||
.icon { color: var(--color-text-dark) }
|
||||
.icon {
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* tslint:disable:variable-name */
|
||||
import N8nNodeIcon from "./NodeIcon.vue";
|
||||
import { StoryFn } from '@storybook/vue';
|
||||
import N8nNodeIcon from './NodeIcon.vue';
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/NodeIcon',
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<font-awesome-icon v-else :icon="name" :style="fontStyleData" />
|
||||
</div>
|
||||
<div v-else :class="$style['node-icon-placeholder']">
|
||||
{{ nodeTypeName? nodeTypeName.charAt(0) : '?' }}
|
||||
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
|
||||
?
|
||||
</div>
|
||||
</n8n-tooltip>
|
||||
|
@ -39,8 +39,7 @@ export default Vue.extend({
|
|||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: (value: string): boolean =>
|
||||
['file', 'icon', 'unknown'].includes(value),
|
||||
validator: (value: string): boolean => ['file', 'icon', 'unknown'].includes(value),
|
||||
},
|
||||
src: {
|
||||
type: String,
|
||||
|
@ -68,8 +67,8 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
iconStyleData (): object {
|
||||
if(!this.size) {
|
||||
iconStyleData(): object {
|
||||
if (!this.size) {
|
||||
return {
|
||||
color: this.color || '',
|
||||
};
|
||||
|
@ -82,7 +81,7 @@ export default Vue.extend({
|
|||
'line-height': `${this.size}px`,
|
||||
};
|
||||
},
|
||||
fontStyleData (): object {
|
||||
fontStyleData(): object {
|
||||
return {
|
||||
'max-width': `${this.size}px`,
|
||||
};
|
||||
|
@ -130,6 +129,6 @@ export default Vue.extend({
|
|||
.disabled {
|
||||
color: '#ccc';
|
||||
-webkit-filter: contrast(40%) brightness(1.5) grayscale(100%);
|
||||
filter: contrast(40%) brightness(1.5) grayscale(100%);
|
||||
filter: contrast(40%) brightness(1.5) grayscale(100%);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
/* tslint:disable:variable-name */
|
||||
|
||||
import N8nNotice from './Notice.vue';
|
||||
import {StoryFn} from "@storybook/vue";
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Notice',
|
||||
|
@ -14,7 +12,7 @@ export default {
|
|||
},
|
||||
};
|
||||
|
||||
const SlotTemplate: StoryFn = (args, {argTypes}) => ({
|
||||
const SlotTemplate: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nNotice,
|
||||
|
@ -22,7 +20,7 @@ const SlotTemplate: StoryFn = (args, {argTypes}) => ({
|
|||
template: `<n8n-notice v-bind="$props">This is a notice! Thread carefully from this point forward.</n8n-notice>`,
|
||||
});
|
||||
|
||||
const PropTemplate: StoryFn = (args, {argTypes}) => ({
|
||||
const PropTemplate: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nNotice,
|
||||
|
@ -53,19 +51,22 @@ Info.args = {
|
|||
export const Sanitized = PropTemplate.bind({});
|
||||
Sanitized.args = {
|
||||
theme: 'warning',
|
||||
content: '<script>alert(1)</script> This content contains a script tag and is <strong>sanitized</strong>.',
|
||||
content:
|
||||
'<script>alert(1)</script> This content contains a script tag and is <strong>sanitized</strong>.',
|
||||
};
|
||||
|
||||
export const Truncated = PropTemplate.bind({});
|
||||
Truncated.args = {
|
||||
theme: 'warning',
|
||||
truncate: true,
|
||||
content: 'This content is long and will be truncated at 150 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
||||
content:
|
||||
'This content is long and will be truncated at 150 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
||||
};
|
||||
|
||||
export const HtmlEdgeCase = PropTemplate.bind({});
|
||||
HtmlEdgeCase.args = {
|
||||
theme: 'warning',
|
||||
truncate: true,
|
||||
content: 'This content is long and will be truncated at 150 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod <a href="">read the documentation</a> ut labore et dolore magna aliqua.',
|
||||
content:
|
||||
'This content is long and will be truncated at 150 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod <a href="">read the documentation</a> ut labore et dolore magna aliqua.',
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div :id="id" :class="classes" role="alert" @click=onClick>
|
||||
<div :id="id" :class="classes" role="alert" @click="onClick">
|
||||
<div class="notice-content">
|
||||
<n8n-text size="small" :compact="true">
|
||||
<slot>
|
||||
|
@ -18,16 +18,14 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import N8nText from "../../components/N8nText";
|
||||
import Locale from "../../mixins/locale";
|
||||
import { uid } from "../../utils";
|
||||
import N8nText from '../../components/N8nText';
|
||||
import Locale from '../../mixins/locale';
|
||||
import { uid } from '../../utils';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'n8n-notice',
|
||||
directives: {},
|
||||
mixins: [
|
||||
Locale,
|
||||
],
|
||||
mixins: [Locale],
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
|
@ -56,11 +54,7 @@ export default Vue.extend({
|
|||
},
|
||||
computed: {
|
||||
classes(): string[] {
|
||||
return [
|
||||
'notice',
|
||||
this.$style.notice,
|
||||
this.$style[this.theme],
|
||||
];
|
||||
return ['notice', this.$style.notice, this.$style[this.theme]];
|
||||
},
|
||||
canTruncate(): boolean {
|
||||
return this.fullContent !== undefined;
|
||||
|
@ -71,18 +65,16 @@ export default Vue.extend({
|
|||
this.showFullContent = !this.showFullContent;
|
||||
},
|
||||
sanitizeHtml(text: string): string {
|
||||
return sanitizeHtml(
|
||||
text, {
|
||||
allowedAttributes: { a: ['data-key', 'href', 'target'] },
|
||||
},
|
||||
);
|
||||
return sanitizeHtml(text, {
|
||||
allowedAttributes: { a: ['data-key', 'href', 'target'] },
|
||||
});
|
||||
},
|
||||
onClick(event: MouseEvent) {
|
||||
if (!(event.target instanceof HTMLElement)) return;
|
||||
|
||||
if (event.target.localName !== 'a') return;
|
||||
|
||||
if (event.target.dataset && event.target.dataset.key) {
|
||||
if (event.target.dataset?.key) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
|
@ -97,7 +89,6 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { render } from '@testing-library/vue';
|
||||
import N8nNotice from "../Notice.vue";
|
||||
import N8nNotice from '../Notice.vue';
|
||||
|
||||
describe('components', () => {
|
||||
describe('N8nNotice', () => {
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
/* tslint:disable:variable-name */
|
||||
|
||||
import N8nPulse from './Pulse.vue';
|
||||
import { StoryFn } from "@storybook/vue";
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Pulse',
|
||||
component: N8nPulse,
|
||||
argTypes: {
|
||||
},
|
||||
argTypes: {},
|
||||
parameters: {
|
||||
backgrounds: { default: '--color-background-light' },
|
||||
},
|
||||
};
|
||||
|
||||
export const Default: StoryFn = (args, {argTypes}) => ({
|
||||
export const Default: StoryFn = () => ({
|
||||
components: {
|
||||
N8nPulse,
|
||||
},
|
||||
|
|
|
@ -17,7 +17,6 @@ export default Vue.extend({
|
|||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
||||
$--light-pulse-color: hsla(
|
||||
var(--color-primary-h),
|
||||
var(--color-primary-s),
|
||||
|
@ -112,5 +111,4 @@ $--dark-pulse-color: hsla(
|
|||
box-shadow: 0 0 0 0 transparent;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,26 @@
|
|||
<template>
|
||||
<label role="radio" tabindex="-1" :class="{'n8n-radio-button': true, [$style.container]: true, [$style.hoverable]: !this.disabled}" aria-checked="true">
|
||||
<input type="radio" tabindex="-1" autocomplete="off" :class="$style.input" :value="value">
|
||||
<div :class="{[$style.button]: true, [$style.active]: active, [$style[size]]: true, [$style.disabled]: disabled}" @click="$emit('click')">{{ label }}</div>
|
||||
<label
|
||||
role="radio"
|
||||
tabindex="-1"
|
||||
:class="{
|
||||
'n8n-radio-button': true,
|
||||
[$style.container]: true,
|
||||
[$style.hoverable]: !this.disabled,
|
||||
}"
|
||||
aria-checked="true"
|
||||
>
|
||||
<input type="radio" tabindex="-1" autocomplete="off" :class="$style.input" :value="value" />
|
||||
<div
|
||||
:class="{
|
||||
[$style.button]: true,
|
||||
[$style.active]: active,
|
||||
[$style[size]]: true,
|
||||
[$style.disabled]: disabled,
|
||||
}"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
{{ label }}
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
|
@ -26,8 +45,7 @@ export default Vue.extend({
|
|||
size: {
|
||||
type: String,
|
||||
default: 'medium',
|
||||
validator: (value: string): boolean =>
|
||||
['small', 'medium'].includes(value),
|
||||
validator: (value: string): boolean => ['small', 'medium'].includes(value),
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
|
|
|
@ -25,8 +25,7 @@ const Template = (args, { argTypes }) => ({
|
|||
components: {
|
||||
N8nRadioButtons,
|
||||
},
|
||||
template:
|
||||
`<n8n-radio-buttons v-model="val" v-bind="$props" @input="onInput">
|
||||
template: `<n8n-radio-buttons v-model="val" v-bind="$props" @input="onInput">
|
||||
</n8n-radio-buttons>`,
|
||||
methods,
|
||||
data() {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<div role="radiogroup" :class="{'n8n-radio-buttons': true, [$style.radioGroup]: true, [$style.disabled]: disabled}">
|
||||
<div
|
||||
role="radiogroup"
|
||||
:class="{ 'n8n-radio-buttons': true, [$style.radioGroup]: true, [$style.disabled]: disabled }"
|
||||
>
|
||||
<RadioButton
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
|
@ -23,8 +26,7 @@ export default Vue.extend({
|
|||
value: {
|
||||
type: String,
|
||||
},
|
||||
options: {
|
||||
},
|
||||
options: {},
|
||||
size: {
|
||||
type: String,
|
||||
},
|
||||
|
@ -36,7 +38,7 @@ export default Vue.extend({
|
|||
RadioButton,
|
||||
},
|
||||
methods: {
|
||||
onClick(option: {label: string, value: string, disabled?: boolean}) {
|
||||
onClick(option: { label: string; value: string; disabled?: boolean }) {
|
||||
if (this.disabled || option.disabled) {
|
||||
return;
|
||||
}
|
||||
|
@ -47,7 +49,6 @@ export default Vue.extend({
|
|||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
||||
.radioGroup {
|
||||
display: inline-flex;
|
||||
line-height: 1;
|
||||
|
@ -61,6 +62,4 @@ export default Vue.extend({
|
|||
.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
|
|
@ -26,7 +26,8 @@ const Template = (args, { argTypes }) => ({
|
|||
return {
|
||||
newWidth: this.width,
|
||||
newHeight: this.height,
|
||||
background: "linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%)",
|
||||
background:
|
||||
'linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%)',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -38,8 +39,7 @@ const Template = (args, { argTypes }) => ({
|
|||
};
|
||||
},
|
||||
},
|
||||
template:
|
||||
`<div style="width: fit-content; height: fit-content">
|
||||
template: `<div style="width: fit-content; height: fit-content">
|
||||
<n8n-resize-wrapper
|
||||
v-bind="$props"
|
||||
@resize="onResize"
|
||||
|
@ -65,13 +65,13 @@ Resize.args = {
|
|||
gridSize: 20,
|
||||
isResizingEnabled: true,
|
||||
supportedDirections: [
|
||||
"right",
|
||||
"top",
|
||||
"bottom",
|
||||
"left",
|
||||
"topLeft",
|
||||
"topRight",
|
||||
"bottomLeft",
|
||||
"bottomRight",
|
||||
'right',
|
||||
'top',
|
||||
'bottom',
|
||||
'left',
|
||||
'topLeft',
|
||||
'topRight',
|
||||
'bottomLeft',
|
||||
'bottomRight',
|
||||
],
|
||||
};
|
||||
|
|
|
@ -19,11 +19,9 @@ function closestNumber(value: number, divisor: number): number {
|
|||
const q = value / divisor;
|
||||
const n1 = divisor * q;
|
||||
|
||||
const n2 = (value * divisor) > 0 ?
|
||||
(divisor * (q + 1)) : (divisor * (q - 1));
|
||||
const n2 = value * divisor > 0 ? divisor * (q + 1) : divisor * (q - 1);
|
||||
|
||||
if (Math.abs(value - n1) < Math.abs(value - n2))
|
||||
return n1;
|
||||
if (Math.abs(value - n1) < Math.abs(value - n2)) return n1;
|
||||
|
||||
return n2;
|
||||
}
|
||||
|
@ -35,7 +33,7 @@ function getSize(min: number, virtual: number, gridSize: number): number {
|
|||
}
|
||||
|
||||
return min;
|
||||
};
|
||||
}
|
||||
|
||||
const directionsCursorMaps: { [key: string]: string } = {
|
||||
right: 'ew-resize',
|
||||
|
@ -43,7 +41,7 @@ const directionsCursorMaps: { [key: string]: string } = {
|
|||
bottom: 'ns-resize',
|
||||
left: 'ew-resize',
|
||||
topLeft: 'nw-resize',
|
||||
topRight : 'ne-resize',
|
||||
topRight: 'ne-resize',
|
||||
bottomLeft: 'sw-resize',
|
||||
bottomRight: 'se-resize',
|
||||
};
|
||||
|
@ -95,8 +93,8 @@ export default Vue.extend({
|
|||
enabledDirections() {
|
||||
const availableDirections = Object.keys(directionsCursorMaps);
|
||||
|
||||
if(!this.isResizingEnabled) return [];
|
||||
if(this.supportedDirections.length === 0) return availableDirections;
|
||||
if (!this.isResizingEnabled) return [];
|
||||
if (this.supportedDirections.length === 0) return availableDirections;
|
||||
|
||||
return this.supportedDirections;
|
||||
},
|
||||
|
@ -156,7 +154,7 @@ export default Vue.extend({
|
|||
const width = getSize(this.minWidth, this.vWidth, this.gridSize);
|
||||
|
||||
const dX = left && width !== this.width ? -1 * (width - this.width) : 0;
|
||||
const dY = top && height !== this.height ? -1 * (height - this.height): 0;
|
||||
const dY = top && height !== this.height ? -1 * (height - this.height) : 0;
|
||||
const x = event.x;
|
||||
const y = event.y;
|
||||
const direction = this.dir;
|
||||
|
@ -204,7 +202,7 @@ export default Vue.extend({
|
|||
height: 12px;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
cursor: ns-resize;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
|
|
|
@ -1,18 +1,9 @@
|
|||
<template>
|
||||
<span>
|
||||
<router-link
|
||||
v-if="useRouterLink"
|
||||
:to="to"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<router-link v-if="useRouterLink" :to="to" v-on="$listeners">
|
||||
<slot></slot>
|
||||
</router-link>
|
||||
<a
|
||||
v-else
|
||||
:href="to"
|
||||
:target="openNewWindow ? '_blank': '_self'"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<a v-else :href="to" :target="openNewWindow ? '_blank' : '_self'" v-on="$listeners">
|
||||
<slot></slot>
|
||||
</a>
|
||||
</span>
|
||||
|
@ -56,4 +47,3 @@ export default Vue.extend({
|
|||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
/* tslint:disable:variable-name */
|
||||
|
||||
import { StoryFn } from '@storybook/vue';
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
import N8nSelect from './Select.vue';
|
||||
import N8nOption from '../N8nOption';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
|
@ -54,7 +52,8 @@ const Template: StoryFn = (args, { argTypes }) => ({
|
|||
N8nOption,
|
||||
N8nIcon,
|
||||
},
|
||||
template: '<n8n-select v-bind="$props" v-model="val" @input="onInput" @change="onChange"><n8n-option value="1">op1</n8n-option><n8n-option value="2">op2</n8n-option></n8n-select>',
|
||||
template:
|
||||
'<n8n-select v-bind="$props" v-model="val" @input="onInput" @change="onChange"><n8n-option value="1">op1</n8n-option><n8n-option value="2">op2</n8n-option></n8n-select>',
|
||||
data() {
|
||||
return {
|
||||
val: '',
|
||||
|
@ -71,7 +70,12 @@ Filterable.args = {
|
|||
defaultFirstOption: true,
|
||||
};
|
||||
|
||||
const selects = ['large', 'medium', 'small', 'mini'].map((size) => `<n8n-select v-bind="$props" v-model="val" @input="onInput" @change="onChange" size="${size}"><n8n-option value="1">op1</n8n-option><n8n-option value="2">op2</n8n-option></n8n-select>`).join('');
|
||||
const selects = ['large', 'medium', 'small', 'mini']
|
||||
.map(
|
||||
(size) =>
|
||||
`<n8n-select v-bind="$props" v-model="val" @input="onInput" @change="onChange" size="${size}"><n8n-option value="1">op1</n8n-option><n8n-option value="2">op2</n8n-option></n8n-select>`,
|
||||
)
|
||||
.join('');
|
||||
|
||||
const ManyTemplate: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
|
@ -96,7 +100,12 @@ Sizes.args = {
|
|||
placeholder: 'placeholder...',
|
||||
};
|
||||
|
||||
const selectsWithIcon = ['xlarge', 'large', 'medium', 'small', 'mini'].map((size) => `<n8n-select v-bind="$props" v-model="val" @input="onInput" size="${size}"><n8n-icon icon="search" slot="prefix" /><n8n-option value="1">op1</n8n-option><n8n-option value="2">op2</n8n-option></n8n-select>`).join('');
|
||||
const selectsWithIcon = ['xlarge', 'large', 'medium', 'small', 'mini']
|
||||
.map(
|
||||
(size) =>
|
||||
`<n8n-select v-bind="$props" v-model="val" @input="onInput" size="${size}"><n8n-icon icon="search" slot="prefix" /><n8n-option value="1">op1</n8n-option><n8n-option value="2">op2</n8n-option></n8n-select>`,
|
||||
)
|
||||
.join('');
|
||||
|
||||
const ManyTemplateWithIcon: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
|
@ -121,7 +130,6 @@ WithIcon.args = {
|
|||
placeholder: 'placeholder...',
|
||||
};
|
||||
|
||||
|
||||
const LimitedWidthTemplate: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
|
@ -129,7 +137,8 @@ const LimitedWidthTemplate: StoryFn = (args, { argTypes }) => ({
|
|||
N8nOption,
|
||||
N8nIcon,
|
||||
},
|
||||
template: '<div style="width:100px;"><n8n-select v-bind="$props" v-model="val" @input="onInput" @change="onChange"><n8n-option value="1" label="opt1 11 1111" /><n8n-option value="2" label="opt2 test very long ipsum"/></n8n-select></div>',
|
||||
template:
|
||||
'<div style="width:100px;"><n8n-select v-bind="$props" v-model="val" @input="onInput" @change="onChange"><n8n-option value="1" label="opt1 11 1111" /><n8n-option value="2" label="opt2 test very long ipsum"/></n8n-select></div>',
|
||||
data() {
|
||||
return {
|
||||
val: '',
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
<template>
|
||||
<div :class="{'n8n-select': true, [$style.container]: true, [$style.withPrepend]: !!$slots.prepend}">
|
||||
<div
|
||||
:class="{
|
||||
'n8n-select': true,
|
||||
[$style.container]: true,
|
||||
[$style.withPrepend]: !!$slots.prepend,
|
||||
}"
|
||||
>
|
||||
<div v-if="$slots.prepend" :class="$style.prepend">
|
||||
<slot name="prepend" />
|
||||
</div>
|
||||
|
@ -41,8 +47,7 @@ export default Vue.extend({
|
|||
ElSelect, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
},
|
||||
value: {},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'large',
|
||||
|
@ -112,21 +117,21 @@ export default Vue.extend({
|
|||
},
|
||||
methods: {
|
||||
focus() {
|
||||
const select = this.$refs.innerSelect as Vue & HTMLElement | undefined;
|
||||
const select = this.$refs.innerSelect as (Vue & HTMLElement) | undefined;
|
||||
if (select) {
|
||||
select.focus();
|
||||
}
|
||||
},
|
||||
blur() {
|
||||
const select = this.$refs.innerSelect as Vue & HTMLElement | undefined;
|
||||
const select = this.$refs.innerSelect as (Vue & HTMLElement) | undefined;
|
||||
if (select) {
|
||||
select.blur();
|
||||
}
|
||||
},
|
||||
focusOnInput() {
|
||||
const select = this.$refs.innerSelect as Vue & HTMLElement | undefined;
|
||||
const select = this.$refs.innerSelect as (Vue & HTMLElement) | undefined;
|
||||
if (select) {
|
||||
const input = select.$refs.input as Vue & HTMLElement | undefined;
|
||||
const input = select.$refs.input as (Vue & HTMLElement) | undefined;
|
||||
if (input) {
|
||||
input.focus();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {render} from '@testing-library/vue';
|
||||
import N8nSelect from "../Select.vue";
|
||||
import N8nOption from "../../N8nOption/Option.vue";
|
||||
import { render } from '@testing-library/vue';
|
||||
import N8nSelect from '../Select.vue';
|
||||
import N8nOption from '../../N8nOption/Option.vue';
|
||||
|
||||
describe('components', () => {
|
||||
describe('N8nSelect', () => {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<span class="n8n-spinner">
|
||||
<div v-if="type === 'ring'" class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||
<n8n-icon
|
||||
v-else
|
||||
icon="spinner"
|
||||
:size="size"
|
||||
spin
|
||||
/>
|
||||
<div v-if="type === 'ring'" class="lds-ring">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<n8n-icon v-else icon="spinner" :size="size" spin />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
@ -23,13 +23,13 @@ export default Vue.extend({
|
|||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
validator (value: string): boolean {
|
||||
validator(value: string): boolean {
|
||||
return ['small', 'medium', 'large'].includes(value);
|
||||
},
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
validator (value: string): boolean {
|
||||
validator(value: string): boolean {
|
||||
return ['dots', 'ring'].includes(value);
|
||||
},
|
||||
default: 'dots',
|
||||
|
@ -40,38 +40,37 @@ export default Vue.extend({
|
|||
|
||||
<style lang="scss">
|
||||
.lds-ring {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
.lds-ring div {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 4px solid var(--color-foreground-xlight);
|
||||
border-radius: 50%;
|
||||
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
border-color: var(--color-primary) transparent transparent transparent;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 4px solid var(--color-foreground-xlight);
|
||||
border-radius: 50%;
|
||||
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
border-color: var(--color-primary) transparent transparent transparent;
|
||||
}
|
||||
.lds-ring div:nth-child(1) {
|
||||
animation-delay: -0.45s;
|
||||
animation-delay: -0.45s;
|
||||
}
|
||||
.lds-ring div:nth-child(2) {
|
||||
animation-delay: -0.3s;
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
.lds-ring div:nth-child(3) {
|
||||
animation-delay: -0.15s;
|
||||
animation-delay: -0.15s;
|
||||
}
|
||||
@keyframes lds-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{'n8n-sticky': true, [$style.sticky]: true, [$style.clickable]: !isResizing}"
|
||||
:class="{ 'n8n-sticky': true, [$style.sticky]: true, [$style.clickable]: !isResizing }"
|
||||
:style="styles"
|
||||
@keydown.prevent
|
||||
>
|
||||
|
@ -39,7 +39,7 @@
|
|||
@keydown.stop
|
||||
@wheel.stop
|
||||
class="sticky-textarea ph-no-capture"
|
||||
:class="{'full-height': !shouldShowFooter}"
|
||||
:class="{ 'full-height': !shouldShowFooter }"
|
||||
>
|
||||
<n8n-input
|
||||
:value="content"
|
||||
|
@ -49,13 +49,9 @@
|
|||
@input="onInput"
|
||||
ref="input"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div v-if="editMode && shouldShowFooter" :class="$style.footer">
|
||||
<n8n-text
|
||||
size="xsmall"
|
||||
aligh="right"
|
||||
>
|
||||
<n8n-text size="xsmall" aligh="right">
|
||||
<span v-html="t('sticky.markdownHint')"></span>
|
||||
</n8n-text>
|
||||
</div>
|
||||
|
@ -142,7 +138,7 @@ export default mixins(Locale).extend({
|
|||
}
|
||||
return this.width;
|
||||
},
|
||||
styles(): { height: string, width: string } {
|
||||
styles(): { height: string; width: string } {
|
||||
return {
|
||||
height: `${this.resHeight}px`,
|
||||
width: `${this.resWidth}px`,
|
||||
|
@ -184,10 +180,7 @@ export default mixins(Locale).extend({
|
|||
watch: {
|
||||
editMode(newMode, prevMode) {
|
||||
setTimeout(() => {
|
||||
if (newMode &&
|
||||
!prevMode &&
|
||||
this.$refs.input
|
||||
) {
|
||||
if (newMode && !prevMode && this.$refs.input) {
|
||||
const textarea = this.$refs.input as HTMLTextAreaElement;
|
||||
if (this.defaultText === this.content) {
|
||||
textarea.select();
|
||||
|
@ -195,7 +188,6 @@ export default mixins(Locale).extend({
|
|||
textarea.focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -227,7 +219,12 @@ export default mixins(Locale).extend({
|
|||
left: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
background: linear-gradient(180deg, var(--color-sticky-default-background), #fff5d600 0.01%, var(--color-sticky-default-background));
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--color-sticky-default-background),
|
||||
#fff5d600 0.01%,
|
||||
var(--color-sticky-default-background)
|
||||
);
|
||||
border-radius: var(--border-radius-base);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,7 @@ import { action } from '@storybook/addon-actions';
|
|||
export default {
|
||||
title: 'Atoms/Tabs',
|
||||
component: N8nTabs,
|
||||
argTypes: {
|
||||
},
|
||||
argTypes: {},
|
||||
parameters: {
|
||||
backgrounds: { default: '--color-background-xlight' },
|
||||
},
|
||||
|
@ -21,8 +20,7 @@ const Template = (args, { argTypes }) => ({
|
|||
components: {
|
||||
N8nTabs,
|
||||
},
|
||||
template:
|
||||
`<n8n-tabs v-model="val" v-bind="$props" @input="onInput">
|
||||
template: `<n8n-tabs v-model="val" v-bind="$props" @input="onInput">
|
||||
</n8n-tabs>`,
|
||||
methods,
|
||||
data() {
|
||||
|
|
|
@ -7,13 +7,18 @@
|
|||
<n8n-icon icon="chevron-right" size="small" />
|
||||
</div>
|
||||
<div ref="tabs" :class="$style.tabs">
|
||||
<div v-for="option in options"
|
||||
<div
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
:id="option.value"
|
||||
:class="{ [$style.alignRight]: option.align === 'right' }"
|
||||
>
|
||||
<n8n-tooltip :disabled="!option.tooltip" placement="bottom">
|
||||
<div slot="content" v-html="option.tooltip" @click="handleTooltipClick(option.value, $event)"></div>
|
||||
<div
|
||||
slot="content"
|
||||
v-html="option.tooltip"
|
||||
@click="handleTooltipClick(option.value, $event)"
|
||||
></div>
|
||||
<a
|
||||
v-if="option.href"
|
||||
target="_blank"
|
||||
|
@ -23,7 +28,9 @@
|
|||
>
|
||||
<div>
|
||||
{{ option.label }}
|
||||
<span :class="$style.external"><n8n-icon icon="external-link-alt" size="small" /></span>
|
||||
<span :class="$style.external"
|
||||
><n8n-icon icon="external-link-alt" size="small"
|
||||
/></span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
@ -56,8 +63,7 @@ export default Vue.extend({
|
|||
container.addEventListener('scroll', (event: Event) => {
|
||||
const width = container.clientWidth;
|
||||
const scrollWidth = container.scrollWidth;
|
||||
// @ts-ignore
|
||||
this.scrollPosition = event.srcElement.scrollLeft; // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||
this.scrollPosition = (event.target as Element).scrollLeft;
|
||||
|
||||
this.canScrollRight = scrollWidth - width > this.scrollPosition;
|
||||
});
|
||||
|
@ -87,10 +93,8 @@ export default Vue.extend({
|
|||
};
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
},
|
||||
options: {
|
||||
},
|
||||
value: {},
|
||||
options: {},
|
||||
},
|
||||
methods: {
|
||||
handleTooltipClick(tab: string, event: MouseEvent) {
|
||||
|
@ -106,7 +110,9 @@ export default Vue.extend({
|
|||
this.scroll(50);
|
||||
},
|
||||
scroll(left: number) {
|
||||
const container = this.$refs.tabs as (HTMLDivElement & { scrollBy: ScrollByFunction }) | undefined;
|
||||
const container = this.$refs.tabs as
|
||||
| (HTMLDivElement & { scrollBy: ScrollByFunction })
|
||||
| undefined;
|
||||
if (container) {
|
||||
container.scrollBy({ left, top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
@ -114,11 +120,13 @@ export default Vue.extend({
|
|||
},
|
||||
});
|
||||
|
||||
type ScrollByFunction = (arg: { left: number, top: number, behavior: 'smooth' | 'instant' | 'auto' }) => void;
|
||||
|
||||
type ScrollByFunction = (arg: {
|
||||
left: number;
|
||||
top: number;
|
||||
behavior: 'smooth' | 'instant' | 'auto';
|
||||
}) => void;
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
position: relative;
|
||||
|
@ -141,8 +149,8 @@ type ScrollByFunction = (arg: { left: number, top: number, behavior: 'smooth' |
|
|||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.tab {
|
||||
|
@ -205,5 +213,4 @@ type ScrollByFunction = (arg: { left: number, top: number, behavior: 'smooth' |
|
|||
composes: button;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -17,8 +17,7 @@ const Template = (args, { argTypes }) => ({
|
|||
components: {
|
||||
N8nTag,
|
||||
},
|
||||
template:
|
||||
'<n8n-tag v-bind="$props"></n8n-tag>',
|
||||
template: '<n8n-tag v-bind="$props"></n8n-tag>',
|
||||
});
|
||||
|
||||
export const Tag = Template.bind({});
|
||||
|
|
|
@ -29,7 +29,11 @@ export default Vue.extend({
|
|||
transition: background-color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: hsl(var(--color-background-base-h), var(--color-background-base-s), calc(var(--color-background-base-l) - 4%));
|
||||
background-color: hsl(
|
||||
var(--color-background-base-h),
|
||||
var(--color-background-base-s),
|
||||
calc(var(--color-background-base-l) - 4%)
|
||||
);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,8 +3,7 @@ import N8nTags from './Tags.vue';
|
|||
export default {
|
||||
title: 'Atoms/Tags',
|
||||
component: N8nTags,
|
||||
argTypes: {
|
||||
},
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
|
@ -12,8 +11,7 @@ const Template = (args, { argTypes }) => ({
|
|||
components: {
|
||||
N8nTags,
|
||||
},
|
||||
template:
|
||||
'<n8n-tags v-bind="$props"></n8n-tags>',
|
||||
template: '<n8n-tags v-bind="$props"></n8n-tags>',
|
||||
});
|
||||
|
||||
export const Tags = Template.bind({});
|
||||
|
@ -34,7 +32,6 @@ Tags.args = {
|
|||
],
|
||||
};
|
||||
|
||||
|
||||
export const Truncated = Template.bind({});
|
||||
Truncated.args = {
|
||||
truncate: true,
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
<template>
|
||||
<div :class="['n8n-tags', $style.tags]">
|
||||
<n8n-tag v-for="tag in visibleTags" :key="tag.id" :text="tag.name" @click="$emit('click', tag.id, $event)"/>
|
||||
<n8n-tag
|
||||
v-for="tag in visibleTags"
|
||||
:key="tag.id"
|
||||
:text="tag.name"
|
||||
@click="$emit('click', tag.id, $event)"
|
||||
/>
|
||||
<n8n-link
|
||||
v-if="truncate && !showAll && hiddenTagsLength > 0"
|
||||
theme="text"
|
||||
|
@ -16,9 +21,9 @@
|
|||
<script lang="ts">
|
||||
import N8nTag from '../N8nTag';
|
||||
import N8nLink from '../N8nLink';
|
||||
import Locale from "../../mixins/locale";
|
||||
import Vue, {PropType} from 'vue';
|
||||
import mixins from "vue-typed-mixins";
|
||||
import Locale from '../../mixins/locale';
|
||||
import { PropType } from 'vue';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
interface ITag {
|
||||
id: string;
|
||||
|
|
|
@ -13,7 +13,15 @@ export default {
|
|||
color: {
|
||||
control: {
|
||||
type: 'select',
|
||||
options: ['primary', 'text-dark', 'text-base', 'text-light', 'text-xlight', 'danger', 'success'],
|
||||
options: [
|
||||
'primary',
|
||||
'text-dark',
|
||||
'text-base',
|
||||
'text-light',
|
||||
'text-xlight',
|
||||
'danger',
|
||||
'success',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -17,11 +17,22 @@ export default Vue.extend({
|
|||
size: {
|
||||
type: String,
|
||||
default: 'medium',
|
||||
validator: (value: string): boolean => ['xsmall', 'small', 'mini', 'medium', 'large', 'xlarge'].includes(value),
|
||||
validator: (value: string): boolean =>
|
||||
['xsmall', 'small', 'mini', 'medium', 'large', 'xlarge'].includes(value),
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
validator: (value: string): boolean => ['primary', 'text-dark', 'text-base', 'text-light', 'text-xlight', 'danger', 'success', 'warning'].includes(value),
|
||||
validator: (value: string): boolean =>
|
||||
[
|
||||
'primary',
|
||||
'text-dark',
|
||||
'text-base',
|
||||
'text-light',
|
||||
'text-xlight',
|
||||
'danger',
|
||||
'success',
|
||||
'warning',
|
||||
].includes(value),
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
|
@ -40,6 +51,7 @@ export default Vue.extend({
|
|||
classes() {
|
||||
const applied = [];
|
||||
if (this.align) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
applied.push(`align-${this.align}`);
|
||||
}
|
||||
if (this.color) {
|
||||
|
@ -50,9 +62,10 @@ export default Vue.extend({
|
|||
applied.push('compact');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
applied.push(`size-${this.size}`);
|
||||
|
||||
applied.push(this.bold? 'bold': 'regular');
|
||||
applied.push(this.bold ? 'bold' : 'regular');
|
||||
|
||||
return applied.map((c) => (this.$style as { [key: string]: string })[c]);
|
||||
},
|
||||
|
@ -141,5 +154,4 @@ export default Vue.extend({
|
|||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
<template>
|
||||
<el-tooltip v-bind="$attrs">
|
||||
<template v-for="(_, slotName) in $slots" #[slotName]>
|
||||
<slot :name="slotName"/>
|
||||
<div :key="slotName" v-if="slotName === 'content' && buttons.length" :class="$style.buttons" :style="{ justifyContent: justifyButtons }">
|
||||
<slot :name="slotName" />
|
||||
<div
|
||||
:key="slotName"
|
||||
v-if="slotName === 'content' && buttons.length"
|
||||
:class="$style.buttons"
|
||||
:style="{ justifyContent: justifyButtons }"
|
||||
>
|
||||
<n8n-button
|
||||
v-for="button in buttons"
|
||||
:key="button.attrs.label"
|
||||
v-bind="button.attrs"
|
||||
v-on="button.listeners"
|
||||
v-for="button in buttons"
|
||||
:key="button.attrs.label"
|
||||
v-bind="button.attrs"
|
||||
v-on="button.listeners"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -15,9 +20,9 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, { PropType } from "vue";
|
||||
import Vue, { PropType } from 'vue';
|
||||
import ElTooltip from 'element-ui/lib/tooltip';
|
||||
import type { IN8nButton } from "@/types";
|
||||
import type { IN8nButton } from '@/types';
|
||||
import N8nButton from '../N8nButton';
|
||||
|
||||
export default Vue.extend({
|
||||
|
@ -31,7 +36,19 @@ export default Vue.extend({
|
|||
justifyButtons: {
|
||||
type: String,
|
||||
default: 'flex-end',
|
||||
validator: (value: string): boolean => ['flex-start', 'flex-end', 'start', 'end', 'left', 'right', 'center', 'space-between', 'space-around', 'space-evenly'].includes(value),
|
||||
validator: (value: string): boolean =>
|
||||
[
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'start',
|
||||
'end',
|
||||
'left',
|
||||
'right',
|
||||
'center',
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly',
|
||||
].includes(value),
|
||||
},
|
||||
buttons: {
|
||||
type: Array as PropType<IN8nButton[]>,
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
/* tslint:disable:variable-name */
|
||||
|
||||
import N8nTree from './Tree.vue';
|
||||
import {StoryFn} from "@storybook/vue";
|
||||
import type { StoryFn } from '@storybook/vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Tree',
|
||||
component: N8nTree,
|
||||
};
|
||||
|
||||
export const Default: StoryFn = (args, {argTypes}) => ({
|
||||
export const Default: StoryFn = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nTree,
|
||||
|
@ -26,32 +24,26 @@ export const Default: StoryFn = (args, {argTypes}) => ({
|
|||
Default.args = {
|
||||
value: {
|
||||
objectKey: {
|
||||
nestedArrayKey: [
|
||||
'in progress',
|
||||
33958053,
|
||||
],
|
||||
nestedArrayKey: ['in progress', 33958053],
|
||||
stringKey: 'word',
|
||||
aLongKey: 'Lorem ipsum dolor sit consectetur adipiscing elit. Sed dignissim aliquam ipsum mattis pellentesque. Phasellus ut ligula fermentum orci elementum dignissim. Vivamus interdum risus eget nibh placerat ultrices. Vivamus orci arcu, iaculis in nulla non, blandit molestie magna. Praesent tristique feugiat odio non vehicula. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse fermentum purus diam, nec auctor elit consectetur nec. Vestibulum ultrices diam magna, in faucibus odio bibendum id. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sollicitudin lacus neque.',
|
||||
aLongKey:
|
||||
'Lorem ipsum dolor sit consectetur adipiscing elit. Sed dignissim aliquam ipsum mattis pellentesque. Phasellus ut ligula fermentum orci elementum dignissim. Vivamus interdum risus eget nibh placerat ultrices. Vivamus orci arcu, iaculis in nulla non, blandit molestie magna. Praesent tristique feugiat odio non vehicula. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse fermentum purus diam, nec auctor elit consectetur nec. Vestibulum ultrices diam magna, in faucibus odio bibendum id. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sollicitudin lacus neque.',
|
||||
objectKey: {
|
||||
myKey: 'what\'s for lunch',
|
||||
myKey: "what's for lunch",
|
||||
yourKey: 'prolle rewe wdyt',
|
||||
},
|
||||
id: 123,
|
||||
},
|
||||
hello: "world",
|
||||
hello: 'world',
|
||||
test: {
|
||||
label: "A cool folder",
|
||||
label: 'A cool folder',
|
||||
children: [
|
||||
{
|
||||
label: "A cool sub-folder 1",
|
||||
children: [
|
||||
{ label: "A cool sub-sub-folder 1" },
|
||||
{ label: "A cool sub-sub-folder 2" },
|
||||
],
|
||||
label: 'A cool sub-folder 1',
|
||||
children: [{ label: 'A cool sub-sub-folder 1' }, { label: 'A cool sub-sub-folder 2' }],
|
||||
},
|
||||
{ label: "This one is not that cool" },
|
||||
{ label: 'This one is not that cool' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -1,17 +1,36 @@
|
|||
<template>
|
||||
<div class="n8n-tree">
|
||||
<div v-for="(label, i) in Object.keys(value || {})" :key="i" :class="{[nodeClass]: !!nodeClass, [$style.indent]: depth > 0}">
|
||||
<div
|
||||
v-for="(label, i) in Object.keys(value || {})"
|
||||
:key="i"
|
||||
:class="{ [nodeClass]: !!nodeClass, [$style.indent]: depth > 0 }"
|
||||
>
|
||||
<div :class="$style.simple" v-if="isSimple(value[label])">
|
||||
<slot v-if="$scopedSlots.label" name="label" v-bind:label="label" v-bind:path="getPath(label)" />
|
||||
<slot
|
||||
v-if="$scopedSlots.label"
|
||||
name="label"
|
||||
v-bind:label="label"
|
||||
v-bind:path="getPath(label)"
|
||||
/>
|
||||
<span v-else>{{ label }}</span>
|
||||
<span>:</span>
|
||||
<slot v-if="$scopedSlots.value" name="value" v-bind:value="value[label]" />
|
||||
<span v-else>{{ value[label] }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<slot v-if="$scopedSlots.label" name="label" v-bind:label="label" v-bind:path="getPath(label)" />
|
||||
<slot
|
||||
v-if="$scopedSlots.label"
|
||||
name="label"
|
||||
v-bind:label="label"
|
||||
v-bind:path="getPath(label)"
|
||||
/>
|
||||
<span v-else>{{ label }}</span>
|
||||
<n8n-tree :path="getPath(label)" :depth="depth + 1" :value="value[label]" :nodeClass="nodeClass">
|
||||
<n8n-tree
|
||||
:path="getPath(label)"
|
||||
:depth="depth + 1"
|
||||
:value="value[label]"
|
||||
:nodeClass="nodeClass"
|
||||
>
|
||||
<template v-for="(index, name) in $scopedSlots" v-slot:[name]="data">
|
||||
<slot :name="name" v-bind="data"></slot>
|
||||
</template>
|
||||
|
@ -26,11 +45,9 @@ import Vue from 'vue';
|
|||
|
||||
export default Vue.extend({
|
||||
name: 'n8n-tree',
|
||||
components: {
|
||||
},
|
||||
components: {},
|
||||
props: {
|
||||
value: {
|
||||
},
|
||||
value: {},
|
||||
path: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
|
@ -70,7 +87,6 @@ export default Vue.extend({
|
|||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
||||
$--spacing: var(--spacing-s);
|
||||
|
||||
.indent {
|
||||
|
@ -82,5 +98,4 @@ $--spacing: var(--spacing-s);
|
|||
margin-left: $--spacing;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -7,7 +7,7 @@ describe('components', () => {
|
|||
const wrapper = render(N8nTree, {
|
||||
props: {
|
||||
value: {
|
||||
"hello": "world",
|
||||
hello: 'world',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -18,13 +18,10 @@ describe('components', () => {
|
|||
const wrapper = render(N8nTree, {
|
||||
props: {
|
||||
value: {
|
||||
"hello": {
|
||||
"test": "world",
|
||||
hello: {
|
||||
test: 'world',
|
||||
},
|
||||
"options": [
|
||||
"yes",
|
||||
"no",
|
||||
],
|
||||
options: ['yes', 'no'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -35,37 +32,30 @@ describe('components', () => {
|
|||
const wrapper = render(N8nTree, {
|
||||
props: {
|
||||
value: {
|
||||
"hello": {
|
||||
"test": "world",
|
||||
hello: {
|
||||
test: 'world',
|
||||
},
|
||||
"options": [
|
||||
"yes",
|
||||
"no",
|
||||
],
|
||||
options: ['yes', 'no'],
|
||||
},
|
||||
},
|
||||
slots: {
|
||||
label: "<span>label</span>",
|
||||
value: "<span>value</span>",
|
||||
label: '<span>label</span>',
|
||||
value: '<span>value</span>',
|
||||
},
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
it('should render each tree with node class', () => {
|
||||
const wrapper = render(N8nTree, {
|
||||
props: {
|
||||
value: {
|
||||
"hello": {
|
||||
"test": "world",
|
||||
hello: {
|
||||
test: 'world',
|
||||
},
|
||||
"options": [
|
||||
"yes",
|
||||
"no",
|
||||
],
|
||||
options: ['yes', 'no'],
|
||||
},
|
||||
nodeClass: "nodeClass",
|
||||
nodeClass: 'nodeClass',
|
||||
},
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
|
|
|
@ -13,8 +13,7 @@ const Template = (args, { argTypes }) => ({
|
|||
components: {
|
||||
N8nUserInfo,
|
||||
},
|
||||
template:
|
||||
'<n8n-user-info v-bind="$props" />',
|
||||
template: '<n8n-user-info v-bind="$props" />',
|
||||
});
|
||||
|
||||
export const Member = Template.bind({});
|
||||
|
|
|
@ -5,23 +5,25 @@
|
|||
</div>
|
||||
|
||||
<div v-if="isPendingUser" :class="$style.pendingUser">
|
||||
<n8n-text :bold="true">{{email}}</n8n-text>
|
||||
<n8n-text :bold="true">{{ email }}</n8n-text>
|
||||
<span :class="$style.pendingBadge"><n8n-badge :bold="true">Pending</n8n-badge></span>
|
||||
</div>
|
||||
<div v-else :class="$style.infoContainer">
|
||||
<div>
|
||||
<n8n-text :bold="true" color="text-dark">{{firstName}} {{lastName}} {{isCurrentUser ? this.t('nds.userInfo.you') : ''}}</n8n-text>
|
||||
<n8n-text :bold="true" color="text-dark"
|
||||
>{{ firstName }} {{ lastName }}
|
||||
{{ isCurrentUser ? this.t('nds.userInfo.you') : '' }}</n8n-text
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<n8n-text size="small" color="text-light">{{email}}</n8n-text>
|
||||
<n8n-text size="small" color="text-light">{{ email }}</n8n-text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import 'vue';
|
||||
import N8nText from '../N8nText';
|
||||
import N8nAvatar from '../N8nAvatar';
|
||||
import N8nBadge from '../N8nBadge';
|
||||
|
@ -67,7 +69,6 @@ export default mixins(Locale).extend({
|
|||
});
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
display: inline-flex;
|
||||
|
@ -84,7 +85,7 @@ export default mixins(Locale).extend({
|
|||
.infoContainer {
|
||||
flex-grow: 1;
|
||||
display: inline-flex;
|
||||
flex-direction: column;;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-left: var(--spacing-xs);
|
||||
}
|
||||
|
@ -101,6 +102,6 @@ export default mixins(Locale).extend({
|
|||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,8 +4,7 @@ import { action } from '@storybook/addon-actions';
|
|||
export default {
|
||||
title: 'Modules/UserSelect',
|
||||
component: N8nUserSelect,
|
||||
argTypes: {
|
||||
},
|
||||
argTypes: {},
|
||||
parameters: {
|
||||
backgrounds: { default: '--color-background-light' },
|
||||
},
|
||||
|
@ -36,34 +35,34 @@ export const UserSelect = Template.bind({});
|
|||
UserSelect.args = {
|
||||
users: [
|
||||
{
|
||||
id: "1",
|
||||
id: '1',
|
||||
firstName: 'Sunny',
|
||||
lastName: 'Side',
|
||||
email: "sunny@n8n.io",
|
||||
email: 'sunny@n8n.io',
|
||||
globalRole: {
|
||||
name: 'owner',
|
||||
id: "1",
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
id: '2',
|
||||
firstName: 'Kobi',
|
||||
lastName: 'Dog',
|
||||
email: "kobi@n8n.io",
|
||||
email: 'kobi@n8n.io',
|
||||
globalRole: {
|
||||
name: 'member',
|
||||
id: "2",
|
||||
id: '2',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
email: "invited@n8n.io",
|
||||
id: '3',
|
||||
email: 'invited@n8n.io',
|
||||
globalRole: {
|
||||
name: 'member',
|
||||
id: "2",
|
||||
id: '2',
|
||||
},
|
||||
},
|
||||
],
|
||||
placeholder: 'Select user to transfer to',
|
||||
currentUserId: "1",
|
||||
currentUserId: '1',
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue