mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -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]
|
[*.yml]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
dist
|
dist
|
||||||
packages/editor-ui
|
packages/editor-ui
|
||||||
packages/design-system
|
package.json
|
||||||
package*.json
|
|
||||||
|
|
||||||
!packages/nodes-base/src
|
!packages/nodes-base/src
|
||||||
!packages/nodes-base/test
|
!packages/nodes-base/test
|
||||||
|
|
|
@ -9,11 +9,9 @@ const config = (module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
ignorePatterns: [
|
ignorePatterns: [
|
||||||
'.eslintrc.js', // TODO: remove this
|
|
||||||
'node_modules/**',
|
'node_modules/**',
|
||||||
'dist/**',
|
'dist/**',
|
||||||
'test/**', // TODO: remove this
|
'test/**', // TODO: remove this
|
||||||
'jest.config.js', // TODO: remove this
|
|
||||||
],
|
],
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
|
@ -14,10 +14,15 @@ module.exports = {
|
||||||
|
|
||||||
parser: 'vue-eslint-parser',
|
parser: 'vue-eslint-parser',
|
||||||
parserOptions: {
|
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: {
|
rules: {
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
'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: {
|
parserOptions: {
|
||||||
project: ['./tsconfig.json'],
|
project: ['./tsconfig.json'],
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
extraFileExtensions: ['.vue'],
|
||||||
},
|
},
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
// TODO: Remove these
|
// TODO: Remove these
|
||||||
'import/no-default-export': 'off',
|
'import/no-default-export': 'off',
|
||||||
'import/no-extraneous-dependencies': 'off',
|
|
||||||
'import/order': 'off',
|
'import/order': 'off',
|
||||||
'prettier/prettier': 'off',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
'@typescript-eslint/member-delimiter-style': 'off',
|
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||||
'@typescript-eslint/naming-convention': 'off',
|
'@typescript-eslint/no-unsafe-return': 'warn',
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-unsafe-member-access': 'warn',
|
||||||
'@typescript-eslint/no-unsafe-argument': 'off',
|
},
|
||||||
'@typescript-eslint/no-unsafe-return': 'off',
|
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
overrides: [
|
||||||
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
{
|
||||||
'@typescript-eslint/prefer-optional-chain': 'off',
|
files: ['src/**/*.stories.{js,ts}'],
|
||||||
'@typescript-eslint/restrict-template-expressions': 'off',
|
rules: {
|
||||||
'@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }],
|
'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
|
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: {
|
postcssLoaderOptions: {
|
||||||
implementation: require('postcss'),
|
implementation: require('postcss'),
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
'storybook-addon-designs',
|
'storybook-addon-designs',
|
||||||
'storybook-addon-themes',
|
'storybook-addon-themes',
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
@use "./fonts.scss";
|
@use './fonts.scss';
|
||||||
|
|
||||||
@use "~/src/css/base.scss" with (
|
@use '~/src/css/base.scss' with (
|
||||||
$font-path: '~element-ui/lib/theme-chalk/fonts',
|
$font-path: '~element-ui/lib/theme-chalk/fonts'
|
||||||
);
|
);
|
||||||
|
|
||||||
@use "~/src/css/reset.scss";
|
@use '~/src/css/reset.scss';
|
||||||
@use "~/src/css/index.scss";
|
@use '~/src/css/index.scss';
|
||||||
|
|
||||||
.multi-container > * {
|
.multi-container > * {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
|
@ -21,15 +21,22 @@
|
||||||
"test:dev": "vitest",
|
"test:dev": "vitest",
|
||||||
"build:storybook": "build-storybook",
|
"build:storybook": "build-storybook",
|
||||||
"storybook": "start-storybook -p 6006",
|
"storybook": "start-storybook -p 6006",
|
||||||
"format": "prettier **/**.{ts,vue} --write",
|
"format": "prettier **/**.{js,ts,vue,css,scss,mdx,html} --write .",
|
||||||
"lint": "tslint -p tsconfig.json -c tslint.json && eslint .",
|
"lint": "eslint --ext .js,.ts,.vue src",
|
||||||
"lintfix": "tslint --fix -p tsconfig.json -c tslint.json && eslint . --fix"
|
"lintfix": "eslint --ext .js,.ts,.vue src --fix"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "1.x",
|
"@fortawesome/fontawesome-svg-core": "1.x",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.x",
|
"@fortawesome/free-solid-svg-icons": "5.x",
|
||||||
"@fortawesome/vue-fontawesome": "2.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": {
|
"devDependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||||
|
@ -43,14 +50,12 @@
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/vue": "^5.8.3",
|
"@testing-library/vue": "^5.8.3",
|
||||||
"@types/markdown-it": "^12.2.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",
|
"@types/sanitize-html": "^2.6.2",
|
||||||
"c8": "7.11.0",
|
"c8": "7.11.0",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"jsdom": "19.0.0",
|
"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",
|
"node-notifier": ">=8.0.1",
|
||||||
"sass": "^1.55.0",
|
"sass": "^1.55.0",
|
||||||
"sass-loader": "^10.1.1",
|
"sass-loader": "^10.1.1",
|
||||||
|
@ -60,16 +65,13 @@
|
||||||
"vite": "^2.9.5",
|
"vite": "^2.9.5",
|
||||||
"@vitejs/plugin-vue2": "^1.1.2",
|
"@vitejs/plugin-vue2": "^1.1.2",
|
||||||
"vitest": "^0.9.3",
|
"vitest": "^0.9.3",
|
||||||
"vue": "^2.7",
|
|
||||||
"vue-class-component": "^7.2.3",
|
"vue-class-component": "^7.2.3",
|
||||||
"vue-loader": "^15.9.7",
|
"vue-loader": "^15.9.7",
|
||||||
"vue-property-decorator": "^9.1.2",
|
"vue-property-decorator": "^9.1.2",
|
||||||
"vue-template-compiler": "^2.7",
|
"vue-template-compiler": "^2.7",
|
||||||
"vue-tsc": "^0.34.8",
|
"vue-tsc": "^0.34.8",
|
||||||
"vue-typed-mixins": "^0.2.0",
|
|
||||||
"vue2-boring-avatars": "0.3.4",
|
"vue2-boring-avatars": "0.3.4",
|
||||||
"webpack": "^4.46.0",
|
"webpack": "^4.46.0"
|
||||||
"xss": "^1.0.10"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"element-ui": "~2.15.7",
|
"element-ui": "~2.15.7",
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
/* tslint:disable:variable-name */
|
|
||||||
|
|
||||||
import N8nActionBox from './ActionBox.vue';
|
import N8nActionBox from './ActionBox.vue';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import {StoryFn} from "@storybook/vue";
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/ActionBox',
|
title: 'Atoms/ActionBox',
|
||||||
|
@ -35,8 +33,9 @@ const Template: StoryFn = (args, { argTypes }) => ({
|
||||||
|
|
||||||
export const ActionBox = Template.bind({});
|
export const ActionBox = Template.bind({});
|
||||||
ActionBox.args = {
|
ActionBox.args = {
|
||||||
emoji: "😿",
|
emoji: '😿',
|
||||||
heading: "Headline you need to know",
|
heading: 'Headline you need to know',
|
||||||
description: "Long description that you should know something is the way it is because of how it is. ",
|
description:
|
||||||
buttonText: "Do something",
|
'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%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
import {render} from '@testing-library/vue';
|
import { render } from '@testing-library/vue';
|
||||||
import N8NActionBox from '../ActionBox.vue';
|
import N8NActionBox from '../ActionBox.vue';
|
||||||
|
|
||||||
describe('N8NActionBox', () => {
|
describe('N8NActionBox', () => {
|
||||||
it('should render correctly', () => {
|
it('should render correctly', () => {
|
||||||
const wrapper = render(N8NActionBox, {
|
const wrapper = render(N8NActionBox, {
|
||||||
props: {
|
props: {
|
||||||
emoji: "😿",
|
emoji: '😿',
|
||||||
heading: "Headline you need to know",
|
heading: 'Headline you need to know',
|
||||||
description: "Long description that you should know something is the way it is because of how it is. ",
|
description:
|
||||||
buttonText: "Do something",
|
'Long description that you should know something is the way it is because of how it is. ',
|
||||||
|
buttonText: 'Do something',
|
||||||
},
|
},
|
||||||
stubs: [
|
stubs: ['n8n-heading', 'n8n-text', 'n8n-button', 'n8n-callout'],
|
||||||
'n8n-heading',
|
|
||||||
'n8n-text',
|
|
||||||
'n8n-button',
|
|
||||||
'n8n-callout',
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
expect(wrapper.html()).toMatchSnapshot();
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import N8nActionDropdown from "./ActionDropdown.vue";
|
import N8nActionDropdown from './ActionDropdown.vue';
|
||||||
import { StoryFn } from '@storybook/vue';
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/ActionDropdown',
|
title: 'Atoms/ActionDropdown',
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['action-dropdown-container', $style.actionDropdownContainer]">
|
<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">
|
<div :class="$style.activator" @click.prevent @blur="onButtonBlur">
|
||||||
<n8n-icon :icon="activatorIcon"/>
|
<n8n-icon :icon="activatorIcon" />
|
||||||
</div>
|
</div>
|
||||||
<el-dropdown-menu slot="dropdown" :class="$style.userActionsMenu">
|
<el-dropdown-menu slot="dropdown" :class="$style.userActionsMenu">
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
|
@ -12,13 +17,15 @@
|
||||||
:disabled="item.disabled"
|
:disabled="item.disabled"
|
||||||
:divided="item.divided"
|
:divided="item.divided"
|
||||||
>
|
>
|
||||||
<div :class="{
|
<div
|
||||||
|
:class="{
|
||||||
[$style.itemContainer]: true,
|
[$style.itemContainer]: true,
|
||||||
[$style.hasCustomStyling]: item.customClass !== undefined,
|
[$style.hasCustomStyling]: item.customClass !== undefined,
|
||||||
[item.customClass]: item.customClass !== undefined,
|
[item.customClass]: item.customClass !== undefined,
|
||||||
}">
|
}"
|
||||||
|
>
|
||||||
<span v-if="item.icon" :class="$style.icon">
|
<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>
|
||||||
<span :class="$style.label">
|
<span :class="$style.label">
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
|
@ -31,7 +38,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue, { PropType } from "vue";
|
import Vue, { PropType } from 'vue';
|
||||||
import ElDropdown from 'element-ui/lib/dropdown';
|
import ElDropdown from 'element-ui/lib/dropdown';
|
||||||
import ElDropdownMenu from 'element-ui/lib/dropdown-menu';
|
import ElDropdownMenu from 'element-ui/lib/dropdown-menu';
|
||||||
import ElDropdownItem from 'element-ui/lib/dropdown-item';
|
import ElDropdownItem from 'element-ui/lib/dropdown-item';
|
||||||
|
@ -78,22 +85,22 @@ export default Vue.extend({
|
||||||
iconSize: {
|
iconSize: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'medium',
|
default: 'medium',
|
||||||
validator: (value: string): boolean =>
|
validator: (value: string): boolean => ['small', 'medium', 'large'].includes(value),
|
||||||
['small', 'medium', 'large'].includes(value),
|
|
||||||
},
|
},
|
||||||
trigger: {
|
trigger: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'click',
|
default: 'click',
|
||||||
validator: (value: string): boolean =>
|
validator: (value: string): boolean => ['click', 'hover'].includes(value),
|
||||||
['click', 'hover'].includes(value),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onSelect(action: string) : void {
|
onSelect(action: string): void {
|
||||||
this.$emit('select', action);
|
this.$emit('select', action);
|
||||||
},
|
},
|
||||||
onButtonBlur(event: FocusEvent): void {
|
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
|
// Hide dropdown when clicking outside of current document
|
||||||
if (elementDropdown && event.relatedTarget === null) {
|
if (elementDropdown && event.relatedTarget === null) {
|
||||||
elementDropdown.hide();
|
elementDropdown.hide();
|
||||||
|
@ -101,11 +108,9 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
||||||
.activator {
|
.activator {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: var(--spacing-2xs);
|
padding: var(--spacing-2xs);
|
||||||
|
@ -131,7 +136,9 @@ export default Vue.extend({
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-right: var(--spacing-2xs);
|
margin-right: var(--spacing-2xs);
|
||||||
|
|
||||||
svg { width: 1.2em !important; }
|
svg {
|
||||||
|
width: 1.2em !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(li.is-disabled) {
|
:global(li.is-disabled) {
|
||||||
|
@ -139,5 +146,4 @@ export default Vue.extend({
|
||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -28,7 +28,8 @@ const Template = (args, { argTypes }) => ({
|
||||||
components: {
|
components: {
|
||||||
N8nActionToggle,
|
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,
|
methods,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,8 @@
|
||||||
@command="onCommand"
|
@command="onCommand"
|
||||||
@visible-change="onVisibleChange"
|
@visible-change="onVisibleChange"
|
||||||
>
|
>
|
||||||
<span :class="{[$style.button]: true, [$style[theme]]: !!theme}">
|
<span :class="{ [$style.button]: true, [$style[theme]]: !!theme }">
|
||||||
<component :is="$options.components.N8nIcon"
|
<component :is="$options.components.N8nIcon" icon="ellipsis-v" :size="iconSize" />
|
||||||
icon="ellipsis-v"
|
|
||||||
:size="iconSize"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<el-dropdown-menu slot="dropdown" data-test-id="action-toggle-dropdown">
|
<el-dropdown-menu slot="dropdown" data-test-id="action-toggle-dropdown">
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
|
@ -21,7 +18,7 @@
|
||||||
:command="action.value"
|
:command="action.value"
|
||||||
:disabled="action.disabled"
|
:disabled="action.disabled"
|
||||||
>
|
>
|
||||||
{{action.label}}
|
{{ action.label }}
|
||||||
<div :class="$style.iconContainer">
|
<div :class="$style.iconContainer">
|
||||||
<component
|
<component
|
||||||
v-if="action.type === 'external-link'"
|
v-if="action.type === 'external-link'"
|
||||||
|
@ -66,8 +63,7 @@ export default Vue.extend({
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'medium',
|
default: 'medium',
|
||||||
validator: (value: string): boolean =>
|
validator: (value: string): boolean => ['mini', 'small', 'medium'].includes(value),
|
||||||
['mini', 'small', 'medium'].includes(value),
|
|
||||||
},
|
},
|
||||||
iconSize: {
|
iconSize: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -75,8 +71,7 @@ export default Vue.extend({
|
||||||
theme: {
|
theme: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'default',
|
default: 'default',
|
||||||
validator: (value: string): boolean =>
|
validator: (value: string): boolean => ['default', 'dark'].includes(value),
|
||||||
['default', 'dark'].includes(value),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -7,19 +7,15 @@
|
||||||
variant="marble"
|
variant="marble"
|
||||||
:colors="getColors(colors)"
|
:colors="getColors(colors)"
|
||||||
/>
|
/>
|
||||||
<div
|
<div v-else :class="[$style.empty, $style[size]]"></div>
|
||||||
v-else
|
<span v-if="firstName" :class="$style.initials">{{ initials }}</span>
|
||||||
:class="[$style.empty, $style[size]]"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<span v-if="firstName" :class="$style.initials">{{initials}}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Avatar from 'vue2-boring-avatars';
|
import Avatar from 'vue2-boring-avatars';
|
||||||
|
|
||||||
const sizes: {[size: string]: number} = {
|
const sizes: { [size: string]: number } = {
|
||||||
small: 28,
|
small: 28,
|
||||||
large: 48,
|
large: 48,
|
||||||
medium: 40,
|
medium: 40,
|
||||||
|
@ -41,7 +37,13 @@ export default Vue.extend({
|
||||||
default: 'medium',
|
default: 'medium',
|
||||||
},
|
},
|
||||||
colors: {
|
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: {
|
components: {
|
||||||
|
@ -49,7 +51,10 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
initials() {
|
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: {
|
methods: {
|
||||||
|
@ -75,7 +80,7 @@ export default Vue.extend({
|
||||||
.empty {
|
.empty {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: var(--color-foreground-dark);
|
background-color: var(--color-foreground-dark);
|
||||||
opacity: .3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.initials {
|
.initials {
|
||||||
|
|
|
@ -20,8 +20,7 @@ const Template = (args, { argTypes }) => ({
|
||||||
components: {
|
components: {
|
||||||
N8nBadge,
|
N8nBadge,
|
||||||
},
|
},
|
||||||
template:
|
template: '<n8n-badge v-bind="$props">Badge</n8n-badge>',
|
||||||
'<n8n-badge v-bind="$props">Badge</n8n-badge>',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Badge = Template.bind({});
|
export const Badge = Template.bind({});
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<span
|
<span :class="['n8n-badge', $style[theme]]">
|
||||||
:class="['n8n-badge', $style[theme]]"
|
|
||||||
>
|
|
||||||
<n8n-text :size="size" :bold="bold" :compact="true">
|
<n8n-text :size="size" :bold="bold" :compact="true">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
|
|
|
@ -16,5 +16,5 @@ const Template = (args, { argTypes }) => ({
|
||||||
|
|
||||||
export const BlockUi = Template.bind({});
|
export const BlockUi = Template.bind({});
|
||||||
BlockUi.args = {
|
BlockUi.args = {
|
||||||
show: false
|
show: false,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<transition name="fade" mode="out-in">
|
<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>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
type BlockUiProps = {
|
type BlockUiProps = {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
const props = withDefaults(defineProps<BlockUiProps>(), {
|
withDefaults(defineProps<BlockUiProps>(), {
|
||||||
show: false,
|
show: false,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* tslint:disable:variable-name */
|
|
||||||
import N8nButton from './Button.vue';
|
import N8nButton from './Button.vue';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import { StoryFn } from "@storybook/vue";
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/Button',
|
title: 'Atoms/Button',
|
||||||
|
@ -63,22 +62,6 @@ const AllSizesTemplate: StoryFn = (args, { argTypes }) => ({
|
||||||
methods,
|
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 }) => ({
|
const AllColorsAndSizesTemplate: StoryFn = (args, { argTypes }) => ({
|
||||||
props: Object.keys(argTypes),
|
props: Object.keys(argTypes),
|
||||||
components: {
|
components: {
|
||||||
|
@ -170,4 +153,3 @@ Square.args = {
|
||||||
label: '48',
|
label: '48',
|
||||||
square: true,
|
square: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,15 +8,8 @@
|
||||||
v-on="$listeners"
|
v-on="$listeners"
|
||||||
>
|
>
|
||||||
<span :class="$style.icon" v-if="loading || icon">
|
<span :class="$style.icon" v-if="loading || icon">
|
||||||
<n8n-spinner
|
<n8n-spinner v-if="loading" :size="size" />
|
||||||
v-if="loading"
|
<n8n-icon v-else-if="icon" :icon="icon" :size="size" />
|
||||||
:size="size"
|
|
||||||
/>
|
|
||||||
<n8n-icon
|
|
||||||
v-else-if="icon"
|
|
||||||
:icon="icon"
|
|
||||||
:size="size"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<span v-if="label || $slots.default">
|
<span v-if="label || $slots.default">
|
||||||
<slot>{{ label }}</slot>
|
<slot>{{ label }}</slot>
|
||||||
|
@ -76,8 +69,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
float: {
|
float: {
|
||||||
type: String,
|
type: String,
|
||||||
validator: (value: string): boolean =>
|
validator: (value: string): boolean => ['left', 'right'].includes(value),
|
||||||
['left', 'right'].includes(value),
|
|
||||||
},
|
},
|
||||||
square: {
|
square: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -96,7 +88,8 @@ export default Vue.extend({
|
||||||
return this.disabled ? 'true' : 'false';
|
return this.disabled ? 'true' : 'false';
|
||||||
},
|
},
|
||||||
classes(): string {
|
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.size ? ` ${this.$style[this.size]}` : ''}` +
|
||||||
`${this.outline ? ` ${this.$style.outline}` : ''}` +
|
`${this.outline ? ` ${this.$style.outline}` : ''}` +
|
||||||
`${this.loading ? ` ${this.$style.loading}` : ''}` +
|
`${this.loading ? ` ${this.$style.loading}` : ''}` +
|
||||||
|
@ -106,7 +99,8 @@ export default Vue.extend({
|
||||||
`${this.block ? ` ${this.$style.block}` : ''}` +
|
`${this.block ? ` ${this.$style.block}` : ''}` +
|
||||||
`${this.active ? ` ${this.$style.active}` : ''}` +
|
`${this.active ? ` ${this.$style.active}` : ''}` +
|
||||||
`${this.icon || this.loading ? ` ${this.$style.icon}` : ''}` +
|
`${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;
|
outline: $focus-outline-width solid $button-focus-outline-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active, &.active {
|
&:active,
|
||||||
|
&.active {
|
||||||
color: $button-active-color;
|
color: $button-active-color;
|
||||||
border-color: $button-active-border-color;
|
border-color: $button-active-border-color;
|
||||||
background-color: $button-active-background-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-color: var(--color-text-dark);
|
||||||
--button-hover-border-color: var(--color-neutral-800);
|
--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 {
|
.success {
|
||||||
|
@ -227,7 +227,12 @@ $loading-overlay-background-color: rgba(255, 255, 255, 0);
|
||||||
--button-hover-background-color: var(--color-success-450);
|
--button-hover-background-color: var(--color-success-450);
|
||||||
--button-hover-border-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 {
|
.warning {
|
||||||
|
@ -241,7 +246,12 @@ $loading-overlay-background-color: rgba(255, 255, 255, 0);
|
||||||
--button-hover-background-color: var(--color-warning-650);
|
--button-hover-background-color: var(--color-warning-650);
|
||||||
--button-hover-border-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 {
|
.danger {
|
||||||
|
@ -256,7 +266,12 @@ $loading-overlay-background-color: rgba(255, 255, 255, 0);
|
||||||
--button-hover-background-color: var(--color-danger-700);
|
--button-hover-background-color: var(--color-danger-700);
|
||||||
--button-hover-border-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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {render} from '@testing-library/vue';
|
import { render } from '@testing-library/vue';
|
||||||
import N8nButton from "../Button.vue";
|
import N8nButton from '../Button.vue';
|
||||||
import ElButton from "../overrides/ElButton.vue";
|
import ElButton from '../overrides/ElButton.vue';
|
||||||
|
|
||||||
const slots = {
|
const slots = {
|
||||||
default: 'Button',
|
default: 'Button',
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
// Vitest Snapshot v1
|
// 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`] = `
|
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>"
|
<!----><span>48</span></button>"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`components > N8nButton > should render correctly 1`] = `
|
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>"
|
<!----><span>Button</span></button>"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
import ElButton from "./ElButton.vue";
|
import ElButton from './ElButton.vue';
|
||||||
|
|
||||||
export default ElButton;
|
export default ElButton;
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<n8n-button
|
<n8n-button ref="button" v-bind="attrs" v-on="$listeners">
|
||||||
ref="button"
|
<slot />
|
||||||
v-bind="attrs"
|
|
||||||
v-on="$listeners"
|
|
||||||
>
|
|
||||||
<slot/>
|
|
||||||
</n8n-button>
|
</n8n-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import N8nCallout from './Callout.vue';
|
import N8nCallout from './Callout.vue';
|
||||||
import N8nLink from '../N8nLink';
|
import N8nLink from '../N8nLink';
|
||||||
import N8nText from '../N8nText';
|
import N8nText from '../N8nText';
|
||||||
import { StoryFn } from '@storybook/vue';
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/Callout',
|
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),
|
props: Object.keys(argTypes),
|
||||||
components: {
|
components: {
|
||||||
N8nLink,
|
N8nLink,
|
||||||
|
@ -79,7 +86,6 @@ customCallout.args = {
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const secondaryCallout = template.bind({});
|
export const secondaryCallout = template.bind({});
|
||||||
secondaryCallout.args = {
|
secondaryCallout.args = {
|
||||||
theme: 'secondary',
|
theme: 'secondary',
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="classes" role="alert">
|
<div :class="classes" role="alert">
|
||||||
|
|
||||||
<div :class="$style['message-section']">
|
<div :class="$style['message-section']">
|
||||||
<div :class="$style.icon">
|
<div :class="$style.icon">
|
||||||
<n8n-icon
|
<n8n-icon :icon="getIcon" :size="theme === 'secondary' ? 'medium' : 'large'" />
|
||||||
:icon="getIcon"
|
|
||||||
:size="theme === 'secondary' ? 'medium' : 'large'"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<slot />
|
<slot />
|
||||||
<slot name="actions" />
|
<slot name="actions" />
|
||||||
|
@ -46,11 +42,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
classes(): string[] {
|
classes(): string[] {
|
||||||
return [
|
return ['n8n-callout', this.$style.callout, this.$style[this.theme]];
|
||||||
'n8n-callout',
|
|
||||||
this.$style.callout,
|
|
||||||
this.$style[this.theme],
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
getIcon(): string {
|
getIcon(): string {
|
||||||
if (Object.keys(CALLOUT_DEFAULT_ICONS).includes(this.theme)) {
|
if (Object.keys(CALLOUT_DEFAULT_ICONS).includes(this.theme)) {
|
||||||
|
@ -79,7 +71,8 @@ export default Vue.extend({
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info, .custom {
|
.info,
|
||||||
|
.custom {
|
||||||
border-color: var(--color-foreground-base);
|
border-color: var(--color-foreground-base);
|
||||||
background-color: var(--color-background-light);
|
background-color: var(--color-background-light);
|
||||||
color: var(--color-info);
|
color: var(--color-info);
|
||||||
|
|
|
@ -8,7 +8,7 @@ describe('components', () => {
|
||||||
props: {
|
props: {
|
||||||
theme: 'info',
|
theme: 'info',
|
||||||
},
|
},
|
||||||
stubs: [ 'n8n-icon', 'n8n-text' ],
|
stubs: ['n8n-icon', 'n8n-text'],
|
||||||
slots: {
|
slots: {
|
||||||
default: '<n8n-text size="small">This is an info callout.</n8n-text>',
|
default: '<n8n-text size="small">This is an info callout.</n8n-text>',
|
||||||
},
|
},
|
||||||
|
@ -20,7 +20,7 @@ describe('components', () => {
|
||||||
props: {
|
props: {
|
||||||
theme: 'success',
|
theme: 'success',
|
||||||
},
|
},
|
||||||
stubs: [ 'n8n-icon', 'n8n-text' ],
|
stubs: ['n8n-icon', 'n8n-text'],
|
||||||
slots: {
|
slots: {
|
||||||
default: '<n8n-text size="small">This is a success callout.</n8n-text>',
|
default: '<n8n-text size="small">This is a success callout.</n8n-text>',
|
||||||
},
|
},
|
||||||
|
@ -32,7 +32,7 @@ describe('components', () => {
|
||||||
props: {
|
props: {
|
||||||
theme: 'warning',
|
theme: 'warning',
|
||||||
},
|
},
|
||||||
stubs: [ 'n8n-icon', 'n8n-text' ],
|
stubs: ['n8n-icon', 'n8n-text'],
|
||||||
slots: {
|
slots: {
|
||||||
default: '<n8n-text size="small">This is a warning callout.</n8n-text>',
|
default: '<n8n-text size="small">This is a warning callout.</n8n-text>',
|
||||||
},
|
},
|
||||||
|
@ -44,7 +44,7 @@ describe('components', () => {
|
||||||
props: {
|
props: {
|
||||||
theme: 'danger',
|
theme: 'danger',
|
||||||
},
|
},
|
||||||
stubs: [ 'n8n-icon', 'n8n-text' ],
|
stubs: ['n8n-icon', 'n8n-text'],
|
||||||
slots: {
|
slots: {
|
||||||
default: '<n8n-text size="small">This is a danger callout.</n8n-text>',
|
default: '<n8n-text size="small">This is a danger callout.</n8n-text>',
|
||||||
},
|
},
|
||||||
|
@ -56,7 +56,7 @@ describe('components', () => {
|
||||||
props: {
|
props: {
|
||||||
theme: 'secondary',
|
theme: 'secondary',
|
||||||
},
|
},
|
||||||
stubs: [ 'n8n-icon', 'n8n-text' ],
|
stubs: ['n8n-icon', 'n8n-text'],
|
||||||
slots: {
|
slots: {
|
||||||
default: '<n8n-text size="small">This is a secondary callout.</n8n-text>',
|
default: '<n8n-text size="small">This is a secondary callout.</n8n-text>',
|
||||||
},
|
},
|
||||||
|
@ -69,7 +69,7 @@ describe('components', () => {
|
||||||
theme: 'custom',
|
theme: 'custom',
|
||||||
icon: 'code-branch',
|
icon: 'code-branch',
|
||||||
},
|
},
|
||||||
stubs: [ 'n8n-icon', 'n8n-text' ],
|
stubs: ['n8n-icon', 'n8n-text'],
|
||||||
slots: {
|
slots: {
|
||||||
default: '<n8n-text size="small">This is a secondary callout.</n8n-text>',
|
default: '<n8n-text size="small">This is a secondary callout.</n8n-text>',
|
||||||
},
|
},
|
||||||
|
@ -82,11 +82,12 @@ describe('components', () => {
|
||||||
theme: 'custom',
|
theme: 'custom',
|
||||||
icon: 'code-branch',
|
icon: 'code-branch',
|
||||||
},
|
},
|
||||||
stubs: [ 'n8n-icon', 'n8n-text', 'n8n-link' ],
|
stubs: ['n8n-icon', 'n8n-text', 'n8n-link'],
|
||||||
slots: {
|
slots: {
|
||||||
default: '<n8n-text size="small">This is a secondary callout.</n8n-text>',
|
default: '<n8n-text size="small">This is a secondary callout.</n8n-text>',
|
||||||
actions: '<n8n-link size="small">Do something!</n8n-link>',
|
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();
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
// Vitest Snapshot v1
|
// Vitest Snapshot v1
|
||||||
|
|
||||||
exports[`components > N8nCallout > should render additional slots correctly 1`] = `
|
exports[`components > N8nCallout > should render additional slots correctly 1`] = `
|
||||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _custom_p74de_16\\">
|
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _custom_dfd91_17\\">
|
||||||
<div class=\\"_message-section_p74de_12\\">
|
<div class=\\"_message-section_dfd91_12\\">
|
||||||
<div class=\\"_icon_p74de_40\\">
|
<div class=\\"_icon_dfd91_41\\">
|
||||||
<n8n-icon-stub icon=\\"code-branch\\" size=\\"large\\"></n8n-icon-stub>
|
<n8n-icon-stub icon=\\"code-branch\\" size=\\"large\\"></n8n-icon-stub>
|
||||||
</div>
|
</div>
|
||||||
<n8n-text-stub size=\\"small\\">This is a secondary callout.</n8n-text-stub> <n8n-link-stub size=\\"small\\">Do something!</n8n-link-stub>
|
<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`] = `
|
exports[`components > N8nCallout > should render custom theme correctly 1`] = `
|
||||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _custom_p74de_16\\">
|
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _custom_dfd91_17\\">
|
||||||
<div class=\\"_message-section_p74de_12\\">
|
<div class=\\"_message-section_dfd91_12\\">
|
||||||
<div class=\\"_icon_p74de_40\\">
|
<div class=\\"_icon_dfd91_41\\">
|
||||||
<n8n-icon-stub icon=\\"code-branch\\" size=\\"large\\"></n8n-icon-stub>
|
<n8n-icon-stub icon=\\"code-branch\\" size=\\"large\\"></n8n-icon-stub>
|
||||||
</div>
|
</div>
|
||||||
<n8n-text-stub size=\\"small\\">This is a secondary callout.</n8n-text-stub>
|
<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`] = `
|
exports[`components > N8nCallout > should render danger theme correctly 1`] = `
|
||||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _danger_p74de_34\\">
|
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _danger_dfd91_35\\">
|
||||||
<div class=\\"_message-section_p74de_12\\">
|
<div class=\\"_message-section_dfd91_12\\">
|
||||||
<div class=\\"_icon_p74de_40\\">
|
<div class=\\"_icon_dfd91_41\\">
|
||||||
<n8n-icon-stub icon=\\"times-circle\\" size=\\"large\\"></n8n-icon-stub>
|
<n8n-icon-stub icon=\\"times-circle\\" size=\\"large\\"></n8n-icon-stub>
|
||||||
</div>
|
</div>
|
||||||
<n8n-text-stub size=\\"small\\">This is a danger callout.</n8n-text-stub>
|
<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`] = `
|
exports[`components > N8nCallout > should render info theme correctly 1`] = `
|
||||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _info_p74de_16\\">
|
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _info_dfd91_16\\">
|
||||||
<div class=\\"_message-section_p74de_12\\">
|
<div class=\\"_message-section_dfd91_12\\">
|
||||||
<div class=\\"_icon_p74de_40\\">
|
<div class=\\"_icon_dfd91_41\\">
|
||||||
<n8n-icon-stub icon=\\"info-circle\\" size=\\"large\\"></n8n-icon-stub>
|
<n8n-icon-stub icon=\\"info-circle\\" size=\\"large\\"></n8n-icon-stub>
|
||||||
</div>
|
</div>
|
||||||
<n8n-text-stub size=\\"small\\">This is an info callout.</n8n-text-stub>
|
<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`] = `
|
exports[`components > N8nCallout > should render secondary theme correctly 1`] = `
|
||||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _secondary_p74de_44\\">
|
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _secondary_dfd91_45\\">
|
||||||
<div class=\\"_message-section_p74de_12\\">
|
<div class=\\"_message-section_dfd91_12\\">
|
||||||
<div class=\\"_icon_p74de_40\\">
|
<div class=\\"_icon_dfd91_41\\">
|
||||||
<n8n-icon-stub icon=\\"info-circle\\" size=\\"medium\\"></n8n-icon-stub>
|
<n8n-icon-stub icon=\\"info-circle\\" size=\\"medium\\"></n8n-icon-stub>
|
||||||
</div>
|
</div>
|
||||||
<n8n-text-stub size=\\"small\\">This is a secondary callout.</n8n-text-stub>
|
<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`] = `
|
exports[`components > N8nCallout > should render success theme correctly 1`] = `
|
||||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _success_p74de_28\\">
|
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _success_dfd91_29\\">
|
||||||
<div class=\\"_message-section_p74de_12\\">
|
<div class=\\"_message-section_dfd91_12\\">
|
||||||
<div class=\\"_icon_p74de_40\\">
|
<div class=\\"_icon_dfd91_41\\">
|
||||||
<n8n-icon-stub icon=\\"check-circle\\" size=\\"large\\"></n8n-icon-stub>
|
<n8n-icon-stub icon=\\"check-circle\\" size=\\"large\\"></n8n-icon-stub>
|
||||||
</div>
|
</div>
|
||||||
<n8n-text-stub size=\\"small\\">This is a success callout.</n8n-text-stub>
|
<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`] = `
|
exports[`components > N8nCallout > should render warning theme correctly 1`] = `
|
||||||
"<div role=\\"alert\\" class=\\"n8n-callout _callout_p74de_1 _warning_p74de_22\\">
|
"<div role=\\"alert\\" class=\\"n8n-callout _callout_dfd91_1 _warning_dfd91_23\\">
|
||||||
<div class=\\"_message-section_p74de_12\\">
|
<div class=\\"_message-section_dfd91_12\\">
|
||||||
<div class=\\"_icon_p74de_40\\">
|
<div class=\\"_icon_dfd91_41\\">
|
||||||
<n8n-icon-stub icon=\\"exclamation-triangle\\" size=\\"large\\"></n8n-icon-stub>
|
<n8n-icon-stub icon=\\"exclamation-triangle\\" size=\\"large\\"></n8n-icon-stub>
|
||||||
</div>
|
</div>
|
||||||
<n8n-text-stub size=\\"small\\">This is a warning callout.</n8n-text-stub>
|
<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 N8nCard from './Card.vue';
|
||||||
import {StoryFn} from "@storybook/vue";
|
import type { StoryFn } from '@storybook/vue';
|
||||||
import N8nButton from "../N8nButton/Button.vue";
|
import N8nButton from '../N8nButton/Button.vue';
|
||||||
import N8nIcon from "../N8nIcon/Icon.vue";
|
import N8nIcon from '../N8nIcon/Icon.vue';
|
||||||
import N8nText from "../N8nText/Text.vue";
|
import N8nText from '../N8nText/Text.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/Card',
|
title: 'Atoms/Card',
|
||||||
component: N8nCard,
|
component: N8nCard,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default: StoryFn = (args, {argTypes}) => ({
|
export const Default: StoryFn = (args, { argTypes }) => ({
|
||||||
props: Object.keys(argTypes),
|
props: Object.keys(argTypes),
|
||||||
components: {
|
components: {
|
||||||
N8nCard,
|
N8nCard,
|
||||||
|
@ -19,7 +17,7 @@ export const Default: StoryFn = (args, {argTypes}) => ({
|
||||||
template: `<n8n-card v-bind="$props">This is a card.</n8n-card>`,
|
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),
|
props: Object.keys(argTypes),
|
||||||
components: {
|
components: {
|
||||||
N8nCard,
|
N8nCard,
|
||||||
|
@ -38,8 +36,7 @@ Hoverable.args = {
|
||||||
hoverable: true,
|
hoverable: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WithSlots: StoryFn = (args, { argTypes }) => ({
|
||||||
export const WithSlots: StoryFn = (args, {argTypes}) => ({
|
|
||||||
props: Object.keys(argTypes),
|
props: Object.keys(argTypes),
|
||||||
components: {
|
components: {
|
||||||
N8nCard,
|
N8nCard,
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="classes" v-on="$listeners">
|
<div :class="classes" v-on="$listeners">
|
||||||
<div :class="$style.icon" v-if="$slots.prepend">
|
<div :class="$style.icon" v-if="$slots.prepend">
|
||||||
<slot name="prepend"/>
|
<slot name="prepend" />
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.content">
|
<div :class="$style.content">
|
||||||
<div :class="$style.header" v-if="$slots.header">
|
<div :class="$style.header" v-if="$slots.header">
|
||||||
<slot name="header"/>
|
<slot name="header" />
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.body" v-if="$slots.default">
|
<div :class="$style.body" v-if="$slots.default">
|
||||||
<slot/>
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.footer" v-if="$slots.footer">
|
<div :class="$style.footer" v-if="$slots.footer">
|
||||||
<slot name="footer"/>
|
<slot name="footer" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.actions" v-if="$slots.append">
|
<div :class="$style.actions" v-if="$slots.append">
|
||||||
<slot name="append"/>
|
<slot name="append" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {render} from '@testing-library/vue';
|
import { render } from '@testing-library/vue';
|
||||||
import N8nCard from "../Card.vue";
|
import N8nCard from '../Card.vue';
|
||||||
|
|
||||||
describe('components', () => {
|
describe('components', () => {
|
||||||
describe('N8nCard', () => {
|
describe('N8nCard', () => {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* tslint:disable:variable-name */
|
import N8nCheckbox from './Checkbox.vue';
|
||||||
import N8nCheckbox from "./Checkbox.vue";
|
import type { StoryFn } from '@storybook/vue';
|
||||||
import { StoryFn } from '@storybook/vue';
|
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -51,20 +51,18 @@ export default Vue.extend({
|
||||||
labelSize: {
|
labelSize: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'medium',
|
default: 'medium',
|
||||||
validator: (value: string): boolean =>
|
validator: (value: string): boolean => ['small', 'medium'].includes(value),
|
||||||
['small', 'medium'].includes(value),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onChange(event: Event) {
|
onChange(event: Event) {
|
||||||
this.$emit("input", event);
|
this.$emit('input', event);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
||||||
.n8nCheckbox {
|
.n8nCheckbox {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
white-space: normal !important;
|
white-space: normal !important;
|
||||||
|
@ -73,5 +71,4 @@ export default Vue.extend({
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,8 +4,7 @@ import { action } from '@storybook/addon-actions';
|
||||||
export default {
|
export default {
|
||||||
title: 'Modules/FormBox',
|
title: 'Modules/FormBox',
|
||||||
component: N8nFormBox,
|
component: N8nFormBox,
|
||||||
argTypes: {
|
argTypes: {},
|
||||||
},
|
|
||||||
parameters: {
|
parameters: {
|
||||||
backgrounds: { default: '--color-background-light' },
|
backgrounds: { default: '--color-background-light' },
|
||||||
},
|
},
|
||||||
|
@ -35,7 +34,7 @@ FormBox.args = {
|
||||||
label: 'Your Email',
|
label: 'Your Email',
|
||||||
type: 'email',
|
type: 'email',
|
||||||
required: true,
|
required: true,
|
||||||
validationRules: [{name: 'VALID_EMAIL'}],
|
validationRules: [{ name: 'VALID_EMAIL' }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -51,7 +50,7 @@ FormBox.args = {
|
||||||
label: 'Your Password',
|
label: 'Your Password',
|
||||||
type: 'password',
|
type: 'password',
|
||||||
required: true,
|
required: true,
|
||||||
validationRules: [{name: 'DEFAULT_PASSWORD_RULES'}],
|
validationRules: [{ name: 'DEFAULT_PASSWORD_RULES' }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -66,4 +65,3 @@ FormBox.args = {
|
||||||
redirectText: 'Go somewhere',
|
redirectText: 'Go somewhere',
|
||||||
redirectLink: 'https://n8n.io',
|
redirectLink: 'https://n8n.io',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div :class="['n8n-form-box', $style.container]">
|
||||||
:class="['n8n-form-box', $style.container]"
|
<div v-if="title" :class="$style.heading">
|
||||||
>
|
<n8n-heading size="xlarge">
|
||||||
<div
|
{{ title }}
|
||||||
v-if="title"
|
|
||||||
:class="$style.heading"
|
|
||||||
>
|
|
||||||
<n8n-heading
|
|
||||||
size="xlarge"
|
|
||||||
>
|
|
||||||
{{title}}
|
|
||||||
</n8n-heading>
|
</n8n-heading>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div :class="$style.inputsContainer">
|
||||||
:class="$style.inputsContainer"
|
|
||||||
>
|
|
||||||
<n8n-form-inputs
|
<n8n-form-inputs
|
||||||
:inputs="inputs"
|
:inputs="inputs"
|
||||||
:eventBus="formBus"
|
:eventBus="formBus"
|
||||||
|
@ -24,16 +15,9 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.buttonsContainer" v-if="secondaryButtonText || buttonText">
|
<div :class="$style.buttonsContainer" v-if="secondaryButtonText || buttonText">
|
||||||
<span
|
<span v-if="secondaryButtonText" :class="$style.secondaryButtonContainer">
|
||||||
v-if="secondaryButtonText"
|
<n8n-link size="medium" theme="text" @click="onSecondaryButtonClick">
|
||||||
:class="$style.secondaryButtonContainer"
|
{{ secondaryButtonText }}
|
||||||
>
|
|
||||||
<n8n-link
|
|
||||||
size="medium"
|
|
||||||
theme="text"
|
|
||||||
@click="onSecondaryButtonClick"
|
|
||||||
>
|
|
||||||
{{secondaryButtonText}}
|
|
||||||
</n8n-link>
|
</n8n-link>
|
||||||
</span>
|
</span>
|
||||||
<n8n-button
|
<n8n-button
|
||||||
|
@ -45,11 +29,8 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.actionContainer">
|
<div :class="$style.actionContainer">
|
||||||
<n8n-link
|
<n8n-link v-if="redirectText && redirectLink" :to="redirectLink">
|
||||||
v-if="redirectText && redirectLink"
|
{{ redirectText }}
|
||||||
:to="redirectLink"
|
|
||||||
>
|
|
||||||
{{redirectText}}
|
|
||||||
</n8n-link>
|
</n8n-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -103,10 +84,10 @@ export default Vue.extend({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onInput(e: {name: string, value: string}) {
|
onInput(e: { name: string; value: string }) {
|
||||||
this.$emit('input', e);
|
this.$emit('input', e);
|
||||||
},
|
},
|
||||||
onSubmit(e: {[key: string]: string}) {
|
onSubmit(e: { [key: string]: string }) {
|
||||||
this.$emit('submit', e);
|
this.$emit('submit', e);
|
||||||
},
|
},
|
||||||
onButtonClick() {
|
onButtonClick() {
|
||||||
|
|
|
@ -4,8 +4,7 @@ import { action } from '@storybook/addon-actions';
|
||||||
export default {
|
export default {
|
||||||
title: 'Modules/FormInput',
|
title: 'Modules/FormInput',
|
||||||
component: N8nFormInput,
|
component: N8nFormInput,
|
||||||
argTypes: {
|
argTypes: {},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
|
|
|
@ -6,7 +6,13 @@
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
ref="inputRef"
|
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">
|
<div :class="showErrors ? $style.errorInput : ''" @keydown.stop @keydown.enter="onEnter">
|
||||||
<slot v-if="hasDefaultSlot" />
|
<slot v-if="hasDefaultSlot" />
|
||||||
<n8n-select
|
<n8n-select
|
||||||
|
@ -21,7 +27,7 @@
|
||||||
ref="inputRef"
|
ref="inputRef"
|
||||||
>
|
>
|
||||||
<n8n-option
|
<n8n-option
|
||||||
v-for="option in (options || [])"
|
v-for="option in options || []"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
:label="option.label"
|
:label="option.label"
|
||||||
|
@ -69,7 +75,7 @@ import N8nInputLabel from '../N8nInputLabel';
|
||||||
import N8nCheckbox from '../N8nCheckbox';
|
import N8nCheckbox from '../N8nCheckbox';
|
||||||
|
|
||||||
import { getValidationError, VALIDATORS } from './validators';
|
import { getValidationError, VALIDATORS } from './validators';
|
||||||
import { Rule, RuleGroup, IValidator } from "../../types";
|
import { Rule, RuleGroup, IValidator } from '../../types';
|
||||||
|
|
||||||
import { t } from '../../locale';
|
import { t } from '../../locale';
|
||||||
|
|
||||||
|
@ -87,9 +93,9 @@ export interface Props {
|
||||||
documentationUrl?: string;
|
documentationUrl?: string;
|
||||||
documentationText?: string;
|
documentationText?: string;
|
||||||
validationRules?: Array<Rule | RuleGroup>;
|
validationRules?: Array<Rule | RuleGroup>;
|
||||||
validators?: {[key: string]: IValidator | RuleGroup};
|
validators?: { [key: string]: IValidator | RuleGroup };
|
||||||
maxlength?: number;
|
maxlength?: number;
|
||||||
options?: Array<{value: string | number, label: string}>;
|
options?: Array<{ value: string | number; label: string }>;
|
||||||
autocomplete?: string;
|
autocomplete?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
focusInitially?: boolean;
|
focusInitially?: boolean;
|
||||||
|
@ -105,11 +111,11 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'validate', shouldValidate: boolean): void,
|
(event: 'validate', shouldValidate: boolean): void;
|
||||||
(event: 'input', value: any): void,
|
(event: 'input', value: any): void;
|
||||||
(event: 'focus'): void,
|
(event: 'focus'): void;
|
||||||
(event: 'blur'): void,
|
(event: 'blur'): void;
|
||||||
(event: 'enter'): void,
|
(event: 'enter'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
|
@ -122,10 +128,10 @@ const slots = useSlots();
|
||||||
const inputRef = ref<HTMLTextAreaElement | null>(null);
|
const inputRef = ref<HTMLTextAreaElement | null>(null);
|
||||||
|
|
||||||
function getInputValidationError(): ReturnType<IValidator['validate']> {
|
function getInputValidationError(): ReturnType<IValidator['validate']> {
|
||||||
const rules = props.validationRules || [];
|
const rules = props.validationRules ?? [];
|
||||||
const validators = {
|
const validators = {
|
||||||
...VALIDATORS,
|
...VALIDATORS,
|
||||||
...(props.validators || {}),
|
...(props.validators ?? {}),
|
||||||
} as { [key: string]: IValidator | RuleGroup };
|
} as { [key: string]: IValidator | RuleGroup };
|
||||||
|
|
||||||
if (props.required) {
|
if (props.required) {
|
||||||
|
@ -149,11 +155,7 @@ function getInputValidationError(): ReturnType<IValidator['validate']> {
|
||||||
|
|
||||||
if (rules[i].hasOwnProperty('rules')) {
|
if (rules[i].hasOwnProperty('rules')) {
|
||||||
const rule = rules[i] as RuleGroup;
|
const rule = rules[i] as RuleGroup;
|
||||||
const error = getValidationError(
|
const error = getValidationError(props.value, validators, rule);
|
||||||
props.value,
|
|
||||||
validators,
|
|
||||||
rule,
|
|
||||||
);
|
|
||||||
if (error) return error;
|
if (error) return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,10 +192,11 @@ const validationError = computed<string | null>(() => {
|
||||||
|
|
||||||
const hasDefaultSlot = computed(() => !!slots.default);
|
const hasDefaultSlot = computed(() => !!slots.default);
|
||||||
|
|
||||||
const showErrors = computed(() => (
|
const showErrors = computed(
|
||||||
|
() =>
|
||||||
!!validationError.value &&
|
!!validationError.value &&
|
||||||
((props.validateOnBlur && state.hasBlurred && !state.isTyping) || props.showValidationWarnings)
|
((props.validateOnBlur && state.hasBlurred && !state.isTyping) || props.showValidationWarnings),
|
||||||
));
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
emit('validate', !validationError.value);
|
emit('validate', !validationError.value);
|
||||||
|
@ -201,7 +204,10 @@ onMounted(() => {
|
||||||
if (props.focusInitially && inputRef.value) inputRef.value.focus();
|
if (props.focusInitially && inputRef.value) inputRef.value.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(() => validationError.value, (error) => emit('validate', !error));
|
watch(
|
||||||
|
() => validationError.value,
|
||||||
|
(error) => emit('validate', !error),
|
||||||
|
);
|
||||||
|
|
||||||
defineExpose({ inputRef });
|
defineExpose({ inputRef });
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
|
import { IValidator, RuleGroup, Validatable } from '../../types';
|
||||||
import { IValidator, RuleGroup } from "../../types";
|
|
||||||
|
|
||||||
export const emailRegex =
|
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,}))$/;
|
/^(([^<>()[\]\\.,;:\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 } = {
|
export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
||||||
REQUIRED: {
|
REQUIRED: {
|
||||||
validate: (value: string | number | boolean | null | undefined) => {
|
validate: (value: Validatable) => {
|
||||||
if (typeof value === 'string' && !!value.trim()) {
|
if (typeof value === 'string' && !!value.trim()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +20,7 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MIN_LENGTH: {
|
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) {
|
if (typeof value === 'string' && value.length < config.minimum) {
|
||||||
return {
|
return {
|
||||||
messageKey: 'formInput.validator.minCharactersRequired',
|
messageKey: 'formInput.validator.minCharactersRequired',
|
||||||
|
@ -33,7 +32,7 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MAX_LENGTH: {
|
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) {
|
if (typeof value === 'string' && value.length > config.maximum) {
|
||||||
return {
|
return {
|
||||||
messageKey: 'formInput.validator.maxCharactersRequired',
|
messageKey: 'formInput.validator.maxCharactersRequired',
|
||||||
|
@ -45,12 +44,12 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CONTAINS_NUMBER: {
|
CONTAINS_NUMBER: {
|
||||||
validate: (value: string | number | boolean | null | undefined, config: { minimum: number }) => {
|
validate: (value: Validatable, config: { minimum: number }) => {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const numberCount = (value.match(/\d/g) || []).length;
|
const numberCount = (value.match(/\d/g) ?? []).length;
|
||||||
if (numberCount < config.minimum) {
|
if (numberCount < config.minimum) {
|
||||||
return {
|
return {
|
||||||
messageKey: 'formInput.validator.numbersRequired',
|
messageKey: 'formInput.validator.numbersRequired',
|
||||||
|
@ -62,7 +61,7 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
VALID_EMAIL: {
|
VALID_EMAIL: {
|
||||||
validate: (value: string | number | boolean | null | undefined) => {
|
validate: (value: Validatable) => {
|
||||||
if (!emailRegex.test(String(value).trim().toLowerCase())) {
|
if (!emailRegex.test(String(value).trim().toLowerCase())) {
|
||||||
return {
|
return {
|
||||||
messageKey: 'formInput.validator.validEmailRequired',
|
messageKey: 'formInput.validator.validEmailRequired',
|
||||||
|
@ -73,12 +72,12 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CONTAINS_UPPERCASE: {
|
CONTAINS_UPPERCASE: {
|
||||||
validate: (value: string | number | boolean | null | undefined, config: { minimum: number }) => {
|
validate: (value: Validatable, config: { minimum: number }) => {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uppercaseCount = (value.match(/[A-Z]/g) || []).length;
|
const uppercaseCount = (value.match(/[A-Z]/g) ?? []).length;
|
||||||
if (uppercaseCount < config.minimum) {
|
if (uppercaseCount < config.minimum) {
|
||||||
return {
|
return {
|
||||||
messageKey: 'formInput.validator.uppercaseCharsRequired',
|
messageKey: 'formInput.validator.uppercaseCharsRequired',
|
||||||
|
@ -101,35 +100,30 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
|
||||||
messageKey: 'formInput.validator.defaultPasswordRequirements',
|
messageKey: 'formInput.validator.defaultPasswordRequirements',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ name: 'MAX_LENGTH', config: {maximum: 64} },
|
{ name: 'MAX_LENGTH', config: { maximum: 64 } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getValidationError = (
|
export const getValidationError = <T extends Validatable, C>(
|
||||||
value: any, // tslint:disable-line:no-any
|
value: T,
|
||||||
validators: { [key: string]: IValidator | RuleGroup },
|
validators: { [key: string]: IValidator | RuleGroup },
|
||||||
validator: IValidator | RuleGroup,
|
validator: IValidator | RuleGroup,
|
||||||
config?: any, // tslint:disable-line:no-any
|
config?: C,
|
||||||
): ReturnType<IValidator['validate']> => {
|
): ReturnType<IValidator['validate']> => {
|
||||||
if (validator.hasOwnProperty('rules')) {
|
if (validator.hasOwnProperty('rules')) {
|
||||||
const rules = (validator as RuleGroup).rules;
|
const rules = (validator as RuleGroup).rules;
|
||||||
for (let i = 0; i < rules.length; i++) {
|
for (let i = 0; i < rules.length; i++) {
|
||||||
if (rules[i].hasOwnProperty('rules')) {
|
if (rules[i].hasOwnProperty('rules')) {
|
||||||
const error = getValidationError(
|
const error = getValidationError(value, validators, rules[i] as RuleGroup, config);
|
||||||
value,
|
|
||||||
validators,
|
|
||||||
rules[i] as RuleGroup,
|
|
||||||
config,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rules[i].hasOwnProperty('name') ) {
|
if (rules[i].hasOwnProperty('name')) {
|
||||||
const rule = rules[i] as {name: string, config?: any}; // tslint:disable-line:no-any
|
const rule = rules[i] as { name: string; config?: C };
|
||||||
if (!validators[rule.name]) {
|
if (!validators[rule.name]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -140,17 +134,14 @@ export const getValidationError = (
|
||||||
validators[rule.name] as IValidator,
|
validators[rule.name] as IValidator,
|
||||||
rule.config,
|
rule.config,
|
||||||
);
|
);
|
||||||
if (error && (validator as RuleGroup).defaultError !== undefined) {
|
if (error && 'defaultError' in validator && validator.defaultError) {
|
||||||
// @ts-ignore
|
|
||||||
return validator.defaultError;
|
return validator.defaultError;
|
||||||
} else if (error) {
|
} else if (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (validator.hasOwnProperty('validate')) {
|
||||||
validator.hasOwnProperty('validate')
|
|
||||||
) {
|
|
||||||
return (validator as IValidator).validate(value, config);
|
return (validator as IValidator).validate(value, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,11 +72,11 @@ FormInputs.args = {
|
||||||
name: 'agree',
|
name: 'agree',
|
||||||
properties: {
|
properties: {
|
||||||
type: 'checkbox',
|
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 ❤️',
|
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',
|
labelSize: 'small',
|
||||||
tooltipText: 'Check this if you agree to be contacted by our marketing team'
|
tooltipText: 'Check this if you agree to be contacted by our marketing team',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<ResizeObserver
|
<ResizeObserver :breakpoints="[{ bp: 'md', width: 500 }]">
|
||||||
:breakpoints="[{bp: 'md', width: 500}]"
|
|
||||||
>
|
|
||||||
<template v-slot="{ bp }">
|
<template v-slot="{ bp }">
|
||||||
<div :class="bp === 'md' || columnView? $style.grid : $style.gridMulti">
|
<div :class="bp === 'md' || columnView ? $style.grid : $style.gridMulti">
|
||||||
<div
|
<div v-for="input in filteredInputs" :key="input.name">
|
||||||
v-for="(input) in filteredInputs"
|
<n8n-text
|
||||||
:key="input.name"
|
color="text-base"
|
||||||
|
v-if="input.properties.type === 'info'"
|
||||||
|
tag="div"
|
||||||
|
align="center"
|
||||||
>
|
>
|
||||||
<n8n-text color="text-base" v-if="input.properties.type === 'info'" tag="div" align="center">
|
{{ input.properties.label }}
|
||||||
{{input.properties.label}}
|
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
<n8n-form-input
|
<n8n-form-input
|
||||||
v-else
|
v-else
|
||||||
|
@ -57,8 +57,8 @@ export default Vue.extend({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showValidationWarnings: false,
|
showValidationWarnings: false,
|
||||||
values: {} as {[key: string]: any},
|
values: {} as { [key: string]: any },
|
||||||
validity: {} as {[key: string]: boolean},
|
validity: {} as { [key: string]: boolean },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -74,10 +74,8 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
filteredInputs(): IFormInput[] {
|
filteredInputs(): IFormInput[] {
|
||||||
return (this.inputs as IFormInput[]).filter(
|
return (this.inputs as IFormInput[]).filter((input) =>
|
||||||
(input) => typeof input.shouldDisplay === 'function'
|
typeof input.shouldDisplay === 'function' ? input.shouldDisplay(this.values) : true,
|
||||||
? input.shouldDisplay(this.values)
|
|
||||||
: true,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isReadyToSubmit(): boolean {
|
isReadyToSubmit(): boolean {
|
||||||
|
@ -96,7 +94,7 @@ export default Vue.extend({
|
||||||
...this.values,
|
...this.values,
|
||||||
[name]: value, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
[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) {
|
onValidate(name: string, valid: boolean) {
|
||||||
Vue.set(this.validity, name, valid);
|
Vue.set(this.validity, name, valid);
|
||||||
|
@ -104,7 +102,7 @@ export default Vue.extend({
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
this.showValidationWarnings = true;
|
this.showValidationWarnings = true;
|
||||||
if (this.isReadyToSubmit) {
|
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]) {
|
if (this.values[input.name]) {
|
||||||
accu[input.name] = this.values[input.name];
|
accu[input.name] = this.values[input.name];
|
||||||
}
|
}
|
||||||
|
@ -133,5 +131,4 @@ export default Vue.extend({
|
||||||
composes: grid;
|
composes: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -21,11 +21,15 @@ export default Vue.extend({
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'medium',
|
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: {
|
color: {
|
||||||
type: String,
|
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: {
|
align: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -36,15 +40,17 @@ export default Vue.extend({
|
||||||
classes() {
|
classes() {
|
||||||
const applied = [];
|
const applied = [];
|
||||||
if (this.align) {
|
if (this.align) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
applied.push(`align-${this.align}`);
|
applied.push(`align-${this.align}`);
|
||||||
}
|
}
|
||||||
if (this.color) {
|
if (this.color) {
|
||||||
applied.push(this.color);
|
applied.push(this.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
applied.push(`size-${this.size}`);
|
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]);
|
return applied.map((c) => (this.$style as { [key: string]: string })[c]);
|
||||||
},
|
},
|
||||||
|
@ -121,5 +127,4 @@ export default Vue.extend({
|
||||||
.align-center {
|
.align-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,15 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<n8n-text
|
<n8n-text :size="size" :color="color" :compact="true" class="n8n-icon">
|
||||||
:size="size"
|
<font-awesome-icon :icon="icon" :spin="spin" :class="$style[size]" />
|
||||||
:color="color"
|
|
||||||
:compact="true"
|
|
||||||
class="n8n-icon"
|
|
||||||
>
|
|
||||||
<font-awesome-icon
|
|
||||||
:icon="icon"
|
|
||||||
:spin="spin"
|
|
||||||
:class="$style[size]"
|
|
||||||
/>
|
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -44,7 +35,6 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.xlarge {
|
.xlarge {
|
||||||
width: var(--font-size-xl) !important;
|
width: var(--font-size-xl) !important;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* tslint:disable:variable-name */
|
|
||||||
import N8nIconButton from './IconButton.vue';
|
import N8nIconButton from './IconButton.vue';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import { StoryFn } from "@storybook/vue";
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/Icon Button',
|
title: 'Atoms/Icon Button',
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<n8n-button
|
<n8n-button square v-bind="$props" v-on="$listeners" />
|
||||||
square
|
|
||||||
v-bind="$props"
|
|
||||||
v-on="$listeners"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -51,8 +47,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
float: {
|
float: {
|
||||||
type: String,
|
type: String,
|
||||||
validator: (value: string): boolean =>
|
validator: (value: string): boolean => ['left', 'right'].includes(value),
|
||||||
['left', 'right'].includes(value),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
/* tslint:disable:variable-name */
|
|
||||||
|
|
||||||
import N8nInfoAccordion from './InfoAccordion.vue';
|
import N8nInfoAccordion from './InfoAccordion.vue';
|
||||||
import { StoryFn } from "@storybook/vue";
|
import type { StoryFn } from '@storybook/vue';
|
||||||
import { action } from "@storybook/addon-actions";
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/Info Accordion',
|
title: 'Atoms/Info Accordion',
|
||||||
component: N8nInfoAccordion,
|
component: N8nInfoAccordion,
|
||||||
argTypes: {
|
argTypes: {},
|
||||||
},
|
|
||||||
parameters: {
|
parameters: {
|
||||||
backgrounds: { default: '--color-background-light' },
|
backgrounds: { default: '--color-background-light' },
|
||||||
},
|
},
|
||||||
|
@ -18,7 +15,7 @@ const methods = {
|
||||||
onClick: action('click'),
|
onClick: action('click'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default: StoryFn = (args, {argTypes}) => ({
|
export const Default: StoryFn = (args, { argTypes }) => ({
|
||||||
props: Object.keys(argTypes),
|
props: Object.keys(argTypes),
|
||||||
components: {
|
components: {
|
||||||
N8nInfoAccordion,
|
N8nInfoAccordion,
|
||||||
|
|
|
@ -1,17 +1,33 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['accordion', $style.container]" >
|
<div :class="['accordion', $style.container]">
|
||||||
<div :class="{[$style.header]: true, [$style.expanded]: expanded }" @click="toggle">
|
<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-icon
|
||||||
<n8n-text :class="$style.headerText" color="text-base" size="small" align="left" bold>{{ title }}</n8n-text>
|
v-if="headerIcon"
|
||||||
<n8n-icon :icon="expanded? 'chevron-up' : 'chevron-down'" bold />
|
: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>
|
||||||
<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 -->
|
<!-- 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-if="items.length > 0" :class="$style.accordionItems">
|
||||||
<div v-for="item in items" :key="item.id" :class="$style.accordionItem">
|
<div v-for="item in items" :key="item.id" :class="$style.accordionItem">
|
||||||
<n8n-tooltip :disabled="!item.tooltip">
|
<n8n-tooltip :disabled="!item.tooltip">
|
||||||
<div slot="content" v-html="item.tooltip" @click="onTooltipClick(item.id, $event)"></div>
|
<div
|
||||||
<n8n-icon :icon="item.icon" :color="item.iconColor" size="small" class="mr-2xs"/>
|
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-tooltip>
|
||||||
<n8n-text size="small" color="text-base">{{ item.label }}</n8n-text>
|
<n8n-text size="small" color="text-base">{{ item.label }}</n8n-text>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,7 +75,7 @@ export default Vue.extend({
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
headerIcon: {
|
headerIcon: {
|
||||||
type: Object as () => { icon: string, color: string },
|
type: Object as PropType<{ icon: string; color: string }>,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -128,5 +144,4 @@ export default Vue.extend({
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
/* tslint:disable:variable-name */
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
|
||||||
import { StoryFn } from '@storybook/vue';
|
|
||||||
import N8nInfoTip from './InfoTip.vue';
|
import N8nInfoTip from './InfoTip.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
<template>
|
<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
|
<n8n-tooltip
|
||||||
v-if="type === 'tooltip'"
|
v-if="type === 'tooltip'"
|
||||||
:placement="tooltipPlacement"
|
:placement="tooltipPlacement"
|
||||||
|
@ -7,14 +14,14 @@
|
||||||
:disabled="type !== 'tooltip'"
|
:disabled="type !== 'tooltip'"
|
||||||
>
|
>
|
||||||
<span :class="$style.iconText">
|
<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>
|
||||||
<span slot="content">
|
<span slot="content">
|
||||||
<slot />
|
<slot />
|
||||||
</span>
|
</span>
|
||||||
</n8n-tooltip>
|
</n8n-tooltip>
|
||||||
<span :class="$style.iconText" v-else>
|
<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>
|
<span>
|
||||||
<slot />
|
<slot />
|
||||||
</span>
|
</span>
|
||||||
|
@ -44,8 +51,7 @@ export default Vue.extend({
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'note',
|
default: 'note',
|
||||||
validator: (value: string): boolean =>
|
validator: (value: string): boolean => ['note', 'tooltip'].includes(value),
|
||||||
['note', 'tooltip'].includes(value),
|
|
||||||
},
|
},
|
||||||
bold: {
|
bold: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
import {render} from '@testing-library/vue';
|
import { render } from '@testing-library/vue';
|
||||||
import N8nInfoTip from "../InfoTip.vue";
|
import N8nInfoTip from '../InfoTip.vue';
|
||||||
|
|
||||||
const slots = {
|
const slots = {
|
||||||
default: [
|
default: ['Need help doing something?', '<a href="/docs" target="_blank">Open docs</a>'],
|
||||||
'Need help doing something?',
|
|
||||||
'<a href="/docs" target="_blank">Open docs</a>',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
const stubs = ['n8n-tooltip'];
|
const stubs = ['n8n-tooltip'];
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
/* tslint:disable:variable-name */
|
|
||||||
import N8nInput from './Input.vue';
|
import N8nInput from './Input.vue';
|
||||||
import N8nIcon from '../N8nIcon';
|
import N8nIcon from '../N8nIcon';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import { StoryFn } from '@storybook/vue';
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/Input',
|
title: 'Atoms/Input',
|
||||||
|
@ -41,7 +40,8 @@ const Template: StoryFn = (args, { argTypes }) => ({
|
||||||
components: {
|
components: {
|
||||||
N8nInput,
|
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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
val: '',
|
val: '',
|
||||||
|
@ -82,14 +82,14 @@ TextArea.args = {
|
||||||
placeholder: 'placeholder...',
|
placeholder: 'placeholder...',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const WithPrefix: StoryFn = (args, { argTypes }) => ({
|
const WithPrefix: StoryFn = (args, { argTypes }) => ({
|
||||||
props: Object.keys(argTypes),
|
props: Object.keys(argTypes),
|
||||||
components: {
|
components: {
|
||||||
N8nIcon,
|
N8nIcon,
|
||||||
N8nInput,
|
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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
val: '',
|
val: '',
|
||||||
|
@ -109,7 +109,8 @@ const WithSuffix: StoryFn = (args, { argTypes }) => ({
|
||||||
N8nIcon,
|
N8nIcon,
|
||||||
N8nInput,
|
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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
val: '',
|
val: '',
|
||||||
|
|
|
@ -33,8 +33,7 @@ export default Vue.extend({
|
||||||
ElInput, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
ElInput, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {},
|
||||||
},
|
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
validator: (value: string): boolean =>
|
validator: (value: string): boolean =>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {render} from '@testing-library/vue';
|
import { render } from '@testing-library/vue';
|
||||||
import N8nInput from "../Input.vue";
|
import N8nInput from '../Input.vue';
|
||||||
|
|
||||||
describe('N8nInput', () => {
|
describe('N8nInput', () => {
|
||||||
it('should render correctly', () => {
|
it('should render correctly', () => {
|
||||||
|
|
|
@ -17,15 +17,21 @@
|
||||||
<n8n-text color="primary" :bold="bold" :size="size" v-if="required">*</n8n-text>
|
<n8n-text color="primary" :bold="bold" :size="size" v-if="required">*</n8n-text>
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
</div>
|
</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-tooltip placement="top" :popper-class="$style.tooltipPopper">
|
||||||
<n8n-icon icon="question-circle" size="small" />
|
<n8n-icon icon="question-circle" size="small" />
|
||||||
<div slot="content" v-html="addTargetBlank(tooltipText)" />
|
<div slot="content" v-html="addTargetBlank(tooltipText)" />
|
||||||
</n8n-tooltip>
|
</n8n-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<div v-if="$slots.options && label" :class="{[$style.overlay]: true, [$style.visible]: showOptions}" />
|
<div
|
||||||
<div v-if="$slots.options" :class="{[$style.options]: true, [$style.visible]: showOptions}">
|
v-if="$slots.options && label"
|
||||||
<slot name="options"/>
|
:class="{ [$style.overlay]: true, [$style.visible]: showOptions }"
|
||||||
|
/>
|
||||||
|
<div v-if="$slots.options" :class="{ [$style.options]: true, [$style.visible]: showOptions }">
|
||||||
|
<slot name="options" />
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -71,8 +77,7 @@ export default Vue.extend({
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'medium',
|
default: 'medium',
|
||||||
validator: (value: string): boolean =>
|
validator: (value: string): boolean => ['small', 'medium'].includes(value),
|
||||||
['small', 'medium'].includes(value),
|
|
||||||
},
|
},
|
||||||
underline: {
|
underline: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -98,7 +103,8 @@ export default Vue.extend({
|
||||||
.inputLabel {
|
.inputLabel {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.container:hover,.inputLabel:hover {
|
.container:hover,
|
||||||
|
.inputLabel:hover {
|
||||||
.infoIcon {
|
.infoIcon {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
@ -136,7 +142,7 @@ export default Vue.extend({
|
||||||
.options {
|
.options {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
background-color: var(--color-background-xlight);
|
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;
|
float: right;
|
||||||
|
@ -147,7 +153,7 @@ export default Vue.extend({
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
opacity: 0;
|
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 {
|
> div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -157,7 +163,11 @@ export default Vue.extend({
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 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);
|
margin-left: var(--spacing-s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -91,4 +91,3 @@ Sizes.args = {
|
||||||
placeholder: 'placeholder...',
|
placeholder: 'placeholder...',
|
||||||
controls: false,
|
controls: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<n8n-route :to="to" :newWindow="newWindow"
|
<n8n-route :to="to" :newWindow="newWindow" v-on="$listeners" class="n8n-link">
|
||||||
v-on="$listeners"
|
<span :class="$style[`${underline ? `${theme}-underline` : theme}`]">
|
||||||
class="n8n-link"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
:class="$style[`${underline ? `${theme}-underline` : theme}`]"
|
|
||||||
>
|
|
||||||
<n8n-text :size="size" :bold="bold">
|
<n8n-text :size="size" :bold="bold">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
|
@ -54,18 +49,13 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
@import "../../utils";
|
@import '../../utils';
|
||||||
|
|
||||||
.primary {
|
.primary {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
color: saturation(
|
color: saturation(--color-primary-h, --color-primary-s, --color-primary-l, -(30%));
|
||||||
--color-primary-h,
|
|
||||||
--color-primary-s,
|
|
||||||
--color-primary-l,
|
|
||||||
-(30%)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,5 +111,4 @@ export default Vue.extend({
|
||||||
composes: secondary;
|
composes: secondary;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<template>
|
<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">
|
<template slot="template">
|
||||||
<div v-if="variant === 'h1'">
|
<div v-if="variant === 'h1'">
|
||||||
<div
|
<div
|
||||||
|
@ -9,9 +13,7 @@
|
||||||
[$style.h1Last]: item === rows && rows > 1 && shrinkLast,
|
[$style.h1Last]: item === rows && rows > 1 && shrinkLast,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<el-skeleton-item
|
<el-skeleton-item :variant="variant" />
|
||||||
:variant="variant"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="variant === 'p'">
|
<div v-else-if="variant === 'p'">
|
||||||
|
@ -20,21 +22,15 @@
|
||||||
:key="index"
|
:key="index"
|
||||||
:class="{
|
:class="{
|
||||||
[$style.pLast]: item === rows && rows > 1 && shrinkLast,
|
[$style.pLast]: item === rows && rows > 1 && shrinkLast,
|
||||||
}">
|
}"
|
||||||
<el-skeleton-item
|
>
|
||||||
:variant="variant"
|
<el-skeleton-item :variant="variant" />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.custom" v-else-if="variant === 'custom'">
|
<div :class="$style.custom" v-else-if="variant === 'custom'">
|
||||||
<el-skeleton-item
|
<el-skeleton-item :variant="variant" />
|
||||||
:variant="variant"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<el-skeleton-item
|
<el-skeleton-item v-else :variant="variant" />
|
||||||
v-else
|
|
||||||
:variant="variant"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
</template>
|
</template>
|
||||||
|
@ -71,7 +67,20 @@ export default Vue.extend({
|
||||||
variant: {
|
variant: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'p',
|
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),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,8 +41,10 @@ export const Markdown = Template.bind({});
|
||||||
Markdown.args = {
|
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![](fileId:1)\n\n**Sample Response:**\n\n![](https://community.n8n.io/uploads/default/original/2X/d/d21ba41d7ac9ff5cd8148fedb07d0f1ff53b2529.png)\n`,
|
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![](fileId:1)\n\n**Sample Response:**\n\n![](https://community.n8n.io/uploads/default/original/2X/d/d21ba41d7ac9ff5cd8148fedb07d0f1ff53b2529.png)\n`,
|
||||||
loading: false,
|
loading: false,
|
||||||
images: [{
|
images: [
|
||||||
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
url: 'https://community.n8n.io/uploads/default/optimized/2X/b/b737a95de4dfe0825d50ca098171e9f33a459e74_2_690x288.png',
|
url: 'https://community.n8n.io/uploads/default/optimized/2X/b/b737a95de4dfe0825d50ca098171e9f33a459e74_2_690x288.png',
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,18 +4,13 @@
|
||||||
v-if="!loading"
|
v-if="!loading"
|
||||||
ref="editor"
|
ref="editor"
|
||||||
class="ph-no-capture"
|
class="ph-no-capture"
|
||||||
:class="$style[theme]" v-html="htmlContent"
|
:class="$style[theme]"
|
||||||
|
v-html="htmlContent"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
/>
|
/>
|
||||||
<div v-else :class="$style.markdown">
|
<div v-else :class="$style.markdown">
|
||||||
<div v-for="(block, index) in loadingBlocks"
|
<div v-for="(block, index) in loadingBlocks" :key="index">
|
||||||
:key="index">
|
<n8n-loading :loading="loading" :rows="loadingRows" animated variant="p" />
|
||||||
<n8n-loading
|
|
||||||
:loading="loading"
|
|
||||||
:rows="loadingRows"
|
|
||||||
animated
|
|
||||||
variant="p"
|
|
||||||
/>
|
|
||||||
<div :class="$style.spacer" />
|
<div :class="$style.spacer" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,10 +21,9 @@
|
||||||
import N8nLoading from '../N8nLoading';
|
import N8nLoading from '../N8nLoading';
|
||||||
import Markdown from 'markdown-it';
|
import Markdown from 'markdown-it';
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
import markdownLink from 'markdown-it-link-attributes';
|
import markdownLink from 'markdown-it-link-attributes';
|
||||||
// @ts-ignore
|
|
||||||
import markdownEmoji from 'markdown-it-emoji';
|
import markdownEmoji from 'markdown-it-emoji';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import markdownTasklists from 'markdown-it-task-lists';
|
import markdownTasklists from 'markdown-it-task-lists';
|
||||||
|
|
||||||
|
@ -41,26 +35,32 @@ const DEFAULT_OPTIONS_MARKDOWN = {
|
||||||
linkify: true,
|
linkify: true,
|
||||||
typographer: true,
|
typographer: true,
|
||||||
breaks: true,
|
breaks: true,
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
const DEFAULT_OPTIONS_LINK_ATTRIBUTES = {
|
const DEFAULT_OPTIONS_LINK_ATTRIBUTES = {
|
||||||
attrs: {
|
attrs: {
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
rel: 'noopener',
|
rel: 'noopener',
|
||||||
},
|
},
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
const DEFAULT_OPTIONS_TASKLISTS = {
|
const DEFAULT_OPTIONS_TASKLISTS = {
|
||||||
label: true,
|
label: true,
|
||||||
labelAfter: true,
|
labelAfter: true,
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
interface IImage {
|
interface IImage {
|
||||||
id: string;
|
id: string;
|
||||||
url: 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({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
|
@ -75,7 +75,7 @@ export default Vue.extend({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
images: {
|
images: {
|
||||||
type: Array,
|
type: Array<IImage>,
|
||||||
},
|
},
|
||||||
loading: {
|
loading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -86,16 +86,14 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
loadingRows: {
|
loadingRows: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: () => {
|
default: () => 3,
|
||||||
return 3;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'markdown',
|
default: 'markdown',
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
type: Object,
|
type: Object as PropType<Options>,
|
||||||
default() {
|
default() {
|
||||||
return {
|
return {
|
||||||
markdown: DEFAULT_OPTIONS_MARKDOWN,
|
markdown: DEFAULT_OPTIONS_MARKDOWN,
|
||||||
|
@ -113,7 +111,6 @@ export default Vue.extend({
|
||||||
|
|
||||||
const imageUrls: { [key: string]: string } = {};
|
const imageUrls: { [key: string]: string } = {};
|
||||||
if (this.images) {
|
if (this.images) {
|
||||||
// @ts-ignore
|
|
||||||
this.images.forEach((image: IImage) => {
|
this.images.forEach((image: IImage) => {
|
||||||
if (!image) {
|
if (!image) {
|
||||||
// Happens if an image got deleted but the workflow
|
// 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 fileIdRegex = new RegExp('fileId:([0-9]+)');
|
||||||
const imageFilesRegex = /\.(jpeg|jpg|gif|png|webp|bmp|tif|tiff|apng|svg|avif)$/;
|
|
||||||
let contentToRender = this.content;
|
let contentToRender = this.content;
|
||||||
if (this.withMultiBreaks) {
|
if (this.withMultiBreaks) {
|
||||||
contentToRender = contentToRender.replaceAll('\n\n', '\n \n');
|
contentToRender = contentToRender.replaceAll('\n\n', '\n \n');
|
||||||
}
|
}
|
||||||
const html = this.md.render(escapeMarkdown(contentToRender));
|
const html = this.md.render(escapeMarkdown(contentToRender));
|
||||||
const safeHtml = xss(html, {
|
const safeHtml = xss(html, {
|
||||||
onTagAttr: (tag, name, value, isWhiteAttr) => {
|
onTagAttr: (tag, name, value) => {
|
||||||
if (tag === 'img' && name === 'src') {
|
if (tag === 'img' && name === 'src') {
|
||||||
if (value.match(fileIdRegex)) {
|
if (value.match(fileIdRegex)) {
|
||||||
const id = value.split('fileId:')[1];
|
const id = value.split('fileId:')[1];
|
||||||
|
@ -147,7 +143,7 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
// Return nothing, means keep the default handling measure
|
// Return nothing, means keep the default handling measure
|
||||||
},
|
},
|
||||||
onTag (tag, code, options) {
|
onTag(tag, code) {
|
||||||
if (tag === 'img' && code.includes(`alt="workflow-screenshot"`)) {
|
if (tag === 'img' && code.includes(`alt="workflow-screenshot"`)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -170,13 +166,13 @@ export default Vue.extend({
|
||||||
onClick(event: MouseEvent) {
|
onClick(event: MouseEvent) {
|
||||||
let clickedLink = null;
|
let clickedLink = null;
|
||||||
|
|
||||||
if(event.target instanceof HTMLAnchorElement) {
|
if (event.target instanceof HTMLAnchorElement) {
|
||||||
clickedLink = event.target;
|
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');
|
const parentLink = event.target.closest('a');
|
||||||
if(parentLink) {
|
if (parentLink) {
|
||||||
clickedLink = parentLink;
|
clickedLink = parentLink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,13 +191,17 @@ export default Vue.extend({
|
||||||
line-height: var(--font-line-height-xloose);
|
line-height: var(--font-line-height-xloose);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4 {
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4 {
|
||||||
margin-bottom: var(--spacing-s);
|
margin-bottom: var(--spacing-s);
|
||||||
font-size: var(--font-size-m);
|
font-size: var(--font-size-m);
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
}
|
}
|
||||||
|
|
||||||
h3, h4 {
|
h3,
|
||||||
|
h4 {
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,7 +210,8 @@ export default Vue.extend({
|
||||||
margin-bottom: var(--spacing-s);
|
margin-bottom: var(--spacing-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul, ol {
|
ul,
|
||||||
|
ol {
|
||||||
margin-bottom: var(--spacing-s);
|
margin-bottom: var(--spacing-s);
|
||||||
padding-left: var(--spacing-m);
|
padding-left: var(--spacing-m);
|
||||||
|
|
||||||
|
@ -261,7 +262,10 @@ export default Vue.extend({
|
||||||
.sticky {
|
.sticky {
|
||||||
color: var(--color-text-dark);
|
color: var(--color-text-dark);
|
||||||
|
|
||||||
h1, h2, h3, h4 {
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4 {
|
||||||
margin-bottom: var(--spacing-2xs);
|
margin-bottom: var(--spacing-2xs);
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
line-height: var(--font-line-height-loose);
|
line-height: var(--font-line-height-loose);
|
||||||
|
@ -275,7 +279,10 @@ export default Vue.extend({
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3, h4, h5, h6 {
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
font-size: var(--font-size-m);
|
font-size: var(--font-size-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,7 +293,8 @@ export default Vue.extend({
|
||||||
line-height: var(--font-line-height-loose);
|
line-height: var(--font-line-height-loose);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul, ol {
|
ul,
|
||||||
|
ol {
|
||||||
margin-bottom: var(--spacing-2xs);
|
margin-bottom: var(--spacing-2xs);
|
||||||
padding-left: var(--spacing-m);
|
padding-left: var(--spacing-m);
|
||||||
|
|
||||||
|
@ -304,7 +312,9 @@ export default Vue.extend({
|
||||||
color: var(--color-secondary);
|
color: var(--color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
pre > code,li > code, p > code {
|
pre > code,
|
||||||
|
li > code,
|
||||||
|
p > code {
|
||||||
color: var(--color-secondary);
|
color: var(--color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +327,7 @@ export default Vue.extend({
|
||||||
img {
|
img {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|
||||||
&[src*="#full-width"] {
|
&[src*='#full-width'] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import N8nMenu from './Menu.vue';
|
import N8nMenu from './Menu.vue';
|
||||||
import N8nIcon from '../N8nIcon';
|
import N8nIcon from '../N8nIcon';
|
||||||
import N8nText from '../N8nText';
|
import N8nText from '../N8nText';
|
||||||
import { StoryFn } from '@storybook/vue';
|
import type { StoryFn } from '@storybook/vue';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/Menu',
|
title: 'Atoms/Menu',
|
||||||
component: N8nMenu,
|
component: N8nMenu,
|
||||||
argTypes: {
|
argTypes: {},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="{
|
<div
|
||||||
|
:class="{
|
||||||
['menu-container']: true,
|
['menu-container']: true,
|
||||||
[$style.container]: true,
|
[$style.container]: true,
|
||||||
[$style.menuCollapsed]: collapsed
|
[$style.menuCollapsed]: collapsed,
|
||||||
}">
|
}"
|
||||||
|
>
|
||||||
<div v-if="$slots.header" :class="$style.menuHeader">
|
<div v-if="$slots.header" :class="$style.menuHeader">
|
||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.menuContent">
|
<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">
|
<div v-if="$slots.menuPrefix" :class="$style.menuPrefix">
|
||||||
<slot name="menuPrefix"></slot>
|
<slot name="menuPrefix"></slot>
|
||||||
</div>
|
</div>
|
||||||
<el-menu
|
<el-menu :defaultActive="defaultActive" :collapse="collapsed" v-on="$listeners">
|
||||||
:defaultActive="defaultActive"
|
|
||||||
:collapse="collapsed"
|
|
||||||
v-on="$listeners"
|
|
||||||
>
|
|
||||||
<n8n-menu-item
|
<n8n-menu-item
|
||||||
v-for="item in upperMenuItems"
|
v-for="item in upperMenuItems"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
|
@ -30,11 +28,7 @@
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</div>
|
</div>
|
||||||
<div :class="[$style.lowerContent, 'pb-2xs']">
|
<div :class="[$style.lowerContent, 'pb-2xs']">
|
||||||
<el-menu
|
<el-menu :defaultActive="defaultActive" :collapse="collapsed" v-on="$listeners">
|
||||||
:defaultActive="defaultActive"
|
|
||||||
:collapse="collapsed"
|
|
||||||
v-on="$listeners"
|
|
||||||
>
|
|
||||||
<n8n-menu-item
|
<n8n-menu-item
|
||||||
v-for="item in lowerMenuItems"
|
v-for="item in lowerMenuItems"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
|
@ -62,7 +56,6 @@ import ElMenu from 'element-ui/lib/menu';
|
||||||
import N8nMenuItem from '../N8nMenuItem';
|
import N8nMenuItem from '../N8nMenuItem';
|
||||||
|
|
||||||
import Vue, { PropType } from 'vue';
|
import Vue, { PropType } from 'vue';
|
||||||
import { Route } from 'vue-router';
|
|
||||||
import { IMenuItem } from '../../types';
|
import { IMenuItem } from '../../types';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
@ -108,9 +101,13 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.mode === 'router') {
|
if (this.mode === 'router') {
|
||||||
const found = this.items.find(item => {
|
const found = this.items.find((item) => {
|
||||||
return Array.isArray(item.activateOnRouteNames) && item.activateOnRouteNames.includes(this.$route.name || '') ||
|
return (
|
||||||
Array.isArray(item.activateOnRoutePaths) && item.activateOnRoutePaths.includes(this.$route.path);
|
(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 : '';
|
this.activeTab = found ? found.id : '';
|
||||||
} else {
|
} else {
|
||||||
|
@ -121,10 +118,14 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
upperMenuItems(): IMenuItem[] {
|
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[] {
|
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: {
|
methods: {
|
||||||
|
@ -178,11 +179,13 @@ export default Vue.extend({
|
||||||
|
|
||||||
.menuCollapsed {
|
.menuCollapsed {
|
||||||
transition: width 150ms ease-in-out;
|
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);
|
padding: var(--spacing-xs) var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import N8nMenuItem from ".";
|
import N8nMenuItem from '.';
|
||||||
import ElMenu from 'element-ui/lib/menu';
|
import ElMenu from 'element-ui/lib/menu';
|
||||||
import { StoryFn } from '@storybook/vue';
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/MenuItem',
|
title: 'Atoms/MenuItem',
|
||||||
|
@ -11,7 +11,7 @@ const template: StoryFn = (args, { argTypes }) => ({
|
||||||
props: Object.keys(argTypes),
|
props: Object.keys(argTypes),
|
||||||
components: {
|
components: {
|
||||||
ElMenu, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
ElMenu, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||||
N8nMenuItem ,
|
N8nMenuItem,
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<div style="width: 200px">
|
<div style="width: 200px">
|
||||||
|
|
|
@ -6,14 +6,19 @@
|
||||||
:class="{
|
:class="{
|
||||||
[$style.submenu]: true,
|
[$style.submenu]: true,
|
||||||
[$style.compact]: compact,
|
[$style.compact]: compact,
|
||||||
[$style.active]: mode === 'router' && isItemActive(item)
|
[$style.active]: mode === 'router' && isItemActive(item),
|
||||||
}"
|
}"
|
||||||
:index="item.id"
|
:index="item.id"
|
||||||
popper-append-to-body
|
popper-append-to-body
|
||||||
:popper-class="`${$style.submenuPopper} ${popperClass}`"
|
:popper-class="`${$style.submenuPopper} ${popperClass}`"
|
||||||
>
|
>
|
||||||
<template slot="title">
|
<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>
|
<span :class="$style.label">{{ item.label }}</span>
|
||||||
</template>
|
</template>
|
||||||
<el-menu-item
|
<el-menu-item
|
||||||
|
@ -32,7 +37,13 @@
|
||||||
<span :class="$style.label">{{ child.label }}</span>
|
<span :class="$style.label">{{ child.label }}</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
</el-submenu>
|
</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
|
<el-menu-item
|
||||||
:id="item.id"
|
:id="item.id"
|
||||||
:class="{
|
:class="{
|
||||||
|
@ -40,12 +51,17 @@
|
||||||
[$style.item]: true,
|
[$style.item]: true,
|
||||||
[$style.disableActiveStyle]: !isItemActive(item),
|
[$style.disableActiveStyle]: !isItemActive(item),
|
||||||
[$style.active]: isItemActive(item),
|
[$style.active]: isItemActive(item),
|
||||||
[$style.compact]: compact
|
[$style.compact]: compact,
|
||||||
}"
|
}"
|
||||||
:index="item.id"
|
:index="item.id"
|
||||||
@click="onItemClick(item)"
|
@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>
|
<span :class="$style.label">{{ item.label }}</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
</n8n-tooltip>
|
</n8n-tooltip>
|
||||||
|
@ -58,8 +74,7 @@ import ElMenuItem from 'element-ui/lib/menu-item';
|
||||||
import N8nTooltip from '../N8nTooltip';
|
import N8nTooltip from '../N8nTooltip';
|
||||||
import N8nIcon from '../N8nIcon';
|
import N8nIcon from '../N8nIcon';
|
||||||
import { IMenuItem } from '../../types';
|
import { IMenuItem } from '../../types';
|
||||||
import Vue from 'vue';
|
import Vue, { PropType } from 'vue';
|
||||||
import { Route } from 'vue-router';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'n8n-menu-item',
|
name: 'n8n-menu-item',
|
||||||
|
@ -71,7 +86,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object as () => IMenuItem,
|
type: Object as PropType<IMenuItem>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
compact: {
|
compact: {
|
||||||
|
@ -97,21 +112,30 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
availableChildren(): IMenuItem[] {
|
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: {
|
methods: {
|
||||||
isItemActive(item: IMenuItem): boolean {
|
isItemActive(item: IMenuItem): boolean {
|
||||||
const isItemActive = this.isActive(item);
|
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;
|
return isItemActive || hasActiveChild;
|
||||||
},
|
},
|
||||||
isActive(item: IMenuItem): boolean {
|
isActive(item: IMenuItem): boolean {
|
||||||
if (this.mode === 'router') {
|
if (this.mode === 'router') {
|
||||||
if (item.activateOnRoutePaths) {
|
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) {
|
} 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;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -127,22 +151,20 @@ export default Vue.extend({
|
||||||
|
|
||||||
if (item.properties.newWindow) {
|
if (item.properties.newWindow) {
|
||||||
window.open(href);
|
window.open(href);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
window.location.assign(item.properties.href);
|
window.location.assign(item.properties.href);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
this.$emit('click', event, item.id);
|
this.$emit('click', event, item.id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
// Element menu-item overrides
|
// 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-font-color: var(--color-text-base);
|
||||||
--menu-item-active-background-color: var(--color-foreground-base);
|
--menu-item-active-background-color: var(--color-foreground-base);
|
||||||
--menu-item-active-font-color: var(--color-text-dark);
|
--menu-item-active-font-color: var(--color-text-dark);
|
||||||
|
@ -152,7 +174,6 @@ export default Vue.extend({
|
||||||
--submenu-item-height: 27px;
|
--submenu-item-height: 27px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.submenu {
|
.submenu {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
|
|
||||||
|
@ -177,7 +198,9 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.icon { color: var(--color-text-dark) }
|
.icon {
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,9 +212,11 @@ export default Vue.extend({
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.icon { color: var(--color-text-dark) }
|
.icon {
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.disableActiveStyle {
|
.disableActiveStyle {
|
||||||
|
@ -214,10 +239,13 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
.active {
|
.active {
|
||||||
&, & :global(.el-submenu__title) {
|
&,
|
||||||
|
& :global(.el-submenu__title) {
|
||||||
background-color: var(--color-foreground-base);
|
background-color: var(--color-foreground-base);
|
||||||
border-radius: var(--border-radius-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 N8nNodeIcon from "./NodeIcon.vue";
|
import type { StoryFn } from '@storybook/vue';
|
||||||
import { StoryFn } from '@storybook/vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/NodeIcon',
|
title: 'Atoms/NodeIcon',
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<font-awesome-icon v-else :icon="name" :style="fontStyleData" />
|
<font-awesome-icon v-else :icon="name" :style="fontStyleData" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else :class="$style['node-icon-placeholder']">
|
<div v-else :class="$style['node-icon-placeholder']">
|
||||||
{{ nodeTypeName? nodeTypeName.charAt(0) : '?' }}
|
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
|
||||||
?
|
?
|
||||||
</div>
|
</div>
|
||||||
</n8n-tooltip>
|
</n8n-tooltip>
|
||||||
|
@ -39,8 +39,7 @@ export default Vue.extend({
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
validator: (value: string): boolean =>
|
validator: (value: string): boolean => ['file', 'icon', 'unknown'].includes(value),
|
||||||
['file', 'icon', 'unknown'].includes(value),
|
|
||||||
},
|
},
|
||||||
src: {
|
src: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -68,8 +67,8 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
iconStyleData (): object {
|
iconStyleData(): object {
|
||||||
if(!this.size) {
|
if (!this.size) {
|
||||||
return {
|
return {
|
||||||
color: this.color || '',
|
color: this.color || '',
|
||||||
};
|
};
|
||||||
|
@ -82,7 +81,7 @@ export default Vue.extend({
|
||||||
'line-height': `${this.size}px`,
|
'line-height': `${this.size}px`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
fontStyleData (): object {
|
fontStyleData(): object {
|
||||||
return {
|
return {
|
||||||
'max-width': `${this.size}px`,
|
'max-width': `${this.size}px`,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
/* tslint:disable:variable-name */
|
|
||||||
|
|
||||||
import N8nNotice from './Notice.vue';
|
import N8nNotice from './Notice.vue';
|
||||||
import {StoryFn} from "@storybook/vue";
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/Notice',
|
title: 'Atoms/Notice',
|
||||||
|
@ -14,7 +12,7 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const SlotTemplate: StoryFn = (args, {argTypes}) => ({
|
const SlotTemplate: StoryFn = (args, { argTypes }) => ({
|
||||||
props: Object.keys(argTypes),
|
props: Object.keys(argTypes),
|
||||||
components: {
|
components: {
|
||||||
N8nNotice,
|
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>`,
|
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),
|
props: Object.keys(argTypes),
|
||||||
components: {
|
components: {
|
||||||
N8nNotice,
|
N8nNotice,
|
||||||
|
@ -53,19 +51,22 @@ Info.args = {
|
||||||
export const Sanitized = PropTemplate.bind({});
|
export const Sanitized = PropTemplate.bind({});
|
||||||
Sanitized.args = {
|
Sanitized.args = {
|
||||||
theme: 'warning',
|
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({});
|
export const Truncated = PropTemplate.bind({});
|
||||||
Truncated.args = {
|
Truncated.args = {
|
||||||
theme: 'warning',
|
theme: 'warning',
|
||||||
truncate: true,
|
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({});
|
export const HtmlEdgeCase = PropTemplate.bind({});
|
||||||
HtmlEdgeCase.args = {
|
HtmlEdgeCase.args = {
|
||||||
theme: 'warning',
|
theme: 'warning',
|
||||||
truncate: true,
|
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>
|
<template>
|
||||||
<div :id="id" :class="classes" role="alert" @click=onClick>
|
<div :id="id" :class="classes" role="alert" @click="onClick">
|
||||||
<div class="notice-content">
|
<div class="notice-content">
|
||||||
<n8n-text size="small" :compact="true">
|
<n8n-text size="small" :compact="true">
|
||||||
<slot>
|
<slot>
|
||||||
|
@ -18,16 +18,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import sanitizeHtml from 'sanitize-html';
|
import sanitizeHtml from 'sanitize-html';
|
||||||
import N8nText from "../../components/N8nText";
|
import N8nText from '../../components/N8nText';
|
||||||
import Locale from "../../mixins/locale";
|
import Locale from '../../mixins/locale';
|
||||||
import { uid } from "../../utils";
|
import { uid } from '../../utils';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'n8n-notice',
|
name: 'n8n-notice',
|
||||||
directives: {},
|
directives: {},
|
||||||
mixins: [
|
mixins: [Locale],
|
||||||
Locale,
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
id: {
|
id: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -56,11 +54,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
classes(): string[] {
|
classes(): string[] {
|
||||||
return [
|
return ['notice', this.$style.notice, this.$style[this.theme]];
|
||||||
'notice',
|
|
||||||
this.$style.notice,
|
|
||||||
this.$style[this.theme],
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
canTruncate(): boolean {
|
canTruncate(): boolean {
|
||||||
return this.fullContent !== undefined;
|
return this.fullContent !== undefined;
|
||||||
|
@ -71,18 +65,16 @@ export default Vue.extend({
|
||||||
this.showFullContent = !this.showFullContent;
|
this.showFullContent = !this.showFullContent;
|
||||||
},
|
},
|
||||||
sanitizeHtml(text: string): string {
|
sanitizeHtml(text: string): string {
|
||||||
return sanitizeHtml(
|
return sanitizeHtml(text, {
|
||||||
text, {
|
|
||||||
allowedAttributes: { a: ['data-key', 'href', 'target'] },
|
allowedAttributes: { a: ['data-key', 'href', 'target'] },
|
||||||
},
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
onClick(event: MouseEvent) {
|
onClick(event: MouseEvent) {
|
||||||
if (!(event.target instanceof HTMLElement)) return;
|
if (!(event.target instanceof HTMLElement)) return;
|
||||||
|
|
||||||
if (event.target.localName !== 'a') return;
|
if (event.target.localName !== 'a') return;
|
||||||
|
|
||||||
if (event.target.dataset && event.target.dataset.key) {
|
if (event.target.dataset?.key) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
@ -97,7 +89,6 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { render } from '@testing-library/vue';
|
import { render } from '@testing-library/vue';
|
||||||
import N8nNotice from "../Notice.vue";
|
import N8nNotice from '../Notice.vue';
|
||||||
|
|
||||||
describe('components', () => {
|
describe('components', () => {
|
||||||
describe('N8nNotice', () => {
|
describe('N8nNotice', () => {
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
/* tslint:disable:variable-name */
|
|
||||||
|
|
||||||
import N8nPulse from './Pulse.vue';
|
import N8nPulse from './Pulse.vue';
|
||||||
import { StoryFn } from "@storybook/vue";
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/Pulse',
|
title: 'Atoms/Pulse',
|
||||||
component: N8nPulse,
|
component: N8nPulse,
|
||||||
argTypes: {
|
argTypes: {},
|
||||||
},
|
|
||||||
parameters: {
|
parameters: {
|
||||||
backgrounds: { default: '--color-background-light' },
|
backgrounds: { default: '--color-background-light' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default: StoryFn = (args, {argTypes}) => ({
|
export const Default: StoryFn = () => ({
|
||||||
components: {
|
components: {
|
||||||
N8nPulse,
|
N8nPulse,
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,7 +17,6 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
||||||
$--light-pulse-color: hsla(
|
$--light-pulse-color: hsla(
|
||||||
var(--color-primary-h),
|
var(--color-primary-h),
|
||||||
var(--color-primary-s),
|
var(--color-primary-s),
|
||||||
|
@ -112,5 +111,4 @@ $--dark-pulse-color: hsla(
|
||||||
box-shadow: 0 0 0 0 transparent;
|
box-shadow: 0 0 0 0 transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,26 @@
|
||||||
<template>
|
<template>
|
||||||
<label role="radio" tabindex="-1" :class="{'n8n-radio-button': true, [$style.container]: true, [$style.hoverable]: !this.disabled}" aria-checked="true">
|
<label
|
||||||
<input type="radio" tabindex="-1" autocomplete="off" :class="$style.input" :value="value">
|
role="radio"
|
||||||
<div :class="{[$style.button]: true, [$style.active]: active, [$style[size]]: true, [$style.disabled]: disabled}" @click="$emit('click')">{{ label }}</div>
|
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>
|
</label>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -26,8 +45,7 @@ export default Vue.extend({
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'medium',
|
default: 'medium',
|
||||||
validator: (value: string): boolean =>
|
validator: (value: string): boolean => ['small', 'medium'].includes(value),
|
||||||
['small', 'medium'].includes(value),
|
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
|
@ -25,8 +25,7 @@ const Template = (args, { argTypes }) => ({
|
||||||
components: {
|
components: {
|
||||||
N8nRadioButtons,
|
N8nRadioButtons,
|
||||||
},
|
},
|
||||||
template:
|
template: `<n8n-radio-buttons v-model="val" v-bind="$props" @input="onInput">
|
||||||
`<n8n-radio-buttons v-model="val" v-bind="$props" @input="onInput">
|
|
||||||
</n8n-radio-buttons>`,
|
</n8n-radio-buttons>`,
|
||||||
methods,
|
methods,
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<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
|
<RadioButton
|
||||||
v-for="option in options"
|
v-for="option in options"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
|
@ -23,8 +26,7 @@ export default Vue.extend({
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
options: {
|
options: {},
|
||||||
},
|
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
@ -36,7 +38,7 @@ export default Vue.extend({
|
||||||
RadioButton,
|
RadioButton,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onClick(option: {label: string, value: string, disabled?: boolean}) {
|
onClick(option: { label: string; value: string; disabled?: boolean }) {
|
||||||
if (this.disabled || option.disabled) {
|
if (this.disabled || option.disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -47,7 +49,6 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
||||||
.radioGroup {
|
.radioGroup {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
@ -61,6 +62,4 @@ export default Vue.extend({
|
||||||
.disabled {
|
.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,8 @@ const Template = (args, { argTypes }) => ({
|
||||||
return {
|
return {
|
||||||
newWidth: this.width,
|
newWidth: this.width,
|
||||||
newHeight: this.height,
|
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: {
|
computed: {
|
||||||
|
@ -38,8 +39,7 @@ const Template = (args, { argTypes }) => ({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
template:
|
template: `<div style="width: fit-content; height: fit-content">
|
||||||
`<div style="width: fit-content; height: fit-content">
|
|
||||||
<n8n-resize-wrapper
|
<n8n-resize-wrapper
|
||||||
v-bind="$props"
|
v-bind="$props"
|
||||||
@resize="onResize"
|
@resize="onResize"
|
||||||
|
@ -65,13 +65,13 @@ Resize.args = {
|
||||||
gridSize: 20,
|
gridSize: 20,
|
||||||
isResizingEnabled: true,
|
isResizingEnabled: true,
|
||||||
supportedDirections: [
|
supportedDirections: [
|
||||||
"right",
|
'right',
|
||||||
"top",
|
'top',
|
||||||
"bottom",
|
'bottom',
|
||||||
"left",
|
'left',
|
||||||
"topLeft",
|
'topLeft',
|
||||||
"topRight",
|
'topRight',
|
||||||
"bottomLeft",
|
'bottomLeft',
|
||||||
"bottomRight",
|
'bottomRight',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,11 +19,9 @@ function closestNumber(value: number, divisor: number): number {
|
||||||
const q = value / divisor;
|
const q = value / divisor;
|
||||||
const n1 = divisor * q;
|
const n1 = divisor * q;
|
||||||
|
|
||||||
const n2 = (value * divisor) > 0 ?
|
const n2 = value * divisor > 0 ? divisor * (q + 1) : divisor * (q - 1);
|
||||||
(divisor * (q + 1)) : (divisor * (q - 1));
|
|
||||||
|
|
||||||
if (Math.abs(value - n1) < Math.abs(value - n2))
|
if (Math.abs(value - n1) < Math.abs(value - n2)) return n1;
|
||||||
return n1;
|
|
||||||
|
|
||||||
return n2;
|
return n2;
|
||||||
}
|
}
|
||||||
|
@ -35,7 +33,7 @@ function getSize(min: number, virtual: number, gridSize: number): number {
|
||||||
}
|
}
|
||||||
|
|
||||||
return min;
|
return min;
|
||||||
};
|
}
|
||||||
|
|
||||||
const directionsCursorMaps: { [key: string]: string } = {
|
const directionsCursorMaps: { [key: string]: string } = {
|
||||||
right: 'ew-resize',
|
right: 'ew-resize',
|
||||||
|
@ -43,7 +41,7 @@ const directionsCursorMaps: { [key: string]: string } = {
|
||||||
bottom: 'ns-resize',
|
bottom: 'ns-resize',
|
||||||
left: 'ew-resize',
|
left: 'ew-resize',
|
||||||
topLeft: 'nw-resize',
|
topLeft: 'nw-resize',
|
||||||
topRight : 'ne-resize',
|
topRight: 'ne-resize',
|
||||||
bottomLeft: 'sw-resize',
|
bottomLeft: 'sw-resize',
|
||||||
bottomRight: 'se-resize',
|
bottomRight: 'se-resize',
|
||||||
};
|
};
|
||||||
|
@ -95,8 +93,8 @@ export default Vue.extend({
|
||||||
enabledDirections() {
|
enabledDirections() {
|
||||||
const availableDirections = Object.keys(directionsCursorMaps);
|
const availableDirections = Object.keys(directionsCursorMaps);
|
||||||
|
|
||||||
if(!this.isResizingEnabled) return [];
|
if (!this.isResizingEnabled) return [];
|
||||||
if(this.supportedDirections.length === 0) return availableDirections;
|
if (this.supportedDirections.length === 0) return availableDirections;
|
||||||
|
|
||||||
return this.supportedDirections;
|
return this.supportedDirections;
|
||||||
},
|
},
|
||||||
|
@ -156,7 +154,7 @@ export default Vue.extend({
|
||||||
const width = getSize(this.minWidth, this.vWidth, this.gridSize);
|
const width = getSize(this.minWidth, this.vWidth, this.gridSize);
|
||||||
|
|
||||||
const dX = left && width !== this.width ? -1 * (width - this.width) : 0;
|
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 x = event.x;
|
||||||
const y = event.y;
|
const y = event.y;
|
||||||
const direction = this.dir;
|
const direction = this.dir;
|
||||||
|
|
|
@ -1,18 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
<router-link
|
<router-link v-if="useRouterLink" :to="to" v-on="$listeners">
|
||||||
v-if="useRouterLink"
|
|
||||||
:to="to"
|
|
||||||
v-on="$listeners"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</router-link>
|
</router-link>
|
||||||
<a
|
<a v-else :href="to" :target="openNewWindow ? '_blank' : '_self'" v-on="$listeners">
|
||||||
v-else
|
|
||||||
:href="to"
|
|
||||||
:target="openNewWindow ? '_blank': '_self'"
|
|
||||||
v-on="$listeners"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
@ -56,4 +47,3 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
/* tslint:disable:variable-name */
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
|
||||||
import { StoryFn } from '@storybook/vue';
|
|
||||||
import N8nSelect from './Select.vue';
|
import N8nSelect from './Select.vue';
|
||||||
import N8nOption from '../N8nOption';
|
import N8nOption from '../N8nOption';
|
||||||
import N8nIcon from '../N8nIcon';
|
import N8nIcon from '../N8nIcon';
|
||||||
|
@ -54,7 +52,8 @@ const Template: StoryFn = (args, { argTypes }) => ({
|
||||||
N8nOption,
|
N8nOption,
|
||||||
N8nIcon,
|
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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
val: '',
|
val: '',
|
||||||
|
@ -71,7 +70,12 @@ Filterable.args = {
|
||||||
defaultFirstOption: true,
|
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 }) => ({
|
const ManyTemplate: StoryFn = (args, { argTypes }) => ({
|
||||||
props: Object.keys(argTypes),
|
props: Object.keys(argTypes),
|
||||||
|
@ -96,7 +100,12 @@ Sizes.args = {
|
||||||
placeholder: 'placeholder...',
|
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 }) => ({
|
const ManyTemplateWithIcon: StoryFn = (args, { argTypes }) => ({
|
||||||
props: Object.keys(argTypes),
|
props: Object.keys(argTypes),
|
||||||
|
@ -121,7 +130,6 @@ WithIcon.args = {
|
||||||
placeholder: 'placeholder...',
|
placeholder: 'placeholder...',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const LimitedWidthTemplate: StoryFn = (args, { argTypes }) => ({
|
const LimitedWidthTemplate: StoryFn = (args, { argTypes }) => ({
|
||||||
props: Object.keys(argTypes),
|
props: Object.keys(argTypes),
|
||||||
components: {
|
components: {
|
||||||
|
@ -129,7 +137,8 @@ const LimitedWidthTemplate: StoryFn = (args, { argTypes }) => ({
|
||||||
N8nOption,
|
N8nOption,
|
||||||
N8nIcon,
|
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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
val: '',
|
val: '',
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<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">
|
<div v-if="$slots.prepend" :class="$style.prepend">
|
||||||
<slot name="prepend" />
|
<slot name="prepend" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,8 +47,7 @@ export default Vue.extend({
|
||||||
ElSelect, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
ElSelect, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {},
|
||||||
},
|
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'large',
|
default: 'large',
|
||||||
|
@ -112,21 +117,21 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
focus() {
|
focus() {
|
||||||
const select = this.$refs.innerSelect as Vue & HTMLElement | undefined;
|
const select = this.$refs.innerSelect as (Vue & HTMLElement) | undefined;
|
||||||
if (select) {
|
if (select) {
|
||||||
select.focus();
|
select.focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
blur() {
|
blur() {
|
||||||
const select = this.$refs.innerSelect as Vue & HTMLElement | undefined;
|
const select = this.$refs.innerSelect as (Vue & HTMLElement) | undefined;
|
||||||
if (select) {
|
if (select) {
|
||||||
select.blur();
|
select.blur();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
focusOnInput() {
|
focusOnInput() {
|
||||||
const select = this.$refs.innerSelect as Vue & HTMLElement | undefined;
|
const select = this.$refs.innerSelect as (Vue & HTMLElement) | undefined;
|
||||||
if (select) {
|
if (select) {
|
||||||
const input = select.$refs.input as Vue & HTMLElement | undefined;
|
const input = select.$refs.input as (Vue & HTMLElement) | undefined;
|
||||||
if (input) {
|
if (input) {
|
||||||
input.focus();
|
input.focus();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {render} from '@testing-library/vue';
|
import { render } from '@testing-library/vue';
|
||||||
import N8nSelect from "../Select.vue";
|
import N8nSelect from '../Select.vue';
|
||||||
import N8nOption from "../../N8nOption/Option.vue";
|
import N8nOption from '../../N8nOption/Option.vue';
|
||||||
|
|
||||||
describe('components', () => {
|
describe('components', () => {
|
||||||
describe('N8nSelect', () => {
|
describe('N8nSelect', () => {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="n8n-spinner">
|
<span class="n8n-spinner">
|
||||||
<div v-if="type === 'ring'" class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
<div v-if="type === 'ring'" class="lds-ring">
|
||||||
<n8n-icon
|
<div></div>
|
||||||
v-else
|
<div></div>
|
||||||
icon="spinner"
|
<div></div>
|
||||||
:size="size"
|
<div></div>
|
||||||
spin
|
</div>
|
||||||
/>
|
<n8n-icon v-else icon="spinner" :size="size" spin />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -23,13 +23,13 @@ export default Vue.extend({
|
||||||
props: {
|
props: {
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
validator (value: string): boolean {
|
validator(value: string): boolean {
|
||||||
return ['small', 'medium', 'large'].includes(value);
|
return ['small', 'medium', 'large'].includes(value);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
validator (value: string): boolean {
|
validator(value: string): boolean {
|
||||||
return ['dots', 'ring'].includes(value);
|
return ['dots', 'ring'].includes(value);
|
||||||
},
|
},
|
||||||
default: 'dots',
|
default: 'dots',
|
||||||
|
@ -73,5 +73,4 @@ export default Vue.extend({
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="{'n8n-sticky': true, [$style.sticky]: true, [$style.clickable]: !isResizing}"
|
:class="{ 'n8n-sticky': true, [$style.sticky]: true, [$style.clickable]: !isResizing }"
|
||||||
:style="styles"
|
:style="styles"
|
||||||
@keydown.prevent
|
@keydown.prevent
|
||||||
>
|
>
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
@keydown.stop
|
@keydown.stop
|
||||||
@wheel.stop
|
@wheel.stop
|
||||||
class="sticky-textarea ph-no-capture"
|
class="sticky-textarea ph-no-capture"
|
||||||
:class="{'full-height': !shouldShowFooter}"
|
:class="{ 'full-height': !shouldShowFooter }"
|
||||||
>
|
>
|
||||||
<n8n-input
|
<n8n-input
|
||||||
:value="content"
|
:value="content"
|
||||||
|
@ -49,13 +49,9 @@
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
ref="input"
|
ref="input"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="editMode && shouldShowFooter" :class="$style.footer">
|
<div v-if="editMode && shouldShowFooter" :class="$style.footer">
|
||||||
<n8n-text
|
<n8n-text size="xsmall" aligh="right">
|
||||||
size="xsmall"
|
|
||||||
aligh="right"
|
|
||||||
>
|
|
||||||
<span v-html="t('sticky.markdownHint')"></span>
|
<span v-html="t('sticky.markdownHint')"></span>
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
</div>
|
</div>
|
||||||
|
@ -142,7 +138,7 @@ export default mixins(Locale).extend({
|
||||||
}
|
}
|
||||||
return this.width;
|
return this.width;
|
||||||
},
|
},
|
||||||
styles(): { height: string, width: string } {
|
styles(): { height: string; width: string } {
|
||||||
return {
|
return {
|
||||||
height: `${this.resHeight}px`,
|
height: `${this.resHeight}px`,
|
||||||
width: `${this.resWidth}px`,
|
width: `${this.resWidth}px`,
|
||||||
|
@ -184,10 +180,7 @@ export default mixins(Locale).extend({
|
||||||
watch: {
|
watch: {
|
||||||
editMode(newMode, prevMode) {
|
editMode(newMode, prevMode) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (newMode &&
|
if (newMode && !prevMode && this.$refs.input) {
|
||||||
!prevMode &&
|
|
||||||
this.$refs.input
|
|
||||||
) {
|
|
||||||
const textarea = this.$refs.input as HTMLTextAreaElement;
|
const textarea = this.$refs.input as HTMLTextAreaElement;
|
||||||
if (this.defaultText === this.content) {
|
if (this.defaultText === this.content) {
|
||||||
textarea.select();
|
textarea.select();
|
||||||
|
@ -195,7 +188,6 @@ export default mixins(Locale).extend({
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -227,7 +219,12 @@ export default mixins(Locale).extend({
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
position: absolute;
|
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);
|
border-radius: var(--border-radius-base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,7 @@ import { action } from '@storybook/addon-actions';
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/Tabs',
|
title: 'Atoms/Tabs',
|
||||||
component: N8nTabs,
|
component: N8nTabs,
|
||||||
argTypes: {
|
argTypes: {},
|
||||||
},
|
|
||||||
parameters: {
|
parameters: {
|
||||||
backgrounds: { default: '--color-background-xlight' },
|
backgrounds: { default: '--color-background-xlight' },
|
||||||
},
|
},
|
||||||
|
@ -21,8 +20,7 @@ const Template = (args, { argTypes }) => ({
|
||||||
components: {
|
components: {
|
||||||
N8nTabs,
|
N8nTabs,
|
||||||
},
|
},
|
||||||
template:
|
template: `<n8n-tabs v-model="val" v-bind="$props" @input="onInput">
|
||||||
`<n8n-tabs v-model="val" v-bind="$props" @input="onInput">
|
|
||||||
</n8n-tabs>`,
|
</n8n-tabs>`,
|
||||||
methods,
|
methods,
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -7,13 +7,18 @@
|
||||||
<n8n-icon icon="chevron-right" size="small" />
|
<n8n-icon icon="chevron-right" size="small" />
|
||||||
</div>
|
</div>
|
||||||
<div ref="tabs" :class="$style.tabs">
|
<div ref="tabs" :class="$style.tabs">
|
||||||
<div v-for="option in options"
|
<div
|
||||||
|
v-for="option in options"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:id="option.value"
|
:id="option.value"
|
||||||
:class="{ [$style.alignRight]: option.align === 'right' }"
|
:class="{ [$style.alignRight]: option.align === 'right' }"
|
||||||
>
|
>
|
||||||
<n8n-tooltip :disabled="!option.tooltip" placement="bottom">
|
<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
|
<a
|
||||||
v-if="option.href"
|
v-if="option.href"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -23,7 +28,9 @@
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{{ option.label }}
|
{{ 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>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@ -56,8 +63,7 @@ export default Vue.extend({
|
||||||
container.addEventListener('scroll', (event: Event) => {
|
container.addEventListener('scroll', (event: Event) => {
|
||||||
const width = container.clientWidth;
|
const width = container.clientWidth;
|
||||||
const scrollWidth = container.scrollWidth;
|
const scrollWidth = container.scrollWidth;
|
||||||
// @ts-ignore
|
this.scrollPosition = (event.target as Element).scrollLeft;
|
||||||
this.scrollPosition = event.srcElement.scrollLeft; // eslint-disable-line @typescript-eslint/no-unsafe-assignment
|
|
||||||
|
|
||||||
this.canScrollRight = scrollWidth - width > this.scrollPosition;
|
this.canScrollRight = scrollWidth - width > this.scrollPosition;
|
||||||
});
|
});
|
||||||
|
@ -87,10 +93,8 @@ export default Vue.extend({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {},
|
||||||
},
|
options: {},
|
||||||
options: {
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleTooltipClick(tab: string, event: MouseEvent) {
|
handleTooltipClick(tab: string, event: MouseEvent) {
|
||||||
|
@ -106,7 +110,9 @@ export default Vue.extend({
|
||||||
this.scroll(50);
|
this.scroll(50);
|
||||||
},
|
},
|
||||||
scroll(left: number) {
|
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) {
|
if (container) {
|
||||||
container.scrollBy({ left, top: 0, behavior: 'smooth' });
|
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>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.container {
|
.container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -205,5 +213,4 @@ type ScrollByFunction = (arg: { left: number, top: number, behavior: 'smooth' |
|
||||||
composes: button;
|
composes: button;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -17,8 +17,7 @@ const Template = (args, { argTypes }) => ({
|
||||||
components: {
|
components: {
|
||||||
N8nTag,
|
N8nTag,
|
||||||
},
|
},
|
||||||
template:
|
template: '<n8n-tag v-bind="$props"></n8n-tag>',
|
||||||
'<n8n-tag v-bind="$props"></n8n-tag>',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Tag = Template.bind({});
|
export const Tag = Template.bind({});
|
||||||
|
|
|
@ -29,7 +29,11 @@ export default Vue.extend({
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
|
|
||||||
&:hover {
|
&: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>
|
</style>
|
||||||
|
|
|
@ -3,8 +3,7 @@ import N8nTags from './Tags.vue';
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/Tags',
|
title: 'Atoms/Tags',
|
||||||
component: N8nTags,
|
component: N8nTags,
|
||||||
argTypes: {
|
argTypes: {},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template = (args, { argTypes }) => ({
|
const Template = (args, { argTypes }) => ({
|
||||||
|
@ -12,8 +11,7 @@ const Template = (args, { argTypes }) => ({
|
||||||
components: {
|
components: {
|
||||||
N8nTags,
|
N8nTags,
|
||||||
},
|
},
|
||||||
template:
|
template: '<n8n-tags v-bind="$props"></n8n-tags>',
|
||||||
'<n8n-tags v-bind="$props"></n8n-tags>',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Tags = Template.bind({});
|
export const Tags = Template.bind({});
|
||||||
|
@ -34,7 +32,6 @@ Tags.args = {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const Truncated = Template.bind({});
|
export const Truncated = Template.bind({});
|
||||||
Truncated.args = {
|
Truncated.args = {
|
||||||
truncate: true,
|
truncate: true,
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['n8n-tags', $style.tags]">
|
<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
|
<n8n-link
|
||||||
v-if="truncate && !showAll && hiddenTagsLength > 0"
|
v-if="truncate && !showAll && hiddenTagsLength > 0"
|
||||||
theme="text"
|
theme="text"
|
||||||
|
@ -16,9 +21,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import N8nTag from '../N8nTag';
|
import N8nTag from '../N8nTag';
|
||||||
import N8nLink from '../N8nLink';
|
import N8nLink from '../N8nLink';
|
||||||
import Locale from "../../mixins/locale";
|
import Locale from '../../mixins/locale';
|
||||||
import Vue, {PropType} from 'vue';
|
import { PropType } from 'vue';
|
||||||
import mixins from "vue-typed-mixins";
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
interface ITag {
|
interface ITag {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -13,7 +13,15 @@ export default {
|
||||||
color: {
|
color: {
|
||||||
control: {
|
control: {
|
||||||
type: 'select',
|
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: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'medium',
|
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: {
|
color: {
|
||||||
type: String,
|
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: {
|
align: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -40,6 +51,7 @@ export default Vue.extend({
|
||||||
classes() {
|
classes() {
|
||||||
const applied = [];
|
const applied = [];
|
||||||
if (this.align) {
|
if (this.align) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
applied.push(`align-${this.align}`);
|
applied.push(`align-${this.align}`);
|
||||||
}
|
}
|
||||||
if (this.color) {
|
if (this.color) {
|
||||||
|
@ -50,9 +62,10 @@ export default Vue.extend({
|
||||||
applied.push('compact');
|
applied.push('compact');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
applied.push(`size-${this.size}`);
|
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]);
|
return applied.map((c) => (this.$style as { [key: string]: string })[c]);
|
||||||
},
|
},
|
||||||
|
@ -141,5 +154,4 @@ export default Vue.extend({
|
||||||
.align-center {
|
.align-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<el-tooltip v-bind="$attrs">
|
<el-tooltip v-bind="$attrs">
|
||||||
<template v-for="(_, slotName) in $slots" #[slotName]>
|
<template v-for="(_, slotName) in $slots" #[slotName]>
|
||||||
<slot :name="slotName"/>
|
<slot :name="slotName" />
|
||||||
<div :key="slotName" v-if="slotName === 'content' && buttons.length" :class="$style.buttons" :style="{ justifyContent: justifyButtons }">
|
<div
|
||||||
|
:key="slotName"
|
||||||
|
v-if="slotName === 'content' && buttons.length"
|
||||||
|
:class="$style.buttons"
|
||||||
|
:style="{ justifyContent: justifyButtons }"
|
||||||
|
>
|
||||||
<n8n-button
|
<n8n-button
|
||||||
v-for="button in buttons"
|
v-for="button in buttons"
|
||||||
:key="button.attrs.label"
|
:key="button.attrs.label"
|
||||||
|
@ -15,9 +20,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue, { PropType } from "vue";
|
import Vue, { PropType } from 'vue';
|
||||||
import ElTooltip from 'element-ui/lib/tooltip';
|
import ElTooltip from 'element-ui/lib/tooltip';
|
||||||
import type { IN8nButton } from "@/types";
|
import type { IN8nButton } from '@/types';
|
||||||
import N8nButton from '../N8nButton';
|
import N8nButton from '../N8nButton';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
@ -31,7 +36,19 @@ export default Vue.extend({
|
||||||
justifyButtons: {
|
justifyButtons: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'flex-end',
|
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: {
|
buttons: {
|
||||||
type: Array as PropType<IN8nButton[]>,
|
type: Array as PropType<IN8nButton[]>,
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
/* tslint:disable:variable-name */
|
|
||||||
|
|
||||||
import N8nTree from './Tree.vue';
|
import N8nTree from './Tree.vue';
|
||||||
import {StoryFn} from "@storybook/vue";
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Atoms/Tree',
|
title: 'Atoms/Tree',
|
||||||
component: N8nTree,
|
component: N8nTree,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default: StoryFn = (args, {argTypes}) => ({
|
export const Default: StoryFn = (args, { argTypes }) => ({
|
||||||
props: Object.keys(argTypes),
|
props: Object.keys(argTypes),
|
||||||
components: {
|
components: {
|
||||||
N8nTree,
|
N8nTree,
|
||||||
|
@ -26,32 +24,26 @@ export const Default: StoryFn = (args, {argTypes}) => ({
|
||||||
Default.args = {
|
Default.args = {
|
||||||
value: {
|
value: {
|
||||||
objectKey: {
|
objectKey: {
|
||||||
nestedArrayKey: [
|
nestedArrayKey: ['in progress', 33958053],
|
||||||
'in progress',
|
|
||||||
33958053,
|
|
||||||
],
|
|
||||||
stringKey: 'word',
|
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: {
|
objectKey: {
|
||||||
myKey: 'what\'s for lunch',
|
myKey: "what's for lunch",
|
||||||
yourKey: 'prolle rewe wdyt',
|
yourKey: 'prolle rewe wdyt',
|
||||||
},
|
},
|
||||||
id: 123,
|
id: 123,
|
||||||
},
|
},
|
||||||
hello: "world",
|
hello: 'world',
|
||||||
test: {
|
test: {
|
||||||
label: "A cool folder",
|
label: 'A cool folder',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
label: "A cool sub-folder 1",
|
label: 'A cool sub-folder 1',
|
||||||
children: [
|
children: [{ label: 'A cool sub-sub-folder 1' }, { label: 'A cool sub-sub-folder 2' }],
|
||||||
{ 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>
|
<template>
|
||||||
<div class="n8n-tree">
|
<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])">
|
<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 v-else>{{ label }}</span>
|
||||||
<span>:</span>
|
<span>:</span>
|
||||||
<slot v-if="$scopedSlots.value" name="value" v-bind:value="value[label]" />
|
<slot v-if="$scopedSlots.value" name="value" v-bind:value="value[label]" />
|
||||||
<span v-else>{{ value[label] }}</span>
|
<span v-else>{{ value[label] }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<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>
|
<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">
|
<template v-for="(index, name) in $scopedSlots" v-slot:[name]="data">
|
||||||
<slot :name="name" v-bind="data"></slot>
|
<slot :name="name" v-bind="data"></slot>
|
||||||
</template>
|
</template>
|
||||||
|
@ -26,11 +45,9 @@ import Vue from 'vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'n8n-tree',
|
name: 'n8n-tree',
|
||||||
components: {
|
components: {},
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {},
|
||||||
},
|
|
||||||
path: {
|
path: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
|
@ -70,7 +87,6 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
||||||
$--spacing: var(--spacing-s);
|
$--spacing: var(--spacing-s);
|
||||||
|
|
||||||
.indent {
|
.indent {
|
||||||
|
@ -82,5 +98,4 @@ $--spacing: var(--spacing-s);
|
||||||
margin-left: $--spacing;
|
margin-left: $--spacing;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -7,7 +7,7 @@ describe('components', () => {
|
||||||
const wrapper = render(N8nTree, {
|
const wrapper = render(N8nTree, {
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
"hello": "world",
|
hello: 'world',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -18,13 +18,10 @@ describe('components', () => {
|
||||||
const wrapper = render(N8nTree, {
|
const wrapper = render(N8nTree, {
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
"hello": {
|
hello: {
|
||||||
"test": "world",
|
test: 'world',
|
||||||
},
|
},
|
||||||
"options": [
|
options: ['yes', 'no'],
|
||||||
"yes",
|
|
||||||
"no",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -35,37 +32,30 @@ describe('components', () => {
|
||||||
const wrapper = render(N8nTree, {
|
const wrapper = render(N8nTree, {
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
"hello": {
|
hello: {
|
||||||
"test": "world",
|
test: 'world',
|
||||||
},
|
},
|
||||||
"options": [
|
options: ['yes', 'no'],
|
||||||
"yes",
|
|
||||||
"no",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
slots: {
|
slots: {
|
||||||
label: "<span>label</span>",
|
label: '<span>label</span>',
|
||||||
value: "<span>value</span>",
|
value: '<span>value</span>',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.html()).toMatchSnapshot();
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should render each tree with node class', () => {
|
it('should render each tree with node class', () => {
|
||||||
const wrapper = render(N8nTree, {
|
const wrapper = render(N8nTree, {
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
"hello": {
|
hello: {
|
||||||
"test": "world",
|
test: 'world',
|
||||||
},
|
},
|
||||||
"options": [
|
options: ['yes', 'no'],
|
||||||
"yes",
|
|
||||||
"no",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
nodeClass: "nodeClass",
|
nodeClass: 'nodeClass',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.html()).toMatchSnapshot();
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
|
|
|
@ -13,8 +13,7 @@ const Template = (args, { argTypes }) => ({
|
||||||
components: {
|
components: {
|
||||||
N8nUserInfo,
|
N8nUserInfo,
|
||||||
},
|
},
|
||||||
template:
|
template: '<n8n-user-info v-bind="$props" />',
|
||||||
'<n8n-user-info v-bind="$props" />',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Member = Template.bind({});
|
export const Member = Template.bind({});
|
||||||
|
|
|
@ -5,23 +5,25 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isPendingUser" :class="$style.pendingUser">
|
<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>
|
<span :class="$style.pendingBadge"><n8n-badge :bold="true">Pending</n8n-badge></span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else :class="$style.infoContainer">
|
<div v-else :class="$style.infoContainer">
|
||||||
<div>
|
<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>
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import 'vue';
|
||||||
import N8nText from '../N8nText';
|
import N8nText from '../N8nText';
|
||||||
import N8nAvatar from '../N8nAvatar';
|
import N8nAvatar from '../N8nAvatar';
|
||||||
import N8nBadge from '../N8nBadge';
|
import N8nBadge from '../N8nBadge';
|
||||||
|
@ -67,7 +69,6 @@ export default mixins(Locale).extend({
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.container {
|
.container {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@ -84,7 +85,7 @@ export default mixins(Locale).extend({
|
||||||
.infoContainer {
|
.infoContainer {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: column;;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-left: var(--spacing-xs);
|
margin-left: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,7 @@ import { action } from '@storybook/addon-actions';
|
||||||
export default {
|
export default {
|
||||||
title: 'Modules/UserSelect',
|
title: 'Modules/UserSelect',
|
||||||
component: N8nUserSelect,
|
component: N8nUserSelect,
|
||||||
argTypes: {
|
argTypes: {},
|
||||||
},
|
|
||||||
parameters: {
|
parameters: {
|
||||||
backgrounds: { default: '--color-background-light' },
|
backgrounds: { default: '--color-background-light' },
|
||||||
},
|
},
|
||||||
|
@ -36,34 +35,34 @@ export const UserSelect = Template.bind({});
|
||||||
UserSelect.args = {
|
UserSelect.args = {
|
||||||
users: [
|
users: [
|
||||||
{
|
{
|
||||||
id: "1",
|
id: '1',
|
||||||
firstName: 'Sunny',
|
firstName: 'Sunny',
|
||||||
lastName: 'Side',
|
lastName: 'Side',
|
||||||
email: "sunny@n8n.io",
|
email: 'sunny@n8n.io',
|
||||||
globalRole: {
|
globalRole: {
|
||||||
name: 'owner',
|
name: 'owner',
|
||||||
id: "1",
|
id: '1',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2",
|
id: '2',
|
||||||
firstName: 'Kobi',
|
firstName: 'Kobi',
|
||||||
lastName: 'Dog',
|
lastName: 'Dog',
|
||||||
email: "kobi@n8n.io",
|
email: 'kobi@n8n.io',
|
||||||
globalRole: {
|
globalRole: {
|
||||||
name: 'member',
|
name: 'member',
|
||||||
id: "2",
|
id: '2',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "3",
|
id: '3',
|
||||||
email: "invited@n8n.io",
|
email: 'invited@n8n.io',
|
||||||
globalRole: {
|
globalRole: {
|
||||||
name: 'member',
|
name: 'member',
|
||||||
id: "2",
|
id: '2',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
placeholder: 'Select user to transfer to',
|
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