diff --git a/frontend/src/features/admin/storage/StoragePanel.tsx b/frontend/src/features/admin/storage/StoragePanel.tsx
index d56003a..8872c84 100644
--- a/frontend/src/features/admin/storage/StoragePanel.tsx
+++ b/frontend/src/features/admin/storage/StoragePanel.tsx
@@ -7,6 +7,7 @@ import { qk } from '@/shared/api/query-keys'
import { cn } from '@/shared/lib/cn'
import { Button } from '@/shared/ui/button'
import { Card, CardContent } from '@/shared/ui/card'
+import { StoragePanelSkeleton } from './StoragePanelSkeleton'
import { getStorageStats } from './api'
import { formatBytes, percentOf } from './format'
@@ -68,7 +69,7 @@ export function StoragePanel() {
}
}
- if (isLoading || !data) return
{t('common.loading')}
+ if (isLoading || !data) return
const { volumeTotalBytes: total, volumeFreeBytes: free, storageBytes: storage } = data
const volumeKnown = total > 0
diff --git a/frontend/src/features/admin/storage/StoragePanelSkeleton.tsx b/frontend/src/features/admin/storage/StoragePanelSkeleton.tsx
new file mode 100644
index 0000000..6193aed
--- /dev/null
+++ b/frontend/src/features/admin/storage/StoragePanelSkeleton.tsx
@@ -0,0 +1,71 @@
+import { useTranslation } from 'react-i18next'
+import { Card, CardContent } from '@/shared/ui/card'
+import { Skeleton } from '@/shared/ui/skeleton'
+
+/** Строк в составе хранилища заранее не знаем — берём типичное число, чтобы не пустовала карточка. */
+const AREA_ROWS = 5
+
+/**
+ * Скелетон панели хранилища: подсчёт размеров идёт по диску и занимает секунды. Повторяет вёрстку
+ * {@link StoragePanel} блок в блок, чтобы после загрузки ничего не переехало.
+ */
+export function StoragePanelSkeleton() {
+ const { t } = useTranslation()
+
+ return (
+
+
+
+
+
+
+
+
+ {Array.from({ length: 4 }, (_, i) => (
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {Array.from({ length: AREA_ROWS }, (_, i) => (
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/frontend/src/shared/ui/skeleton.tsx b/frontend/src/shared/ui/skeleton.tsx
new file mode 100644
index 0000000..63584b4
--- /dev/null
+++ b/frontend/src/shared/ui/skeleton.tsx
@@ -0,0 +1,19 @@
+import { type HTMLAttributes, forwardRef } from 'react'
+import { cn } from '@/shared/lib/cn'
+
+/**
+ * Заглушка блока на время загрузки. Только форма — размеры задаёт вызывающий код, чтобы скелетон
+ * повторял реальную вёрстку и страница не прыгала при появлении данных.
+ */
+export const Skeleton = forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ ),
+)
+Skeleton.displayName = 'Skeleton'