forked from coracle/flotilla
feat: redesign toast notifications for UX (#148)
Co-authored-by: userAdityaa <aditya.chaudhary1558@gmail.com> Co-committed-by: userAdityaa <aditya.chaudhary1558@gmail.com>
This commit is contained in:
@@ -1,28 +1,98 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {parse, renderAsHtml} from "@welshman/content"
|
import {parse, renderAsHtml} from "@welshman/content"
|
||||||
|
import Close from "@assets/icons/close.svg?dataurl"
|
||||||
import {fly} from "@lib/transition"
|
import {fly} from "@lib/transition"
|
||||||
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import {toast, popToast} from "@app/util/toast"
|
import {toast, popToast} from "@app/util/toast"
|
||||||
|
|
||||||
|
let touchStartY = 0
|
||||||
|
let touchStartTime = 0
|
||||||
|
let dragY = $state(0)
|
||||||
|
let isSettling = $state(false)
|
||||||
|
let containerEl = $state<HTMLDivElement | undefined>(undefined)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if ($toast) {
|
||||||
|
dragY = 0
|
||||||
|
isSettling = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!containerEl) return
|
||||||
|
containerEl.addEventListener("touchmove", onTouchMove, {passive: false})
|
||||||
|
return () => containerEl!.removeEventListener("touchmove", onTouchMove)
|
||||||
|
})
|
||||||
|
|
||||||
const onActionClick = () => {
|
const onActionClick = () => {
|
||||||
$toast!.action!.onclick()
|
$toast!.action!.onclick()
|
||||||
popToast($toast!.id)
|
popToast($toast!.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onClose = () => popToast($toast!.id)
|
||||||
|
|
||||||
|
const onTouchStart = (e: TouchEvent) => {
|
||||||
|
touchStartY = e.touches[0].clientY
|
||||||
|
touchStartTime = Date.now()
|
||||||
|
dragY = 0
|
||||||
|
isSettling = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTouchMove = (e: TouchEvent) => {
|
||||||
|
const delta = e.touches[0].clientY - touchStartY
|
||||||
|
if (delta < 0) {
|
||||||
|
e.preventDefault()
|
||||||
|
isSettling = false
|
||||||
|
dragY = delta
|
||||||
|
} else {
|
||||||
|
dragY = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTouchEnd = (e: TouchEvent) => {
|
||||||
|
const delta = e.changedTouches[0].clientY - touchStartY
|
||||||
|
const duration = Date.now() - touchStartTime
|
||||||
|
const isQuickFlick = duration < 400 && delta < 0
|
||||||
|
const isSlowDismiss = delta < -40
|
||||||
|
|
||||||
|
if (isQuickFlick || isSlowDismiss) {
|
||||||
|
dragY = 0
|
||||||
|
popToast($toast!.id)
|
||||||
|
} else {
|
||||||
|
isSettling = true
|
||||||
|
dragY = 0
|
||||||
|
setTimeout(() => {
|
||||||
|
isSettling = false
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $toast}
|
{#if $toast}
|
||||||
{@const theme = $toast.theme || "info"}
|
{@const theme = $toast.theme || "info"}
|
||||||
<div transition:fly class="bottom-sai right-sai toast z-toast">
|
<div
|
||||||
|
bind:this={containerEl}
|
||||||
|
transition:fly={{y: -20}}
|
||||||
|
class="fixed z-toast top-[calc(var(--sait)+0.5rem)] left-[calc(var(--sail)+0.5rem)] right-[calc(var(--sair)+0.5rem)] flex flex-col gap-2 md:right-4 md:bottom-4 md:top-auto md:left-auto md:w-80"
|
||||||
|
style={dragY !== 0 || isSettling
|
||||||
|
? `transform: translateY(${dragY}px)${isSettling ? "; transition: transform 200ms ease-out" : ""}`
|
||||||
|
: ""}
|
||||||
|
ontouchstart={onTouchStart}
|
||||||
|
ontouchend={onTouchEnd}>
|
||||||
{#key $toast.id}
|
{#key $toast.id}
|
||||||
<div
|
<div
|
||||||
role="alert"
|
role="alert"
|
||||||
class="alert flex justify-center whitespace-normal text-left"
|
class="alert relative flex justify-center whitespace-normal text-left"
|
||||||
class:bg-base-100={theme === "info"}
|
class:bg-base-100={theme === "info"}
|
||||||
class:text-base-content={theme === "info"}
|
class:text-base-content={theme === "info"}
|
||||||
class:alert-error={theme === "error"}>
|
class:alert-error={theme === "error"}>
|
||||||
<p class:welshman-content-error={theme === "error"}>
|
<Button
|
||||||
|
class="absolute -top-2 -right-2 btn btn-circle btn-neutral btn-xs hidden md:inline-flex"
|
||||||
|
onclick={onClose}>
|
||||||
|
<Icon icon={Close} size={3} />
|
||||||
|
</Button>
|
||||||
|
<p class="md:pr-6" class:welshman-content-error={theme === "error"}>
|
||||||
{#if $toast.message}
|
{#if $toast.message}
|
||||||
{@html renderAsHtml(parse({content: $toast.message}))}
|
{@html renderAsHtml(parse({content: $toast.message}))}
|
||||||
{#if $toast.action}
|
{#if $toast.action}
|
||||||
@@ -35,9 +105,6 @@
|
|||||||
<Component toast={$toast} {...props} />
|
<Component toast={$toast} {...props} />
|
||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
<Button class="flex items-center opacity-75" onclick={() => popToast($toast.id)}>
|
|
||||||
<Icon icon={CloseCircle} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user