my website at ewancroft.uk
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add navigation progress bar for page transitions

+49 -5
+3
src/lib/components/layout/index.ts
··· 6 6 // DynamicLinks stays local — it uses the DID-bound service wrapper. 7 7 export { default as DynamicLinks } from './main/DynamicLinks.svelte'; 8 8 9 + // Navigation progress bar for page transitions. 10 + export { default as NavigationProgress } from './main/NavigationProgress.svelte'; 11 + 9 12 // These are shared and prop-only — re-export from the package. 10 13 export { ThemeToggle, WolfToggle, ScrollToTop } from '@ewanc26/ui'; 11 14 export { LinkCard, ProfileCard } from '@ewanc26/ui';
+43
src/lib/components/layout/main/NavigationProgress.svelte
··· 1 + <script lang="ts"> 2 + import { navigating } from '$app/stores'; 3 + 4 + let isLoading = $state(false); 5 + let progress = $state(0); 6 + let showBar = $state(false); 7 + let fadeOut = $state(false); 8 + 9 + $effect(() => { 10 + if ($navigating) { 11 + isLoading = true; 12 + showBar = true; 13 + fadeOut = false; 14 + // Start at a small width, then advance 15 + progress = 30; 16 + requestAnimationFrame(() => { 17 + progress = 80; 18 + }); 19 + } else if (isLoading) { 20 + // Navigation complete — fill to 100% 21 + progress = 100; 22 + isLoading = false; 23 + // Fade out after the bar completes 24 + fadeOut = true; 25 + setTimeout(() => { 26 + showBar = false; 27 + progress = 0; 28 + fadeOut = false; 29 + }, 300); 30 + } 31 + }); 32 + </script> 33 + 34 + {#if showBar} 35 + <div 36 + class="fixed top-0 left-0 z-[60] h-0.5 bg-primary-500 transition-[width] duration-300 ease-out dark:bg-primary-400" 37 + class:opacity-0={fadeOut} 38 + class:transition-opacity={fadeOut} 39 + style="width: {progress}%" 40 + role="progressbar" 41 + aria-label="Loading page" 42 + ></div> 43 + {/if}
+1
src/lib/components/layout/main/index.ts
··· 1 1 // DynamicLinks uses the app's DID-bound fetchLinks wrapper — keep it local. 2 2 export { default as DynamicLinks } from './DynamicLinks.svelte'; 3 3 export { default as ScrollToTop } from './ScrollToTop.svelte'; 4 + export { default as NavigationProgress } from './NavigationProgress.svelte';
+2 -5
src/routes/+layout.svelte
··· 1 1 <script lang="ts"> 2 2 import '../app.css'; 3 - import { Header, Footer, ScrollToTop } from '$lib/components/layout'; 3 + import { Header, Footer, ScrollToTop, NavigationProgress } from '$lib/components/layout'; 4 4 import HappyMacEasterEgg from '$lib/components/HappyMacEasterEgg.svelte'; 5 5 import { type SiteMetadata } from '$lib/helper/siteMeta'; 6 6 import type { ProfileData, SiteInfoData } from '$lib/services/atproto'; ··· 42 42 console.error('[App] Unhandled promise rejection:', event.reason); 43 43 }; 44 44 }); 45 - 46 - 47 45 48 46 // Compute fediverse:creator from AP instance and username 49 47 const fediverseCreator = $derived.by(() => { ··· 85 83 <link rel="manifest" href="/favicon/site.webmanifest" /> 86 84 </svelte:head> 87 85 88 - 89 - 90 86 <div 91 87 class="flex min-h-screen flex-col overflow-x-hidden bg-canvas-50 text-ink-900 dark:bg-canvas-950 dark:text-ink-50" 92 88 > 89 + <NavigationProgress /> 93 90 <Header profile={data.profile} /> 94 91 95 92 <main id="main-content" class="container mx-auto grow px-4 py-8" tabindex="-1">