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
2 changes: 1 addition & 1 deletion docsite/api/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ slug: /

Welcome to the React Native macOS API reference documentation. This section covers macOS-specific props and events that extend the standard React Native components.

Most of the additional functionality out of React Native macOS directly is in the form of additional props and callback events implemented on `<View>`, to provide macOS and desktop specific behavior
Most of the additional functionality out of React Native macOS directly is in the form of additional props and callback events implemented on `<View>`, to provide macOS and desktop specific behavior. We also have some additional APIs, like platform specific colors.
50 changes: 50 additions & 0 deletions docsite/api/platform-color.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
sidebar_label: 'Platform Colors'
sidebar_position: 2
---

# Platform Colors

React Native macOS extends the core `PlatformColor` API with helpers that map directly to AppKit system colors. These helpers make it easier to adopt macOS appearance and accessibility behaviors without writing native code.

## `DynamicColorMacOS`

`DynamicColorMacOS` creates a color that automatically adapts to light, dark, and high-contrast appearances on macOS.

:::note
`DynamicColorIOS` works on macOS too, they are essentially equivalent
:::

| Option | Description |
| -------------------- | --------------------------------------------------------------- |
| `light` | Color used in the standard light appearance. |
| `dark` | Color used in the standard dark appearance. |
| `highContrastLight` | Optional color for high-contrast light mode. Defaults to `light`.|
| `highContrastDark` | Optional color for high-contrast dark mode. Defaults to `dark`. |

## `ColorWithSystemEffectMacOS`

`ColorWithSystemEffectMacOS(color, effect)` wraps an existing color so AppKit can apply control state effects such as pressed, disabled, or rollover.

| Parameter | Description |
| --------- | ----------- |
| `color` | A string produced by `PlatformColor`, `DynamicColorMacOS`, or a CSS color string. |
| `effect` | One of `none`, `pressed`, `deepPressed`, `disabled`, or `rollover`. |

