feat: add banner component

This commit is contained in:
madkarmaa
2025-11-12 10:42:52 +01:00
parent 1f5122cc49
commit baea96a4be
3 changed files with 112 additions and 0 deletions

View File

@@ -34,6 +34,7 @@
},
"dependencies": {
"@iconify-json/material-symbols": "^1.2.45",
"@madkarma/svelte-storable": "^1.0.4",
"unplugin-icons": "^22.5.0"
}
}

24
pnpm-lock.yaml generated
View File

@@ -11,6 +11,9 @@ importers:
'@iconify-json/material-symbols':
specifier: ^1.2.45
version: 1.2.45
'@madkarma/svelte-storable':
specifier: ^1.0.4
version: 1.0.4(svelte@5.43.5)(typescript@5.9.3)
unplugin-icons:
specifier: ^22.5.0
version: 22.5.0(svelte@5.43.5)
@@ -319,6 +322,16 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@madkarma/svelte-storable@1.0.4':
resolution: {integrity: sha512-E1vyfs89MzEjT4JJwUH23qKuwia50NP48zRXPLuOtEtIqXl46AW0ZbwpBFXGoSaHodhFOrbYROSLQ4iZCjbJag==}
peerDependencies:
svelte: ^5.0.0
'@madkarma/ts-utils@1.1.2':
resolution: {integrity: sha512-iQqKMtcd0QXSHzHm0L0VNHcUyAuYyd9W05F9fG/pvdB3YZOQzfN85v6pFUH5BdjbjhFGOIs/JwmXBEu290YRww==}
peerDependencies:
typescript: ^5.9.3
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -1452,6 +1465,17 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@madkarma/svelte-storable@1.0.4(svelte@5.43.5)(typescript@5.9.3)':
dependencies:
'@madkarma/ts-utils': 1.1.2(typescript@5.9.3)
svelte: 5.43.5
transitivePeerDependencies:
- typescript
'@madkarma/ts-utils@1.1.2(typescript@5.9.3)':
dependencies:
typescript: 5.9.3
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5

View File

@@ -0,0 +1,87 @@
<script lang="ts">
import { onMount } from 'svelte';
import { slide } from 'svelte/transition';
import { cubicInOut } from 'svelte/easing';
import storable from '@madkarma/svelte-storable';
import type { WithChildren } from '$types';
import Info from 'virtual:icons/material-symbols/info-outline';
import Warning from 'virtual:icons/material-symbols/warning-outline';
import Error from 'virtual:icons/material-symbols/error-outline';
import Close from 'virtual:icons/material-symbols/close';
const readBannerIds = storable<string[]>('read_banner_ids', []);
type Props = {
type: 'info' | 'warning' | 'error';
id: string;
permanent?: boolean;
closed?: boolean;
} & WithChildren;
let {
type,
children,
id,
permanent = false,
closed = $bindable($readBannerIds.includes(id))
}: Props = $props();
const Icon = type === 'info' ? Info : type === 'warning' ? Warning : Error;
let visible = $state(false);
onMount(() => {
if (closed) return;
visible = true;
});
</script>
{#if visible}
<div class="banner {type}" {id} transition:slide={{ duration: 200, easing: cubicInOut }}>
<Icon />
<div class="content">
{@render children()}
</div>
{#if !permanent}
<button
class="close"
type="button"
onclick={() => {
closed = true;
visible = false;
$readBannerIds = [...$readBannerIds, id];
}}
>
<Close />
</button>
{/if}
</div>
{/if}
<style>
.banner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1rem 2rem;
position: sticky;
top: 0;
left: 0;
z-index: 9999;
&.info {
}
&.warning {
}
&.error {
}
}
.content {
flex: 1;
}
</style>