mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(editor): Alert design system component (#4834)
* feat(editor): Alert component * feat(editor): style improvements
This commit is contained in:
parent
7b819ad5a1
commit
9dbb3ea182
|
@ -0,0 +1,65 @@
|
||||||
|
import type { StoryFn } from '@storybook/vue';
|
||||||
|
import N8nAlert from './Alert.vue';
|
||||||
|
import N8nIcon from '../N8nIcon';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Atoms/Alert',
|
||||||
|
component: N8nAlert,
|
||||||
|
argTypes: {
|
||||||
|
type: {
|
||||||
|
type: 'select',
|
||||||
|
options: ['success', 'info', 'warning', 'error'],
|
||||||
|
},
|
||||||
|
effect: {
|
||||||
|
type: 'select',
|
||||||
|
options: ['light', 'dark'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template: StoryFn = (args, { argTypes }) => ({
|
||||||
|
props: Object.keys(argTypes),
|
||||||
|
components: {
|
||||||
|
N8nAlert,
|
||||||
|
},
|
||||||
|
template:
|
||||||
|
'<div style="position: relative; width: 100%; height: 300px;"><n8n-alert v-bind="$props"><template #aside>custom content slot</template></n8n-alert></div>',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ContentAsProps = Template.bind({});
|
||||||
|
ContentAsProps.args = {
|
||||||
|
type: 'info',
|
||||||
|
effect: 'light',
|
||||||
|
title: 'Alert title',
|
||||||
|
description: 'Alert description',
|
||||||
|
center: false,
|
||||||
|
showIcon: true,
|
||||||
|
background: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const TemplateForSlots: StoryFn = (args, { argTypes }) => ({
|
||||||
|
props: Object.keys(argTypes),
|
||||||
|
components: {
|
||||||
|
N8nAlert,
|
||||||
|
N8nIcon,
|
||||||
|
},
|
||||||
|
template: `<div style="position: relative; width: 100%; height: 300px;">
|
||||||
|
<n8n-alert v-bind="$props">
|
||||||
|
<template #title>Title</template>
|
||||||
|
<template>Description</template>
|
||||||
|
<template #aside><button>Button</button></template>
|
||||||
|
<template #icon>
|
||||||
|
<n8n-icon icon="grin-stars" size="xlarge" />
|
||||||
|
</template>
|
||||||
|
</n8n-alert>
|
||||||
|
</div>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ContentInSlots = TemplateForSlots.bind({});
|
||||||
|
ContentInSlots.args = {
|
||||||
|
type: 'info',
|
||||||
|
effect: 'light',
|
||||||
|
center: false,
|
||||||
|
background: true,
|
||||||
|
showIcon: false,
|
||||||
|
};
|
242
packages/design-system/src/components/N8nAlert/Alert.vue
Normal file
242
packages/design-system/src/components/N8nAlert/Alert.vue
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
<template>
|
||||||
|
<div :class="alertBoxClassNames" role="alert">
|
||||||
|
<div :class="$style.content">
|
||||||
|
<span v-if="showIcon || $slots.icon" :class="$style.icon">
|
||||||
|
<n8n-icon v-if="showIcon" :icon="icon" />
|
||||||
|
<slot v-else-if="$slots.icon" name="icon" />
|
||||||
|
</span>
|
||||||
|
<div :class="$style.text">
|
||||||
|
<div v-if="$slots.title || title" :class="$style.title">
|
||||||
|
<slot name="title">{{ title }}</slot>
|
||||||
|
</div>
|
||||||
|
<div v-if="$slots.default || description" :class="$style.description">
|
||||||
|
<slot>{{ description }}</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="$slots.aside" :class="$style.aside">
|
||||||
|
<slot name="aside" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, useCssModule } from 'vue';
|
||||||
|
import N8nIcon from '../N8nIcon';
|
||||||
|
|
||||||
|
type AlertProps = {
|
||||||
|
title?: string;
|
||||||
|
type?: 'success' | 'warning' | 'info' | 'error';
|
||||||
|
description?: string;
|
||||||
|
center?: boolean;
|
||||||
|
showIcon?: boolean;
|
||||||
|
effect?: 'light' | 'dark';
|
||||||
|
background?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<AlertProps>(), {
|
||||||
|
type: 'info',
|
||||||
|
effect: 'light',
|
||||||
|
showIcon: true,
|
||||||
|
background: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const icon = computed(() => {
|
||||||
|
/* eslint-disable prettier/prettier */
|
||||||
|
switch (props.type) {
|
||||||
|
case 'success':
|
||||||
|
return 'check-circle';
|
||||||
|
case 'warning':
|
||||||
|
return 'exclamation-triangle';
|
||||||
|
case 'error':
|
||||||
|
return 'times-circle';
|
||||||
|
default:
|
||||||
|
return 'info-circle';
|
||||||
|
}
|
||||||
|
/* eslint-enable */
|
||||||
|
});
|
||||||
|
|
||||||
|
const style = useCssModule();
|
||||||
|
const alertBoxClassNames = computed(() => {
|
||||||
|
const classNames = ['n8n-alert', style.alert];
|
||||||
|
if (props.type) {
|
||||||
|
classNames.push(style[props.type]);
|
||||||
|
}
|
||||||
|
if (props.effect) {
|
||||||
|
classNames.push(style[props.effect]);
|
||||||
|
}
|
||||||
|
if (props.center) {
|
||||||
|
classNames.push(style.center);
|
||||||
|
}
|
||||||
|
if (props.background) {
|
||||||
|
classNames.push(style.background);
|
||||||
|
}
|
||||||
|
return classNames;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
@import '../../css/common/var.scss';
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
min-height: 60px;
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: $alert-padding;
|
||||||
|
|
||||||
|
&.center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
&.light {
|
||||||
|
color: var(--color-success);
|
||||||
|
|
||||||
|
&.background {
|
||||||
|
background-color: $color-success-lighter;
|
||||||
|
border-color: var(--color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-alert__description {
|
||||||
|
color: var(--color-success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
color: $color-white;
|
||||||
|
|
||||||
|
&:not(.background) {
|
||||||
|
color: var(--color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.background {
|
||||||
|
background-color: var(--color-success);
|
||||||
|
border-color: $color-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.info {
|
||||||
|
&.light {
|
||||||
|
color: var(--color-info);
|
||||||
|
|
||||||
|
&.background {
|
||||||
|
background-color: $alert-info-color;
|
||||||
|
border-color: var(--color-info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
color: $color-white;
|
||||||
|
|
||||||
|
&:not(.background) {
|
||||||
|
color: var(--color-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.background {
|
||||||
|
background-color: var(--color-info);
|
||||||
|
border-color: $color-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-alert__description {
|
||||||
|
color: var(--color-info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
&.light {
|
||||||
|
color: var(--color-warning);
|
||||||
|
|
||||||
|
&.background {
|
||||||
|
background-color: $alert-warning-color;
|
||||||
|
border-color: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-alert__description {
|
||||||
|
color: var(--color-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
color: $color-white;
|
||||||
|
|
||||||
|
&:not(.background) {
|
||||||
|
color: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.background {
|
||||||
|
background-color: var(--color-warning);
|
||||||
|
border-color: $color-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
&.light {
|
||||||
|
color: var(--color-danger);
|
||||||
|
|
||||||
|
&.background {
|
||||||
|
background-color: $alert-danger-color;
|
||||||
|
border-color: var(--color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-alert__description {
|
||||||
|
color: var(--color-danger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
color: $color-white;
|
||||||
|
|
||||||
|
&:not(.background) {
|
||||||
|
color: var(--color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.background {
|
||||||
|
background-color: var(--color-danger);
|
||||||
|
border-color: $color-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-flex;
|
||||||
|
color: inherit;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: var(--spacing-2xs);
|
||||||
|
padding-right: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: $alert-title-font-size;
|
||||||
|
line-height: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: $alert-description-font-size;
|
||||||
|
margin: 5px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: var(--spacing-s);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { render, screen } from '@testing-library/vue';
|
||||||
|
import N8nAlert from '../Alert.vue';
|
||||||
|
import N8nIcon from '../../N8nIcon';
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
describe('N8nAlert', () => {
|
||||||
|
it('should render with props', () => {
|
||||||
|
render(N8nAlert, {
|
||||||
|
props: { title: 'Title', description: 'Message' },
|
||||||
|
});
|
||||||
|
expect(screen.getByRole('alert')).toBeVisible();
|
||||||
|
expect(screen.getByText('Title')).toBeVisible();
|
||||||
|
expect(screen.getByText('Message')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render slots instead of props', () => {
|
||||||
|
const { container } = render(
|
||||||
|
N8nAlert,
|
||||||
|
{
|
||||||
|
props: { showIcon: false },
|
||||||
|
slots: {
|
||||||
|
title: 'Title',
|
||||||
|
default: 'Message',
|
||||||
|
aside: '<button>Click me</button>',
|
||||||
|
icon: '<n8n-icon icon="plus-circle" />',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(localVue) => {
|
||||||
|
localVue.component('n8n-icon', N8nIcon);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(screen.getByRole('alert')).toBeVisible();
|
||||||
|
expect(screen.getByText('Title')).toBeVisible();
|
||||||
|
expect(screen.getByText('Message')).toBeVisible();
|
||||||
|
expect(screen.getByRole('button')).toBeVisible();
|
||||||
|
expect(container.querySelector('.n8n-icon')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
1
packages/design-system/src/components/N8nAlert/index.ts
Normal file
1
packages/design-system/src/components/N8nAlert/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './Alert.vue';
|
|
@ -2,6 +2,7 @@ import Vue from 'vue';
|
||||||
import N8nActionBox from '../components/N8nActionBox';
|
import N8nActionBox from '../components/N8nActionBox';
|
||||||
import N8nActionDropdown from '../components/N8nActionDropdown';
|
import N8nActionDropdown from '../components/N8nActionDropdown';
|
||||||
import N8nActionToggle from '../components/N8nActionToggle';
|
import N8nActionToggle from '../components/N8nActionToggle';
|
||||||
|
import N8nAlert from '../components/N8nAlert';
|
||||||
import N8nAvatar from '../components/N8nAvatar';
|
import N8nAvatar from '../components/N8nAvatar';
|
||||||
import N8nBadge from '../components/N8nBadge';
|
import N8nBadge from '../components/N8nBadge';
|
||||||
import N8nBlockUi from '../components/N8nBlockUi';
|
import N8nBlockUi from '../components/N8nBlockUi';
|
||||||
|
@ -50,6 +51,7 @@ export default {
|
||||||
app.component('n8n-action-box', N8nActionBox);
|
app.component('n8n-action-box', N8nActionBox);
|
||||||
app.component('n8n-action-dropdown', N8nActionDropdown);
|
app.component('n8n-action-dropdown', N8nActionDropdown);
|
||||||
app.component('n8n-action-toggle', N8nActionToggle);
|
app.component('n8n-action-toggle', N8nActionToggle);
|
||||||
|
app.component('n8n-alert', N8nAlert);
|
||||||
app.component('n8n-avatar', N8nAvatar);
|
app.component('n8n-avatar', N8nAvatar);
|
||||||
app.component('n8n-badge', N8nBadge);
|
app.component('n8n-badge', N8nBadge);
|
||||||
app.component('n8n-block-ui', N8nBlockUi);
|
app.component('n8n-block-ui', N8nBlockUi);
|
||||||
|
|
Loading…
Reference in a new issue