UX: Skeleton-Ladebildschirm (Seitenstruktur statt Text), shadcn Skeleton
Build & Deploy / deploy (push) Successful in 24s
Build & Deploy / deploy (push) Successful in 24s
This commit is contained in:
+2
-8
@@ -2,6 +2,7 @@ import { Routes, Route, Navigate } from 'react-router'
|
|||||||
import { Component, Suspense, lazy, type ReactNode } from 'react'
|
import { Component, Suspense, lazy, type ReactNode } from 'react'
|
||||||
import { StoreProvider, useStore } from './store/store'
|
import { StoreProvider, useStore } from './store/store'
|
||||||
import AppLayout from './components/AppLayout'
|
import AppLayout from './components/AppLayout'
|
||||||
|
import LadeSkeleton from './components/LadeSkeleton'
|
||||||
import Login from './pages/Login'
|
import Login from './pages/Login'
|
||||||
|
|
||||||
// Seiten werden beim ersten Aufruf nachgeladen (Code-Splitting) –
|
// Seiten werden beim ersten Aufruf nachgeladen (Code-Splitting) –
|
||||||
@@ -51,14 +52,7 @@ class FehlerGrenze extends Component<{ children: ReactNode }, { fehler: boolean
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Ladebildschirm() {
|
function Ladebildschirm() {
|
||||||
return (
|
return <LadeSkeleton />
|
||||||
<div className="flex min-h-screen items-center justify-center bg-[#22333b]">
|
|
||||||
<div className="text-center text-slate-300">
|
|
||||||
<div className="text-lg font-semibold text-white">Hinterstein Immobilien</div>
|
|
||||||
<div className="mt-1 text-sm">Mieterportal wird geladen …</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function RequireAuth({ children }: { children: React.ReactElement }) {
|
function RequireAuth({ children }: { children: React.ReactElement }) {
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { Skeleton } from './ui/skeleton'
|
||||||
|
import logo from '../assets/logo.png'
|
||||||
|
|
||||||
|
/** Skeleton-Ladebildschirm: zeigt statt Text die ungefähre Seitenstruktur
|
||||||
|
* (Kopf, Suche, Listenzeilen) mit Puls-Animation – wirkt schneller und
|
||||||
|
* orientiert den Nutzer, während die Daten geladen werden. */
|
||||||
|
export default function LadeSkeleton() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen bg-slate-50 dark:bg-slate-950" aria-busy="true" aria-label="Mieterportal wird geladen">
|
||||||
|
{/* Desktop-Sidebar */}
|
||||||
|
<aside className="hidden w-64 flex-col gap-4 border-r bg-[#22333b] p-4 lg:flex">
|
||||||
|
<div className="flex items-center gap-3 px-2 py-2">
|
||||||
|
<img src={logo} alt="" className="h-9 w-9 rounded-full bg-white object-contain p-0.5" />
|
||||||
|
<Skeleton className="h-4 w-32 bg-white/20" />
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 space-y-2.5 px-2">
|
||||||
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
|
<Skeleton key={i} className="h-8 w-full bg-white/10" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div className="flex min-w-0 flex-1 flex-col">
|
||||||
|
{/* Mobile top bar */}
|
||||||
|
<header className="flex items-center gap-3 border-b bg-white px-4 py-3 dark:bg-slate-900 lg:hidden">
|
||||||
|
<img src={logo} alt="" className="h-9 w-9 rounded-full bg-white object-contain p-0.5 ring-1 ring-slate-200" />
|
||||||
|
<Skeleton className="h-4 w-40" />
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className="min-w-0 flex-1 space-y-6 p-4 lg:p-8">
|
||||||
|
{/* Seitenkopf */}
|
||||||
|
<div className="flex flex-wrap items-end justify-between gap-3">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Skeleton className="h-7 w-56" />
|
||||||
|
<Skeleton className="h-4 w-72 max-w-full" />
|
||||||
|
</div>
|
||||||
|
<Skeleton className="h-9 w-32" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filterzeile */}
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Skeleton className="h-9 w-56" />
|
||||||
|
<Skeleton className="h-9 w-36" />
|
||||||
|
<Skeleton className="h-9 w-32" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Listenzeilen */}
|
||||||
|
<div className="overflow-hidden rounded-2xl border bg-white dark:border-slate-700 dark:bg-slate-900">
|
||||||
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="flex items-center gap-4 border-b px-4 py-3.5 last:border-0 dark:border-slate-800"
|
||||||
|
>
|
||||||
|
<Skeleton className="h-8 w-8 shrink-0 rounded-xl" />
|
||||||
|
<div className="min-w-0 flex-1 space-y-2">
|
||||||
|
<Skeleton className="h-4 w-1/3" />
|
||||||
|
<Skeleton className="h-3 w-1/2" />
|
||||||
|
</div>
|
||||||
|
<Skeleton className="hidden h-4 w-24 sm:block" />
|
||||||
|
<Skeleton className="h-8 w-8" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="skeleton"
|
data-slot="skeleton"
|
||||||
className={cn("bg-accent animate-pulse rounded-md", className)}
|
className={cn("bg-slate-200/70 dark:bg-slate-800/60 animate-pulse rounded-md", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user