♻️ refactor: centralize AppLoadingSkeleton in shared-ui

Add configurable AppLoadingSkeleton component to @manacore/shared-ui
with multiple layout presets: list, tasks, sidebar, centered, minimal.

Migrate 3 apps to use the shared component:
- contacts: uses default 'list' layout
- todo: uses 'tasks' layout
- questions: uses 'sidebar' layout

Apps with highly specific layouts (calendar, clock) retain their
local implementations for now.
This commit is contained in:
Till-JS 2026-01-29 15:24:29 +01:00
parent cdac341882
commit 8804ab77a2
13 changed files with 494 additions and 306 deletions

View file

@ -1,117 +0,0 @@
<script lang="ts">
/**
* AppLoadingSkeleton - Full page loading skeleton for initial app load
* Shows a minimal skeleton layout while auth is being checked
*/
import { SkeletonBox } from '@manacore/shared-ui';
</script>
<div class="app-loading-skeleton" role="status" aria-label="App wird geladen...">
<!-- Header placeholder -->
<div class="header-skeleton">
<SkeletonBox width="120px" height="32px" borderRadius="8px" />
<div class="header-nav">
<SkeletonBox width="80px" height="32px" borderRadius="16px" />
<SkeletonBox width="80px" height="32px" borderRadius="16px" />
<SkeletonBox width="80px" height="32px" borderRadius="16px" />
</div>
<SkeletonBox width="36px" height="36px" borderRadius="50%" />
</div>
<!-- Content placeholder -->
<div class="content-skeleton">
<!-- Page title -->
<div class="title-row">
<SkeletonBox width="200px" height="32px" />
<SkeletonBox width="120px" height="40px" borderRadius="8px" />
</div>
<!-- Search bar -->
<SkeletonBox width="100%" height="48px" borderRadius="12px" />
<!-- List items -->
<div class="list-skeleton">
{#each Array(5) as _, i}
<div class="list-item" style="opacity: {Math.max(0.3, 1 - i * 0.15)};">
<SkeletonBox width="48px" height="48px" borderRadius="50%" />
<div class="item-content">
<SkeletonBox width="60%" height="18px" />
<SkeletonBox width="40%" height="14px" />
</div>
</div>
{/each}
</div>
</div>
</div>
<style>
.app-loading-skeleton {
min-height: 100vh;
background: hsl(var(--background));
}
.header-skeleton {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 2rem;
border-bottom: 1px solid hsl(var(--border));
}
.header-nav {
display: flex;
gap: 0.5rem;
}
.content-skeleton {
max-width: 80rem;
margin: 0 auto;
padding: 2rem;
}
.title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.5rem;
}
.list-skeleton {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-top: 1.5rem;
}
.list-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 12px;
}
.item-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
@media (max-width: 768px) {
.header-nav {
display: none;
}
.header-skeleton {
padding: 1rem;
}
.content-skeleton {
padding: 1rem;
}
}
</style>

View file

@ -22,8 +22,8 @@ export { default as TagGridSkeleton } from './TagGridSkeleton.svelte';
export { default as DuplicateGroupSkeleton } from './DuplicateGroupSkeleton.svelte';
export { default as DuplicateListSkeleton } from './DuplicateListSkeleton.svelte';
// App Loading Skeleton
export { default as AppLoadingSkeleton } from './AppLoadingSkeleton.svelte';
// App Loading Skeleton (from shared-ui)
export { AppLoadingSkeleton } from '@manacore/shared-ui';
// Import Preview Skeleton
export { default as ImportPreviewSkeleton } from './ImportPreviewSkeleton.svelte';

View file

