Skip to content

Commit e847df5

Browse files
sangun-kangclaude
andcommitted
feat(flavor): embed Inhouse Icons woff2 into published CSS
Consumers were missing the icon glyph inside components that render an <Icon/> internally (e.g. Callout). The SCSS pipeline strips the upstream @font-face block because Storybook supplies its own override via .storybook/icon-font-override.scss, but that override does not travel with the published package. Resolve by inlining node_modules/@pepabo-inhouse/icon/dist/inhouse-icons.woff2 as a base64 data URL and prepending a single @font-face rule to each dist/css/{flavor}.css and once to dist/css/all.css. woff2-only (>=97% browser support as of 2026); woff and eot are intentionally skipped to minimize size. The .storybook/flavors/*.css outputs keep the old behavior (no @font-face) because Storybook continues to rely on its local override and would otherwise emit duplicate declarations. Per-file size delta: ~+12 KB (base64-encoded 8.9 KB woff2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8995d32 commit e847df5

1 file changed

Lines changed: 26 additions & 2 deletions

File tree

scripts/build-flavor-css.mjs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,26 @@ const NODE_MODULES = path.join(ROOT, 'node_modules')
99
const ENTRY_SCSS = path.join(ROOT, 'src', '_all.scss')
1010
const OUTPUT_DIR = path.join(ROOT, '.storybook', 'flavors')
1111
const DIST_DIR = path.join(ROOT, 'dist', 'css')
12+
const ICON_FONT_PATH = path.join(
13+
NODE_MODULES,
14+
'@pepabo-inhouse',
15+
'icon',
16+
'dist',
17+
'inhouse-icons.woff2'
18+
)
1219

1320
const FLAVORS = ['pepper', 'minne', 'apollo', 'nachiguro', 'flippers', 'kung-pu', 'lolipop']
1421

22+
// Inline the icon font as a base64 data URL so consumers get icons out of the
23+
// box with a single CSS import. Only woff2 is included (≥97% browser support
24+
// as of 2026). Storybook keeps its own @font-face override in
25+
// .storybook/icon-font-override.scss, so the .storybook/flavors/*.css outputs
26+
// intentionally omit this block to avoid double declarations.
27+
const iconFontFace = (() => {
28+
const base64 = fs.readFileSync(ICON_FONT_PATH).toString('base64')
29+
return `@font-face{font-family:"Inhouse Icons";font-weight:400;font-style:normal;src:url("data:font/woff2;base64,${base64}") format("woff2")}\n`
30+
})()
31+
1532
// Inline PostCSS plugin to prefix selectors with [data-flavor="xxx"].
1633
// When `scopeRoot` is true, `:root` selectors are rewritten to the prefix so
1734
// token declarations no longer live at document level. Required for the
@@ -106,8 +123,11 @@ for (const flavor of FLAVORS) {
106123
prefixPlugin(`[data-flavor="${flavor}"]`),
107124
]).process(css, { from: undefined })
108125

126+
// Storybook output: no @font-face (Storybook has its own override)
109127
fs.writeFileSync(path.join(OUTPUT_DIR, `${flavor}.css`), prefixed.css)
110-
fs.writeFileSync(path.join(DIST_DIR, `${flavor}.css`), prefixed.css)
128+
// Published output: prepend the icon @font-face so consumers get icons
129+
// when importing a single per-flavor CSS file
130+
fs.writeFileSync(path.join(DIST_DIR, `${flavor}.css`), iconFontFace + prefixed.css)
111131

112132
// Combined build: `:root` rescoped to [data-flavor="xxx"] so concatenating
113133
// all flavors does not produce cascade conflicts at :root level
@@ -123,7 +143,11 @@ for (const flavor of FLAVORS) {
123143
}
124144
}
125145

126-
fs.writeFileSync(path.join(DIST_DIR, 'all.css'), combinedParts.join('\n'))
146+
// Combined CSS: single @font-face at the top, then all 7 flavors
147+
fs.writeFileSync(
148+
path.join(DIST_DIR, 'all.css'),
149+
iconFontFace + combinedParts.join('\n')
150+
)
127151
console.log(` ✓ all.css (combined, ${FLAVORS.length} flavors)`)
128152

129153
console.log('Done!')

0 commit comments

Comments
 (0)