feat(web): render component in notifications (#10990)

This commit is contained in:
Michel Heusschen 2024-07-10 16:05:04 +02:00 committed by GitHub
parent 1dd1d36120
commit 59aa347912
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 88 additions and 30 deletions

View file

@ -1,3 +1,4 @@
import NotificationComponentTest from '$lib/components/shared-components/notification/__tests__/notification-component-test.svelte';
import '@testing-library/jest-dom';
import { cleanup, render, type RenderResult } from '@testing-library/svelte';
import { NotificationType } from '../notification';
@ -37,4 +38,24 @@ describe('NotificationCard component', () => {
expect(sut.getByTestId('title')).toHaveTextContent('info');
expect(sut.getByTestId('message')).toHaveTextContent('Notification message');
});
it('shows title and renders component', () => {
sut = render(NotificationCard, {
notification: {
id: 1234,
type: NotificationType.Info,
timeout: 1,
action: { type: 'discard' },
component: {
type: NotificationComponentTest,
props: {
href: 'link',
},
},
},
});
expect(sut.getByTestId('title')).toHaveTextContent('info');
expect(sut.getByTestId('message').innerHTML).toEqual('Notification <b>message</b> with <a href="link">link</a>');
});
});

View file

@ -0,0 +1,5 @@
<script lang="ts">
export let href: string;
</script>
Notification <b>message</b> with <a {href}>link</a>

View file

@ -2,16 +2,18 @@
import { fade } from 'svelte/transition';
import Icon from '$lib/components/elements/icon.svelte';
import {
type Notification,
isComponentNotification,
notificationController,
NotificationType,
type ComponentNotification,
type Notification,
} from '$lib/components/shared-components/notification/notification';
import { onMount } from 'svelte';
import { mdiCloseCircleOutline, mdiInformationOutline, mdiWindowClose } from '@mdi/js';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { t } from 'svelte-i18n';
export let notification: Notification;
export let notification: Notification | ComponentNotification;
$: icon = notification.type === NotificationType.Error ? mdiCloseCircleOutline : mdiInformationOutline;
$: hoverStyle = notification.action.type === 'discard' ? 'hover:cursor-pointer' : '';
@ -93,9 +95,8 @@
</div>
<p class="whitespace-pre-wrap pl-[28px] pr-[16px] text-sm" data-testid="message">
{#if notification.html}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html notification.message}
{#if isComponentNotification(notification)}
<svelte:component this={notification.component.type} {...notification.component.props} />
{:else}
{notification.message}
{/if}

View file

@ -1,3 +1,4 @@
import type { ComponentProps, ComponentType, SvelteComponent } from 'svelte';
import { writable } from 'svelte/store';
export enum NotificationType {
@ -15,11 +16,6 @@ export type Notification = {
id: number;
type: NotificationType;
message: string;
/**
* Allow HTML to be inserted within the message. Make sure to verify/encode
* variables that may be interpoalted into 'message'
*/
html?: boolean;
/** The action to take when the notification is clicked */
action: NotificationAction;
button?: NotificationButton;
@ -32,13 +28,37 @@ type NoopAction = { type: 'noop' };
export type NotificationAction = DiscardAction | NoopAction;
export type NotificationOptions = Partial<Exclude<Notification, 'id'>> & { message: string };
type Component<T extends ComponentType> = {
type: T;
props: ComponentProps<InstanceType<T>>;
};
type BaseNotificationOptions<T, R extends keyof T> = Partial<Omit<T, 'id'>> & Pick<T, R>;
export type NotificationOptions = BaseNotificationOptions<Notification, 'message'>;
export type ComponentNotificationOptions<T extends ComponentType> = BaseNotificationOptions<
ComponentNotification<T>,
'component'
>;
export type ComponentNotification<T extends ComponentType = ComponentType<SvelteComponent>> = Omit<
Notification,
'message'
> & {
component: Component<T>;
};
export const isComponentNotification = <T extends ComponentType>(
notification: Notification | ComponentNotification<T>,
): notification is ComponentNotification<T> => {
return 'component' in notification;
};
function createNotificationList() {
const notificationList = writable<Notification[]>([]);
const notificationList = writable<(Notification | ComponentNotification)[]>([]);
let count = 1;
const show = (options: NotificationOptions) => {
const show = <T>(options: T extends ComponentType ? ComponentNotificationOptions<T> : NotificationOptions) => {
notificationList.update((currentList) => {
currentList.push({
id: count++,