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 N8nActionDropdown from '../components/N8nActionDropdown';
|
||||
import N8nActionToggle from '../components/N8nActionToggle';
|
||||
import N8nAlert from '../components/N8nAlert';
|
||||
import N8nAvatar from '../components/N8nAvatar';
|
||||
import N8nBadge from '../components/N8nBadge';
|
||||
import N8nBlockUi from '../components/N8nBlockUi';
|
||||
|
@ -50,6 +51,7 @@ export default {
|
|||
app.component('n8n-action-box', N8nActionBox);
|
||||
app.component('n8n-action-dropdown', N8nActionDropdown);
|
||||
app.component('n8n-action-toggle', N8nActionToggle);
|
||||
app.component('n8n-alert', N8nAlert);
|
||||
app.component('n8n-avatar', N8nAvatar);
|
||||
app.component('n8n-badge', N8nBadge);
|
||||
app.component('n8n-block-ui', N8nBlockUi);
|
||||
|
|
Loading…
Reference in a new issue