Skip to content
Draft
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 @@ -24,7 +24,7 @@ boost_config = get_boost_config()
boost_compiler_flags = boost_config[:compiler_flags]

Pod::Spec.new do |s|
source_files = "*.{m,mm,cpp,h}", "platform/ios/**/*.{m,mm,cpp,h}"
source_files = ["*.{m,mm,cpp,h}", "platform/ios/**/*.{m,mm,cpp,h}"]
header_search_paths = [
"\"$(PODS_ROOT)/boost\"",
"\"$(PODS_TARGET_SRCROOT)/../../../\"",
Expand All @@ -43,7 +43,7 @@ Pod::Spec.new do |s|
s.platforms = min_supported_versions
s.source = source
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
s.source_files = source_files
s.source = source
s.header_dir = "react/renderer/graphics"
# [macOS Restrict UIKit to iOS and visionOS
s.ios.framework = "UIKit"
Expand All @@ -54,7 +54,7 @@ Pod::Spec.new do |s|
if ENV['USE_FRAMEWORKS']
s.module_name = "React_graphics"
s.header_mappings_dir = "../../.."
header_search_paths = header_search_paths + ["\"$(PODS_TARGET_SRCROOT)/platform/ios\""]
header_search_paths = header_search_paths + ["\"$(PODS_TARGET_SRCROOT)/platform/ios\"", "\"$(PODS_TARGET_SRCROOT)/platform/macos\""] # [macOS]
end

s.pod_target_xcconfig = { "USE_HEADERMAP" => "NO",
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 @@ -224,6 +251,21 @@ int32_t hashFromUIColor(const std::shared_ptr<void> &uiColor)
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,33 @@
#import "RCTPlatformColorUtils.h"

#import <Foundation/Foundation.h>
#import <TargetConditionals.h>
#import <React/RCTUIKit.h> // [macOS]
#if TARGET_OS_OSX // [macOS
#import <AppKit/AppKit.h>
#endif // macOS]
#import <react/renderer/graphics/HostPlatformColor.h>
#import <react/utils/ManagedObjectWrapper.h>

#include <string>
#include <vector>

NS_ASSUME_NONNULL_BEGIN

static NSString *const kColorSuffix = @"Color";
static NSString *const kFallbackARGBKey = @"fallback-argb";
#if TARGET_OS_OSX // [macOS
static NSString *const kFallbackKey = @"fallback";
static NSString *const kSelectorKey = @"selector";
static NSString *const kIndexKey = @"index";
#endif // macOS]

static NSDictionary<NSString *, NSDictionary *> *_PlatformColorSelectorsDict()
{
static NSDictionary<NSString *, NSDictionary *> *dict;
static dispatch_once_t once_token;
dispatch_once(&once_token, ^(void) {
#if !TARGET_OS_OSX // [macOS]
dict = @{
// https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors
// Label Colors
Expand Down Expand Up @@ -130,6 +141,105 @@
kFallbackARGBKey : @(0x00000000) // iOS 13.0
},
};
#else // [macOS
NSMutableDictionary<NSString *, NSDictionary *> *map = [@{
// https://developer.apple.com/documentation/appkit/nscolor/ui_element_colors
// Label Colors
@"labelColor": @{},
@"secondaryLabelColor": @{},
@"tertiaryLabelColor": @{},
@"quaternaryLabelColor": @{},
// Text Colors
@"textColor": @{},
@"placeholderTextColor": @{},
@"selectedTextColor": @{},
@"textBackgroundColor": @{},
@"selectedTextBackgroundColor": @{},
@"keyboardFocusIndicatorColor": @{},
@"unemphasizedSelectedTextColor": @{
kFallbackKey: @"selectedTextColor"
},
@"unemphasizedSelectedTextBackgroundColor": @{
kFallbackKey: @"textBackgroundColor"
},
// Content Colors
@"linkColor": @{},
@"separatorColor": @{
kFallbackKey: @"gridColor"
},
@"selectedContentBackgroundColor": @{
kFallbackKey: @"alternateSelectedControlColor"
},
@"unemphasizedSelectedContentBackgroundColor": @{
kFallbackKey: @"secondarySelectedControlColor"
},
// Menu Colors
@"selectedMenuItemTextColor": @{},
// Table Colors
@"gridColor": @{},
@"headerTextColor": @{},
@"alternatingEvenContentBackgroundColor": @{
kSelectorKey: @"alternatingContentBackgroundColors",
kIndexKey: @0,
kFallbackKey: @"controlAlternatingRowBackgroundColors"
},
@"alternatingOddContentBackgroundColor": @{
kSelectorKey: @"alternatingContentBackgroundColors",
kIndexKey: @1,
kFallbackKey: @"controlAlternatingRowBackgroundColors"
},
// Control Colors
@"controlAccentColor": @{
kFallbackKey: @"controlColor"
},
@"controlColor": @{},
@"controlBackgroundColor": @{},
@"controlTextColor": @{},
@"disabledControlTextColor": @{},
@"selectedControlColor": @{},
@"selectedControlTextColor": @{},
@"alternateSelectedControlTextColor": @{},
@"scrubberTexturedBackgroundColor": @{},
// Window Colors
@"windowBackgroundColor": @{},
@"windowFrameTextColor": @{},
@"underPageBackgroundColor": @{},
// Highlights and Shadows
@"findHighlightColor": @{
kFallbackKey: @"highlightColor"
},
@"highlightColor": @{},
@"shadowColor": @{},
// https://developer.apple.com/documentation/appkit/nscolor/standard_colors
// Standard Colors
@"systemBlueColor": @{},
@"systemBrownColor": @{},
@"systemGrayColor": @{},
@"systemGreenColor": @{},
@"systemOrangeColor": @{},
@"systemPinkColor": @{},
@"systemPurpleColor": @{},
@"systemRedColor": @{},
@"systemYellowColor": @{},
// Transparent Color
@"clearColor" : @{},
} mutableCopy];

NSMutableDictionary<NSString *, NSDictionary *> *aliases = [NSMutableDictionary new];
for (NSString *objcSelector in map) {
NSMutableDictionary *entry = [map[objcSelector] mutableCopy];
if ([entry objectForKey:kSelectorKey] == nil) {
entry[kSelectorKey] = objcSelector;
}
if ([objcSelector hasSuffix:kColorSuffix]) {
NSString *swiftSelector = [objcSelector substringToIndex:[objcSelector length] - [kColorSuffix length]];
aliases[swiftSelector] = entry;
}
}
[map addEntriesFromDictionary:aliases];

dict = [map copy];
#endif // macOS]
});
return dict;
}
Expand All @@ -154,21 +264,59 @@
NSDictionary<NSString *, NSDictionary *> *platformColorSelectorsDict = _PlatformColorSelectorsDict();
NSDictionary<NSString *, id> *colorInfo = platformColorSelectorsDict[platformColorString];
if (colorInfo) {
#if !TARGET_OS_OSX // [macOS]
SEL objcColorSelector = NSSelectorFromString([platformColorString stringByAppendingString:kColorSuffix]);
if (![RCTUIColor respondsToSelector:objcColorSelector]) { // [macOS]
if (![RCTUIColor respondsToSelector:objcColorSelector]) {
NSNumber *fallbackRGB = colorInfo[kFallbackARGBKey];
if (fallbackRGB) {
return _UIColorFromHexValue(fallbackRGB);
}
} else {
Class uiColorClass = [RCTUIColor class]; // [macOS]
Class uiColorClass = [RCTUIColor class];
IMP imp = [uiColorClass methodForSelector:objcColorSelector];
id (*getUIColor)(id, SEL) = ((id(*)(id, SEL))imp);
id colorObject = getUIColor(uiColorClass, objcColorSelector);
if ([colorObject isKindOfClass:[RCTUIColor class]]) { // [macOS]
if ([colorObject isKindOfClass:[RCTUIColor class]]) {
return colorObject;
}
}
#else // [macOS
NSString *selectorName = colorInfo[kSelectorKey];
if (selectorName == nil) {
selectorName = [platformColorString stringByAppendingString:kColorSuffix];
}

SEL objcColorSelector = NSSelectorFromString(selectorName);
if (![RCTUIColor respondsToSelector:objcColorSelector]) {
NSNumber *fallbackRGB = colorInfo[kFallbackARGBKey];
if (fallbackRGB) {
return _UIColorFromHexValue(fallbackRGB);
}
NSString *fallbackColorName = colorInfo[kFallbackKey];
if (fallbackColorName) {
return _UIColorFromSemanticString(fallbackColorName);
}
} else {
Class colorClass = [RCTUIColor class];
IMP imp = [colorClass methodForSelector:objcColorSelector];
id (*getColor)(id, SEL) = ((id(*)(id, SEL))imp);
id colorObject = getColor(colorClass, objcColorSelector);
if ([colorObject isKindOfClass:[NSArray class]]) {
NSNumber *index = colorInfo[kIndexKey];
if (index != nil) {
NSArray *colors = colorObject;
NSUInteger idx = [index unsignedIntegerValue];
if (idx < colors.count) {
colorObject = colors[idx];
}
}
}

if ([colorObject isKindOfClass:[RCTUIColor class]]) {
return colorObject;
}
}
#endif
}
return nil;
}
Expand Down
Loading
Loading