feat(icons): prevent layout shift by pre-calculating icon size for SSR#882
Open
naporin0624 wants to merge 30 commits intomainfrom
Open
feat(icons): prevent layout shift by pre-calculating icon size for SSR#882naporin0624 wants to merge 30 commits intomainfrom
naporin0624 wants to merge 30 commits intomainfrom
Conversation
b48d874 to
bb7e06e
Compare
Extract icon size calculation into a pure `calcActualSize` function that can run during SSR without instantiating the Web Component. - IconSizing union type ensures scale, unsafeNonGuidelineScale, and unsafeNonGuidelineSize are mutually exclusive at the type level - Input validation: throws TypeError for invalid name, zero, negative, and Infinity values - Strengthen render() guard to reject size <= 0 - Mark forceResizedSize/scaledSize getters as @deprecated - Read pre-computed size from data-charcoal-icon-size via dataset
- Export calcActualSize and IconSizing from @charcoal-ui/icons - Add packages/icons/css/icon.css with .charcoal-icon class that uses --charcoal-icon-ssr-size CSS variable for layout shift prevention
- Use calcActualSize in useMemo to pre-calculate icon dimensions
- Set --charcoal-icon-ssr-size CSS variable and data-charcoal-icon-size
attribute on pixiv-icon for SSR sizing
- Add .charcoal-icon class for CSS-based size reservation
- Merge user style with SSR CSS variable (prevents {...rest} overwrite)
- Pass through scale and unsafe-non-guideline-scale for backward compat
- Use IconSizing union type for OwnProps (mutually exclusive sizing)
- Rename fixedSize to unsafeNonGuidelineSize for naming consistency
Verify Icon correctly sets data-charcoal-icon-size, --charcoal-icon-ssr-size CSS variable, charcoal-icon class, scale/unsafe-non-guideline-scale passthrough, and user style merging via jsdom.
…ut shift Use vitest browser mode (@vitest/browser + playwright) to verify pixiv-icon has correct dimensions via getBoundingClientRect in a real Chromium browser. Two describe blocks prove no layout shift: 1. Web Component upgrade blocked → CSS alone sizes the element correctly 2. Web Component upgraded → same sizes (no shift on hydration) Shared test cases cover scale, unsafeNonGuidelineScale, unsafeNonGuidelineSize, Inline icons, and components using Icon (HintText, TagItem).
bb7e06e to
fdaae27
Compare
|
Size Change: +5.04 kB (+1.34%) Total Size: 381 kB 📦 View Changed
ℹ️ View Unchanged
|
|
Visit the preview URL for this PR (updated for commit c077202): https://pixiv-charcoal-web--pr882-fix-icon-layout-shif-8yx13ehg.web.app (expires Mon, 13 Apr 2026 13:21:48 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: 314b26d3adca98a761c7e4d9922ebb206ff024a0 |
6612e84 to
1d4d4fa
Compare
…ne-scale
parseInt('3.75', 10) truncates to 3, but unsafe-non-guideline-scale
accepts decimal values (e.g. '3.75'). Use Number() to preserve the
fractional part. This fixes the image snapshot mismatch for the
WithUnsafe story.
1d4d4fa to
e4931a5
Compare
vite:import-analysis cannot resolve the @charcoal-ui/icons-css alias in CI. Use a virtual module plugin to inline icon.css content instead.
Define CSS string directly in the test file. The icon.css content is small (4 lines) and this avoids import resolution issues in CI where the @charcoal-ui/icons-css alias cannot be resolved by vite.
…ser test The previous approach monkey-patched customElements.define to block pixiv-icon registration, but Vite pre-bundles dependencies so the side effect runs before the patch. Test CSS sizing mechanism directly with plain HTML elements instead.
Root `vitest` runs without config and picks up all *.test.* files. Browser tests (*.browser.test.*) require a real browser via Playwright, so they fail in the default jsdom/node environment. - Exclude *.browser.test.* from `pnpm test` via --exclude flag - Add `test:browser` script that runs browser tests separately - Install Playwright chromium in CI with actions/cache
Two Playwright versions (1.51.1 and 1.58.2) coexisted in the lockfile, causing CI to install revision 1161 browsers while vitest required revision 1208. Align all Playwright dependencies to ^1.58.2 via specifier updates and pnpm overrides, and simplify the CI install step.
fb52828 to
cbe01a5
Compare
…alSize Eliminate nested switch statements by extracting size calculation logic into dedicated functions for better readability.
…rate module Move calcActualSize, IconSizing, parseIconName, inlineSize, guidelineSize24, and isPositiveFinite into src/calcActualSize.ts.
Move DOM attribute parsing into inline getters on the calcActualSize argument object instead of separate local variables.
mimokmt
approved these changes
Apr 6, 2026
Add pre-definition size rules based on icon name prefix and scale attributes. Also fix snapshot test comparison and add vitest forks pool.
Replace `cd packages/react && npx vitest run --config` with vitest workspace configuration and `--project browser` filtering.
…ne styles React Icon and PixivIcon WC now inject --charcoal-icon-unsafe-scale as an inline CSS variable, enabling the :not(:defined) CSS to correctly reserve space for unsafe-non-guideline-scale before JS bootstrap. Also fixes MultiSelect using dashed prop form which bypassed calcActualSize.
Replace outdated manual CSS example with documentation for @charcoal-ui/icons/css/icon.css and its two mechanisms: .charcoal-icon class (React) and :not(:defined) selector (vanilla HTML).
…riables BREAKING CHANGE: Remove unsafe-non-guideline-scale HTML attribute from PixivIcon web component. Sizing is now determined by CSS variables (--charcoal-icon-unsafe-scale, --charcoal-icon-ssr-size) and data attributes (data-charcoal-icon-size) instead. - Remove unsafe-non-guideline-scale from observedAttributes and Props - Remove deprecated forceResizedSize getter - Extend props getter to read from CSS variables and data attributes - React Icon always sets data-charcoal-icon-size with calculated size - Remove &[unsafe-non-guideline-scale] CSS block from icon.css
…proach Replace unsafe-non-guideline-scale attribute examples with data-charcoal-icon-size and --charcoal-icon-ssr-size usage.
e67c49f to
57ed0e8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
やったこと
@charcoal-ui/iconsにcalcActualSize関数とIconSizingunion 型を追加し、SSR 環境でもアイコンサイズを事前計算できるようにした@charcoal-ui/iconsに SSR 用 CSS (icon.css) を追加し、CSS 変数でカスタム要素の hydration 前にサイズを確保する仕組みを導入@charcoal-ui/reactのIconコンポーネントで、事前計算したサイズを--charcoal-icon-ssr-sizeCSS 変数とdata-charcoal-icon-size属性として出力するようにしたcalcActualSizeパラメータnamestring"<size>/<Name>"形式のアイコン名(例:"24/Add","Inline/Search")。サイズプレフィックスはInline(baseSize=16)、正の整数(24,32等)のいずれかscale1 | 2 | 3Inlineでは 1→16, 2→32(テキストに並ぶアイコンのため 24px ではなく行の高さに合わせた 16/32 の二値。既存実装 080ef02 からscaleは1 | 2のみ対応)。24では24 * scale。その他のサイズでは無視されるunsafeNonGuidelineScalenumberbaseSize * scaleを返す。ガイドライン外のためunsafeprefixunsafeNonGuidelineSizenumber3 つの sizing オプション(
scale/unsafeNonGuidelineScale/unsafeNonGuidelineSize)はIconSizingunion 型で排他的に制約される。優先順位:
unsafeNonGuidelineSize>unsafeNonGuidelineScale>scale(デフォルト 1)動作確認環境
チェックリスト