diff --git a/src/frontend/src/components/Skeleton.tsx b/src/frontend/src/components/Skeleton.tsx new file mode 100644 index 0000000..2e2eefa --- /dev/null +++ b/src/frontend/src/components/Skeleton.tsx @@ -0,0 +1,111 @@ +/** + * Skeleton loading placeholder components. + * Uses the .skeleton CSS class for shimmer animation. + */ + +interface SkeletonProps { + width?: string | number; + height?: string | number; + borderRadius?: string; + style?: React.CSSProperties; +} + +/** Generic skeleton block. */ +export function Skeleton({ width = '100%', height = 16, borderRadius, style }: SkeletonProps) { + return ( +
+ ); +} + +/** Skeleton for a table row. */ +export function SkeletonRow({ columns = 6 }: { columns?: number }) { + return ( + + {Array.from({ length: columns }).map((_, i) => ( + + + + ))} + + ); +} + +/** Skeleton for the full content table. */ +export function SkeletonTable({ rows = 8, columns = 6 }: { rows?: number; columns?: number }) { + return ( +
+ + + {Array.from({ length: rows }).map((_, i) => ( + + ))} + +
+
+ ); +} + +/** Skeleton for the channel detail header. */ +export function SkeletonChannelHeader() { + return ( +
+ +
+ + +
+ + + +
+
+
+ ); +} + +/** Skeleton for the channels list page. */ +export function SkeletonChannelsList({ rows = 4 }: { rows?: number }) { + return ( +
+
+ +
+ + +
+
+
+ +
+
+ ); +} diff --git a/src/frontend/src/pages/ChannelDetail.tsx b/src/frontend/src/pages/ChannelDetail.tsx index 406e3e1..9d50cd9 100644 --- a/src/frontend/src/pages/ChannelDetail.tsx +++ b/src/frontend/src/pages/ChannelDetail.tsx @@ -28,6 +28,7 @@ import { PlatformBadge } from '../components/PlatformBadge'; import { StatusBadge } from '../components/StatusBadge'; import { QualityLabel } from '../components/QualityLabel'; import { DownloadProgressBar } from '../components/DownloadProgressBar'; +import { SkeletonChannelHeader, SkeletonTable } from '../components/Skeleton'; import { Pagination } from '../components/Pagination'; import { Modal } from '../components/Modal'; import { useDownloadProgress } from '../contexts/DownloadProgressContext'; @@ -667,9 +668,11 @@ export function ChannelDetail() { if (channelLoading) { return ( -
- - Loading channel... +
+ +
+ +
); } @@ -735,7 +738,7 @@ export function ChannelDetail() { gap: 'var(--space-5)', padding: 'var(--space-5)', backgroundColor: 'var(--bg-card)', - borderRadius: 'var(--radius-lg)', + borderRadius: 'var(--radius-xl)', border: '1px solid var(--border)', marginBottom: 'var(--space-6)', alignItems: 'flex-start', @@ -1030,7 +1033,7 @@ export function ChannelDetail() {
) : null} {contentLoading ? ( -
- - Loading content... -
+ ) : hasPlaylistGroups ? ( renderPlaylistGroups(playlistGroups!) ) : ( @@ -1196,7 +1196,7 @@ export function ChannelDetail() { padding: 'var(--space-3) var(--space-5)', backgroundColor: 'var(--bg-card)', border: '1px solid var(--border)', - borderRadius: 'var(--radius-lg)', + borderRadius: 'var(--radius-xl)', boxShadow: 'var(--shadow-lg)', }} > diff --git a/src/frontend/src/pages/Channels.tsx b/src/frontend/src/pages/Channels.tsx index 585f91c..db1bab8 100644 --- a/src/frontend/src/pages/Channels.tsx +++ b/src/frontend/src/pages/Channels.tsx @@ -8,6 +8,7 @@ import { PlatformBadge } from '../components/PlatformBadge'; import { StatusBadge } from '../components/StatusBadge'; import { ProgressBar } from '../components/ProgressBar'; import { AddChannelModal } from '../components/AddChannelModal'; +import { SkeletonChannelsList } from '../components/Skeleton'; import type { ChannelWithCounts } from '@shared/types/api'; // ── Helpers ── @@ -184,12 +185,7 @@ export function Channels() { ); if (isLoading) { - return ( -
- - Loading channels... -
- ); + return ; } if (error) { @@ -339,7 +335,7 @@ export function Channels() {