From 89391410daa96d99147c96bbd07172512354524a Mon Sep 17 00:00:00 2001 From: Richard R Date: Wed, 13 May 2026 23:17:47 -0600 Subject: [PATCH] refactor(ui): replace loading text with skeleton placeholders across panels Update multiple UI panels to use skeleton components instead of loading text or spinners. Standardize loading states in AudiobookExportModal, AdminFeaturesPanel, AdminProvidersPanel, DocumentSelectionModal, and SegmentsSidebar for improved visual consistency and user feedback. --- src/components/AudiobookExportModal.tsx | 52 +++++++++++++++++-- src/components/admin/AdminFeaturesPanel.tsx | 44 ++++++++++++++-- src/components/admin/AdminProvidersPanel.tsx | 27 +++++++++- .../documents/DocumentSelectionModal.tsx | 17 +++++- src/components/reader/SegmentsSidebar.tsx | 45 ++++++++++++++-- 5 files changed, 173 insertions(+), 12 deletions(-) diff --git a/src/components/AudiobookExportModal.tsx b/src/components/AudiobookExportModal.tsx index 70095db..f15828f 100644 --- a/src/components/AudiobookExportModal.tsx +++ b/src/components/AudiobookExportModal.tsx @@ -7,7 +7,6 @@ import { ProgressPopup } from '@/components/ProgressPopup'; import { ProgressCard } from '@/components/ProgressCard'; import { DownloadIcon, CheckCircleIcon, XCircleIcon, ClockIcon, ChevronUpDownIcon, RefreshIcon, DotsVerticalIcon } from '@/components/icons/Icons'; import { ConfirmDialog } from '@/components/ConfirmDialog'; -import { LoadingSpinner } from '@/components/Spinner'; import { useConfig } from '@/contexts/ConfigContext'; import { useTTS } from '@/contexts/TTSContext'; import { VoicesControlBase } from '@/components/player/VoicesControlBase'; @@ -472,9 +471,7 @@ export function AudiobookExportModal({ subtitle="Only leaving the document cancels generation." > {isLoadingExisting ? ( -
- -
+ ) : ( <>
@@ -937,3 +934,50 @@ export function AudiobookExportModal({ ); } + +function AudiobookSettingsSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ {Array.from({ length: 4 }).map((_, index) => ( +
+ ))} +
+
+
+ ); +} diff --git a/src/components/admin/AdminFeaturesPanel.tsx b/src/components/admin/AdminFeaturesPanel.tsx index 14c6e69..b1f5975 100644 --- a/src/components/admin/AdminFeaturesPanel.tsx +++ b/src/components/admin/AdminFeaturesPanel.tsx @@ -172,9 +172,7 @@ export function AdminFeaturesPanel() { if (!data) { return ( -
-

Fetching current values…

-
+ ); } @@ -340,6 +338,46 @@ export function AdminFeaturesPanel() { ); } +function AdminFeaturesSkeleton() { + return ( +
+
} + > +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
} + > +
+
+
+
+
+
+
+ ); +} + function SourceBadge({ source, dirty, diff --git a/src/components/admin/AdminProvidersPanel.tsx b/src/components/admin/AdminProvidersPanel.tsx index 4384d99..6a0663d 100644 --- a/src/components/admin/AdminProvidersPanel.tsx +++ b/src/components/admin/AdminProvidersPanel.tsx @@ -502,7 +502,7 @@ export function AdminProvidersPanel() {
{isLoading ? ( -

Loading…

+ ) : providers.length === 0 ? (

No shared providers configured yet.

) : ( @@ -547,3 +547,28 @@ export function AdminProvidersPanel() { ); } + +function ProvidersListSkeleton() { + const rows = Array.from({ length: 4 }); + return ( +
+ {rows.map((_, index) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+ ); +} diff --git a/src/components/documents/DocumentSelectionModal.tsx b/src/components/documents/DocumentSelectionModal.tsx index 4084a1e..666b5a1 100644 --- a/src/components/documents/DocumentSelectionModal.tsx +++ b/src/components/documents/DocumentSelectionModal.tsx @@ -164,7 +164,7 @@ export function DocumentSelectionModal({
{isLoading ? ( -
Loading documents...
+ ) : errorMessage ? (
{errorMessage}
) : files.length === 0 ? ( @@ -229,3 +229,18 @@ export function DocumentSelectionModal({ ); } + +function DocumentSelectionSkeleton() { + const rows = Array.from({ length: 9 }); + return ( +
+ {rows.map((_, index) => ( +
+
+
+
+
+ ))} +
+ ); +} diff --git a/src/components/reader/SegmentsSidebar.tsx b/src/components/reader/SegmentsSidebar.tsx index 5219932..e7a84f1 100644 --- a/src/components/reader/SegmentsSidebar.tsx +++ b/src/components/reader/SegmentsSidebar.tsx @@ -631,7 +631,7 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }: ) : null} ) : isManifestLoading ? ( - Loading… +
) : hasManifestError ? ( error ) : ( @@ -645,7 +645,7 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
{manifestErrorMessage}
)} {isManifestLoading && ( -
Loading segments…
+ )} {hasLoadedManifest && rowsToRender.length === 0 && (
@@ -787,13 +787,52 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }: )} {hasLoadedManifest && isLoadingMoreManifest && ( -
Loading more segments…
+ )}
); } +function SegmentsListSkeleton() { + return ( +
+
+
+ {Array.from({ length: 8 }).map((_, index) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+
+ ); +} + +function SegmentsListSkeletonRows() { + return ( +
+
+ {Array.from({ length: 3 }).map((_, index) => ( +
+ ))} +
+
+ ); +} + function SegmentMetadataPopover({ row }: { row: TTSSegmentRow }) { return (