Coves frontend - a photon fork
1<script lang="ts" module>
2 interface PageScopedError {
3 scope: string
4 message: string
5 }
6
7 let errors = $state<PageScopedError[]>([])
8
9 export function pushError(args: { scope: string; message: string }) {
10 errors.push(args)
11 }
12
13 export function clearErrorScope(scope: string | null | undefined) {
14 errors = errors.filter((error) => error.scope != scope)
15 }
16</script>
17
18<script lang="ts">
19 import { onDestroy, type Snippet } from 'svelte'
20 import { ExclamationTriangle, Icon } from 'svelte-hero-icons/dist'
21 import { expoOut } from 'svelte/easing'
22 import { fly, slide } from 'svelte/transition'
23
24 onDestroy(() => {
25 clearErrorScope(scope)
26 })
27 interface Props {
28 scope?: string | undefined | null
29 message?: string
30 class?: string
31 children?: Snippet
32 }
33
34 let { scope, message, children, class: clazz = '' }: Props = $props()
35
36 let scopedErrors = $derived(
37 errors.filter((e) => e.scope == scope || e.scope == 'global'),
38 )
39</script>
40
41{#if scopedErrors.length > 0 || message}
42 <div
43 class={['flex flex-col gap-4', clazz]}
44 in:slide={{ duration: 400, easing: expoOut }}
45 out:slide={{ duration: 400, delay: 400, easing: expoOut }}
46 >
47 <div
48 in:fly|global={{
49 y: -8,
50 duration: 400,
51 delay: 200,
52 opacity: 0,
53 easing: expoOut,
54 }}
55 out:fly={{ y: -8, duration: 400, opacity: 0, easing: expoOut }}
56 class="info-container material-error"
57 >
58 <Icon
59 src={ExclamationTriangle}
60 size="20"
61 micro
62 class="inline-block rounded-lg clear-both float-left mr-2"
63 />
64 {#if message}
65 {message}
66 {/if}
67 {@render children?.()}
68 {#each scopedErrors as error}
69 <p>{error.message}</p>
70 {/each}
71 </div>
72 </div>
73{/if}
74
75<style>
76 @reference '../../../app.css';
77
78 .info-container {
79 border-radius: var(--radius-2xl);
80 padding: calc(var(--spacing) * 2.5) calc(var(--spacing) * 3);
81 font-size: var(--text-sm);
82 }
83</style>