mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-13 11:50:56 +00:00
EZQMS-562: Introduced reusable NotificationToast
component (#4873)
Signed-off-by: Petr Vyazovetskiy <develop.pit@gmail.com>
This commit is contained in:
parent
13d391bde5
commit
8dd0a60a70
112
packages/ui/src/components/NotificationToast.svelte
Normal file
112
packages/ui/src/components/NotificationToast.svelte
Normal file
@ -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>
|
@ -140,6 +140,7 @@ export { default as NavItem } from './components/NavItem.svelte'
|
|||||||
export { default as NavGroup } from './components/NavGroup.svelte'
|
export { default as NavGroup } from './components/NavGroup.svelte'
|
||||||
export { default as Modal } from './components/Modal.svelte'
|
export { default as Modal } from './components/Modal.svelte'
|
||||||
export { default as AccordionItem } from './components/AccordionItem.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 IconAdd } from './components/icons/Add.svelte'
|
||||||
export { default as IconCircleAdd } from './components/icons/CircleAdd.svelte'
|
export { default as IconCircleAdd } from './components/icons/CircleAdd.svelte'
|
||||||
|
@ -2,20 +2,8 @@
|
|||||||
import { getMetadata } from '@hcengineering/platform'
|
import { getMetadata } from '@hcengineering/platform'
|
||||||
import presentation, { copyTextToClipboard, createQuery } from '@hcengineering/presentation'
|
import presentation, { copyTextToClipboard, createQuery } from '@hcengineering/presentation'
|
||||||
import { Issue, IssueStatus } from '@hcengineering/tracker'
|
import { Issue, IssueStatus } from '@hcengineering/tracker'
|
||||||
import {
|
import { Button, Notification, navigate, parseLocation, NotificationToast } from '@hcengineering/ui'
|
||||||
AnySvelteComponent,
|
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
IconCheckCircle,
|
|
||||||
IconClose,
|
|
||||||
IconInfo,
|
|
||||||
Notification,
|
|
||||||
NotificationSeverity,
|
|
||||||
navigate,
|
|
||||||
parseLocation
|
|
||||||
} from '@hcengineering/ui'
|
|
||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
import { fade } from 'svelte/transition'
|
|
||||||
|
|
||||||
import { statusStore } from '@hcengineering/view-resources'
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
@ -27,10 +15,10 @@
|
|||||||
|
|
||||||
const issueQuery = createQuery()
|
const issueQuery = createQuery()
|
||||||
|
|
||||||
let issue: Issue | undefined
|
let issue: Issue | undefined = undefined
|
||||||
let status: IssueStatus | undefined
|
let status: IssueStatus | undefined = undefined
|
||||||
|
|
||||||
const { title, subTitle, severity, params } = notification
|
const { subTitle, params } = notification
|
||||||
|
|
||||||
$: issueQuery.query(
|
$: issueQuery.query(
|
||||||
tracker.class.Issue,
|
tracker.class.Issue,
|
||||||
@ -45,30 +33,7 @@
|
|||||||
status = $statusStore.byId.get(issue.status)
|
status = $statusStore.byId.get(issue.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getIcon = (): AnySvelteComponent | undefined => {
|
function handleIssueOpened (): void {
|
||||||
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 = () => {
|
|
||||||
if (params?.issueUrl) {
|
if (params?.issueUrl) {
|
||||||
const url = new URL(params?.issueUrl)
|
const url = new URL(params?.issueUrl)
|
||||||
const frontUrl = getMetadata(presentation.metadata.FrontUrl) ?? window.location.origin
|
const frontUrl = getMetadata(presentation.metadata.FrontUrl) ?? window.location.origin
|
||||||
@ -80,67 +45,34 @@
|
|||||||
|
|
||||||
onRemove()
|
onRemove()
|
||||||
}
|
}
|
||||||
const handleCopyUrl = () => {
|
|
||||||
if (issue) {
|
function handleCopyUrl (): void {
|
||||||
copyTextToClipboard(params?.issueUrl)
|
if (issue === undefined) {
|
||||||
|
void copyTextToClipboard(params?.issueUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$: icon = getIcon()
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="notify-container" in:fade out:fade>
|
<NotificationToast title={notification.title} severity={notification.severity} onClose={onRemove}>
|
||||||
<div class="flex-between">
|
<svelte:fragment slot="content">
|
||||||
<div class="flex-row-center">
|
<div class="flex-row-center flex-wrap gap-2 reverse">
|
||||||
{#if icon}
|
{#if status === undefined && issue}
|
||||||
<div class="mr-2"><Icon {icon} size="medium" fill={getIconColor()} /></div>
|
<IssueStatusIcon value={status} space={issue.space} size="small" />
|
||||||
{/if}
|
{/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>
|
</div>
|
||||||
<Button icon={IconClose} kind="ghost" size="small" on:click={onRemove} />
|
</svelte:fragment>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content flex-row-center flex-wrap gap-2 reverse">
|
<svelte:fragment slot="buttons">
|
||||||
{#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">
|
|
||||||
<Button label={tracker.string.ViewIssue} on:click={handleIssueOpened} />
|
<Button label={tracker.string.ViewIssue} on:click={handleIssueOpened} />
|
||||||
<Button icon={view.icon.CopyLink} label={tracker.string.CopyIssueUrl} on:click={handleCopyUrl} />
|
<Button icon={view.icon.CopyLink} label={tracker.string.CopyIssueUrl} on:click={handleCopyUrl} />
|
||||||
</div>
|
</svelte:fragment>
|
||||||
</div>
|
</NotificationToast>
|
||||||
|
|
||||||
<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>
|
|
||||||
|
Loading…
Reference in New Issue
Block a user