button-loading.svelte RawHistoryFolder
1<script lang="ts">
2 import { onDestroy } from 'svelte';
3
4 // Props with defaults:
5 // - text: button label when not loading (default "Envoyer")
6 // - loading: indicates if the button should show a spinner (default false)
7 // - spinnerChar: the character to use for the spinner animation (default '.')
8 let { onclick, disabled = $bindable(false), text = 'Envoyer', loading = $bindable(false), spinnerChar = $bindable('.') } = $props();
9
10 // Using $state for internal reactive variables.
11 let animatedDots = $state(''); // will hold the animated spinner text
12 let count = $state(1);
13 let isDecreasing = $state(true);
14 let intervalID = $state<number | null>(null);
15
16 // Effect that starts/stops the spinner based on loading state.
17 $effect(() => {
18 if (loading) {
19 console.debug("LoadingButton: Spinner started");
20 // Immediately update spinner text to show 3 dots and start decreasing
21 animatedDots = spinnerChar.repeat(3);
22 count = 3;
23 isDecreasing = true;
24
25 if (!intervalID) {
26 intervalID = window.setInterval(() => {
27 // Ping-pong logic: 3,2,1,2,3...
28 count = count + (isDecreasing ? -1 : 1);
29 if (count <= 1) {
30 count = 1;
31 isDecreasing = false;
32 } else if (count >= 3) {
33 count = 3;
34 isDecreasing = true;
35 }
36 animatedDots = spinnerChar.repeat(count);
37 console.debug("LoadingButton: Animated dots updated to", animatedDots);
38 }, 500);
39 }
40 } else {
41 console.debug("LoadingButton: Spinner stopped");
42 if (intervalID) {
43 clearInterval(intervalID);
44 intervalID = null;
45 }
46 // When not loading, display the default text.
47 animatedDots = text;
48 }
49 });
50
51 onDestroy(() => {
52 if (intervalID) {
53 console.debug("LoadingButton: onDestroy clearing interval");
54 clearInterval(intervalID);
55 }
56 });
57</script>
58
59<button onclick={onclick} disabled={disabled}>
60 {#if loading}
61 <span>{animatedDots}</span>
62 {:else}
63 <span>{text}</span>
64 {/if}
65</button>
66
67<style>
68 button {
69 padding: 0.5rem 1rem;
70 font-size: 1rem;
71 cursor: pointer;
72 }
73 button:disabled {
74 cursor: not-allowed;
75 opacity: 0.7;
76 }
77</style>