diff --git a/Gemfile.lock b/Gemfile.lock index e304f8287968..dce502adf470 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,11 +3,12 @@ GEM specs: CFPropertyList (3.0.6) rexml - activesupport (7.0.8) + activesupport (6.1.7.10) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) + zeitwerk (~> 2.3) addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) @@ -17,12 +18,12 @@ GEM benchmark (0.4.0) bigdecimal (3.1.9) claide (1.1.0) - cocoapods (1.14.2) + cocoapods (1.15.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.14.2) + cocoapods-core (= 1.15.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 2.0) + cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-trunk (>= 1.6.0, < 2.0) @@ -35,7 +36,7 @@ GEM nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.14.2) + cocoapods-core (1.15.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -46,7 +47,7 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-downloader (2.0) + cocoapods-downloader (2.1) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -81,13 +82,14 @@ GEM ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - xcodeproj (1.23.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) rexml (~> 3.2.4) + zeitwerk (2.6.18) PLATFORMS ruby @@ -106,4 +108,4 @@ RUBY VERSION ruby 3.2.0p0 BUNDLED WITH - 2.4.12 + 2.4.6 diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts b/packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts index 62803cabce7e..c90d2f69e94c 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts @@ -71,6 +71,25 @@ export interface TVViewPropsIOS { } export interface ViewPropsIOS extends TVViewPropsIOS { + /** + * Controls how iOS determines the shape used by the legacy `shadow*` + * properties. This does not affect `boxShadow`. + * + * - `auto`: Uses an optimized border-box path when the view has an opaque + * background. Otherwise derives the shadow from the composited content alpha. + * - `border-box`: Always uses the view's bounds and resolved border radii. + * This avoids content-alpha offscreen rendering, but callers must guarantee + * that the intended shadow silhouette matches the view's border box. + * - `content-alpha`: Always derives the shadow from the rendered alpha of the + * view and its children. This supports irregular silhouettes but may require + * offscreen rendering. + * + * When omitted, `auto` is used. + * + * @platform ios + */ + shadowPathIOS?: 'auto' | 'border-box' | 'content-alpha' | undefined; + /** * Whether this view should be rendered as a bitmap before compositing. * diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.js b/packages/react-native/Libraries/Components/View/ViewPropTypes.js index 36cb09adf1a5..1d8a7ee78877 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.js +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.js @@ -394,6 +394,25 @@ export type TVViewPropsIOS = $ReadOnly<{ }>; export type ViewPropsIOS = $ReadOnly<{ + /** + * Controls how iOS determines the shape used by the legacy `shadow*` + * properties. This does not affect `boxShadow`. + * + * - `auto`: Uses an optimized border-box path when the view has an opaque + * background. Otherwise derives the shadow from the composited content alpha. + * - `border-box`: Always uses the view's bounds and resolved border radii. + * This avoids content-alpha offscreen rendering, but callers must guarantee + * that the intended shadow silhouette matches the view's border box. + * - `content-alpha`: Always derives the shadow from the rendered alpha of the + * view and its children. This supports irregular silhouettes but may require + * offscreen rendering. + * + * When omitted, `auto` is used. + * + * @platform ios + */ + shadowPathIOS?: ?('auto' | 'border-box' | 'content-alpha'), + /** * Whether this `View` should be rendered as a bitmap before compositing. * diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js index 5c96ddeabeff..4e7438b12e52 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js @@ -206,6 +206,7 @@ const validAttributesForNonEventProps = { shadowColor: {process: require('../StyleSheet/processColor').default}, shadowOffset: {diff: require('../Utilities/differ/sizesDiffer').default}, shadowOpacity: true, + shadowPathIOS: true, shadowRadius: true, needsOffscreenAlphaCompositing: true, overflow: true, diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 48f497bce262..61e9aa6e4425 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -22,6 +22,7 @@ #import #import #import +#import #import #import #import @@ -272,6 +273,11 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & needsInvalidateLayer = YES; } + // `shadowPathIOS` + if (oldViewProps.shadowPathIOS != newViewProps.shadowPathIOS) { + needsInvalidateLayer = YES; + } + // `shadowRadius` if (oldViewProps.shadowRadius != newViewProps.shadowRadius) { self.layer.shadowRadius = (CGFloat)newViewProps.shadowRadius; @@ -829,20 +835,36 @@ - (void)invalidateLayer // Stage 1. Shadow Path BOOL const layerHasShadow = layer.shadowOpacity > 0 && CGColorGetAlpha(layer.shadowColor) > 0; - if (layerHasShadow) { - if (CGColorGetAlpha(_backgroundColor.CGColor) > 0.999) { - // If view has a solid background color, calculate shadow path from border. - const RCTCornerInsets cornerInsets = - RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero); - CGPathRef shadowPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, nil, NO); - layer.shadowPath = shadowPath; - CGPathRelease(shadowPath); - } else { - // Can't accurately calculate box shadow, so fall back to pixel-based shadow. - layer.shadowPath = nil; - } - } else { + if (!layerHasShadow) { layer.shadowPath = nil; + } else { + ShadowPathMode shadowPathMode = resolveShadowPathMode(_props->shadowPathIOS); + RCTCornerRadii cornerRadii = RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii); + const RCTCornerInsets cornerInsets = RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero); + + switch (shadowPathMode) { + case ShadowPathMode::BorderBox: { + CGPathRef shadowPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, nil, NO); + layer.shadowPath = shadowPath; + CGPathRelease(shadowPath); + break; + } + case ShadowPathMode::ContentAlpha: + layer.shadowPath = nil; + break; + case ShadowPathMode::Auto: + if (CGColorGetAlpha(_backgroundColor.CGColor) > 0.999) { + CGPathRef shadowPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, nil, NO); + layer.shadowPath = shadowPath; + CGPathRelease(shadowPath); + } else { + layer.shadowPath = nil; + } + break; + case ShadowPathMode::Default: + react_native_assert(false); + break; + } } #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170000 /* __IPHONE_17_0 */ diff --git a/packages/react-native/React/Views/RCTViewManager.m b/packages/react-native/React/Views/RCTViewManager.m index f017cb87caae..6094303e0e23 100644 --- a/packages/react-native/React/Views/RCTViewManager.m +++ b/packages/react-native/React/Views/RCTViewManager.m @@ -439,6 +439,13 @@ - (void)updateAccessibilityTraitsForRole:(RCTView *)view withDefaultView:(RCTVie // filtered by view configs. } +RCT_CUSTOM_VIEW_PROPERTY(shadowPathIOS, NSString *, RCTView) +{ + // Property is only to be used in the new renderer. + // It is necessary to add it here, otherwise it gets + // filtered by view configs. +} + RCT_CUSTOM_VIEW_PROPERTY(mixBlendMode, NSString *, RCTView) { // Property is only to be used in the new renderer. diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp index 5bf3c6e4179e..0ae053486178 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -180,6 +180,15 @@ BaseViewProps::BaseViewProps( "shadowOpacity", sourceProps.shadowOpacity, {})), + shadowPathIOS( + ReactNativeFeatureFlags::enableCppPropsIteratorSetter() + ? sourceProps.shadowPathIOS + : convertRawProp( + context, + rawProps, + "shadowPathIOS", + sourceProps.shadowPathIOS, + ShadowPathMode::Default)), shadowRadius( ReactNativeFeatureFlags::enableCppPropsIteratorSetter() ? sourceProps.shadowRadius @@ -379,6 +388,7 @@ void BaseViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(shadowColor); RAW_SET_PROP_SWITCH_CASE_BASIC(shadowOffset); RAW_SET_PROP_SWITCH_CASE_BASIC(shadowOpacity); + RAW_SET_PROP_SWITCH_CASE_BASIC(shadowPathIOS); RAW_SET_PROP_SWITCH_CASE_BASIC(shadowRadius); RAW_SET_PROP_SWITCH_CASE_BASIC(transform); RAW_SET_PROP_SWITCH_CASE_BASIC(backfaceVisibility); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h index 6258d1375dae..e59eeda50f54 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -63,6 +63,7 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps { SharedColor shadowColor{}; Size shadowOffset{0, -3}; Float shadowOpacity{}; + ShadowPathMode shadowPathIOS{ShadowPathMode::Default}; Float shadowRadius{3}; Cursor cursor{}; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp index 19e6f2106e00..0d2e0be786a0 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp @@ -69,6 +69,7 @@ void ViewShadowNode::initialize() noexcept { isColorMeaningful(viewProps.backgroundColor) || hasBorder() || !viewProps.testId.empty() || !viewProps.boxShadow.empty() || !viewProps.backgroundImage.empty() || + viewProps.shadowPathIOS != ShadowPathMode::Default || HostPlatformViewTraitsInitializer::formsView(viewProps) || viewProps.outlineWidth > 0; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h b/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h index 349b1e332e03..39301d055ea7 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h @@ -961,6 +961,34 @@ inline void fromRawValue( react_native_expect(false); } +inline void fromRawValue( + const PropsParserContext& context, + const RawValue& value, + ShadowPathMode& result) { + result = ShadowPathMode::Default; + react_native_expect(value.hasType()); + if (!value.hasType()) { + return; + } + + auto stringValue = (std::string)value; + if (stringValue == "auto") { + result = ShadowPathMode::Auto; + return; + } + if (stringValue == "border-box") { + result = ShadowPathMode::BorderBox; + return; + } + if (stringValue == "content-alpha") { + result = ShadowPathMode::ContentAlpha; + return; + } + + LOG(ERROR) << "Could not parse ShadowPathMode:" << stringValue; + react_native_expect(false); +} + inline void fromRawValue( const PropsParserContext& context, const RawValue& value, diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h index 96d379b6c4bf..9b556e877284 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h @@ -94,6 +94,21 @@ enum class BorderStyle : uint8_t { Solid, Dotted, Dashed }; enum class OutlineStyle : uint8_t { Solid, Dotted, Dashed }; +enum class ShadowPathMode : uint8_t { + Default, + Auto, + BorderBox, + ContentAlpha, +}; + +inline ShadowPathMode resolveShadowPathMode(ShadowPathMode propMode) { + if (propMode == ShadowPathMode::Default) { + return ShadowPathMode::Auto; + } + + return propMode; +} + struct CornerRadii { float vertical{0.0f}; float horizontal{0.0f}; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/tests/ConversionsTest.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/tests/ConversionsTest.cpp index a21bade8c222..2ebd7db09be8 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/tests/ConversionsTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/tests/ConversionsTest.cpp @@ -9,9 +9,38 @@ #include #include +#include namespace facebook::react { +TEST(ConversionsTest, shadow_path_modes) { + PropsParserContext context{-1, ContextContainer{}}; + ShadowPathMode result = ShadowPathMode::Default; + RawValue autoValue{folly::dynamic("auto")}; + RawValue borderBoxValue{folly::dynamic("border-box")}; + RawValue contentAlphaValue{folly::dynamic("content-alpha")}; + + fromRawValue(context, autoValue, result); + EXPECT_EQ(result, ShadowPathMode::Auto); + + fromRawValue(context, borderBoxValue, result); + EXPECT_EQ(result, ShadowPathMode::BorderBox); + + fromRawValue(context, contentAlphaValue, result); + EXPECT_EQ(result, ShadowPathMode::ContentAlpha); +} + +TEST(ConversionsTest, resolves_default_shadow_path_mode) { + EXPECT_EQ( + resolveShadowPathMode(ShadowPathMode::Default), + ShadowPathMode::Auto); + EXPECT_EQ( + resolveShadowPathMode(ShadowPathMode::Auto), ShadowPathMode::Auto); + EXPECT_EQ( + resolveShadowPathMode(ShadowPathMode::ContentAlpha), + ShadowPathMode::ContentAlpha); +} + TEST(ConversionsTest, unprocessed_box_shadow_string) { RawValue value{ folly::dynamic("10px 2px 0 5px #fff, inset 20px 10px 5px 0 #000")}; diff --git a/packages/rn-tester/Gemfile b/packages/rn-tester/Gemfile index 03087ee0ccd9..bc9f6785d6e7 100644 --- a/packages/rn-tester/Gemfile +++ b/packages/rn-tester/Gemfile @@ -16,3 +16,4 @@ gem 'bigdecimal' gem 'logger' gem 'benchmark' gem 'mutex_m' +gem 'nkf' diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index c140a0d4d73b..55750f568f1e 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -2752,7 +2752,7 @@ SPEC CHECKSUMS: React-timing: 1e6a8acb66e2b7ac9d418956617fd1fdb19322fd React-utils: 52bbb03f130319ef82e4c3bc7a85eaacdb1fec87 ReactAppDependencyProvider: 433ddfb4536948630aadd5bd925aff8a632d2fe3 - ReactCodegen: cb2a3dea7ad220568d9453e17d275dbd9f9bc044 + ReactCodegen: f9a16823d5e01de57cc449c9451d6ddc2a424f64 ReactCommon: 394c6b92765cf6d211c2c3f7f6bc601dffb316a6 ReactCommon-Samples: d083c8092a5ad0df33b1ca589cb2d909fbd6914e ScreenshotManager: cd9e43b0c749ba38101effc22be58f4a69440d5f @@ -2761,4 +2761,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: b0abc972ac4c3a335250548a31500196b1a2c163 -COCOAPODS: 1.16.2 +COCOAPODS: 1.15.2 diff --git a/packages/rn-tester/js/examples/Playground/PlaygroundExample.js b/packages/rn-tester/js/examples/Playground/PlaygroundExample.js index b622cf822f8d..8b1d2ea730d0 100644 --- a/packages/rn-tester/js/examples/Playground/PlaygroundExample.js +++ b/packages/rn-tester/js/examples/Playground/PlaygroundExample.js @@ -11,8 +11,12 @@ import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; import Playground from './RNTesterPlayground'; +import ShadowPathPlayground from './RNTesterShadowPathPlayground'; export const title = Playground.title; export const framework = 'React'; export const description = 'Test out new features and ideas.'; -export const examples: Array = [Playground]; +export const examples: Array = [ + Playground, + ShadowPathPlayground, +]; diff --git a/packages/rn-tester/js/examples/Playground/RNTesterShadowPathPlayground.js b/packages/rn-tester/js/examples/Playground/RNTesterShadowPathPlayground.js new file mode 100644 index 000000000000..c10dbf45c59d --- /dev/null +++ b/packages/rn-tester/js/examples/Playground/RNTesterShadowPathPlayground.js @@ -0,0 +1,273 @@ +/** + * 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 + */ + +import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; + +import * as React from 'react'; +import {useEffect, useRef, useState} from 'react'; +import { + Animated, + Pressable, + StyleSheet, + Switch, + Text, + View, +} from 'react-native'; + +type ShadowPathMode = 'auto' | 'border-box' | 'content-alpha'; +type ShadowPathSelection = 'default' | ShadowPathMode; + +const SHADOW_PATH_OPTIONS: $ReadOnlyArray<{ + label: string, + value: ShadowPathSelection, +}> = [ + {label: 'Default', value: 'default'}, + {label: 'Auto', value: 'auto'}, + {label: 'Border Box', value: 'border-box'}, + {label: 'Content Alpha', value: 'content-alpha'}, +]; + +function ShadowPathPlayground(): React.Node { + const [selectedMode, setSelectedMode] = + useState('default'); + const [animationEnabled, setAnimationEnabled] = useState(false); + const animationProgressRef = useRef(null); + if (animationProgressRef.current == null) { + animationProgressRef.current = new Animated.Value(0); + } + const animationProgress = animationProgressRef.current; + + useEffect(() => { + if (!animationEnabled) { + animationProgress.stopAnimation(); + animationProgress.setValue(0); + return; + } + + const growAnimation = Animated.timing(animationProgress, { + duration: 700, + toValue: 1, + useNativeDriver: true, + }); + const shrinkAnimation = Animated.timing(animationProgress, { + duration: 700, + toValue: 0, + useNativeDriver: true, + }); + const animationSequence = Animated.sequence([ + growAnimation, + shrinkAnimation, + ]); + const animation = Animated.loop(animationSequence); + animation.start(); + + return () => { + animation.stop(); + }; + }, [animationEnabled, animationProgress]); + + const scale = animationProgress.interpolate({ + inputRange: [0, 1], + outputRange: [1, 1.12], + }); + const animatedStyle = {transform: [{scale}]}; + const shadowPathProps: {shadowPathIOS?: ShadowPathMode} = + selectedMode === 'default' ? {} : {shadowPathIOS: selectedMode}; + const activeMode = SHADOW_PATH_OPTIONS.find( + option => option.value === selectedMode, + ); + + if (activeMode == null) { + throw new Error('Selected shadow path mode must have a label.'); + } + + return ( + + + {SHADOW_PATH_OPTIONS.map(option => { + const selected = option.value === selectedMode; + return ( + setSelectedMode(option.value)} + style={[ + styles.segment, + selected ? styles.selectedSegment : null, + ]}> + + {option.label} + + + ); + })} + + + + Scale animation + + + + Active mode: {activeMode.label} + Omitted default: Auto + + Translucent button hierarchy + + + + Translucent button + + + + + Irregular composited content + + Content Alpha follows the circles. Border Box intentionally shadows the + host bounds instead. + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + gap: 12, + padding: 16, + }, + segmentedControl: { + borderColor: '#8e8e93', + borderRadius: 8, + borderWidth: StyleSheet.hairlineWidth, + flexDirection: 'row', + overflow: 'hidden', + }, + segment: { + alignItems: 'center', + borderColor: '#8e8e93', + borderRightWidth: StyleSheet.hairlineWidth, + flex: 1, + justifyContent: 'center', + minHeight: 38, + paddingHorizontal: 4, + }, + selectedSegment: { + backgroundColor: '#0a84ff', + }, + segmentLabel: { + color: '#3c3c43', + fontSize: 12, + textAlign: 'center', + }, + selectedSegmentLabel: { + color: 'white', + fontWeight: '600', + }, + animationRow: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + }, + label: { + fontSize: 16, + }, + status: { + color: '#636366', + fontVariant: ['tabular-nums'], + }, + heading: { + fontSize: 17, + fontWeight: '600', + marginTop: 8, + }, + explanation: { + color: '#636366', + lineHeight: 19, + }, + sampleStage: { + alignItems: 'center', + backgroundColor: '#d1d1d6', + justifyContent: 'center', + minHeight: 140, + overflow: 'visible', + }, + buttonShadowHost: { + borderRadius: 8, + shadowColor: 'black', + shadowOffset: {width: 0, height: 1}, + shadowOpacity: 0.14, + shadowRadius: 4, + }, + translucentButton: { + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.12)', + borderRadius: 8, + justifyContent: 'center', + minHeight: 40, + minWidth: 160, + overflow: 'hidden', + }, + buttonLabel: { + color: '#1c1c1e', + fontWeight: '600', + }, + irregularShadowHost: { + height: 72, + position: 'relative', + shadowColor: 'black', + shadowOffset: {width: 0, height: 1}, + shadowOpacity: 0.25, + shadowRadius: 4, + width: 160, + }, + circle: { + backgroundColor: '#ff9f0a', + borderRadius: 28, + height: 56, + position: 'absolute', + top: 8, + width: 56, + }, + leftCircle: { + left: 8, + }, + centerCircle: { + backgroundColor: '#ff375f', + left: 52, + }, + rightCircle: { + backgroundColor: '#bf5af2', + left: 96, + }, +}); + +export default ({ + title: 'iOS Shadow Path', + name: 'ios-shadow-path', + description: + 'Compares legacy iOS shadow path modes and their dynamic-shadow behavior.', + platform: 'ios', + render: (): React.Node => , +}: RNTesterModuleExample); diff --git a/packages/rn-tester/package.json b/packages/rn-tester/package.json index 9078a93920cc..d4f5e7208a5a 100644 --- a/packages/rn-tester/package.json +++ b/packages/rn-tester/package.json @@ -53,9 +53,6 @@ "components": { "RNTMyNativeView": { "className": "RNTMyNativeViewComponentView" - }, - "CustomView": { - "className": "CustomViewComponentView" } } }