Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,27 @@
import type {ProcessedColorValue} from './processColor';
import type {NativeColorValue} from './StyleSheet';

import parsePlatformColorArgs from './parsePlatformColorArgs';

/** The actual type of the opaque NativeColorValue on Android platform */
type LocalNativeColorValue = {
resource_paths?: Array<string>,
fallback?: string,
};

export const PlatformColor = (...names: Array<string>): NativeColorValue => {
export const PlatformColor = (
...args: Array<string | {fallback: string}>
): NativeColorValue => {
const {names, fallback} = parsePlatformColorArgs(args);
// Raw fallback (when present) is passed to native untouched and only parsed
// on a token miss.
const color: LocalNativeColorValue =
fallback == null
? {resource_paths: names}
: {resource_paths: names, fallback};
/* $FlowExpectedError[incompatible-type]
* LocalNativeColorValue is the actual type of the opaque NativeColorValue on Android platform */
return {resource_paths: names} as LocalNativeColorValue;
return color as LocalNativeColorValue;
};

export const normalizeColorObject = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ import {OpaqueColorValue} from './StyleSheet';
*
* @see https://reactnative.dev/docs/platformcolor#example
*/
export function PlatformColor(...colors: string[]): OpaqueColorValue;
export function PlatformColor(
...colors: Array<string | {fallback: string}>
): OpaqueColorValue;
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
import type {ProcessedColorValue} from './processColor';
import type {ColorValue, NativeColorValue} from './StyleSheet';

import parsePlatformColorArgs from './parsePlatformColorArgs';

/** The actual type of the opaque NativeColorValue on iOS platform */
type LocalNativeColorValue = {
semantic?: Array<string>,
fallback?: string,
dynamic?: {
light: ?(ColorValue | ProcessedColorValue),
dark: ?(ColorValue | ProcessedColorValue),
Expand All @@ -22,9 +25,16 @@ type LocalNativeColorValue = {
},
};

export const PlatformColor = (...names: Array<string>): NativeColorValue => {
export const PlatformColor = (
...args: Array<string | {fallback: string}>
): NativeColorValue => {
const {names, fallback} = parsePlatformColorArgs(args);
// Raw fallback (when present) is passed to native untouched and only parsed
// on a token miss.
const color: LocalNativeColorValue =
fallback == null ? {semantic: names} : {semantic: names, fallback};
// $FlowExpectedError[incompatible-type] LocalNativeColorValue is the iOS LocalNativeColorValue type
return {semantic: names} as LocalNativeColorValue;
return color as LocalNativeColorValue;
};

export type DynamicColorIOSTuplePrivate = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type {NativeColorValue} from './StyleSheet';
* @see https://reactnative.dev/docs/platformcolor#example
*/
declare export function PlatformColor(
...names: Array<string>
...names: Array<string | {fallback: string}>
): NativeColorValue;

declare export function normalizeColorObject(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,21 @@ describe('processColor', () => {
const expectedColor = {dynamic: {light: 0xff000000, dark: 0xffffffff}};
expect(processedColor).toEqual(expectedColor);
});

// The macOS and Windows PlatformColor entry points carry the fallback with
// the same trailing-{fallback} detection as iOS and Android. This
// integration test harness only executes as iOS and Android, so that
// shared behavior is exercised by the iOS and Android cases here rather
// than duplicated for platforms the harness cannot run.
it('should carry an unprocessed fallback on iOS PlatformColor colors', () => {
const color = PlatformColorIOS('systemRedColor', {fallback: '#ff0000'});
const processedColor = processColor(color);
const expectedColor = {
semantic: ['systemRedColor'],
fallback: '#ff0000',
};
expect(processedColor).toEqual(expectedColor);
});
}
});

Expand All @@ -120,6 +135,18 @@ describe('processColor', () => {
const expectedColor = {resource_paths: ['?attr/colorPrimary']};
expect(processedColor).toEqual(expectedColor);
});

it('should carry an unprocessed fallback on Android PlatformColor colors', () => {
const color = PlatformColorAndroid('?attr/colorPrimary', {
fallback: '#000000',
});
const processedColor = processColor(color);
const expectedColor = {
resource_paths: ['?attr/colorPrimary'],
fallback: '#000000',
};
expect(processedColor).toEqual(expectedColor);
});
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

/**
* Splits the variadic `PlatformColor(...)` arguments into the leading color
* token names and the optional trailing `{fallback}` options object. The
* per-platform `PlatformColor` implementations differ only in the native object
* they build from this result, so the argument parsing is shared here.
*/
export default function parsePlatformColorArgs(
args: Array<string | {fallback: string}>,
): {names: Array<string>, fallback: ?string} {
const lastArg = args[args.length - 1];
if (__DEV__) {
args.forEach((arg, index) => {
if (typeof arg !== 'object' || arg == null) {
return;
}
if (index !== args.length - 1) {
console.error(
'PlatformColor: an options object is only honored as the final argument; one in any other position is ignored.',
);
} else if (typeof arg.fallback !== 'string') {
console.error(
'PlatformColor: the trailing options object must be of the form {fallback: string}; it is ignored.',
);
}
});
}
// The {fallback} options object is only honored as the trailing argument.
const fallback =
lastArg != null &&
typeof lastArg === 'object' &&
typeof lastArg.fallback === 'string'
? lastArg.fallback
: null;
// Collect the leading string tokens; a non-string non-trailing arg (a lint
// error) is dropped.
const names: Array<string> = [];
const nameCount = fallback == null ? args.length : args.length - 1;
for (let i = 0; i < nameCount; i++) {
const arg = args[i];
if (typeof arg === 'string') {
names.push(arg);
}
}
return {names, fallback};
}
13 changes: 10 additions & 3 deletions packages/react-native/ReactNativeApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<d895833bed3d3c274a6647773deaa023>>
* @generated SignedSource<<53280bff7703ea0e9e912e22543fd568>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
Expand Down Expand Up @@ -3491,7 +3491,14 @@ declare class PixelRatio {
static startDetecting(): void
}
declare type Platform = typeof Platform
declare function PlatformColor(...names: Array<string>): NativeColorValue
declare function PlatformColor(
...names: Array<
| string
| {
fallback: string
}
>
): NativeColorValue
declare type PlatformConfig = {}
declare type PlatformOSType =
"android" | "ios" | "macos" | "native" | "web" | "windows"
Expand Down Expand Up @@ -5866,7 +5873,7 @@ export {
PermissionsAndroid, // 8a0bc8d8
PixelRatio, // 10d9e32d
Platform, // b73caa89
PlatformColor, // 8297ec62
PlatformColor, // d083d341
PlatformOSType, // 0a17561e
PlatformSelectSpec, // 09ed7758
PointValue, // 69db075f
Expand Down
Loading