From 8c4fd7656747558e0d1d084e3baea98880d7c59d Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Wed, 8 Apr 2026 17:22:20 +0900 Subject: [PATCH] Fix prefix issue --- src/__tests__/utils.test.ts | 41 ++++++++++++++++++- .../__snapshots__/codegen.test.ts.snap | 2 +- src/codegen/props/effect.ts | 9 ++-- src/codegen/props/text-shadow.ts | 11 ++++- src/utils.ts | 9 ++-- 5 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index c671dab..d788288 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -37,7 +37,7 @@ describe('propsToPropsWithTypography', () => { figma.getStyleByIdAsync = (id: string) => Promise.resolve( id === 'ts-1' - ? ({ id: 'ts-1', name: 'Typography/Body' } as unknown as BaseStyle) + ? ({ id: 'ts-1', name: 'mobile/Body' } as unknown as BaseStyle) : null, ) as ReturnType @@ -63,6 +63,45 @@ describe('propsToPropsWithTypography', () => { figma.getStyleByIdAsync = origGetStyle }) + it('should preserve scoped (non-breakpoint) prefix in typography key', async () => { + const origGetLocal = figma.getLocalTextStylesAsync + const origGetStyle = figma.getStyleByIdAsync + figma.getLocalTextStylesAsync = () => + Promise.resolve([{ id: 'ts-cms' } as unknown as TextStyle]) as ReturnType< + typeof figma.getLocalTextStylesAsync + > + figma.getStyleByIdAsync = (id: string) => + Promise.resolve( + id === 'ts-cms' + ? ({ + id: 'ts-cms', + name: 'cms/bodyLgBold', + } as unknown as BaseStyle) + : null, + ) as ReturnType + + // Async path — "cms/" is not a breakpoint, so the full name must be + // converted to camelCase to match the devup.json export key. + const r1 = await propsToPropsWithTypography( + { fontFamily: 'Arial', fontSize: 18 }, + 'ts-cms', + ) + expect(r1.typography).toBe('cmsBodyLgBold') + expect(r1.fontFamily).toBeUndefined() + expect(r1.fontSize).toBeUndefined() + + // Sync fast path — same assertion via the resolved cache branch. + const r2 = await propsToPropsWithTypography( + { fontFamily: 'Inter', fontSize: 20 }, + 'ts-cms', + ) + expect(r2.typography).toBe('cmsBodyLgBold') + expect(r2.fontFamily).toBeUndefined() + + figma.getLocalTextStylesAsync = origGetLocal + figma.getStyleByIdAsync = origGetStyle + }) + it('should return early from sync path when textStyleId not in resolved set', async () => { const origGetLocal = figma.getLocalTextStylesAsync const origGetStyle = figma.getStyleByIdAsync diff --git a/src/codegen/__tests__/__snapshots__/codegen.test.ts.snap b/src/codegen/__tests__/__snapshots__/codegen.test.ts.snap index 4ba3f7d..fef14a6 100644 --- a/src/codegen/__tests__/__snapshots__/codegen.test.ts.snap +++ b/src/codegen/__tests__/__snapshots__/codegen.test.ts.snap @@ -637,7 +637,7 @@ exports[`Codegen renders text node with TRUNCATE auto resize and fixed sizing 1` `; exports[`Codegen renders text node with textStyleId and typography 1`] = ` -" +" Heading " `; diff --git a/src/codegen/props/effect.ts b/src/codegen/props/effect.ts index c01e4c0..d356cb5 100644 --- a/src/codegen/props/effect.ts +++ b/src/codegen/props/effect.ts @@ -1,5 +1,6 @@ import { optimizeHex } from '../../utils/optimize-hex' import { rgbaToHex } from '../../utils/rgba-to-hex' +import { styleNameToTypography } from '../../utils/style-name-to-typography' import { toCamel } from '../../utils/to-camel' import { addPx } from '../utils/add-px' import { getVariableByIdCached } from '../utils/variable-cache' @@ -9,6 +10,10 @@ type BoundVars = Record | undefined /** * Resolve effectStyleId to a `$token` for the entire shadow value. * The effect style name IS the shadow token (not a color token). + * + * Must match the key written by export-devup.ts via styleNameToTypography, + * so only breakpoint prefixes are stripped. Scoped prefixes (e.g. "cms/xyz") + * stay part of the token name. */ async function _resolveEffectStyleToken( node: SceneNode, @@ -18,9 +23,7 @@ async function _resolveEffectStyleToken( if (!styleId || typeof styleId !== 'string') return null const style = await figma.getStyleByIdAsync(styleId) if (style?.name) { - // Strip responsive level prefix (e.g. "3/testShadow" → "testShadow") - const parts = style.name.split('/') - return `$${toCamel(parts[parts.length - 1])}` + return `$${styleNameToTypography(style.name).name}` } return null } diff --git a/src/codegen/props/text-shadow.ts b/src/codegen/props/text-shadow.ts index 7fa7d9f..a12110e 100644 --- a/src/codegen/props/text-shadow.ts +++ b/src/codegen/props/text-shadow.ts @@ -1,11 +1,19 @@ import { optimizeHex } from '../../utils/optimize-hex' import { rgbaToHex } from '../../utils/rgba-to-hex' +import { styleNameToTypography } from '../../utils/style-name-to-typography' import { toCamel } from '../../utils/to-camel' import { addPx } from '../utils/add-px' import { getVariableByIdCached } from '../utils/variable-cache' type BoundVars = Record | undefined +/** + * Resolve effectStyleId to a `$token` for the text shadow value. + * + * Must match the key written by export-devup.ts via styleNameToTypography, + * so only breakpoint prefixes are stripped. Scoped prefixes (e.g. "cms/xyz") + * stay part of the token name. + */ async function _resolveEffectStyleToken( node: SceneNode, ): Promise { @@ -14,8 +22,7 @@ async function _resolveEffectStyleToken( if (!styleId || typeof styleId !== 'string') return null const style = await figma.getStyleByIdAsync(styleId) if (style?.name) { - const parts = style.name.split('/') - return `$${toCamel(parts[parts.length - 1])}` + return `$${styleNameToTypography(style.name).name}` } return null } diff --git a/src/utils.ts b/src/utils.ts index 5245783..69b31d8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import { toCamel } from './utils/to-camel' +import { styleNameToTypography } from './utils/style-name-to-typography' import { toPascal } from './utils/to-pascal' // Cache for figma.getStyleByIdAsync() — keyed by style ID @@ -27,8 +27,11 @@ function applyTypographyStyle( ret: Record, style: BaseStyle, ): void { - const split = style.name.split('/') - ret.typography = toCamel(split[split.length - 1]) + // Must match the key that export-devup.ts writes via styleNameToTypography, + // otherwise `typography="..."` references miss the exported devup.json key. + // Only breakpoint prefixes (mobile/tablet/desktop/{number}) are stripped; + // scoped prefixes like `cms/bodyLgBold` stay intact → `cmsBodyLgBold`. + ret.typography = styleNameToTypography(style.name).name delete ret.fontFamily delete ret.fontSize delete ret.fontWeight