|
1 | 1 | <script lang="ts"> |
2 | | - import { asset, resolve } from "$app/paths"; |
| 2 | + import { asset, resolve } from '$app/paths'; |
3 | 3 |
|
4 | 4 | type LogoProps = { |
5 | 5 | class?: string; |
6 | | - size?: 'sm' | 'md' | 'lg' | 'xl'; |
| 6 | + size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'; |
7 | 7 | text?: string; |
8 | 8 | showText?: boolean; |
| 9 | + logo?: string | null; |
| 10 | + logoDark?: string | null; |
| 11 | + }; |
| 12 | +
|
| 13 | + let { |
| 14 | + class: className = '', |
| 15 | + size = 'md', |
| 16 | + text = '', |
| 17 | + showText = true, |
| 18 | + logo, |
| 19 | + logoDark |
| 20 | + }: LogoProps = $props(); |
| 21 | +
|
| 22 | + // Probed in order when no explicit `logo` is configured via metadata. |
| 23 | + const DEFAULT_FORMATS = ['/logo.svg', '/logo.png', '/logo.jpg', '/logo.jpeg']; |
| 24 | +
|
| 25 | + function normalizePath(value: string | null | undefined): string | null { |
| 26 | + const trimmed = value?.trim(); |
| 27 | + if (!trimmed) return null; |
| 28 | + return trimmed.startsWith('/') ? trimmed : `/${trimmed}`; |
9 | 29 | } |
10 | 30 |
|
11 | | - let { class: className = '', size = 'md', text = '', showText = true }: LogoProps = $props(); |
| 31 | + const explicitLight = $derived(normalizePath(logo)); |
| 32 | + const darkPath = $derived(normalizePath(logoDark)); |
| 33 | +
|
| 34 | + let lightFallbackIndex = $state(0); |
| 35 | + let hasLight = $state(true); |
| 36 | + let hasDark = $state(true); |
12 | 37 |
|
13 | | - let hasLogo = $state(true); |
| 38 | + const lightSrc = $derived.by(() => { |
| 39 | + if (!hasLight) return null; |
| 40 | + if (explicitLight) return asset(explicitLight); |
| 41 | + if (lightFallbackIndex >= DEFAULT_FORMATS.length) return null; |
| 42 | + return asset(DEFAULT_FORMATS[lightFallbackIndex]); |
| 43 | + }); |
| 44 | + const darkSrc = $derived(darkPath && hasDark ? asset(darkPath) : null); |
14 | 45 |
|
15 | | - // Size classes for logo image |
16 | 46 | const sizeClasses = { |
| 47 | + xs: 'h-6', |
17 | 48 | sm: 'h-10', |
18 | 49 | md: 'h-14', |
19 | 50 | lg: 'h-18', |
|
22 | 53 |
|
23 | 54 | // Font size classes for logo text - matching MyST's responsive sizing |
24 | 55 | const textSizeClasses = { |
| 56 | + xs: 'text-sm sm:text-md tracking-tight', |
25 | 57 | sm: 'text-md sm:text-lg tracking-tight', |
26 | 58 | md: 'text-md sm:text-xl tracking-tight', |
27 | 59 | lg: 'text-lg sm:text-2xl tracking-tight', |
28 | 60 | xl: 'text-xl sm:text-3xl tracking-tight' |
29 | 61 | }; |
30 | 62 |
|
31 | | - // Handle fallback to different image formats |
32 | | - function handleImageError(event: Event) { |
33 | | - const img = event.target as HTMLImageElement; |
34 | | - const currentSrc = img.src; |
35 | | -
|
36 | | - // Try formats in priority order: svg -> png -> jpg -> jpeg |
37 | | - if (currentSrc.endsWith('/logo.svg')) { |
38 | | - img.src = asset('/logo.png'); |
39 | | - } else if (currentSrc.endsWith('/logo.png')) { |
40 | | - img.src = asset('/logo.jpg'); |
41 | | - } else if (currentSrc.endsWith('/logo.jpg')) { |
42 | | - img.src = asset('/logo.jpeg'); |
| 63 | + function handleLightError() { |
| 64 | + if (!explicitLight && lightFallbackIndex < DEFAULT_FORMATS.length - 1) { |
| 65 | + lightFallbackIndex += 1; |
43 | 66 | } else { |
44 | | - // All formats failed, hide the logo |
45 | | - hasLogo = false; |
| 67 | + hasLight = false; |
46 | 68 | } |
47 | 69 | } |
| 70 | +
|
| 71 | + function handleDarkError() { |
| 72 | + hasDark = false; |
| 73 | + } |
48 | 74 | </script> |
49 | 75 |
|
50 | | -{#if hasLogo} |
| 76 | +{#if lightSrc || darkSrc || (showText && text)} |
51 | 77 | <a href={resolve('/')} class="flex items-center gap-3 {className}"> |
52 | | - <img |
53 | | - src={asset('/logo.svg')} |
54 | | - alt="Logo" |
55 | | - class="{sizeClasses[size]} w-auto" |
56 | | - onerror={handleImageError} |
57 | | - /> |
| 78 | + {#if lightSrc} |
| 79 | + <img |
| 80 | + src={lightSrc} |
| 81 | + alt="Logo" |
| 82 | + class="{sizeClasses[size]} w-auto {darkSrc ? 'dark:hidden' : ''}" |
| 83 | + onerror={handleLightError} |
| 84 | + /> |
| 85 | + {/if} |
| 86 | + {#if darkSrc} |
| 87 | + <img |
| 88 | + src={darkSrc} |
| 89 | + alt="Logo" |
| 90 | + class="{sizeClasses[size]} w-auto {lightSrc ? 'hidden dark:block' : ''}" |
| 91 | + onerror={handleDarkError} |
| 92 | + /> |
| 93 | + {/if} |
58 | 94 | {#if showText && text} |
59 | | - <span class="{textSizeClasses[size]}"> |
| 95 | + <span class={textSizeClasses[size]}> |
60 | 96 | {text} |
61 | 97 | </span> |
62 | 98 | {/if} |
|
0 commit comments