Skip to content

Commit

Permalink
Accessible colors for DynamicColorIOS (#31651)
Browse files Browse the repository at this point in the history
Summary:
Allow you to harvest the `UIAccessibilityContrastHigh` trait from iOS to show accessible colors when high contrast mode is enabled.

```jsx
// usage

PlatformColorIOS({
  light: '#eeeeee',
  dark: '#333333',
  highContrastLight: '#ffffff',
  highContrastDark: '#000000',
});

// {
//   "dynamic": {
//     "light": "#eeeeee",
//     "dark": "#333333",
//     "highContrastLight": "#ffffff",
//     "highContrastDark": "#000000",
//   }
// }
```

This is how apple's own dynamic system colors work under the hood (https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/color/#dynamic-system-colors)

 ---

The react native docs mention that more keys may become available in the future, which this PR is adding:

> In the future, more keys might become available for different user preferences, like high contrast.

https://reactnative.dev/docs/dynamiccolorios

## Changelog

<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
https://github.com/facebook/react-native/wiki/Changelog
-->

[iOS] [Added] - High contrast dynamic color options for dark and light mode.

Pull Request resolved: #31651

Test Plan: Added unit tests for `normalizeColor` to pass the high contrast colors downstream to RCTConvert

Reviewed By: lunaleaps

Differential Revision: D28922536

Pulled By: p-sun

fbshipit-source-id: f81417f003c3adefac50e994e62b9be14ffa91a1
  • Loading branch information
birkir authored and kelset committed Jun 16, 2021
1 parent 6b5b72c commit 6287c44
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 7 deletions.
17 changes: 16 additions & 1 deletion Libraries/StyleSheet/PlatformColorValueTypes.ios.js
Expand Up @@ -16,6 +16,8 @@ export opaque type NativeColorValue = {
dynamic?: {
light: ?(ColorValue | ProcessedColorValue),
dark: ?(ColorValue | ProcessedColorValue),
highContrastLight?: ?(ColorValue | ProcessedColorValue),
highContrastDark?: ?(ColorValue | ProcessedColorValue),
},
};

Expand All @@ -26,12 +28,21 @@ export const PlatformColor = (...names: Array<string>): ColorValue => {
export type DynamicColorIOSTuplePrivate = {
light: ColorValue,
dark: ColorValue,
highContrastLight?: ColorValue,
highContrastDark?: ColorValue,
};

export const DynamicColorIOSPrivate = (
tuple: DynamicColorIOSTuplePrivate,
): ColorValue => {
return {dynamic: {light: tuple.light, dark: tuple.dark}};
return {
dynamic: {
light: tuple.light,
dark: tuple.dark,
highContrastLight: tuple.highContrastLight,
highContrastDark: tuple.highContrastDark,
},
};
};

export const normalizeColorObject = (
Expand All @@ -49,6 +60,8 @@ export const normalizeColorObject = (
dynamic: {
light: normalizeColor(dynamic.light),
dark: normalizeColor(dynamic.dark),
highContrastLight: normalizeColor(dynamic.highContrastLight),
highContrastDark: normalizeColor(dynamic.highContrastDark),
},
};
return dynamicColor;
Expand All @@ -67,6 +80,8 @@ export const processColorObject = (
dynamic: {
light: processColor(dynamic.light),
dark: processColor(dynamic.dark),
highContrastLight: processColor(dynamic.highContrastLight),
highContrastDark: processColor(dynamic.highContrastDark),
},
};
return dynamicColor;
Expand Down
9 changes: 8 additions & 1 deletion Libraries/StyleSheet/PlatformColorValueTypesIOS.ios.js
Expand Up @@ -14,8 +14,15 @@ import {DynamicColorIOSPrivate} from './PlatformColorValueTypes';
export type DynamicColorIOSTuple = {
light: ColorValue,
dark: ColorValue,
highContrastLight?: ColorValue,
highContrastDark?: ColorValue,
};

export const DynamicColorIOS = (tuple: DynamicColorIOSTuple): ColorValue => {
return DynamicColorIOSPrivate({light: tuple.light, dark: tuple.dark});
return DynamicColorIOSPrivate({
light: tuple.light,
dark: tuple.dark,
highContrastLight: tuple.highContrastLight,
highContrastDark: tuple.highContrastDark,
});
};
2 changes: 2 additions & 0 deletions Libraries/StyleSheet/PlatformColorValueTypesIOS.js
Expand Up @@ -13,6 +13,8 @@ import type {ColorValue} from './StyleSheet';
export type DynamicColorIOSTuple = {
light: ColorValue,
dark: ColorValue,
highContrastLight?: ColorValue,
highContrastDark?: ColorValue,
};

export const DynamicColorIOS = (tuple: DynamicColorIOSTuple): ColorValue => {
Expand Down
19 changes: 19 additions & 0 deletions Libraries/StyleSheet/__tests__/normalizeColor-test.js
Expand Up @@ -43,6 +43,25 @@ describe('iOS', () => {
expect(normalizedColor).toEqual(expectedColor);
});

it('should normalize iOS Dynamic colors with accessible colors', () => {
const color = DynamicColorIOS({
light: 'black',
dark: 'white',
highContrastLight: 'red',
highContrastDark: 'blue',
});
const normalizedColor = normalizeColor(color);
const expectedColor = {
dynamic: {
light: 'black',
dark: 'white',
highContrastLight: 'red',
highContrastDark: 'blue',
},
};
expect(normalizedColor).toEqual(expectedColor);
});

it('should normalize iOS Dynamic colors with PlatformColor colors', () => {
const color = DynamicColorIOS({
light: PlatformColor('systemBlackColor'),
Expand Down
24 changes: 20 additions & 4 deletions React/Base/RCTConvert.m
Expand Up @@ -879,13 +879,29 @@ + (UIColor *)UIColor:(id)json
UIColor *lightColor = [RCTConvert UIColor:light];
id dark = [appearances objectForKey:@"dark"];
UIColor *darkColor = [RCTConvert UIColor:dark];
id highContrastLight = [appearances objectForKey:@"highContrastLight"];
UIColor *highContrastLightColor = [RCTConvert UIColor:highContrastLight];
id highContrastDark = [appearances objectForKey:@"highContrastDark"];
UIColor *highContrastDarkColor = [RCTConvert UIColor:highContrastDark];
if (lightColor != nil && darkColor != nil) {
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
if (@available(iOS 13.0, *)) {
UIColor *color =
[UIColor colorWithDynamicProvider:^UIColor *_Nonnull(UITraitCollection *_Nonnull collection) {
return collection.userInterfaceStyle == UIUserInterfaceStyleDark ? darkColor : lightColor;
}];
UIColor *color = [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull collection) {
if (collection.userInterfaceStyle == UIUserInterfaceStyleDark) {
if (collection.accessibilityContrast == UIAccessibilityContrastHigh && highContrastDarkColor != nil) {
return highContrastDarkColor;
} else {
return darkColor;
}
} else {
if (collection.accessibilityContrast == UIAccessibilityContrastHigh && highContrastLightColor != nil) {
return highContrastLightColor;
} else {
return lightColor;
}
}
}];
return color;
} else {
#endif
Expand Down
Expand Up @@ -60,7 +60,6 @@ module.exports = {
const properties = args[0].properties;
if (
!(
properties.length === 2 &&
properties[0].type === 'Property' &&
properties[0].key.name === 'light' &&
properties[1].type === 'Property' &&
Expand Down

0 comments on commit 6287c44

Please sign in to comment.