diff --git a/apps/fluent-tester/ios/Podfile b/apps/fluent-tester/ios/Podfile
index 1126346941..8ac79cbc3f 100644
--- a/apps/fluent-tester/ios/Podfile
+++ b/apps/fluent-tester/ios/Podfile
@@ -24,6 +24,9 @@ use_test_app! do |target|
target.app do
platform :ios, '14.0'
+ # There is a bug where autolinking isn't working, do specify these manually.
+ pod 'FRNFontMetrics', :path => '../../../packages/experimental/NativeFontMetrics/FRNFontMetrics.podspec'
+
script_phase name: 'Start Packager',
script: start_packager_script,
execution_position: :before_compile
diff --git a/apps/fluent-tester/ios/Podfile.lock b/apps/fluent-tester/ios/Podfile.lock
index 002c72f636..53a9106022 100644
--- a/apps/fluent-tester/ios/Podfile.lock
+++ b/apps/fluent-tester/ios/Podfile.lock
@@ -10,12 +10,14 @@ PODS:
- React-jsi (= 0.68.5)
- ReactCommon/turbomodule/core (= 0.68.5)
- fmt (6.2.1)
- - FRNAvatar (0.16.20):
+ - FRNAvatar (0.16.24):
- MicrosoftFluentUI (= 0.8.3)
- React
- FRNDatePicker (0.7.3):
- MicrosoftFluentUI (= 0.8.3)
- React
+ - FRNFontMetrics (0.2.0):
+ - React
- glog (0.3.5)
- MicrosoftFluentUI (0.8.3):
- MicrosoftFluentUI/ActivityIndicator_ios (= 0.8.3)
@@ -384,7 +386,7 @@ PODS:
- glog
- react-native-menu (0.1.2):
- React
- - react-native-slider (4.3.2):
+ - react-native-slider (4.3.3):
- React-Core
- React-perflogger (0.68.5)
- React-RCTActionSheet (0.68.5):
@@ -468,6 +470,7 @@ DEPENDENCIES:
- FBReactNativeSpec (from `../../../node_modules/react-native/React/FBReactNativeSpec`)
- FRNAvatar (from `../../../packages/experimental/Avatar`)
- FRNDatePicker (from `../../../packages/experimental/NativeDatePicker`)
+ - FRNFontMetrics (from `../../../packages/experimental/NativeFontMetrics/FRNFontMetrics.podspec`)
- glog (from `../../../node_modules/react-native/third-party-podspecs/glog.podspec`)
- RCT-Folly (from `../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTRequired (from `../../../node_modules/react-native/Libraries/RCTRequired`)
@@ -522,6 +525,8 @@ EXTERNAL SOURCES:
:path: "../../../packages/experimental/Avatar"
FRNDatePicker:
:path: "../../../packages/experimental/NativeDatePicker"
+ FRNFontMetrics:
+ :path: "../../../packages/experimental/NativeFontMetrics/FRNFontMetrics.podspec"
glog:
:podspec: "../../../node_modules/react-native/third-party-podspecs/glog.podspec"
RCT-Folly:
@@ -595,8 +600,9 @@ SPEC CHECKSUMS:
FBLazyVector: 2b47ff52037bd9ae07cc9b051c9975797814b736
FBReactNativeSpec: dd89c4a5591e20015aa55c6efbf9c7740a83efbf
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
- FRNAvatar: de1aec8a9011ade478f2148677b4f2c076d77110
+ FRNAvatar: 3911021ed95a08f19e0ee696712aa48b6dff010a
FRNDatePicker: 241cd55b8d2b63d4427d782951f31504f09fbe1a
+ FRNFontMetrics: 472e7952e454ece364a91babd8bb2a32219676e7
glog: 476ee3e89abb49e07f822b48323c51c57124b572
MicrosoftFluentUI: e30487dd18aba04beeed4caf1ce1988073f8b03a
RCT-Folly: 4d8508a426467c48885f1151029bc15fa5d7b3b8
@@ -613,7 +619,7 @@ SPEC CHECKSUMS:
React-jsinspector: eb202e43b3879aba9a14f3f65788aec85d4e1ea9
React-logger: 98f663b292a60967ebbc6d803ae96c1381183b6d
react-native-menu: 9fe07f72e075b250295eeae25425490cc9608951
- react-native-slider: e540525ea731783850802b7af457d8551edb0711
+ react-native-slider: 7d19220da2f2ae7cbb9aa80127cb73c597fa221f
React-perflogger: 0458a87ea9a7342079e7a31b0d32b3734fb8415f
React-RCTActionSheet: 22538001ea2926dea001111dd2846c13a0730bc9
React-RCTAnimation: 732ce66878d4aa151d56a0d142b1105aa12fd313
@@ -632,6 +638,6 @@ SPEC CHECKSUMS:
RNSVG: 302bfc9905bd8122f08966dc2ce2d07b7b52b9f8
Yoga: c4d61225a466f250c35c1ee78d2d0b3d41fe661c
-PODFILE CHECKSUM: eeba196fb25cf059c631787109cecd08a4ac85a6
+PODFILE CHECKSUM: 819f14a4e3e6e335a0b1993fe37edad50db02d86
COCOAPODS: 1.11.3
diff --git a/change/@fluentui-react-native-experimental-native-font-metrics-a7673fa7-cc45-446e-83e0-02ce3ab92578.json b/change/@fluentui-react-native-experimental-native-font-metrics-a7673fa7-cc45-446e-83e0-02ce3ab92578.json
new file mode 100644
index 0000000000..91e3edda09
--- /dev/null
+++ b/change/@fluentui-react-native-experimental-native-font-metrics-a7673fa7-cc45-446e-83e0-02ce3ab92578.json
@@ -0,0 +1,7 @@
+{
+ "type": "minor",
+ "comment": "Send new font metrics information through a JS event",
+ "packageName": "@fluentui-react-native/experimental-native-font-metrics",
+ "email": "adgleitm@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/change/@fluentui-react-native-tester-955d714f-c8eb-47c4-bec6-5a032f9171d2.json b/change/@fluentui-react-native-tester-955d714f-c8eb-47c4-bec6-5a032f9171d2.json
new file mode 100644
index 0000000000..0fb5c5404a
--- /dev/null
+++ b/change/@fluentui-react-native-tester-955d714f-c8eb-47c4-bec6-5a032f9171d2.json
@@ -0,0 +1,7 @@
+{
+ "type": "minor",
+ "comment": "Add Dynamic Type support",
+ "packageName": "@fluentui-react-native/tester",
+ "email": "adgleitm@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/change/@fluentui-react-native-text-234cd2bf-f18b-4347-8a20-cfe76b66a60d.json b/change/@fluentui-react-native-text-234cd2bf-f18b-4347-8a20-cfe76b66a60d.json
new file mode 100644
index 0000000000..56ea4c9b65
--- /dev/null
+++ b/change/@fluentui-react-native-text-234cd2bf-f18b-4347-8a20-cfe76b66a60d.json
@@ -0,0 +1,7 @@
+{
+ "type": "minor",
+ "comment": "Add Dynamic Type support",
+ "packageName": "@fluentui-react-native/text",
+ "email": "adgleitm@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/package.nuspec b/package.nuspec
index 036df5f17b..bb28b9b883 100644
--- a/package.nuspec
+++ b/package.nuspec
@@ -12,10 +12,12 @@
+
+
diff --git a/packages/components/text/package.json b/packages/components/text/package.json
index e281b32af0..50e1f55f3e 100644
--- a/packages/components/text/package.json
+++ b/packages/components/text/package.json
@@ -28,6 +28,7 @@
"dependencies": {
"@uifabricshared/foundation-compose": "^1.12.22",
"@fluentui-react-native/adapters": ">=0.10.0 <1.0.0",
+ "@fluentui-react-native/experimental-native-font-metrics": "^0.2.0",
"@fluentui-react-native/framework": "0.8.21",
"@fluentui-react-native/interactive-hooks": ">=0.21.2 <1.0.0",
"@fluentui-react-native/theme-tokens": ">=0.21.3 <1.0.0",
diff --git a/packages/components/text/src/Text.tsx b/packages/components/text/src/Text.tsx
index c0e6c3f625..3fb19246eb 100644
--- a/packages/components/text/src/Text.tsx
+++ b/packages/components/text/src/Text.tsx
@@ -15,6 +15,7 @@ import { I18nManager, Platform, Text as RNText } from 'react-native';
import { textName, TextProps, TextTokens } from './Text.types';
import { useTextTokens } from './TextTokens';
import React from 'react';
+import { useFontMetricsScaleFactors } from '@fluentui-react-native/experimental-native-font-metrics';
const emptyProps = {};
export const Text = compressible((props: TextProps, useTokens: UseTokens) => {
@@ -49,6 +50,9 @@ export const Text = compressible((props: TextProps, useTo
// get the tokens from the theme
let [tokens, cache] = useTokens(theme);
+ // TODO(#2268): Remove once RN Core properly supports Dynamic Type scaling
+ const fontMetricsScaleFactors = useFontMetricsScaleFactors();
+
const textAlign = I18nManager.isRTL
? align === 'start'
? 'right'
@@ -79,6 +83,9 @@ export const Text = compressible((props: TextProps, useTo
[onPress, onAccessibilityTap],
);
+ // TODO(#2268): Remove once RN Core properly supports Dynamic Type scaling
+ const dynamicTypeVariant = Platform.OS === 'ios' ? tokens.dynamicTypeRamp : undefined;
+
// override tokens from props
[tokens, cache] = patchTokens(tokens, cache, {
color,
@@ -106,6 +113,19 @@ export const Text = compressible((props: TextProps, useTo
['color', 'fontStyle', 'textAlign', 'textDecorationLine', ...fontStyles.keys],
);
+ // [TODO(#2268): Remove once RN Core properly supports Dynamic Type scaling
+ let scaleStyleAdjustments: TextTokens = emptyProps;
+ // tokenStyle.fontSize and tokenStyle.lineHeight can also be strings (e.g., "14px").
+ // Therefore, we only support scaling for number-based size values in order to avoid any messy calculations.
+ if (dynamicTypeVariant !== undefined && typeof tokenStyle.fontSize === 'number' && typeof tokenStyle.lineHeight === 'number') {
+ const scaleFactor = fontMetricsScaleFactors[dynamicTypeVariant] ?? 1;
+ scaleStyleAdjustments = {
+ fontSize: tokenStyle.fontSize * scaleFactor,
+ lineHeight: tokenStyle.lineHeight * scaleFactor,
+ };
+ }
+ // ]TODO(#2268)
+
const isWinPlatform = Platform.OS === (('win32' as any) || 'windows');
const filteredProps = {
onKeyUp: isWinPlatform ? onKeyUp : undefined,
@@ -124,9 +144,10 @@ export const Text = compressible((props: TextProps, useTo
...keyProps,
...filteredProps,
...extra,
+ ...(dynamicTypeVariant !== undefined && { allowFontScaling: false }), // TODO(#2268): Remove once RN Core properly supports Dynamic Type scaling
onPress,
numberOfLines: truncate || !wrap ? 1 : 0,
- style: mergeStyles(tokenStyle, props.style, extra?.style),
+ style: mergeStyles(tokenStyle, props.style, extra?.style, scaleStyleAdjustments),
};
return (
diff --git a/packages/components/text/src/Text.types.ts b/packages/components/text/src/Text.types.ts
index f2c2ea3af2..ef75c24911 100644
--- a/packages/components/text/src/Text.types.ts
+++ b/packages/components/text/src/Text.types.ts
@@ -8,7 +8,11 @@ export const textName = 'Text';
* Text tokens, these are the internally configurable values for Text elements. In particular these
* drive decisions on how to build the styles
*/
-export type TextTokens = Omit & IForegroundColorTokens & Omit;
+export type TextTokens = Omit &
+ IForegroundColorTokens &
+ Omit & {
+ dynamicTypeRamp?: string; // TODO(#2268): Remove once RN Core properly supports Dynamic Type scaling
+ };
export type TextAlign = 'start' | 'center' | 'end' | 'justify';
export type TextFont = 'base' | 'monospace' | 'numeric';
diff --git a/packages/components/text/src/Variants.ios.ts b/packages/components/text/src/Variants.ios.ts
index 592238c71f..5c1142547c 100644
--- a/packages/components/text/src/Variants.ios.ts
+++ b/packages/components/text/src/Variants.ios.ts
@@ -1,43 +1,57 @@
import { Text } from './Text';
+// TODO(#2268): Remove "as any" designations once RN Core properly supports Dynamic Type scaling
+
export const Caption1 = Text.customize({
variant: 'caption1',
-});
+ dynamicTypeRamp: 'footnote',
+} as any);
export const Caption1Strong = Text.customize({
variant: 'caption1Strong',
-});
+ dynamicTypeRamp: 'footnote',
+} as any);
export const Caption2 = Text.customize({
variant: 'caption2',
-});
+ dynamicTypeRamp: 'caption1',
+} as any);
export const Body1 = Text.customize({
variant: 'body1',
-});
+ dynamicTypeRamp: 'body',
+} as any);
export const Body1Strong = Text.customize({
variant: 'body1Strong',
-});
+ dynamicTypeRamp: 'body',
+} as any);
export const Body2 = Text.customize({
variant: 'body2',
-});
+ dynamicTypeRamp: 'subheadline',
+} as any);
export const Body2Strong = Text.customize({
variant: 'body2Strong',
-});
+ dynamicTypeRamp: 'subheadline',
+} as any);
export const Subtitle1 = null; // Not supported on iOS
export const Subtitle1Strong = null; // Not supported on iOS
export const Subtitle2 = null; // Not supported on iOS
export const Subtitle2Strong = null; // Not supported on iOS
export const Title1 = Text.customize({
variant: 'title1',
-});
+ dynamicTypeRamp: 'title1',
+} as any);
export const Title1Strong = null; // Not supported on iOS
export const Title2 = Text.customize({
variant: 'title2',
-});
+ dynamicTypeRamp: 'title2',
+} as any);
export const Title3 = Text.customize({
variant: 'title3',
-});
+ dynamicTypeRamp: 'title3',
+} as any);
export const LargeTitle = Text.customize({
variant: 'largeTitle',
-});
+ dynamicTypeRamp: 'largeTitle',
+} as any);
export const Display = Text.customize({
variant: 'display',
-});
+ dynamicTypeRamp: 'largeTitle',
+} as any);
diff --git a/packages/experimental/NativeFontMetrics/ios/FRNFontMetrics.m b/packages/experimental/NativeFontMetrics/ios/FRNFontMetrics.m
index 543b60f425..d266b0e706 100644
--- a/packages/experimental/NativeFontMetrics/ios/FRNFontMetrics.m
+++ b/packages/experimental/NativeFontMetrics/ios/FRNFontMetrics.m
@@ -85,7 +85,7 @@ + (BOOL)requiresMainQueueSetup
return YES;
}
-RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(allScaleFactors)
+RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(currentScaleFactors)
{
NSMutableDictionary *result = [NSMutableDictionary new];
[FRNRecognizedTextStyles() enumerateKeysAndObjectsUsingBlock:^(NSString * styleString, __unused NSNumber * boxedTextStyle, __unused BOOL * stop) {
@@ -129,7 +129,7 @@ - (void)stopObserving
- (void)onFontMetricsChanged:(NSNotification *)notification {
if (_hasListeners) {
- [self sendEventWithName:@"onFontMetricsChanged" body:@{@"newScaleFactors": [self allScaleFactors]}];
+ [self sendEventWithName:@"onFontMetricsChanged" body:@{@"newScaleFactors": [self currentScaleFactors]}];
}
}
diff --git a/packages/experimental/NativeFontMetrics/src/NativeFontMetrics.ios.ts b/packages/experimental/NativeFontMetrics/src/NativeFontMetrics.ios.ts
index 250b15d964..d53e52f2ba 100644
--- a/packages/experimental/NativeFontMetrics/src/NativeFontMetrics.ios.ts
+++ b/packages/experimental/NativeFontMetrics/src/NativeFontMetrics.ios.ts
@@ -4,7 +4,8 @@ import { ScaleFactors, TextStyle } from './NativeFontMetrics.types';
export const NativeFontMetrics = NativeModules.FRNFontMetrics;
interface NativeFontMetricsInterface {
- allScaleFactors(): ScaleFactors;
+ currentScaleFactors(): ScaleFactors;
scaleFactorForStyle(style: TextStyle): number;
}
+
export default NativeFontMetrics as NativeFontMetricsInterface;
diff --git a/packages/experimental/NativeFontMetrics/src/NativeFontMetrics.ts b/packages/experimental/NativeFontMetrics/src/NativeFontMetrics.ts
index 709bceb520..6bac794156 100644
--- a/packages/experimental/NativeFontMetrics/src/NativeFontMetrics.ts
+++ b/packages/experimental/NativeFontMetrics/src/NativeFontMetrics.ts
@@ -1,12 +1,11 @@
-import { ScaleFactors, TextStyle } from './NativeFontMetrics.types';
+import { TextStyle } from './NativeFontMetrics.types';
-interface NativeFontMetricsInterface {
- allScaleFactors(): ScaleFactors;
- scaleFactorForStyle(style: TextStyle): number;
-}
-
-const NativeFontMetrics: NativeFontMetricsInterface = {
- allScaleFactors: () => {
+const NativeFontMetrics = {
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ addListener: (_: string) => {},
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ removeListeners: (_: number) => {},
+ currentScaleFactors: () => {
console.warn('NativeFontMetrics is only available on iOS');
return {};
},
diff --git a/packages/experimental/NativeFontMetrics/src/NativeFontMetrics.types.ts b/packages/experimental/NativeFontMetrics/src/NativeFontMetrics.types.ts
index 3c00d12e7a..30b518bec9 100644
--- a/packages/experimental/NativeFontMetrics/src/NativeFontMetrics.types.ts
+++ b/packages/experimental/NativeFontMetrics/src/NativeFontMetrics.types.ts
@@ -1,3 +1,9 @@
+export interface FontMetrics {
+ readonly scaleFactors: ScaleFactors;
+}
+
+export type ScaleFactors = { [K in TextStyle]?: number };
+
export type TextStyle =
| 'caption2'
| 'caption1'
@@ -10,5 +16,3 @@ export type TextStyle =
| 'title2'
| 'title1'
| 'largeTitle';
-
-export type ScaleFactors = { [K in TextStyle]?: number };
diff --git a/packages/experimental/NativeFontMetrics/src/fontMetrics.ios.ts b/packages/experimental/NativeFontMetrics/src/fontMetrics.ios.ts
new file mode 100644
index 0000000000..8177540e44
--- /dev/null
+++ b/packages/experimental/NativeFontMetrics/src/fontMetrics.ios.ts
@@ -0,0 +1,25 @@
+import { NativeEventEmitter } from 'react-native';
+import NativeFontMetrics from './NativeFontMetrics';
+import { FontMetrics, ScaleFactors } from './NativeFontMetrics.types';
+
+class FontMetricsImpl implements FontMetrics {
+ _scaleFactors: ScaleFactors;
+
+ constructor() {
+ if (NativeFontMetrics) {
+ this._scaleFactors = NativeFontMetrics.currentScaleFactors();
+ const eventEmitter = new NativeEventEmitter(NativeFontMetrics as any);
+ eventEmitter.addListener('onFontMetricsChanged', ({ newScaleFactors }) => {
+ this._scaleFactors = newScaleFactors;
+ });
+ } else {
+ this._scaleFactors = {};
+ }
+ }
+
+ get scaleFactors(): ScaleFactors {
+ return this._scaleFactors;
+ }
+}
+
+export const fontMetrics = new FontMetricsImpl() as FontMetrics;
diff --git a/packages/experimental/NativeFontMetrics/src/fontMetrics.ts b/packages/experimental/NativeFontMetrics/src/fontMetrics.ts
new file mode 100644
index 0000000000..24f180d1b1
--- /dev/null
+++ b/packages/experimental/NativeFontMetrics/src/fontMetrics.ts
@@ -0,0 +1,3 @@
+import { FontMetrics } from './NativeFontMetrics.types';
+
+export const fontMetrics = { scaleFactors: {} } as FontMetrics;
diff --git a/packages/experimental/NativeFontMetrics/src/index.ts b/packages/experimental/NativeFontMetrics/src/index.ts
index cefd8c0449..1ec967c30b 100644
--- a/packages/experimental/NativeFontMetrics/src/index.ts
+++ b/packages/experimental/NativeFontMetrics/src/index.ts
@@ -1,2 +1 @@
-export * from './NativeFontMetrics';
export * from './useFontMetrics';
diff --git a/packages/experimental/NativeFontMetrics/src/useFontMetrics.ios.ts b/packages/experimental/NativeFontMetrics/src/useFontMetrics.ios.ts
new file mode 100644
index 0000000000..e2564312da
--- /dev/null
+++ b/packages/experimental/NativeFontMetrics/src/useFontMetrics.ios.ts
@@ -0,0 +1,29 @@
+import { useMemo } from 'react';
+import { NativeEventEmitter } from 'react-native';
+import { useSubscription } from 'use-subscription';
+import { fontMetrics } from './fontMetrics';
+import NativeFontMetrics from './NativeFontMetrics';
+import { ScaleFactors } from './NativeFontMetrics.types';
+
+const eventEmitter = NativeFontMetrics ? new NativeEventEmitter(NativeFontMetrics as any) : undefined;
+
+export function useFontMetricsScaleFactors(): ScaleFactors {
+ if (!eventEmitter) {
+ return {};
+ }
+
+ const subscription = useMemo(
+ () => ({
+ getCurrentValue: () => fontMetrics.scaleFactors,
+ subscribe: (callback) => {
+ const appearanceSubscription = eventEmitter.addListener('onFontMetricsChanged', callback);
+ return () => {
+ appearanceSubscription.remove();
+ };
+ },
+ }),
+ [],
+ );
+
+ return useSubscription(subscription);
+}
diff --git a/packages/experimental/NativeFontMetrics/src/useFontMetrics.ts b/packages/experimental/NativeFontMetrics/src/useFontMetrics.ts
index c391dd29b5..28e7664b11 100644
--- a/packages/experimental/NativeFontMetrics/src/useFontMetrics.ts
+++ b/packages/experimental/NativeFontMetrics/src/useFontMetrics.ts
@@ -1,29 +1,6 @@
-import { useMemo } from 'react';
-import { NativeEventEmitter, Platform } from 'react-native';
-import { useSubscription } from 'use-subscription';
-import NativeFontMetrics from './NativeFontMetrics';
import { ScaleFactors } from './NativeFontMetrics.types';
-const eventEmitter = new NativeEventEmitter(NativeFontMetrics as any);
-
-export function useFontMetrics(): ScaleFactors {
- if (Platform.OS !== 'ios') {
- console.warn('NativeFontMetrics is only available on iOS');
- return {};
- }
-
- const subscription = useMemo(
- () => ({
- getCurrentValue: () => NativeFontMetrics.allScaleFactors(),
- subscribe: (callback) => {
- const appearanceSubscription = eventEmitter.addListener('onFontMetricsChanged', callback);
- return () => {
- appearanceSubscription.remove();
- };
- },
- }),
- [],
- );
-
- return useSubscription(subscription);
+export function useFontMetricsScaleFactors(): ScaleFactors {
+ // Stubbed out for non-iOS platforms
+ return {};
}