diff --git a/.changeset/kind-colors-share.md b/.changeset/kind-colors-share.md new file mode 100644 index 00000000000..0adc18d073d --- /dev/null +++ b/.changeset/kind-colors-share.md @@ -0,0 +1,6 @@ +--- +"@fluentui-react-native/design": patch +"@fluentui-react-native/theme-tokens": patch +--- + +Share high-contrast alias token processing across Windows platforms from the design package and expose it through the theme-tokens compatibility shim. diff --git a/.changeset/warm-themes-reuse.md b/.changeset/warm-themes-reuse.md new file mode 100644 index 00000000000..e37505cb3ad --- /dev/null +++ b/.changeset/warm-themes-reuse.md @@ -0,0 +1,5 @@ +--- +"@fluentui-react-native/win32-theme": patch +--- + +Reuse high-contrast alias tokens from `@fluentui-react-native/design/tokens/legacy`. diff --git a/apps/bundle-size/package.json b/apps/bundle-size/package.json index ed39a903236..8d85881ed28 100644 --- a/apps/bundle-size/package.json +++ b/apps/bundle-size/package.json @@ -34,6 +34,7 @@ "@rnx-kit/metro-config": "catalog:", "@rnx-kit/metro-resolver-symlinks": "catalog:", "@rnx-kit/metro-serializer-esbuild": "catalog:", + "@rnx-kit/tools-filesystem": "^0.2.0", "metro": "^0.83.1", "oxc-resolver": "catalog:" }, diff --git a/apps/bundle-size/scripts/measure.mjs b/apps/bundle-size/scripts/measure.mjs index 96ded9771d2..72e23713b47 100644 --- a/apps/bundle-size/scripts/measure.mjs +++ b/apps/bundle-size/scripts/measure.mjs @@ -1,3 +1,4 @@ +import { readJSONFileSync, writeJSONFileSync } from '@rnx-kit/tools-filesystem'; import { spawnSync } from 'node:child_process'; import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'; import { mkdir } from 'node:fs/promises'; @@ -7,7 +8,7 @@ import { gzipSync } from 'node:zlib'; const workspaceRoot = dirname(dirname(fileURLToPath(import.meta.url))); const repositoryRoot = dirname(dirname(workspaceRoot)); -const yarnVersion = JSON.parse(readFileSync(join(repositoryRoot, 'package.json'), 'utf8')).packageManager.split('@')[1]; +const yarnVersion = readJSONFileSync(join(repositoryRoot, 'package.json')).packageManager.split('@')[1]; const yarnPath = join(repositoryRoot, '.yarn', 'releases', `yarn-${yarnVersion}.cjs`); const configPath = join(workspaceRoot, 'scenarios.json'); const defaultBaselinePath = join(workspaceRoot, 'baseline.json'); @@ -68,7 +69,7 @@ function getWorkspacePackage(source) { while (directory.startsWith(packagesRoot)) { const manifestPath = join(directory, 'package.json'); if (existsSync(manifestPath)) { - return JSON.parse(readFileSync(manifestPath, 'utf8')).name; + return readJSONFileSync(manifestPath).name; } directory = dirname(directory); } @@ -153,8 +154,8 @@ function runBundle(platform, scenario, resetCache) { } const bundle = readFileSync(bundlePath); - const sourceMap = JSON.parse(readFileSync(sourceMapPath, 'utf8')); - const metafile = JSON.parse(readFileSync(metafilePath, 'utf8')); + const sourceMap = readJSONFileSync(sourceMapPath); + const metafile = readJSONFileSync(metafilePath); const contributions = getWorkspaceContributions(metafile); return { @@ -263,7 +264,7 @@ const { if (updateBaseline && selectedPlatforms) { throw new Error('Baseline updates must include every configured platform; omit --platform'); } -const selectedConfig = JSON.parse(readFileSync(selectedConfigPath, 'utf8')); +const selectedConfig = readJSONFileSync(selectedConfigPath); const platforms = selectedPlatforms ?? selectedConfig.platforms; await mkdir(entryRoot, { recursive: true }); @@ -284,12 +285,10 @@ const currentBaseline = { results: measurements.map(baselineResult), }; if (updateBaseline) { - writeFileSync(selectedBaselinePath, `${JSON.stringify(currentBaseline, null, 2)}\n`); + writeJSONFileSync(selectedBaselinePath, currentBaseline); } -const baseline = existsSync(selectedBaselinePath) - ? JSON.parse(readFileSync(selectedBaselinePath, 'utf8')) - : { schemaVersion: 1, results: [] }; +const baseline = existsSync(selectedBaselinePath) ? readJSONFileSync(selectedBaselinePath) : { schemaVersion: 1, results: [] }; if (baseline.schemaVersion !== 1) { throw new Error(`Unsupported baseline schema version: ${baseline.schemaVersion}`); } @@ -315,7 +314,7 @@ const report = { }; const reportPath = join(outputRoot, 'results.json'); const markdownReportPath = join(outputRoot, 'report.md'); -writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); +writeJSONFileSync(reportPath, report); writeFileSync(markdownReportPath, createMarkdownReport(results)); console.table(results); diff --git a/packages/agentic/design/src/tokens/legacy/highContrast/__tests__/processAliasTokens.test.ts b/packages/agentic/design/src/tokens/legacy/highContrast/__tests__/processAliasTokens.test.ts new file mode 100644 index 00000000000..63b1eed6ff3 --- /dev/null +++ b/packages/agentic/design/src/tokens/legacy/highContrast/__tests__/processAliasTokens.test.ts @@ -0,0 +1,27 @@ +import { processAliasTokens, transformWin32PlatformColorName, transformWindowsPlatformColorName } from '../processAliasTokens'; + +jest.mock('react-native', () => ({ + PlatformColor: (color: string) => `PlatformColor('${color}')`, +})); + +const createAliasTokens = () => ({ + colors: { + buttonFace: 'PlatformColor(ButtonFace)', + }, +}); + +it('maps Windows platform colors to SystemColor names', () => { + expect(processAliasTokens(createAliasTokens(), transformWindowsPlatformColorName)).toEqual({ + colors: { + buttonFace: "PlatformColor('SystemColorButtonFaceColor')", + }, + }); +}); + +it('preserves raw Win32 platform color names', () => { + expect(processAliasTokens(createAliasTokens(), transformWin32PlatformColorName)).toEqual({ + colors: { + buttonFace: "PlatformColor('ButtonFace')", + }, + }); +}); diff --git a/packages/agentic/design/src/tokens/legacy/highContrast/processAliasTokens.ts b/packages/agentic/design/src/tokens/legacy/highContrast/processAliasTokens.ts new file mode 100644 index 00000000000..77b08cdf010 --- /dev/null +++ b/packages/agentic/design/src/tokens/legacy/highContrast/processAliasTokens.ts @@ -0,0 +1,24 @@ +import { PlatformColor } from 'react-native'; + +type AliasTokens = Record>; +type PlatformColorNameTransform = (color: string) => string; + +export const transformWindowsPlatformColorName: PlatformColorNameTransform = (color) => `SystemColor${color}Color`; +export const transformWin32PlatformColorName: PlatformColorNameTransform = (color) => color; + +export function processAliasTokens(aliasTokens: T, transformColorName: PlatformColorNameTransform): T { + // The imported token JSON is intentionally mutated once when its module loads. + for (const key in aliasTokens) { + const tokenGroup: Record = aliasTokens[key]; + for (const innerKey in tokenGroup) { + const entry = tokenGroup[innerKey]; + if (typeof entry === 'string' && entry.includes('PlatformColor')) { + const color = transformColorName(entry.substring(14, entry.length - 1)); + // eslint-disable-next-line @react-native/platform-colors + tokenGroup[innerKey] = PlatformColor(color); + } + } + } + + return aliasTokens; +} diff --git a/packages/agentic/design/src/tokens/legacy/highContrast/tokens-alias.ts b/packages/agentic/design/src/tokens/legacy/highContrast/tokens-alias.ts index adca9a813c1..1508a5d7ba0 100644 --- a/packages/agentic/design/src/tokens/legacy/highContrast/tokens-alias.ts +++ b/packages/agentic/design/src/tokens/legacy/highContrast/tokens-alias.ts @@ -1,20 +1,5 @@ -import { PlatformColor } from 'react-native'; - import aliasTokens from '@fluentui-react-native/design-tokens-win32/hc/tokens-aliases.json'; -export const hcAliasTokens = processAliasTokens(aliasTokens); - -function processAliasTokens(aliasTokens: any) { - for (const key in aliasTokens) { - for (const innerKey in aliasTokens[key]) { - const entry = aliasTokens[key][innerKey]; - if (typeof entry === 'string' && entry.includes('PlatformColor')) { - const color = 'SystemColor' + entry.substring(14, entry.length - 1) + 'Color'; - // eslint-disable-next-line @react-native/platform-colors - aliasTokens[key][innerKey] = PlatformColor(color); - } - } - } +import { processAliasTokens, transformWindowsPlatformColorName } from './processAliasTokens'; - return aliasTokens; -} +export const hcAliasTokens = processAliasTokens(aliasTokens, transformWindowsPlatformColorName); diff --git a/packages/agentic/design/src/tokens/legacy/highContrast/tokens-alias.win32.ts b/packages/agentic/design/src/tokens/legacy/highContrast/tokens-alias.win32.ts index 2004e703e6f..8980d515be8 100644 --- a/packages/agentic/design/src/tokens/legacy/highContrast/tokens-alias.win32.ts +++ b/packages/agentic/design/src/tokens/legacy/highContrast/tokens-alias.win32.ts @@ -1,20 +1,5 @@ -import { PlatformColor } from 'react-native'; - import aliasTokens from '@fluentui-react-native/design-tokens-win32/hc/tokens-aliases.json'; -export const hcAliasTokens = processAliasTokens(aliasTokens); - -function processAliasTokens(aliasTokens: any) { - for (const key in aliasTokens) { - for (const innerKey in aliasTokens[key]) { - const entry = aliasTokens[key][innerKey]; - if (typeof entry === 'string' && entry.includes('PlatformColor')) { - const color = entry.substring(14, entry.length - 1); - // eslint-disable-next-line @react-native/platform-colors - aliasTokens[key][innerKey] = PlatformColor(color); - } - } - } +import { processAliasTokens, transformWin32PlatformColorName } from './processAliasTokens'; - return aliasTokens; -} +export const hcAliasTokens = processAliasTokens(aliasTokens, transformWin32PlatformColorName); diff --git a/packages/agentic/design/src/tokens/legacy/index.ts b/packages/agentic/design/src/tokens/legacy/index.ts index 2832ce3c035..cc73c61e274 100644 --- a/packages/agentic/design/src/tokens/legacy/index.ts +++ b/packages/agentic/design/src/tokens/legacy/index.ts @@ -1,2 +1,3 @@ export { default as globalTokens } from './tokens-global'; export { getAliasTokens, getShadowTokens } from './getTokens'; +export { hcAliasTokens } from './highContrast/tokens-alias'; diff --git a/packages/shim/theme-tokens/README.md b/packages/shim/theme-tokens/README.md index a84ebbb4912..a955de89ad2 100644 --- a/packages/shim/theme-tokens/README.md +++ b/packages/shim/theme-tokens/README.md @@ -1,7 +1,8 @@ # Theme-tokens -> **Deprecated:** Import `globalTokens`, `getAliasTokens`, and `getShadowTokens` -> from `@fluentui-react-native/design/tokens/legacy` instead. +> **Deprecated:** Import `globalTokens`, `getAliasTokens`, `getShadowTokens`, +> and `hcAliasTokens` from `@fluentui-react-native/design/tokens/legacy` +> instead. This package is a compatibility shim and will not receive new APIs. It preserves the existing entry point while consumers migrate to the design package. diff --git a/packages/shim/theme-tokens/src/index.ts b/packages/shim/theme-tokens/src/index.ts index 7547d35e1dd..e57f4d2183d 100644 --- a/packages/shim/theme-tokens/src/index.ts +++ b/packages/shim/theme-tokens/src/index.ts @@ -1,4 +1,4 @@ // This package is a compatibility shim. Legacy Fluent token values now live in // the `@fluentui-react-native/design/tokens/legacy` submodule. These explicit // re-exports preserve the existing `@fluentui-react-native/theme-tokens` entry point. -export { getAliasTokens, getShadowTokens, globalTokens } from '@fluentui-react-native/design/tokens/legacy'; +export { getAliasTokens, getShadowTokens, globalTokens, hcAliasTokens } from '@fluentui-react-native/design/tokens/legacy'; diff --git a/packages/theming/win32-theme/src/getOfficeTokens.ts b/packages/theming/win32-theme/src/getOfficeTokens.ts index 4a00bbdc1e3..081faf497ed 100644 --- a/packages/theming/win32-theme/src/getOfficeTokens.ts +++ b/packages/theming/win32-theme/src/getOfficeTokens.ts @@ -5,8 +5,7 @@ import colorfulShadowTokens from '@fluentui-react-native/design-tokens-win32/col import darkGrayAliasTokens from '@fluentui-react-native/design-tokens-win32/darkgray/tokens-aliases.json'; import darkGrayShadowTokens from '@fluentui-react-native/design-tokens-win32/darkgray/tokens-shadow.json'; import hcShadowTokens from '@fluentui-react-native/design-tokens-win32/hc/tokens-shadow.json'; - -import { hcAliasTokens } from './highContrast/tokens-alias'; +import { hcAliasTokens } from '@fluentui-react-native/design/tokens/legacy'; export function getOfficeAliasTokens(officeTheme: string) { if (officeTheme === 'White' || officeTheme === 'Colorful') { diff --git a/packages/theming/win32-theme/src/highContrast/tokens-alias.ts b/packages/theming/win32-theme/src/highContrast/tokens-alias.ts deleted file mode 100644 index 2004e703e6f..00000000000 --- a/packages/theming/win32-theme/src/highContrast/tokens-alias.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { PlatformColor } from 'react-native'; - -import aliasTokens from '@fluentui-react-native/design-tokens-win32/hc/tokens-aliases.json'; - -export const hcAliasTokens = processAliasTokens(aliasTokens); - -function processAliasTokens(aliasTokens: any) { - for (const key in aliasTokens) { - for (const innerKey in aliasTokens[key]) { - const entry = aliasTokens[key][innerKey]; - if (typeof entry === 'string' && entry.includes('PlatformColor')) { - const color = entry.substring(14, entry.length - 1); - // eslint-disable-next-line @react-native/platform-colors - aliasTokens[key][innerKey] = PlatformColor(color); - } - } - } - - return aliasTokens; -} diff --git a/yarn.lock b/yarn.lock index 7d5f1e93546..e57c893f470 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2652,6 +2652,7 @@ __metadata: "@rnx-kit/metro-config": "catalog:" "@rnx-kit/metro-resolver-symlinks": "catalog:" "@rnx-kit/metro-serializer-esbuild": "catalog:" + "@rnx-kit/tools-filesystem": "npm:^0.2.0" "@types/react": "npm:~19.1.4" metro: "npm:^0.83.1" oxc-resolver: "catalog:"