Skip to content

Commit 9b15405

Browse files
committed
Refactor PlatformColor API to use options objects
Replaces the builder proxy pattern for PlatformColor with a simpler API that accepts either a string or an options object, supporting modifiers like alpha, prominence, and contentHeadroom directly. Updates iOS and Android implementations, type exports, and rn-tester examples to use the new object-based API, and adds examples for combined and mixed modifier usage.
1 parent ee503ad commit 9b15405

4 files changed

Lines changed: 148 additions & 173 deletions

File tree

packages/react-native/Libraries/StyleSheet/PlatformColorValueTypes.android.js

Lines changed: 28 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,59 +11,43 @@
1111
import type {ProcessedColorValue} from './processColor';
1212
import type {NativeColorValue} from './StyleSheet';
1313

14+
export type ColorProminence =
15+
| 'primary'
16+
| 'secondary'
17+
| 'tertiary'
18+
| 'quaternary';
19+
20+
export type PlatformColorOptions = {
21+
name: string,
22+
alpha?: number,
23+
prominence?: ColorProminence,
24+
contentHeadroom?: number,
25+
};
26+
27+
export type PlatformColorSpec = string | PlatformColorOptions;
28+
1429
/** The actual type of the opaque NativeColorValue on Android platform */
1530
type LocalNativeColorValue = {
1631
resource_paths?: Array<string>,
1732
};
1833

