From ce14f6266b30caadb477b08d4257b82c769a74c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milorad=20FIlipovi=C4=87?= Date: Wed, 1 Nov 2023 09:26:06 +0100 Subject: [PATCH] feat(editor): Implement the `UserStack` design system component (#7559) Adds [`UserStack` component](https://www.figma.com/file/5MfPO6Eg2YVfXETAzoGahf/Debt-days-August2023?type=design&node-id=10-13056&mode=design&t=tuJAgCdzkN506SlE-0) to our design system --- .../N8nUserStack/UserStack.stories.ts | 191 ++++++++++++++++++ .../src/components/N8nUserStack/UserStack.vue | 181 +++++++++++++++++ .../N8nUserStack/__tests__/UserStack.spec.ts | 118 +++++++++++ .../src/components/N8nUserStack/index.ts | 3 + .../design-system/src/components/index.ts | 1 + packages/design-system/src/plugin.ts | 2 + packages/design-system/src/types/user.ts | 2 + 7 files changed, 498 insertions(+) create mode 100644 packages/design-system/src/components/N8nUserStack/UserStack.stories.ts create mode 100644 packages/design-system/src/components/N8nUserStack/UserStack.vue create mode 100644 packages/design-system/src/components/N8nUserStack/__tests__/UserStack.spec.ts create mode 100644 packages/design-system/src/components/N8nUserStack/index.ts diff --git a/packages/design-system/src/components/N8nUserStack/UserStack.stories.ts b/packages/design-system/src/components/N8nUserStack/UserStack.stories.ts new file mode 100644 index 0000000000..b19bbe3e67 --- /dev/null +++ b/packages/design-system/src/components/N8nUserStack/UserStack.stories.ts @@ -0,0 +1,191 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { StoryFn } from '@storybook/vue3'; +import UserStack from './UserStack.vue'; + +export default { + title: 'Modules/UserStack', + component: UserStack, +}; + +const Template: StoryFn = (args) => ({ + setup: () => ({ args }), + props: args, + components: { + UserStack, + }, + template: '', +}); + +export const WithGroups = Template.bind({}); +WithGroups.args = { + currentUserEmail: 'sunny@n8n.io', + users: { + Owners: [ + { + id: '1', + firstName: 'Sunny', + lastName: 'Side', + fullName: 'Sunny Side', + email: 'sunny@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: true, + signInType: 'email', + disabled: false, + }, + { + id: '2', + firstName: 'Kobi', + lastName: 'Dog', + fullName: 'Kobi Dog', + email: 'kobi@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: false, + signInType: 'ldap', + disabled: true, + }, + ], + 'Other users': [ + { + id: '3', + firstName: 'John', + lastName: 'Doe', + fullName: 'John Doe', + email: 'john@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: false, + signInType: 'email', + disabled: false, + }, + { + id: '4', + firstName: 'Jane', + lastName: 'Doe', + fullName: 'Jane Doe', + email: 'jane@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: false, + signInType: 'ldap', + disabled: true, + }, + { + id: '5', + firstName: 'Test', + lastName: 'User', + fullName: 'Test User', + email: 'test@n8n.io', + isDefaultUser: false, + isPendingUser: true, + isOwner: false, + signInType: 'email', + disabled: false, + }, + ], + 'Empty Group': [], + }, +}; + +export const SingleGroup = Template.bind({}); +SingleGroup.args = { + currentUserEmail: 'sunny@n8n.io', + users: { + Owners: [ + { + id: '1', + firstName: 'Sunny', + lastName: 'Side', + fullName: 'Sunny Side', + email: 'sunny@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: true, + signInType: 'email', + disabled: false, + }, + { + id: '2', + firstName: 'Kobi', + lastName: 'Dog', + fullName: 'Kobi Dog', + email: 'kobi@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: false, + signInType: 'ldap', + disabled: true, + }, + { + id: '4', + firstName: 'Jane', + lastName: 'Doe', + fullName: 'Jane Doe', + email: 'jane@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: false, + signInType: 'ldap', + disabled: true, + }, + { + id: '5', + firstName: 'Test', + lastName: 'User', + fullName: 'Test User', + email: 'test@n8n.io', + isDefaultUser: false, + isPendingUser: true, + isOwner: false, + signInType: 'email', + disabled: false, + }, + ], + }, +}; + +export const NoCutoff = Template.bind({}); +NoCutoff.args = { + currentUserEmail: 'sunny@n8n.io', + users: { + Owners: [ + { + id: '1', + firstName: 'Sunny', + lastName: 'Side', + fullName: 'Sunny Side', + email: 'sunny@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: true, + signInType: 'email', + disabled: false, + }, + { + id: '2', + firstName: 'Kobi', + lastName: 'Dog', + fullName: 'Kobi Dog', + email: 'kobi@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: false, + signInType: 'ldap', + disabled: true, + }, + { + id: '3', + firstName: 'John', + lastName: 'Doe', + fullName: 'John Doe', + email: 'john@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: false, + signInType: 'email', + disabled: false, + }, + ], + }, +}; diff --git a/packages/design-system/src/components/N8nUserStack/UserStack.vue b/packages/design-system/src/components/N8nUserStack/UserStack.vue new file mode 100644 index 0000000000..a437006d09 --- /dev/null +++ b/packages/design-system/src/components/N8nUserStack/UserStack.vue @@ -0,0 +1,181 @@ + + + + + + + diff --git a/packages/design-system/src/components/N8nUserStack/__tests__/UserStack.spec.ts b/packages/design-system/src/components/N8nUserStack/__tests__/UserStack.spec.ts new file mode 100644 index 0000000000..ff3ac1b897 --- /dev/null +++ b/packages/design-system/src/components/N8nUserStack/__tests__/UserStack.spec.ts @@ -0,0 +1,118 @@ +import { render } from '@testing-library/vue'; +import UserStack from '../UserStack.vue'; +import { N8nAvatar, N8nUserInfo } from '@/main'; + +describe('UserStack', () => { + it('should render flat user list', () => { + const { container } = render(UserStack, { + props: { + currentUserEmail: 'hello@n8n.io', + users: { + Owners: [ + { + id: '1', + firstName: 'Sunny', + lastName: 'Side', + fullName: 'Sunny Side', + email: 'hello@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: true, + signInType: 'email', + disabled: false, + }, + { + id: '2', + firstName: 'Kobi', + lastName: 'Dog', + fullName: 'Kobi Dog', + email: 'kobi@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: false, + signInType: 'ldap', + disabled: true, + }, + ], + }, + }, + global: { + components: { + 'n8n-avatar': N8nAvatar, + 'n8n-user-info': N8nUserInfo, + }, + }, + }); + expect(container.querySelector('.user-stack')).toBeInTheDocument(); + expect(container.querySelectorAll('svg')).toHaveLength(2); + }); + + it('should not show all avatars', async () => { + const { container } = render(UserStack, { + props: { + currentUserEmail: 'hello@n8n.io', + users: { + Owners: [ + { + id: '1', + firstName: 'Sunny', + lastName: 'Side', + fullName: 'Sunny Side', + email: 'hello@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: true, + signInType: 'email', + disabled: false, + }, + { + id: '2', + firstName: 'Kobi', + lastName: 'Dog', + fullName: 'Kobi Dog', + email: 'kobi@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: false, + signInType: 'ldap', + disabled: true, + }, + { + id: '3', + firstName: 'John', + lastName: 'Doe', + fullName: 'John Doe', + email: 'john@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: false, + signInType: 'email', + disabled: false, + }, + { + id: '4', + firstName: 'Jane', + lastName: 'Doe', + fullName: 'Jane Doe', + email: 'jane@n8n.io', + isDefaultUser: false, + isPendingUser: false, + isOwner: false, + signInType: 'ldap', + disabled: true, + }, + ], + }, + }, + global: { + components: { + 'n8n-avatar': N8nAvatar, + 'n8n-user-info': N8nUserInfo, + }, + }, + }); + expect(container.querySelector('.user-stack')).toBeInTheDocument(); + expect(container.querySelectorAll('svg')).toHaveLength(2); + expect(container.querySelector('.hiddenBadge')).toHaveTextContent('+2'); + }); +}); diff --git a/packages/design-system/src/components/N8nUserStack/index.ts b/packages/design-system/src/components/N8nUserStack/index.ts new file mode 100644 index 0000000000..8b8266be89 --- /dev/null +++ b/packages/design-system/src/components/N8nUserStack/index.ts @@ -0,0 +1,3 @@ +import N8nUserStack from './UserStack.vue'; + +export default N8nUserStack; diff --git a/packages/design-system/src/components/index.ts b/packages/design-system/src/components/index.ts index 9e9cbd616e..2d4d1b185f 100644 --- a/packages/design-system/src/components/index.ts +++ b/packages/design-system/src/components/index.ts @@ -46,6 +46,7 @@ export { default as N8nTags } from './N8nTags'; export { default as N8nText } from './N8nText'; export { default as N8nTooltip } from './N8nTooltip'; export { default as N8nTree } from './N8nTree'; +export { default as N8nUserStack } from './N8nUserStack'; export { default as N8nUserInfo } from './N8nUserInfo'; export { default as N8nUserSelect } from './N8nUserSelect'; export { default as N8nUsersList } from './N8nUsersList'; diff --git a/packages/design-system/src/plugin.ts b/packages/design-system/src/plugin.ts index b7a153469d..2904f35dc1 100644 --- a/packages/design-system/src/plugin.ts +++ b/packages/design-system/src/plugin.ts @@ -51,6 +51,7 @@ import { N8nUserInfo, N8nUserSelect, N8nUsersList, + N8nUserStack, } from './components'; export const N8nPlugin: Plugin<{}> = { @@ -103,6 +104,7 @@ export const N8nPlugin: Plugin<{}> = { app.component('n8n-text', N8nText); app.component('n8n-tooltip', N8nTooltip); app.component('n8n-tree', N8nTree); + app.component('n8n-user-stack', N8nUserStack); app.component('n8n-user-info', N8nUserInfo); app.component('n8n-users-list', N8nUsersList); app.component('n8n-user-select', N8nUserSelect); diff --git a/packages/design-system/src/types/user.ts b/packages/design-system/src/types/user.ts index d67cdc2151..b5b7f512cf 100644 --- a/packages/design-system/src/types/user.ts +++ b/packages/design-system/src/types/user.ts @@ -18,3 +18,5 @@ export interface UserAction { type?: 'external-link'; guard?: (user: IUser) => boolean; } + +export type UserStackGroups = { [groupName: string]: IUser[] };