```javascript
import {
ColorWithSystemEffectMacOS,
DynamicColorMacOS,
PlatformColor,
StyleSheet,
} from 'react-native';

const styles = StyleSheet.create({
buttonPressed: {
backgroundColor: ColorWithSystemEffectMacOS(
PlatformColor('controlColor'),
'pressed',
),
},
});
```
1 change: 1 addition & 0 deletions docsite/sidebarsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
const sidebars: SidebarsConfig = {
apiSidebar: [
'intro',
'platform-color',
'view-props',
'view-events',
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,19 @@ struct DynamicColor {
int32_t highContrastDarkColor = 0;
};

#if TARGET_OS_OSX // [macOS
struct ColorWithSystemEffect {
int32_t color = 0;
std::string effect;
};
#endif // macOS]

struct Color {
Color(int32_t color);
Color(const DynamicColor& dynamicColor);
#if TARGET_OS_OSX // [macOS
Color(const ColorWithSystemEffect& colorWithSystemEffect);
#endif // macOS]
Color(const ColorComponents& components);
Color() : uiColor_(nullptr){};
int32_t getColor() const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,33 @@

namespace facebook::react {

#if TARGET_OS_OSX // [macOS
RCTUIColor *_Nullable UIColorFromColorWithSystemEffect(
RCTUIColor *baseColor,
const std::string &systemEffectString)
{
if (baseColor == nil) {
return nil;
}

NSColor *colorWithEffect = baseColor;
if (!systemEffectString.empty()) {
if (systemEffectString == "none") {
colorWithEffect = [baseColor colorWithSystemEffect:NSColorSystemEffectNone];
} else if (systemEffectString == "pressed") {
colorWithEffect = [baseColor colorWithSystemEffect:NSColorSystemEffectPressed];
} else if (systemEffectString == "deepPressed") {
colorWithEffect = [baseColor colorWithSystemEffect:NSColorSystemEffectDeepPressed];
} else if (systemEffectString == "disabled") {
colorWithEffect = [baseColor colorWithSystemEffect:NSColorSystemEffectDisabled];
} else if (systemEffectString == "rollover") {
colorWithEffect = [baseColor colorWithSystemEffect:NSColorSystemEffectRollover];
}
}
return colorWithEffect;
}
#endif // macOS]

namespace {

bool UIColorIsP3ColorSpace(const std::shared_ptr<void> &uiColor)
Expand Down Expand Up @@ -120,7 +147,20 @@
int32_t ColorFromUIColor(RCTPlatformColor *color) // [macOS]
{
CGFloat rgba[4];
#if !TARGET_OS_OSX // [macOS]
[color getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
#else // [macOS
// Resolve dynamic/semantic colors against the current effective appearance
// so that dark mode colors are correctly extracted.
[[NSApp effectiveAppearance] performAsCurrentDrawingAppearance:^{
NSColor *resolvedColor = [color colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
if (resolvedColor) {
[resolvedColor getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];

Check failure on line 158 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, newarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 158 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, newarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 158 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, newarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 158 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, newarch

cannot refer to declaration with an array type inside block

Check failure on line 158 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, oldarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 158 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, oldarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 158 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, oldarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 158 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, oldarch

cannot refer to declaration with an array type inside block
} else {
[color getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];

Check failure on line 160 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, newarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 160 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, newarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 160 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, newarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 160 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, newarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 160 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, oldarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 160 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, oldarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 160 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, oldarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')

Check failure on line 160 in packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

View workflow job for this annotation

GitHub Actions / Build RNTester / macos, oldarch

cannot initialize a parameter of type 'CGFloat * _Nullable' (aka 'double *') with an rvalue of type 'const CGFloat *' (aka 'const double *')
}
}];
#endif // macOS]
return ColorFromColorComponents({(float)rgba[0], (float)rgba[1], (float)rgba[2], (float)rgba[3]});
}

Expand Down Expand Up @@ -170,9 +210,7 @@
return 0;
}

#if TARGET_OS_OSX // [macOS]
return ColorFromUIColor(uiColor);
#else // [macOS
#if !TARGET_OS_OSX // [macOS]
static UITraitCollection *darkModeTraitCollection =
[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
auto darkColor = ColorFromUIColorForSpecificTraitCollection(uiColor, darkModeTraitCollection);
Expand Down Expand Up @@ -202,6 +240,32 @@
darkAccessibilityContrastColor,
lightAccessibilityContrastColor,
UIColorIsP3ColorSpace(uiColor));
#else // [macOS
// Hash both light and dark appearance colors to properly distinguish
// dynamic colors that change with appearance.
RCTPlatformColor *color = (RCTPlatformColor *)unwrapManagedObject(uiColor);
__block int32_t darkColor = 0;
__block int32_t lightColor = 0;

[[NSAppearance appearanceNamed:NSAppearanceNameDarkAqua] performAsCurrentDrawingAppearance:^{
NSColor *resolved = [color colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
if (resolved) {
CGFloat rgba[4];
[resolved getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
darkColor = ColorFromColorComponents({(float)rgba[0], (float)rgba[1], (float)rgba[2], (float)rgba[3]});
}
}];

[[NSAppearance appearanceNamed:NSAppearanceNameAqua] performAsCurrentDrawingAppearance:^{
NSColor *resolved = [color colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
if (resolved) {
CGFloat rgba[4];
[resolved getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
lightColor = ColorFromColorComponents({(float)rgba[0], (float)rgba[1], (float)rgba[2], (float)rgba[3]});
}
}];

return facebook::react::hash_combine(darkColor, lightColor);
#endif // macOS]
}

Expand All @@ -224,6 +288,21 @@
0);
}

#if TARGET_OS_OSX // [macOS
Color::Color(const ColorWithSystemEffect &colorWithSystemEffect)
{
RCTUIColor *baseColor = UIColorFromInt32(colorWithSystemEffect.color);
RCTUIColor *colorWithEffect =
UIColorFromColorWithSystemEffect(baseColor, colorWithSystemEffect.effect);
if (colorWithEffect != nil) {
uiColor_ = wrapManagedObject(colorWithEffect);
}
uiColorHashValue_ = facebook::react::hash_combine(
colorWithSystemEffect.color,
std::hash<std::string>{}(colorWithSystemEffect.effect));
}
#endif // macOS]

Color::Color(const ColorComponents &components)
{
uiColor_ = wrapManagedObject(UIColorFromComponentsColor(components));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,24 @@ SharedColor parsePlatformColor(const ContextContainer &contextContainer, int32_t
items.at("dynamic").hasType<std::unordered_map<std::string, RawValue>>()) {
auto dynamicItems = (std::unordered_map<std::string, RawValue>)items.at("dynamic");
return RCTPlatformColorComponentsFromDynamicItems(contextContainer, surfaceId, dynamicItems);
#if TARGET_OS_OSX // [macOS
} else if (
items.find("colorWithSystemEffect") != items.end() &&
items.at("colorWithSystemEffect").hasType<std::unordered_map<std::string, RawValue>>()) {
auto colorWithSystemEffectItems =
(std::unordered_map<std::string, RawValue>)items.at("colorWithSystemEffect");
if (colorWithSystemEffectItems.find("baseColor") != colorWithSystemEffectItems.end() &&
colorWithSystemEffectItems.find("systemEffect") != colorWithSystemEffectItems.end() &&
colorWithSystemEffectItems.at("systemEffect").hasType<std::string>()) {
SharedColor baseColorShared{};
fromRawValue(contextContainer, surfaceId, colorWithSystemEffectItems.at("baseColor"), baseColorShared);
if (baseColorShared) {
std::string systemEffect = (std::string)colorWithSystemEffectItems.at("systemEffect");
auto baseColor = (*baseColorShared).getColor();
return SharedColor(Color(ColorWithSystemEffect{baseColor, systemEffect}));
}
}
#endif // macOS]
}
}

Expand Down
Loading
Loading