mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
feat(settings): replace single-spinner loading with skeleton rows on Thresholds and Sources
Replaces the centred `<Loader2 /> Loading` line in `<ThresholdsEditorSection>` and `<SourcesSection>` with a skeleton list whose row count matches the loaded UI (14 rows each — one per `METRIC_ORDER` entry and one per `SOURCE_PRIORITY_METRIC_KEYS` entry respectively). The skeleton rows reuse the live UI's container classes (`border-border space-y-3 rounded-lg border p-4` on Thresholds; `border-border bg-background/30 space-y-2 rounded-md border p-3` on Sources) so the post-fetch swap lands in place. The `<Skeleton>` primitive's `motion-reduce:animate-none` covers the reduced-motion preference. Combined with the `min-h` shell reserve from the previous commit, this removes the page-height jump that landed under the sticky sidebar when these two sections finished loading.
This commit is contained in:
1 parent
e060ab33a2
commit
9c8660a4e0
2 files changed
+70
-14
No files matched your search
@@ -40,6 +40,7 @@ import {
|
||||
} from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
@@ -314,10 +315,7 @@ export function SourcesSection() {
|
||||
</p>
|
||||
|
||||
{isLoading || !priority ? (
|
||||
<div className="text-muted-foreground flex items-center gap-2 text-sm">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
{t("common.loading")}
|
||||
</div>
|
||||
<SourcesSkeletonList />
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{SOURCE_PRIORITY_METRIC_KEYS.map((metric) => {
|
||||
@@ -564,3 +562,33 @@ export function SourcesSection() {
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skeleton placeholder rendered while `/api/auth/me/source-priority`
|
||||
* is in flight. Reserves one row per `SOURCE_PRIORITY_METRIC_KEYS`
|
||||
* entry at roughly the loaded height so the page does not jump when
|
||||
* the fetched ladder list swaps in. The pulsing animation honours
|
||||
* `prefers-reduced-motion` via Tailwind's `motion-reduce:animate-none`.
|
||||
*/
|
||||
function SourcesSkeletonList() {
|
||||
return (
|
||||
<div
|
||||
className="space-y-3"
|
||||
data-testid="sources-skeleton"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{SOURCE_PRIORITY_METRIC_KEYS.map((metric) => (
|
||||
<div
|
||||
key={metric}
|
||||
className="border-border bg-background/30 space-y-2 rounded-md border p-3"
|
||||
>
|
||||
<Skeleton className="h-4 w-40" />
|
||||
<div className="space-y-1">
|
||||
<Skeleton className="h-9 w-full rounded-md" />
|
||||
<Skeleton className="h-9 w-full rounded-md" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,15 +3,11 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
SlidersHorizontal,
|
||||
RotateCcw,
|
||||
Loader2,
|
||||
AlertTriangle,
|
||||
} from "lucide-react";
|
||||
import { SlidersHorizontal, RotateCcw, AlertTriangle } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useTranslations, useFormatters } from "@/lib/i18n/context";
|
||||
@@ -147,10 +143,7 @@ export function ThresholdsEditorSection({ id }: { id: string }) {
|
||||
</div>
|
||||
|
||||
{isLoading || !data ? (
|
||||
<div className="text-muted-foreground flex items-center gap-2 text-sm">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
{t("common.loading")}
|
||||
</div>
|
||||
<ThresholdsSkeletonList />
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{METRIC_ORDER.map((metric) => (
|
||||
@@ -170,6 +163,41 @@ export function ThresholdsEditorSection({ id }: { id: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skeleton placeholder rendered while `/api/user/thresholds` is in
|
||||
* flight. Reserves one row per `METRIC_ORDER` entry at roughly the
|
||||
* loaded height so the page does not jump when the fetched list
|
||||
* swaps in. The pulsing animation honours `prefers-reduced-motion`
|
||||
* via Tailwind's `motion-reduce:animate-none`.
|
||||
*/
|
||||
function ThresholdsSkeletonList() {
|
||||
return (
|
||||
<div
|
||||
className="space-y-3"
|
||||
data-testid="thresholds-skeleton"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{METRIC_ORDER.map((metric) => (
|
||||
<div
|
||||
key={metric}
|
||||
className="border-border space-y-3 rounded-lg border p-4"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="min-w-0 space-y-2">
|
||||
<Skeleton className="h-4 w-32" />
|
||||
<Skeleton className="h-3 w-48" />
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<Skeleton className="h-3 w-12" />
|
||||
<Skeleton className="h-5 w-9 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface MetricRowProps {
|
||||
metric: ThresholdMetric;
|
||||
effective: EffectiveRange | undefined;
|
||||
|
||||
Reference in new issue
Block a user