diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx index 928edefc2..b558a7f49 100644 --- a/apps/web/src/app/[locale]/page.tsx +++ b/apps/web/src/app/[locale]/page.tsx @@ -25,6 +25,7 @@ import { OpenClawPanel } from '@/components/ai/openclaw-panel' import { HostGrid, type HostInfo, type HostService } from '@/components/infra/host-grid' import { AppLayout } from '@/components/layout' import { PageTabs, type TabConfig } from '@/components/layout/page-tabs' +import { LobsterLoading } from '@/components/shared/lobster-loading' const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? '' @@ -49,7 +50,7 @@ function AlertsAndApprovalsTab() { }).finally(() => setLoading(false)) }, []) - if (loading) return
{tc('loading')}
+ if (loading) return return (
@@ -151,7 +152,7 @@ function DispositionTab() { .finally(() => setLoading(false)) }, []) - if (loading) return
{tc('loading')}
+ if (loading) return const s = data?.summary if (!s || s.total === 0) return ( @@ -906,9 +907,7 @@ export default function Home({ params }: { params: { locale: string } }) {
{isIncidentsLoading ? ( -
- {tCommon('loading')} -
+ ) : incidentsError ? (
{incidentsError}
) : (incidents?.length ?? 0) === 0 ? ( diff --git a/apps/web/src/components/layout/page-tabs.tsx b/apps/web/src/components/layout/page-tabs.tsx index 43b6bb4ce..acfdcb6b5 100644 --- a/apps/web/src/components/layout/page-tabs.tsx +++ b/apps/web/src/components/layout/page-tabs.tsx @@ -56,10 +56,29 @@ export interface PageTabsProps { function TabSkeleton() { return ( -
-
-
-
+
+ +
+ + + + + + + + + + + + + +
+ + 載入中... +
) } diff --git a/apps/web/src/components/shared/lobster-loading.tsx b/apps/web/src/components/shared/lobster-loading.tsx new file mode 100644 index 000000000..2b2daa4d9 --- /dev/null +++ b/apps/web/src/components/shared/lobster-loading.tsx @@ -0,0 +1,79 @@ +'use client' + +/** + * LobsterLoading — 小龍蝦載入動畫 + * ================================= + * Sprint 5: 所有「載入中」用小龍蝦動畫取代純文字 + * 設計: Q版龍蝦上下浮動 + 文字提示 + * + * 建立時間: 2026-04-08 (台北時區) + */ + +import { useTranslations } from 'next-intl' + +interface LobsterLoadingProps { + /** 自訂提示文字 (預設 '載入中...') */ + text?: string + /** 大小: 'sm' (24px) | 'md' (36px) | 'lg' (48px) */ + size?: 'sm' | 'md' | 'lg' +} + +const SIZES = { + sm: { svg: 24, bob: 2, fontSize: 11 }, + md: { svg: 36, bob: 3, fontSize: 12 }, + lg: { svg: 48, bob: 4, fontSize: 13 }, +} + +export function LobsterLoading({ text, size = 'md' }: LobsterLoadingProps) { + const tc = useTranslations('common') + const s = SIZES[size] + const displayText = text ?? tc('loading') + + return ( +
+ +
+ + + + + + {/* 左鉗 */} + + + {/* 右鉗 */} + + + {/* 觸角 */} + + + {/* 尾巴 */} + + +
+
+ {displayText} +
+
+ ) +} + +export default LobsterLoading