From 60a59ef703b2267d0e407e981212c8d31d5115f3 Mon Sep 17 00:00:00 2001 From: haotool Date: Sat, 27 Jun 2026 20:54:44 +0800 Subject: [PATCH] =?UTF-8?q?fix(ratewise):=20=E6=94=B6=E6=96=82=E9=A6=96?= =?UTF-8?q?=E9=A0=81=E6=8F=9B=E7=AE=97=E5=99=A8=20CLS=20=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E9=AB=98=E5=BA=A6=EF=BC=88#454=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SingleConverter 匯率卡片與 pill 槽位預留 min-height,移除趨勢圖 translate 進場 - RateWise 資料來源 footer 保留 min-h-6 避免載入後位移 - 補 design-tokens SSOT 與 core 測試 測試:pnpm --filter @app/ratewise exec vitest run SingleConverter.core.test.tsx Co-authored-by: Cursor --- .changeset/ratewise-homepage-cls-454.md | 5 ++++ apps/ratewise/src/config/design-tokens.ts | 11 ++++++++ .../src/features/ratewise/RateWise.tsx | 20 +++++++++------ .../ratewise/components/SingleConverter.tsx | 25 +++++++++---------- .../__tests__/SingleConverter.core.test.tsx | 25 ++++++++++--------- .../dev/002_development_reward_penalty_log.md | 7 +++++- 6 files changed, 59 insertions(+), 34 deletions(-) create mode 100644 .changeset/ratewise-homepage-cls-454.md diff --git a/.changeset/ratewise-homepage-cls-454.md b/.changeset/ratewise-homepage-cls-454.md new file mode 100644 index 000000000..23761306e --- /dev/null +++ b/.changeset/ratewise-homepage-cls-454.md @@ -0,0 +1,5 @@ +--- +'@app/ratewise': patch +--- + +首頁換算器預留固定高度,減少匯率卡片與計價基準 pill 載入時的版面跳動(CLS) diff --git a/apps/ratewise/src/config/design-tokens.ts b/apps/ratewise/src/config/design-tokens.ts index a4935f7e5..dd93f1086 100644 --- a/apps/ratewise/src/config/design-tokens.ts +++ b/apps/ratewise/src/config/design-tokens.ts @@ -711,6 +711,17 @@ export const singleConverterLayoutTokens = { /** 次要匯率文字 */ rateSubText: 'text-sm short:text-xs tiny:text-xs micro:text-[10px] nano:text-[10px]', + /** 匯率文字區塊最小高度(含計價基準 pill 槽位,避免 async 載入位移) */ + rateTextBlock: + 'min-h-[5rem] compact:min-h-[4.5rem] short:min-h-[4rem] tiny:min-h-[3.75rem] micro:min-h-[3.5rem] nano:min-h-[3.25rem]', + + /** 計價基準 pill 槽位(無 pill 時仍保留高度) */ + rateBasisSlot: 'mt-1.5 flex min-h-6 items-center justify-center', + + /** 匯率卡片本體最小高度(資訊區 + 圖表) */ + cardMinHeight: + 'min-h-[10.5rem] compact:min-h-[9.5rem] short:min-h-[8.75rem] tiny:min-h-[8rem] micro:min-h-[7.25rem] nano:min-h-[6.75rem]', + /** 趨勢圖高度 - 線性遞減 */ chartHeight: 'h-20 compact:h-16 short:h-14 tiny:h-12 micro:h-10 nano:h-8', diff --git a/apps/ratewise/src/features/ratewise/RateWise.tsx b/apps/ratewise/src/features/ratewise/RateWise.tsx index 83bb89db6..8c4a291ec 100644 --- a/apps/ratewise/src/features/ratewise/RateWise.tsx +++ b/apps/ratewise/src/features/ratewise/RateWise.tsx @@ -263,12 +263,12 @@ const RateWise = ({ rememberConverterView = true }: { rememberConverterView?: bo - {/* 資料來源與更新時間區塊 - 現代化精簡設計,固定在底部 */} - {!ratesLoading && lastUpdate && ( -
+ {/* 資料來源與更新時間區塊 - 固定最小高度避免載入完成後 footer 位移 */} +
+ {!ratesLoading && lastUpdate ? ( {effectiveRateSource === 'exchange-shop' && moneyBoxRate?.currency === exchangeShopCurrency ? ( @@ -292,8 +292,12 @@ const RateWise = ({ rememberConverterView = true }: { rememberConverterView?: bo )} -
- )} + ) : ( + + )} +
diff --git a/apps/ratewise/src/features/ratewise/components/SingleConverter.tsx b/apps/ratewise/src/features/ratewise/components/SingleConverter.tsx index 272240b98..a3ef4e831 100644 --- a/apps/ratewise/src/features/ratewise/components/SingleConverter.tsx +++ b/apps/ratewise/src/features/ratewise/components/SingleConverter.tsx @@ -117,7 +117,6 @@ export const SingleConverter = ({ const [trendData, setTrendData] = useState([]); const [_loadingTrend, setLoadingTrend] = useState(false); const [isSwapping, setIsSwapping] = useState(false); - const [showTrend, setShowTrend] = useState(false); const [trendDateKey, setTrendDateKey] = useState(() => getLocalDateKey()); const swapButtonRef = useRef(null); @@ -157,12 +156,6 @@ export const SingleConverter = ({ ); const reverseRate = getReciprocalExchangeRate(exchangeRate); - // 趨勢圖進場動畫 - useEffect(() => { - const timer = setTimeout(() => setShowTrend(true), 300); - return () => clearTimeout(timer); - }, []); - // 處理交換按鈕點擊 const handleSwap = () => { setIsSwapping(true); @@ -486,7 +479,7 @@ export const SingleConverter = ({
{/* 匯率卡片 - 一體化設計,無切分感 */}
{/* * 微光效果 - 極淺的漸層覆蓋 @@ -509,8 +502,8 @@ export const SingleConverter = ({ onRateSourceChange={onRateSourceChange ?? (() => undefined)} /> - {/* 匯率顯示 - 使用 SSOT text 色 */} -
+ {/* 匯率顯示 - 使用 SSOT text 色;固定高度避免計價基準 pill / live 匯率載入 CLS */} +
@@ -521,15 +514,21 @@ export const SingleConverter = ({ > 1 {toCurrency} = {formatExchangeRate(reverseRate)} {fromCurrency}
+
{/* 滿版趨勢圖 - 無獨立背景,繼承父元素漸層實現一體化 */}
{ expect(trendChart).toHaveClass(className); }); }); + + it('匯率文字區塊應預留計價基準 pill 槽位高度', () => { + render(); + + const rateTextBlock = screen.getByText(/1 TWD =/).parentElement; + expect(rateTextBlock).toHaveClass(singleConverterLayoutTokens.rateCard.rateTextBlock); + }); }); describe('匯率類型切換', () => { @@ -433,21 +440,15 @@ describe('SingleConverter - 核心功能測試', () => { }); }); - describe('趨勢圖進場動畫', () => { - it('should show trend chart after 300ms delay', () => { + describe('趨勢圖 CLS 穩定', () => { + it('趨勢圖容器應自初始渲染起保持可見且固定高度', () => { render(); - // 初始狀態不應該顯示趨勢圖(opacity-0) - const trendContainer = document.querySelector('.opacity-0.translate-y-4'); - expect(trendContainer).toBeInTheDocument(); - - // 300ms 後應該顯示(opacity-100) - act(() => { - vi.advanceTimersByTime(400); + const trendChart = screen.getByTestId('trend-chart'); + expect(trendChart).not.toHaveClass('opacity-0'); + singleConverterLayoutTokens.rateCard.chartHeight.split(' ').forEach((className) => { + expect(trendChart).toHaveClass(className); }); - - const visibleTrend = document.querySelector('.opacity-100.translate-y-0'); - expect(visibleTrend).toBeInTheDocument(); }); }); diff --git a/docs/dev/002_development_reward_penalty_log.md b/docs/dev/002_development_reward_penalty_log.md index 37bc1f6ef..68190bc9a 100644 --- a/docs/dev/002_development_reward_penalty_log.md +++ b/docs/dev/002_development_reward_penalty_log.md @@ -2,7 +2,7 @@ > 版本:outline-v2-ultra > 原則:每筆只保留日期、ID、原因、解法。 -> 本次分數變化:+1(reward 1、penalty 0、neutral 0)|累計總分:+74 +> 本次分數變化:+1(reward 1、penalty 0、neutral 0)|累計總分:+75 ## 新增模板(4 行) @@ -13,6 +13,11 @@ ## 條目(新→舊) +- 日期:2026-06-27 +- ID:reward-ratewise-homepage-cls-454-hotfix +- 原因:#441 計價基準 pill 與趨勢圖進場動畫使首頁 CLS 達 0.237,超過 LHCI 門檻 0.1 +- 解法:SingleConverter 匯率卡片固定 min-height、預留 pill 槽位、移除趨勢圖 translate 動畫;RateWise footer 保留 min-h-6 + - 日期:2026-06-27 - ID:reward-moneybox-schema-migration-cdn-fallback - 原因:MoneyBox fetch 僅比對匯率數字,schema v2 遷移未觸發 data commit/purge;jsDelivr @data 分支快取最長 12h