fix: Prettier and less invasive toasts (#7758)
* Toast update * ToastType * nit adjustements * nit smaller toast
This commit is contained in:
@@ -36,21 +36,23 @@
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export type ToastType = AlertType
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import { CheckCircle2, XCircleIcon } from 'lucide-svelte'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import { type ToastAction } from '$lib/toast'
|
||||
import { processMessage } from './toast'
|
||||
import { onDestroy, untrack } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { classes, icons, type AlertType } from '$lib/components/common/alert/model'
|
||||
|
||||
interface Props {
|
||||
message: string
|
||||
toastId: string
|
||||
error?: boolean
|
||||
type?: ToastType
|
||||
actions?: ToastAction[]
|
||||
errorMessage?: string | undefined
|
||||
duration?: number
|
||||
@@ -59,7 +61,7 @@
|
||||
let {
|
||||
message,
|
||||
toastId,
|
||||
error = false,
|
||||
type = 'success',
|
||||
actions = [],
|
||||
errorMessage = undefined,
|
||||
duration = 5000
|
||||
@@ -75,81 +77,103 @@
|
||||
onDestroy(() => {
|
||||
delete toastStates[toastId]
|
||||
})
|
||||
let state = $derived.by(() => toastStates[toastId] as ToastState | undefined)
|
||||
let _state = $derived.by(() => toastStates[toastId] as ToastState | undefined)
|
||||
$effect(() => {
|
||||
if (!state) {
|
||||
if (!_state) {
|
||||
toast.pop(toastId)
|
||||
}
|
||||
})
|
||||
|
||||
let color = error
|
||||
? { text: 'text-red-400', bg: 'bg-red-400' }
|
||||
: { text: 'text-green-400', bg: 'bg-green-300' }
|
||||
let color = classes[type]
|
||||
|
||||
let hover = $derived(Object.values(toastStates).some((state) => state.hover))
|
||||
let containerClass = {
|
||||
success: 'toast-success',
|
||||
error: 'toast-error',
|
||||
info: 'toast-info',
|
||||
warning: 'toast-warning'
|
||||
}[type]
|
||||
|
||||
let Icon = $derived(icons[type])
|
||||
|
||||
let showMore = $state(false)
|
||||
const MAX_MSG_LEN = 160
|
||||
let isLongMessage = $derived(message.length > MAX_MSG_LEN)
|
||||
let displayMessage = $derived(
|
||||
isLongMessage && !showMore ? message.slice(0, MAX_MSG_LEN) + '... ' : message
|
||||
)
|
||||
// let hover = $derived(Object.values(toastStates).some((state) => state.hover))
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class={twMerge(
|
||||
'pointer-events-auto w-full max-w-sm overflow-hidden bg-surface-tertiary drop-shadow-base shadow-lg ring-1 ring-black ring-opacity-5 border rounded-md',
|
||||
error ? 'toast-error' : 'toast-success'
|
||||
'pointer-events-auto w-full overflow-hidden rounded-md relative flex items-center bg-surface',
|
||||
containerClass
|
||||
)}
|
||||
onmouseenter={() => state && (state.hover = true)}
|
||||
onmouseleave={() => state && (state.hover = false)}
|
||||
onmouseenter={() => _state && (_state.hover = true)}
|
||||
onmouseleave={() => _state && (_state.hover = false)}
|
||||
>
|
||||
<div class="p-2 min-h-[60px] flex flex-col">
|
||||
<div class="flex items-start w-full">
|
||||
<div class="flex-shrink-0 mt-0.5">
|
||||
{#if error}
|
||||
<XCircleIcon class="h-4 w-4 {color.text}" />
|
||||
{:else}
|
||||
<CheckCircle2 class="h-4 w-4 {color.text}" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="ml-3 flex-1 w-0">
|
||||
<p class="text-sm text-primary break-words">{@html processMessage(message)}</p>
|
||||
{#if errorMessage}
|
||||
<p class="text-xs {color.text} w-full overflow-auto mt-2">
|
||||
{errorMessage}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="ml-4 flex flex-shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleClose}
|
||||
class="inline-flex rounded-md text-gray-400 hover:text-primary focus:outline-none"
|
||||
>
|
||||
<span class="sr-only">Close</span>
|
||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path
|
||||
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center h-full w-full min-h-10 rounded-md px-2 py-1 {color.descriptionClass} {color.bgClass}"
|
||||
>
|
||||
<div class="flex-shrink-0 mt-0.5">
|
||||
<Icon class="h-4 w-4 {color.iconClass}" />
|
||||
</div>
|
||||
<div class="mt-2 flex flex-col gap-2 w-full items-center">
|
||||
<div class="ml-3 flex-1 w-0">
|
||||
<p class="text-xs break-words">
|
||||
{@html processMessage(displayMessage)}
|
||||
{#if isLongMessage && !showMore}
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 {color.descriptionClass} font-medium hover:underline focus:outline-none"
|
||||
onclick={() => (showMore = true)}
|
||||
>
|
||||
Show more
|
||||
</button>
|
||||
{/if}
|
||||
</p>
|
||||
{#if errorMessage}
|
||||
<p class="text-xs {color.descriptionClass} w-full overflow-auto mt-2">
|
||||
{errorMessage}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center ml-2">
|
||||
{#each actions as action, index (index)}
|
||||
<Button
|
||||
variant={action.buttonType ?? 'default'}
|
||||
variant={action.buttonType ?? 'subtle'}
|
||||
unifiedSize="sm"
|
||||
onClick={() => {
|
||||
action.callback()
|
||||
toast.pop(toastId)
|
||||
}}
|
||||
wrapperClasses="w-full"
|
||||
btnClasses="{color.descriptionClass} font-medium"
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="ml-4 flex flex-shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleClose}
|
||||
class="inline-flex rounded-md hover:text-primary focus:outline-none"
|
||||
>
|
||||
<span class="sr-only">Close</span>
|
||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path
|
||||
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Duration indicator -->
|
||||
<div
|
||||
class="h-0.5 transition-colors {hover ? 'bg-gray-300' : color.bg}"
|
||||
style="width: {Math.max(0, 1 - (state?.elapsed ?? duration) / duration) * 100}%"
|
||||
class="h-[1px] absolute bottom-0 transition-colors bg-current {color.iconClass} opacity-60"
|
||||
style="width: {Math.max(0, 1 - (_state?.elapsed ?? duration) / duration) * 100}%"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { type AlertType, classes } from './model'
|
||||
import { type AlertType, classes, icons } from './model'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import {
|
||||
AlertCircle,
|
||||
AlertTriangle,
|
||||
CheckCircle2,
|
||||
Info,
|
||||
ChevronDown,
|
||||
ChevronUp
|
||||
} from 'lucide-svelte'
|
||||
import { ChevronDown, ChevronUp } from 'lucide-svelte'
|
||||
import { slide } from 'svelte/transition'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
@@ -54,13 +47,6 @@
|
||||
children
|
||||
}: Props = $props()
|
||||
|
||||
const icons: Record<AlertType, any> = {
|
||||
info: Info,
|
||||
warning: AlertCircle,
|
||||
error: AlertTriangle,
|
||||
success: CheckCircle2
|
||||
}
|
||||
|
||||
function toggleCollapse() {
|
||||
if (collapsible) {
|
||||
isCollapsed = !isCollapsed
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { AlertCircle, AlertTriangle, CheckCircle2, Info } from 'lucide-svelte'
|
||||
|
||||
export type AlertType = 'success' | 'error' | 'warning' | 'info'
|
||||
|
||||
export const classes: Record<AlertType, Record<string, string>> = {
|
||||
@@ -27,3 +29,10 @@ export const classes: Record<AlertType, Record<string, string>> = {
|
||||
descriptionClass: 'text-green-700 dark:text-green-100/90'
|
||||
}
|
||||
}
|
||||
|
||||
export const icons: Record<AlertType, any> = {
|
||||
info: Info,
|
||||
warning: AlertCircle,
|
||||
error: AlertTriangle,
|
||||
success: CheckCircle2
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Toast from '$lib/components/Toast.svelte'
|
||||
import Toast, { type ToastType } from '$lib/components/Toast.svelte'
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import type { ComponentProps } from 'svelte'
|
||||
import type { Button } from './components/common'
|
||||
@@ -11,15 +11,18 @@ export type ToastAction = {
|
||||
|
||||
export function sendUserToast(
|
||||
message: string,
|
||||
error: boolean = false,
|
||||
_type: boolean | ToastType = 'success',
|
||||
actions: ToastAction[] = [],
|
||||
errorMessage: string | undefined = undefined,
|
||||
duration: number = 5000
|
||||
): void {
|
||||
const type = typeof _type === 'boolean' ? (_type ? 'error' : 'success') : _type
|
||||
const error = type === 'error'
|
||||
if (globalThis.windmillToast) {
|
||||
globalThis.windmillToast({
|
||||
message,
|
||||
error,
|
||||
type,
|
||||
actions,
|
||||
errorMessage,
|
||||
duration
|
||||
@@ -34,7 +37,7 @@ export function sendUserToast(
|
||||
src: Toast,
|
||||
props: {
|
||||
message,
|
||||
error,
|
||||
type,
|
||||
actions,
|
||||
errorMessage,
|
||||
duration
|
||||
@@ -47,7 +50,10 @@ export function sendUserToast(
|
||||
'--toastPadding': '0',
|
||||
'--toastMsgPadding': '0',
|
||||
'--toastBackground': '#00000000',
|
||||
'--toastBorderRadius': '0.4rem'
|
||||
'--toastBorderRadius': '0.4rem',
|
||||
'--toastWidth': '34rem',
|
||||
'--toastMinHeight': '1rem',
|
||||
'--toastBoxShadow': 'none'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
next: 0, // next progress value
|
||||
pausable: true, // pause progress bar tween on mouse hover
|
||||
dismissable: true, // allow dismiss with close button
|
||||
reversed: false, // insert new toast to bottom of stack
|
||||
intro: { x: 256 }, // toast intro fly animation settings
|
||||
reversed: true, // insert new toast to bottom of stack
|
||||
intro: { y: -32 }, // toast intro fly animation settings
|
||||
theme: {} // css var overrides
|
||||
}
|
||||
|
||||
@@ -43,11 +43,22 @@
|
||||
|
||||
<style>
|
||||
.wrap {
|
||||
display: contents;
|
||||
display: flex;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 0.875rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
|
||||
--toastContainerRight: auto;
|
||||
--toastContainerTop: auto;
|
||||
--toastContainerBottom: 1rem;
|
||||
}
|
||||
.wrap :global(strong) {
|
||||
font-weight: 600;
|
||||
|
||||
.wrap :global(._toastContainer) {
|
||||
height: 20rem;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user