From 8dd0a60a70b4a88f2c0bb9d06e54ae9fbc2e6887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pete=20An=C3=B8ther?= <develop.pit@gmail.com> Date: Tue, 5 Mar 2024 02:39:49 -0300 Subject: [PATCH] EZQMS-562: Introduced reusable `NotificationToast` component (#4873) Signed-off-by: Petr Vyazovetskiy <develop.pit@gmail.com> --- .../src/components/NotificationToast.svelte | 112 ++++++++++++++++ packages/ui/src/index.ts | 1 + .../issues/IssueNotification.svelte | 122 ++++-------------- 3 files changed, 140 insertions(+), 95 deletions(-) create mode 100644 packages/ui/src/components/NotificationToast.svelte diff --git a/packages/ui/src/components/NotificationToast.svelte b/packages/ui/src/components/NotificationToast.svelte new file mode 100644 index 0000000000..aba306a501 --- /dev/null +++ b/packages/ui/src/components/NotificationToast.svelte @@ -0,0 +1,112 @@ +<!-- +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +--> + +<script lang="ts"> + import type { ComponentType } from 'svelte' + import { fade } from 'svelte/transition' + import Button from './Button.svelte' + import Icon from './Icon.svelte' + import CheckCircle from './icons/CheckCircle.svelte' + import Close from './icons/Close.svelte' + import Info from './icons/Info.svelte' + import { NotificationSeverity } from './notifications/NotificationSeverity' + + export let onClose: (() => void) | undefined = undefined + export let severity: NotificationSeverity | undefined = undefined + export let title: string + + const getIcon = (): ComponentType | undefined => { + switch (severity) { + case NotificationSeverity.Success: + return CheckCircle + case NotificationSeverity.Error: + case NotificationSeverity.Info: + case NotificationSeverity.Warning: + return Info + } + } + + $: icon = getIcon() +</script> + +<div class="root" in:fade out:fade> + <div class="flex-between"> + <div class="flex-row-center"> + {#if icon} + <div + class="mr-2" + class:icon-success={severity === NotificationSeverity.Success} + class:icon-error={severity === NotificationSeverity.Error} + class:icon-info={severity === NotificationSeverity.Info} + class:icon-warning={severity === NotificationSeverity.Warning} + > + <Icon {icon} size="medium" /> + </div> + {/if} + <span class="overflow-label fs-bold text-base caption-color">{title}</span> + </div> + {#if onClose !== undefined} + <Button icon={Close} kind="ghost" size="small" on:click={onClose} /> + {/if} + </div> + + <div class="content"> + <slot name="content" /> + </div> + + {#if $$slots.buttons} + <div class="flex-between gap-2"> + <slot name="buttons" /> + </div> + {/if} +</div> + +<style lang="scss"> + .root { + overflow: hidden; + display: flex; + flex-direction: column; + margin: 0.75rem; + padding: 0.5rem; + min-width: 25rem; + max-width: 35rem; + min-height: 7rem; + color: var(--theme-caption-color); + background-color: var(--theme-popup-color); + border: 1px solid var(--theme-popup-divider); + border-radius: 0.5rem; + box-shadow: var(--theme-popup-shadow); + + .icon-success { + color: var(--theme-won-color); + } + .icon-error { + color: var(--theme-lost-color); + } + .icon-info { + color: var(--primary-color-skyblue); + } + .icon-warning { + color: var(--theme-warning-color); + } + + .content { + flex-grow: 1; + margin: 1rem 0 1.25rem; + } + } +</style> diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index feab0dc87b..9ba0b1a78d 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -140,6 +140,7 @@ export { default as NavItem } from './components/NavItem.svelte' export { default as NavGroup } from './components/NavGroup.svelte' export { default as Modal } from './components/Modal.svelte' export { default as AccordionItem } from './components/AccordionItem.svelte' +export { default as NotificationToast } from './components/NotificationToast.svelte' export { default as IconAdd } from './components/icons/Add.svelte' export { default as IconCircleAdd } from './components/icons/CircleAdd.svelte' diff --git a/plugins/tracker-resources/src/components/issues/IssueNotification.svelte b/plugins/tracker-resources/src/components/issues/IssueNotification.svelte index 84682fb04f..69672f6812 100644 --- a/plugins/tracker-resources/src/components/issues/IssueNotification.svelte +++ b/plugins/tracker-resources/src/components/issues/IssueNotification.svelte @@ -2,20 +2,8 @@ import { getMetadata } from '@hcengineering/platform' import presentation, { copyTextToClipboard, createQuery } from '@hcengineering/presentation' import { Issue, IssueStatus } from '@hcengineering/tracker' - import { - AnySvelteComponent, - Button, - Icon, - IconCheckCircle, - IconClose, - IconInfo, - Notification, - NotificationSeverity, - navigate, - parseLocation - } from '@hcengineering/ui' + import { Button, Notification, navigate, parseLocation, NotificationToast } from '@hcengineering/ui' import view from '@hcengineering/view' - import { fade } from 'svelte/transition' import { statusStore } from '@hcengineering/view-resources' import tracker from '../../plugin' @@ -27,10 +15,10 @@ const issueQuery = createQuery() - let issue: Issue | undefined - let status: IssueStatus | undefined + let issue: Issue | undefined = undefined + let status: IssueStatus | undefined = undefined - const { title, subTitle, severity, params } = notification + const { subTitle, params } = notification $: issueQuery.query( tracker.class.Issue, @@ -45,30 +33,7 @@ status = $statusStore.byId.get(issue.status) } - const getIcon = (): AnySvelteComponent | undefined => { - switch (severity) { - case NotificationSeverity.Success: - return IconCheckCircle - case NotificationSeverity.Error: - case NotificationSeverity.Info: - case NotificationSeverity.Warning: - return IconInfo - } - } - - const getIconColor = () => { - switch (severity) { - case NotificationSeverity.Success: - return '#34db80' - case NotificationSeverity.Error: - return '#eb5757' - case NotificationSeverity.Info: - return '#93caf3' - case NotificationSeverity.Warning: - return '#f2994a' - } - } - const handleIssueOpened = () => { + function handleIssueOpened (): void { if (params?.issueUrl) { const url = new URL(params?.issueUrl) const frontUrl = getMetadata(presentation.metadata.FrontUrl) ?? window.location.origin @@ -80,67 +45,34 @@ onRemove() } - const handleCopyUrl = () => { - if (issue) { - copyTextToClipboard(params?.issueUrl) + + function handleCopyUrl (): void { + if (issue === undefined) { + void copyTextToClipboard(params?.issueUrl) } } - $: icon = getIcon() </script> -<div class="notify-container" in:fade out:fade> - <div class="flex-between"> - <div class="flex-row-center"> - {#if icon} - <div class="mr-2"><Icon {icon} size="medium" fill={getIconColor()} /></div> +<NotificationToast title={notification.title} severity={notification.severity} onClose={onRemove}> + <svelte:fragment slot="content"> + <div class="flex-row-center flex-wrap gap-2 reverse"> + {#if status === undefined && issue} + <IssueStatusIcon value={status} space={issue.space} size="small" /> {/if} - <span class="overflow-label fs-bold caption-color">{title}</span> + {#if issue} + <IssuePresenter value={issue} /> + {/if} + <span class="overflow-label"> + {subTitle} + </span> + <span class="content-dark-color"> + {params?.subTitlePostfix} + </span> </div> - <Button icon={IconClose} kind="ghost" size="small" on:click={onRemove} /> - </div> + </svelte:fragment> - <div class="content flex-row-center flex-wrap gap-2 reverse"> - {#if status && issue} - <IssueStatusIcon value={status} space={issue.space} size="small" /> - {/if} - {#if issue} - <IssuePresenter value={issue} /> - {/if} - <span class="overflow-label"> - {subTitle} - </span> - <span class="content-dark-color"> - {params?.subTitlePostfix} - </span> - </div> - <div class="flex-between gap-2"> + <svelte:fragment slot="buttons"> <Button label={tracker.string.ViewIssue} on:click={handleIssueOpened} /> <Button icon={view.icon.CopyLink} label={tracker.string.CopyIssueUrl} on:click={handleCopyUrl} /> - </div> -</div> - -<style lang="scss"> - .notify-container { - overflow: hidden; - display: flex; - flex-direction: column; - margin: 0.75rem; - padding: 0.5rem; - min-width: 25rem; - max-width: 35rem; - min-height: 7rem; - color: var(--theme-caption-color); - background-color: var(--theme-popup-color); - border: 1px solid var(--theme-popup-divider); - border-radius: 0.5rem; - box-shadow: var(--theme-popup-shadow); - - .content { - flex-grow: 1; - margin: 1rem 0 1.25rem; - padding: 0 1rem; - // border: 1px solid var(--theme-divider-color); - border-radius: 0.5rem; - } - } -</style> + </svelte:fragment> +</NotificationToast>