feat: migrate to apache echarts (#4)
Co-authored-by: terraputix <terraputix@mailbox.org> Reviewed-on: useweb/ombrella#4
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
<!--
|
||||
ChartContainer.svelte — Consistent chart layout wrapper
|
||||
|
||||
Provides a container with:
|
||||
- Consistent padding and spacing
|
||||
- Loading overlay with spinner
|
||||
- Fade transitions
|
||||
- Responsive min-height calculation
|
||||
- Slot for chart content
|
||||
|
||||
Usage:
|
||||
<ChartContainer loading={!chartsReady} chartCount={3}>
|
||||
{#each charts as chart}
|
||||
<EChart option={chart.option} />
|
||||
{/each}
|
||||
</ChartContainer>
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
// ─── Props ──────────────────────────────────────────────────────────────────
|
||||
|
||||
interface Props {
|
||||
/** Whether the charts are still loading */
|
||||
loading?: boolean;
|
||||
/** Number of charts being rendered (used for min-height calculation) */
|
||||
chartCount?: number;
|
||||
/** Height per individual chart in pixels (default: 300) */
|
||||
chartHeight?: number;
|
||||
/** Extra vertical padding in pixels added to the total min-height (default: 2) */
|
||||
extraPadding?: number;
|
||||
/** Optional CSS class for the outer wrapper */
|
||||
class?: string;
|
||||
/** Slot content (charts go here) */
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
loading = true,
|
||||
chartCount = 1,
|
||||
chartHeight = 300,
|
||||
extraPadding = 2,
|
||||
class: className = '',
|
||||
children
|
||||
}: Props = $props();
|
||||
|
||||
// ─── Computed ───────────────────────────────────────────────────────────────
|
||||
|
||||
let minHeight = $derived(chartHeight * chartCount + extraPadding);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="chart-container relative {className}"
|
||||
style:min-height="{minHeight}px"
|
||||
>
|
||||
<!-- Chart content area -->
|
||||
<div
|
||||
class="chart-content"
|
||||
in:fade={{ duration: 300 }}
|
||||
out:fade={{ duration: 300 }}
|
||||
>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Loading overlay -->
|
||||
<div
|
||||
class="loading-overlay absolute inset-0 z-30 flex items-center justify-center rounded-lg bg-accent transition-opacity duration-300"
|
||||
class:pointer-events-none={!loading}
|
||||
class:opacity-0={!loading}
|
||||
class:opacity-100={loading}
|
||||
>
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<svg
|
||||
class="lucide lucide-loader-circle animate-spin text-muted-foreground"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
||||
</svg>
|
||||
<span class="sr-only">Loading charts...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.chart-container {
|
||||
/* Negative horizontal margin on mobile to use full width, reset on md+ */
|
||||
margin-left: -1.5rem;
|
||||
margin-right: -1.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.chart-container {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Smooth transition for the loading overlay */
|
||||
.loading-overlay {
|
||||
will-change: opacity;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user