@ -1,47 +0,0 @@
<div class="flex min-h-screen animate-pulse">
<!-- Sidebar -->
<aside class="w-64 border-r border-border bg-card">
<!-- Header -->
<div class="flex h-16 items-center border-b border-border px-4">
<div class="h-6 w-28 rounded bg-muted"></div>
</div>
<!-- New Question Button -->
<div class="p-4">
<div class="h-10 w-full rounded-lg bg-muted"></div>
</div>
<!-- Navigation -->
<nav class="space-y-1 px-2">
<div class="h-10 w-full rounded-lg bg-muted"></div>
<div class="my-4 px-3">
<div class="h-3 w-20 rounded bg-muted"></div>
</div>
{#each Array(4) as _}
<div class="h-10 w-full rounded-lg bg-muted"></div>
{/each}
</nav>
</aside>
<!-- Main Content -->
<main class="flex-1 p-6">
<div class="mb-6">
<div class="h-8 w-48 rounded bg-muted"></div>
<div class="mt-2 h-4 w-32 rounded bg-muted"></div>
</div>
<div class="mb-6 flex gap-4">
<div class="h-10 flex-1 rounded-lg bg-muted"></div>
<div class="h-10 w-32 rounded-lg bg-muted"></div>
<div class="h-10 w-24 rounded-lg bg-muted"></div>
</div>
<div class="space-y-3">
{#each Array(5) as _}
<div class="h-24 rounded-xl bg-muted"></div>
{/each}
</div>
</main>
</div>

View file

@ -1,3 +1,5 @@
export { default as QuestionSkeleton } from './QuestionSkeleton.svelte';
export { default as QuestionDetailSkeleton } from './QuestionDetailSkeleton.svelte';
export { default as AppLoadingSkeleton } from './AppLoadingSkeleton.svelte';
// App Loading Skeleton (from shared-ui with 'sidebar' layout)
export { AppLoadingSkeleton } from '@manacore/shared-ui';

View file

@ -28,7 +28,7 @@
</script>
{#if !appReady}
<AppLoadingSkeleton />
<AppLoadingSkeleton layout="sidebar" />
{:else}
<div class="min-h-screen bg-background text-foreground">
{@render children()}

View file

@ -1,130 +0,0 @@
<script lang="ts">
/**
* AppLoadingSkeleton - Full page loading skeleton for initial app load
* Shows a minimal skeleton layout while auth is being checked
*/
import { SkeletonBox } from '@manacore/shared-ui';
</script>
<div class="app-loading-skeleton" role="status" aria-label="App wird geladen...">
<!-- Header placeholder -->
<div class="header-skeleton">
<SkeletonBox width="140px" height="32px" borderRadius="8px" />
<div class="header-nav">
<SkeletonBox width="36px" height="36px" borderRadius="50%" />
</div>
</div>
<!-- Content placeholder -->
<div class="content-skeleton">
<!-- Page title -->
<div class="title-row">
<SkeletonBox width="180px" height="28px" />
</div>
<SkeletonBox width="220px" height="16px" />
<!-- Quick add bar -->
<div class="quick-add-skeleton">
<SkeletonBox width="100%" height="52px" borderRadius="12px" />
</div>
<!-- Task sections -->
<div class="sections-skeleton">
<!-- Section header -->
<div class="section-header">
<SkeletonBox width="100px" height="20px" />
<SkeletonBox width="28px" height="28px" borderRadius="50%" />
</div>
<!-- Task items -->
<div class="task-list">
{#each Array(4) as _, i}
<div class="task-item" style="opacity: {Math.max(0.3, 1 - i * 0.18)};">
<SkeletonBox width="22px" height="22px" borderRadius="6px" />
<div class="task-content">
<SkeletonBox width="{70 - i * 8}%" height="18px" />
<SkeletonBox width="{40 + i * 5}%" height="14px" />
</div>
<SkeletonBox width="24px" height="24px" borderRadius="4px" />
</div>
{/each}
</div>
</div>
</div>
</div>
<style>
.app-loading-skeleton {
min-height: 100vh;
background: hsl(var(--background));
}
.header-skeleton {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.5rem;
border-bottom: 1px solid hsl(var(--border));
}
.header-nav {
display: flex;
gap: 0.5rem;
}
.content-skeleton {
max-width: 48rem;
margin: 0 auto;
padding: 1.5rem 1rem;
}
.title-row {
margin-bottom: 0.5rem;
}
.quick-add-skeleton {
margin: 1.5rem 0;
}
.sections-skeleton {
margin-top: 1.5rem;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 0;
margin-bottom: 0.5rem;
}
.task-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.task-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.875rem 1rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 12px;
}
.task-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.375rem;
}
@media (max-width: 768px) {
.content-skeleton {
padding: 1rem;
}
}
</style>

View file

@ -5,8 +5,8 @@
* Built on top of @manacore/shared-ui skeleton primitives.
*/
// App Loading Skeleton
export { default as AppLoadingSkeleton } from './AppLoadingSkeleton.svelte';
// App Loading Skeleton (from shared-ui with 'tasks' layout)
export { AppLoadingSkeleton } from '@manacore/shared-ui';
// Task List Skeletons
export { default as TaskItemSkeleton } from './TaskItemSkeleton.svelte';

View file

@ -24,7 +24,7 @@
</script>
{#if !appReady}
<AppLoadingSkeleton />
<AppLoadingSkeleton layout="tasks" listItemCount={4} />
{:else}
<div class="min-h-screen bg-background text-foreground">
{@render children()}