1934
/**
20-
* Creates a builder proxy with no-op methods for cross-platform compatibility.
21-
* On Android, alpha/prominence/contentHeadroom are iOS-specific and have no effect.
35+
* Extracts color name from a spec (string or options object).
36+
* On Android, only the name is used - modifiers like alpha/prominence are iOS-specific.
2237
*/
23-
function createPlatformColorBuilder(
24-
data: LocalNativeColorValue,
25-
): NativeColorValue {
26-
const handler = {
27-
get(
28-
target: LocalNativeColorValue,
29-
prop: string,
30-
): mixed {
31-
// No-op builder methods for cross-platform compatibility
32-
// Return the builder function only if the property hasn't been set yet
33-
if (prop === 'alpha' || prop === 'prominence' || prop === 'contentHeadroom') {
34-
if (prop in target) {
35-
// $FlowFixMe[incompatible-return]
36-
return target[prop];
37-
}
38-
return (_value: mixed): NativeColorValue => {
39-
// On Android, these modifiers have no effect - just return the same proxy
40-
return new Proxy(target, handler);
41-
};
42-
}
43-
// $FlowFixMe[incompatible-return]
44-
return target[prop];
45-
},
46-
has(target: LocalNativeColorValue, prop: string): boolean {
47-
return prop in target;
48-
},
49-
ownKeys(target: LocalNativeColorValue): Array<string> {
50-
return Object.keys(target);
51-
},
52-
getOwnPropertyDescriptor(
53-
target: LocalNativeColorValue,
54-
prop: string,
55-
): ?{value: mixed, writable: boolean, enumerable: boolean, configurable: boolean} {
56-
return Object.getOwnPropertyDescriptor(target, prop);
57-
},
58-
};
59-
60-
// $FlowExpectedError[incompatible-return] Proxy is compatible with NativeColorValue
61-
return new Proxy(data, handler);
38+
function getColorName(spec: PlatformColorSpec): string {
39+
if (typeof spec === 'string') {
40+
return spec;
41+
}
42+
return spec.name;
6243
}
6344

64-
export const PlatformColor = (...names: Array<string>): NativeColorValue => {
65-
const data: LocalNativeColorValue = {resource_paths: names};
66-
return createPlatformColorBuilder(data);
45+
export const PlatformColor = (
46+
...specs: Array<PlatformColorSpec>
47+
): NativeColorValue => {
48+
const names = specs.map(getColorName);
49+
// $FlowExpectedError[incompatible-return] LocalNativeColorValue is compatible with NativeColorValue
50+
return {resource_paths: names};
6751
};
6852

6953
export const normalizeColorObject = (

packages/react-native/Libraries/StyleSheet/PlatformColorValueTypes.ios.js

Lines changed: 24 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -17,85 +17,42 @@ export type ColorProminence =
1717
| 'tertiary'
1818
| 'quaternary';
1919

20+
export type PlatformColorOptions = {
21+
name: string,
22+
alpha?: number,
23+
prominence?: ColorProminence,
24+
contentHeadroom?: number,
25+
};
26+
27+
export type PlatformColorSpec = string | PlatformColorOptions;
28+
2029
/** The actual type of the opaque NativeColorValue on iOS platform */
2130
type LocalNativeColorValue = {
22-
semantic?: Array<string>,
31+
semantic?: Array<PlatformColorOptions>,
2332
dynamic?: {
2433
light: ?(ColorValue | ProcessedColorValue),
2534
dark: ?(ColorValue | ProcessedColorValue),
2635
highContrastLight?: ?(ColorValue | ProcessedColorValue),
2736
highContrastDark?: ?(ColorValue | ProcessedColorValue),
2837
},
29-
alpha?: number,
30-
prominence?: ColorProminence,
31-
contentHeadroom?: number,
3238
};
3339

3440
/**
35-
* Creates a builder proxy that allows chaining methods while maintaining
36-
* the underlying color data object for native consumption.
41+
* Normalizes a color spec (string or options object) to options object format.
3742
*/
38-
function createPlatformColorBuilder(
39-
data: LocalNativeColorValue,
40-
): NativeColorValue {
41-
const handler = {
42-
get(
43-
target: LocalNativeColorValue,
44-
prop: string,
45-
): mixed {
46-
// For builder method properties, return the value if already set,
47-
// otherwise return a builder function
48-
if (prop === 'alpha') {
49-
if ('alpha' in target) {
50-
return target.alpha;
51-
}
52-
return (value: number): NativeColorValue => {
53-
target.alpha = value;
54-
return new Proxy(target, handler);
55-
};
56-
}
57-
if (prop === 'prominence') {
58-
if ('prominence' in target) {
59-
return target.prominence;
60-
}
61-
return (value: ColorProminence): NativeColorValue => {
62-
target.prominence = value;
63-
return new Proxy(target, handler);
64-
};
65-
}
66-
if (prop === 'contentHeadroom') {
67-
if ('contentHeadroom' in target) {
68-
return target.contentHeadroom;
69-
}
70-
return (value: number): NativeColorValue => {
71-
target.contentHeadroom = value;
72-
return new Proxy(target, handler);
73-
};
74-
}
75-
// $FlowFixMe[incompatible-return]
76-
return target[prop];
77-
},
78-
has(target: LocalNativeColorValue, prop: string): boolean {
79-
return prop in target;
80-
},
81-
ownKeys(target: LocalNativeColorValue): Array<string> {
82-
return Object.keys(target);
83-
},
84-
getOwnPropertyDescriptor(
85-
target: LocalNativeColorValue,
86-
prop: string,
87-
): ?{value: mixed, writable: boolean, enumerable: boolean, configurable: boolean} {
88-
return Object.getOwnPropertyDescriptor(target, prop);
89-
},
90-
};
91-
92-
// $FlowExpectedError[incompatible-return] Proxy is compatible with NativeColorValue
93-
return new Proxy(data, handler);
43+
function normalizeColorSpec(spec: PlatformColorSpec): PlatformColorOptions {
44+
if (typeof spec === 'string') {
45+
return {name: spec};
46+
}
47+
return spec;
9448
}
9549

96-
export const PlatformColor = (...names: Array<string>): NativeColorValue => {
97-
const data: LocalNativeColorValue = {semantic: names};
98-
return createPlatformColorBuilder(data);
50+
export const PlatformColor = (
51+
...specs: Array<PlatformColorSpec>
52+
): NativeColorValue => {
53+
const normalizedSpecs = specs.map(normalizeColorSpec);
54+
// $FlowExpectedError[incompatible-return] LocalNativeColorValue is compatible with NativeColorValue
55+
return {semantic: normalizedSpecs};
9956
};
10057

10158
export type DynamicColorIOSTuplePrivate = {
@@ -108,16 +65,15 @@ export type DynamicColorIOSTuplePrivate = {
10865
export const DynamicColorIOSPrivate = (
10966
tuple: DynamicColorIOSTuplePrivate,
11067
): ColorValue => {
111-
const data: LocalNativeColorValue = {
68+
// $FlowExpectedError[incompatible-return] LocalNativeColorValue is compatible with ColorValue
69+
return {
11270
dynamic: {
11371
light: tuple.light,
11472
dark: tuple.dark,
11573
highContrastLight: tuple.highContrastLight,
11674
highContrastDark: tuple.highContrastDark,
11775
},
11876
};
119-
// $FlowExpectedError[incompatible-return] Proxy is compatible with ColorValue
120-
return createPlatformColorBuilder(data);
12177
};
12278

12379
const _normalizeColorObject = (
@@ -143,15 +99,6 @@ const _normalizeColorObject = (
14399
highContrastDark: normalizeColor(dynamic.highContrastDark),
144100
},
145101
};
146-
if (color.alpha != null) {
147-
dynamicColor.alpha = color.alpha;
148-
}
149-
if (color.prominence != null) {
150-
dynamicColor.prominence = color.prominence;
151-
}
152-
if (color.contentHeadroom != null) {
153-
dynamicColor.contentHeadroom = color.contentHeadroom;
154-
}
155102
return dynamicColor;
156103
}
157104
return null;
@@ -181,15 +128,6 @@ const _processColorObject = (
181128
highContrastDark: processColor(dynamic.highContrastDark),
182129
},
183130
};
184-
if (color.alpha != null) {
185-
dynamicColor.alpha = color.alpha;
186-
}
187-
if (color.prominence != null) {
188-
dynamicColor.prominence = color.prominence;
189-
}
190-
if (color.contentHeadroom != null) {
191-
dynamicColor.contentHeadroom = color.contentHeadroom;
192-
}
193131
return dynamicColor;
194132
}
195133
return color;

packages/react-native/Libraries/StyleSheet/PlatformColorValueTypesIOS.ios.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ import type {ColorValue} from './StyleSheet';
1212

1313
import {DynamicColorIOSPrivate} from './PlatformColorValueTypes.ios';
1414

15-
export type {ColorProminence} from './PlatformColorValueTypes.ios';
15+
export type {
16+
ColorProminence,
17+
PlatformColorOptions,
18+
PlatformColorSpec,
19+
} from './PlatformColorValueTypes.ios';
1620

1721
export type DynamicColorIOSTuple = {
1822
light: ColorValue,

0 commit comments

Comments
 (0)