development #3
@@ -1,6 +1,7 @@
 | 
			
		||||
<script>
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { createEventDispatcher } from "svelte";
 | 
			
		||||
  import { fade } from "svelte/transition";
 | 
			
		||||
  import { Toast, ToastType } from "$lib/toast";
 | 
			
		||||
  import SuccessIcon from "./SuccessIcon.svelte";
 | 
			
		||||
  import ErrorIcon from "./ErrorIcon.svelte";
 | 
			
		||||
  import InfoIcon from "./InfoIcon.svelte";
 | 
			
		||||
@@ -8,24 +9,23 @@
 | 
			
		||||
 | 
			
		||||
  const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
  export let type = "error";
 | 
			
		||||
  export let dismissible = true;
 | 
			
		||||
  export let toastData: Toast;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<article class={type} role="alert" transition:fade>
 | 
			
		||||
  {#if type === "success"}
 | 
			
		||||
<article class={toastData.type.toString().toLowerCase()} role="alert" transition:fade>
 | 
			
		||||
  {#if toastData.type === ToastType.Success}
 | 
			
		||||
    <SuccessIcon width="1.1em" />
 | 
			
		||||
  {:else if type === "error"}
 | 
			
		||||
  {:else if toastData.type === ToastType.Error}
 | 
			
		||||
    <ErrorIcon width="1.1em" />
 | 
			
		||||
  {:else}
 | 
			
		||||
    <InfoIcon width="1.1em" />
 | 
			
		||||
  {/if}
 | 
			
		||||
 | 
			
		||||
  <div class="text">
 | 
			
		||||
    <slot />
 | 
			
		||||
    {toastData.text}
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  {#if dismissible}
 | 
			
		||||
  {#if toastData.dismissable}
 | 
			
		||||
    <button class="close" on:click={() => dispatch("dismiss")}>
 | 
			
		||||
      <CloseIcon width="0.8em" />
 | 
			
		||||
    </button>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,11 +7,7 @@
 | 
			
		||||
{#if $toasts}
 | 
			
		||||
  <section>
 | 
			
		||||
    {#each $toasts as toast (toast.id)}
 | 
			
		||||
      <Toast
 | 
			
		||||
        type={toast.type}
 | 
			
		||||
        dismissible={toast.dismissible}
 | 
			
		||||
        on:dismiss={() => dismissToast(toast.id)}>{toast.message}</Toast
 | 
			
		||||
      >
 | 
			
		||||
      <Toast toastData = {toast} on:dismiss={() => dismissToast(toast.id)} />
 | 
			
		||||
    {/each}
 | 
			
		||||
  </section>
 | 
			
		||||
{/if}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,26 @@
 | 
			
		||||
class Toast {
 | 
			
		||||
    constructor(text: String, type: String, dismissable: boolean, timeout: number ) {
 | 
			
		||||
        id = text
 | 
			
		||||
/**
 | 
			
		||||
 * @enum Used to refer to the type of toast being displayed
 | 
			
		||||
 */
 | 
			
		||||
export enum ToastType {
 | 
			
		||||
    Info = "info",
 | 
			
		||||
    Success = "success",
 | 
			
		||||
    Error = "error"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    id: String;
 | 
			
		||||
    type: String;
 | 
			
		||||
    dismissible: boolean;
 | 
			
		||||
/**
 | 
			
		||||
 * @class Toast Notification
 | 
			
		||||
 */
 | 
			
		||||
export class Toast {
 | 
			
		||||
    constructor(text: String, type: ToastType, dismissable: boolean, timeout: number ) {
 | 
			
		||||
        this.text = text;
 | 
			
		||||
        this.type = type;
 | 
			
		||||
        this.dismissable = dismissable;
 | 
			
		||||
        this.timeout = timeout;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    id: number = 0;
 | 
			
		||||
    text: String;
 | 
			
		||||
    type: ToastType;
 | 
			
		||||
    dismissable: Boolean;
 | 
			
		||||
    timeout: number;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
<script>
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
    import { getJson } from "$lib/data";
 | 
			
		||||
    import { removeMessage } from "$lib/snackbar"
 | 
			
		||||
	import { Toast, ToastType } from "$lib/toast";
 | 
			
		||||
    import { addToast } from "./store";
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
@@ -79,4 +80,5 @@
 | 
			
		||||
            <h1>Unable to load portfolio overview data</h1>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {addToast(new Toast("Unable to load me.json", ToastType.Error, true, 3000))}
 | 
			
		||||
{/await}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<script>
 | 
			
		||||
	import Snackbar from '../snackbar.svelte';
 | 
			
		||||
	import Toasts from "../Toasts.svelte";
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
@@ -52,5 +52,6 @@
 | 
			
		||||
</nav>
 | 
			
		||||
 | 
			
		||||
<div class="main-container fade">
 | 
			
		||||
    <Toasts></Toasts>
 | 
			
		||||
    <slot />
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
<script>
 | 
			
		||||
    import Main from '../main.svelte';
 | 
			
		||||
    import Snackbar from '../snackbar.svelte';
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<Main></Main>
 | 
			
		||||
<Snackbar></Snackbar>
 | 
			
		||||
@@ -1,73 +0,0 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
    let messages: string[] = [];
 | 
			
		||||
 | 
			
		||||
	export function addMessage(text: string) {
 | 
			
		||||
		messages = [...messages, text];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    export function removeMessage() {
 | 
			
		||||
		if(messages.length == 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        messages = messages.splice(1, messages.length-1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
    /* Flex container for all of the elements waiting to be shown*/
 | 
			
		||||
    .snackbar-container {
 | 
			
		||||
        position: fixed;
 | 
			
		||||
        top: 0;
 | 
			
		||||
        left: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        margin: 1rem;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        gap: 1em;
 | 
			
		||||
        z-index: 1000;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /* The snackbar - position it at the bottom and in the middle of the screen */
 | 
			
		||||
    .snackbar-item {
 | 
			
		||||
        background-color: var(--bg-2);
 | 
			
		||||
        color: var(--green); 
 | 
			
		||||
        width: 10em;
 | 
			
		||||
        text-align: center; 
 | 
			
		||||
        border-radius: 1em; 
 | 
			
		||||
        padding: 16px; 
 | 
			
		||||
        left: 50%;
 | 
			
		||||
        -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
 | 
			
		||||
        animation: fadein 0.5s, fadeout 0.5s 2.5s;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Animations to fade the snackbar in and out */
 | 
			
		||||
    @-webkit-keyframes fadein {
 | 
			
		||||
        from {bottom: 0; opacity: 0;}
 | 
			
		||||
        to {bottom: 30px; opacity: 1;}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @keyframes fadein {
 | 
			
		||||
        from {bottom: 0; opacity: 0;}
 | 
			
		||||
        to {bottom: 30px; opacity: 1;}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @-webkit-keyframes fadeout {
 | 
			
		||||
        from {bottom: 30px; opacity: 1;}
 | 
			
		||||
        to {bottom: 0; opacity: 0;}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @keyframes fadeout {
 | 
			
		||||
        from {bottom: 30px; opacity: 1;}
 | 
			
		||||
        to {bottom: 0; opacity: 0;}
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
<button on:click={() => addMessage("Hello")}>Hello</button>
 | 
			
		||||
 | 
			
		||||
<div class="snackbar-container">
 | 
			
		||||
    {#each messages as message}
 | 
			
		||||
        <div class="snackbar-item" on:click={removeMessage}>{message}</div>
 | 
			
		||||
    {/each}
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										17
									
								
								src/store.ts
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/store.ts
									
									
									
									
									
								
							@@ -1,16 +1,17 @@
 | 
			
		||||
import { writable } from "svelte/store";
 | 
			
		||||
import { ToastType, type Toast } from "$lib/toast";
 | 
			
		||||
import { writable, type Writable } from "svelte/store";
 | 
			
		||||
 | 
			
		||||
export const toasts = writable([]);
 | 
			
		||||
export const toasts: Writable<Toast[]> = writable([]);
 | 
			
		||||
 | 
			
		||||
export const addToast = (toast: ) => {
 | 
			
		||||
export const addToast = (toast: Toast) => {
 | 
			
		||||
  // Create a unique ID so we can easily find/remove it
 | 
			
		||||
  // if it is dismissible/has a timeout.
 | 
			
		||||
  const id = Math.floor(Math.random() * 10000);
 | 
			
		||||
  toast.id = Math.floor(Math.random() * 10000);
 | 
			
		||||
 | 
			
		||||
  // Setup some sensible defaults for a toast.
 | 
			
		||||
  const defaults = {
 | 
			
		||||
    id,
 | 
			
		||||
    type: "info",
 | 
			
		||||
    id: toast.id,
 | 
			
		||||
    type: ToastType.Info,
 | 
			
		||||
    dismissible: true,
 | 
			
		||||
    timeout: 3000,
 | 
			
		||||
  };
 | 
			
		||||
@@ -19,9 +20,9 @@ export const addToast = (toast: ) => {
 | 
			
		||||
  toasts.update((all) => [{ ...defaults, ...toast }, ...all]);
 | 
			
		||||
 | 
			
		||||
  // If toast is dismissible, dismiss it after "timeout" amount of time.
 | 
			
		||||
  if (toast.timeout) setTimeout(() => dismissToast(id), toast.timeout);
 | 
			
		||||
  if (toast.timeout) setTimeout(() => dismissToast(toast.id), toast.timeout);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const dismissToast = (id) => {
 | 
			
		||||
export const dismissToast = (id: number) => {
 | 
			
		||||
  toasts.update((all) => all.filter((t) => t.id !== id));
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user