diff --git a/apps/basic-example/src/App.tsx b/apps/basic-example/src/App.tsx
index fceb9a2ba2..c2c3714337 100644
--- a/apps/basic-example/src/App.tsx
+++ b/apps/basic-example/src/App.tsx
@@ -6,6 +6,7 @@ import Navigator from './Navigator';
import NativeDetector from './NativeDetector';
import RuntimeDecoration from './RuntimeDecoration';
+import ContentsButton from './ContentsButton';
const EXAMPLES = [
{
@@ -16,6 +17,10 @@ const EXAMPLES = [
name: 'Native Detector',
component: NativeDetector,
},
+ {
+ name: 'Contents Button',
+ component: ContentsButton,
+ },
];
const Stack = Navigator.create();
diff --git a/apps/basic-example/src/ContentsButton.tsx b/apps/basic-example/src/ContentsButton.tsx
new file mode 100644
index 0000000000..23906db5ad
--- /dev/null
+++ b/apps/basic-example/src/ContentsButton.tsx
@@ -0,0 +1,522 @@
+import React from 'react';
+import { View, StyleSheet, Text, SafeAreaView } from 'react-native';
+import {
+ GestureHandlerRootView,
+ ScrollView,
+ RectButton,
+} from 'react-native-gesture-handler';
+
+export default function ComplexUI() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const colors = ['#782AEB', '#38ACDD', '#57B495', '#FF6259', '#FFD61E'];
+
+function Avatars() {
+ return (
+
+ {colors.map((color) => (
+
+ {color.slice(1, 3)}
+
+ ))}
+
+ );
+}
+
+function Gallery() {
+ return (
+
+ Basic Gallery
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function SizeConstraints() {
+ return (
+
+ Size Constraints
+
+
+ Min/Max
+
+
+ 1:1
+
+
+ Flex
+
+
+
+ );
+}
+
+function FlexboxTests() {
+ return (
+
+ Flexbox Layouts
+
+
+ Start
+
+
+ Center
+
+
+ End
+
+
+
+
+ Wrap 1
+
+
+ Wrap 2
+
+
+ Wrap 3
+
+
+ Wrap 4
+
+
+
+ );
+}
+
+function PositioningTests() {
+ return (
+
+ Positioning
+
+
+ Z-Index
+
+
+ Absolute
+
+
+ Relative
+
+
+
+ );
+}
+
+function SpacingTests() {
+ return (
+
+ Spacing & Overflow
+
+
+ Padding
+
+
+ Margin
+
+
+ Overflow Hidden Test
+
+
+
+ );
+}
+
+function VisualEffects() {
+ return (
+
+ Visual Effects
+
+
+ Shadow
+
+
+ Opacity
+
+
+
+ );
+}
+
+function ComplexCombinations() {
+ return (
+
+ Complex Combinations
+
+
+ Complex 1
+
+
+ Complex 2
+
+
+ Complex 3
+
+
+ Complex 4
+
+
+
+ );
+}
+
+function Transforms() {
+ return (
+
+ Transform
+
+
+ Transform
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#f5f5f5',
+ },
+ scrollContent: {
+ paddingBottom: 50,
+ },
+ paddedContainer: {
+ padding: 16,
+ },
+ section: {
+ marginBottom: 30,
+ },
+ sectionTitle: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ marginBottom: 12,
+ color: '#333',
+ },
+ gap: {
+ gap: 10,
+ },
+ row: {
+ flexDirection: 'row',
+ },
+
+ // Avatar styles
+ avatars: {
+ width: 90,
+ height: 90,
+ borderWidth: 2,
+ borderColor: '#001A72',
+ borderTopLeftRadius: 30,
+ borderTopRightRadius: 5,
+ borderBottomLeftRadius: 5,
+ borderBottomRightRadius: 30,
+ marginHorizontal: 4,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ avatarLabel: {
+ color: '#F8F9FF',
+ fontSize: 24,
+ fontWeight: 'bold',
+ },
+
+ // Gallery styles
+ fullWidthButton: {
+ width: '100%',
+ height: 160,
+ backgroundColor: '#FF6259',
+ borderTopRightRadius: 30,
+ borderTopLeftRadius: 30,
+ borderWidth: 1,
+ borderColor: '#000',
+ },
+ leftButton: {
+ flex: 1,
+ height: 160,
+ backgroundColor: '#FFD61E',
+ borderBottomLeftRadius: 30,
+ borderWidth: 5,
+ borderColor: '#000',
+ },
+ rightButton: {
+ flex: 1,
+ backgroundColor: '#782AEB',
+ height: 160,
+ borderBottomRightRadius: 30,
+ borderWidth: 8,
+ borderColor: '#000',
+ },
+
+ // Size constraint styles
+ minMaxButton: {
+ minWidth: 80,
+ maxWidth: 120,
+ minHeight: 40,
+ maxHeight: 80,
+ backgroundColor: '#38ACDD',
+ borderRadius: 10,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ aspectRatioButton: {
+ width: 80,
+ aspectRatio: 1,
+ backgroundColor: '#57B495',
+ borderRadius: 40,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ flexGrowButton: {
+ flexGrow: 1,
+ height: 60,
+ backgroundColor: '#FF6259',
+ borderRadius: 15,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+
+ // Flexbox styles
+ flexContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ height: 80,
+ backgroundColor: '#e0e0e0',
+ borderRadius: 10,
+ padding: 10,
+ marginBottom: 10,
+ },
+ flexStart: {
+ alignSelf: 'flex-start',
+ backgroundColor: '#782AEB',
+ padding: 10,
+ borderRadius: 5,
+ },
+ flexCenter: {
+ alignSelf: 'center',
+ backgroundColor: '#38ACDD',
+ padding: 10,
+ borderRadius: 5,
+ },
+ flexEnd: {
+ alignSelf: 'flex-end',
+ backgroundColor: '#57B495',
+ padding: 10,
+ borderRadius: 5,
+ },
+ flexWrapContainer: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ gap: 5,
+ },
+ wrapItem: {
+ width: '48%',
+ height: 50,
+ backgroundColor: '#FFD61E',
+ borderRadius: 8,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+
+ // Positioning styles
+ positionContainer: {
+ height: 80,
+ backgroundColor: '#e0e0e0',
+ borderRadius: 10,
+ position: 'relative',
+ },
+ absoluteButton: {
+ position: 'absolute',
+ top: 10,
+ right: 10,
+ backgroundColor: '#FF6259',
+ padding: 8,
+ borderRadius: 5,
+ },
+ relativeButton: {
+ position: 'relative',
+ top: 20,
+ left: 20,
+ backgroundColor: '#782AEB',
+ padding: 8,
+ borderRadius: 5,
+ },
+ zIndexButton: {
+ position: 'absolute',
+ top: 10,
+ left: 10,
+ zIndex: 10,
+ backgroundColor: '#57B495',
+ padding: 8,
+ borderRadius: 5,
+ },
+
+ // Spacing styles
+ paddingButton: {
+ flex: 1,
+ backgroundColor: '#38ACDD',
+ paddingVertical: 20,
+ paddingHorizontal: 15,
+ borderRadius: 10,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ marginButton: {
+ flex: 1,
+ backgroundColor: '#FFD61E',
+ margin: 10,
+ padding: 10,
+ borderRadius: 10,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ overflowButton: {
+ flex: 1,
+ height: 60,
+ backgroundColor: '#782AEB',
+ borderRadius: 10,
+ overflow: 'hidden',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+
+ // Visual effect styles
+ shadowButton: {
+ flex: 1,
+ height: 60,
+ backgroundColor: '#FF6259',
+ borderRadius: 10,
+ justifyContent: 'center',
+ alignItems: 'center',
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 4 },
+ shadowOpacity: 0.3,
+ shadowRadius: 8,
+ elevation: 8,
+ },
+ opacityButton: {
+ flex: 1,
+ height: 60,
+ backgroundColor: '#782AEB',
+ borderRadius: 10,
+ justifyContent: 'center',
+ alignItems: 'center',
+ opacity: 0.7,
+ },
+
+ // Complex combination styles
+ complexGrid: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ gap: 10,
+ },
+ complexButton1: {
+ width: '48%',
+ height: 100,
+ backgroundColor: '#FF6259',
+ borderRadius: 20,
+ borderWidth: 2,
+ borderColor: '#782AEB',
+ justifyContent: 'center',
+ alignItems: 'center',
+ shadowColor: '#000',
+ shadowOffset: { width: 2, height: 2 },
+ shadowOpacity: 0.2,
+ shadowRadius: 4,
+ elevation: 4,
+ marginBottom: 5,
+ },
+ complexButton2: {
+ width: '48%',
+ minHeight: 80,
+ maxHeight: 120,
+ backgroundColor: '#38ACDD',
+ borderTopLeftRadius: 30,
+ borderBottomRightRadius: 30,
+ paddingVertical: 15,
+ paddingHorizontal: 10,
+ justifyContent: 'center',
+ alignItems: 'center',
+ overflow: 'hidden',
+ },
+ complexButton3: {
+ width: '48%',
+ aspectRatio: 1.5,
+ backgroundColor: '#57B495',
+ borderRadius: 15,
+ borderWidth: 4,
+ borderColor: '#FFD61E',
+ justifyContent: 'center',
+ alignItems: 'center',
+ opacity: 0.9,
+ marginTop: 10,
+ },
+ complexButton4: {
+ width: '48%',
+ height: 80,
+ backgroundColor: '#FFD61E',
+ borderRadius: 10,
+ borderWidth: 1,
+ borderColor: '#FF6259',
+ justifyContent: 'center',
+ alignItems: 'center',
+ shadowColor: '#782AEB',
+ shadowOffset: { width: 0, height: 6 },
+ shadowOpacity: 0.4,
+ shadowRadius: 10,
+ elevation: 10,
+ marginTop: 10,
+ },
+
+ // Transform styles
+ transformButton: {
+ width: '48%',
+ height: 100,
+ backgroundColor: '#38ACDD',
+ borderRadius: 15,
+ justifyContent: 'center',
+ alignItems: 'center',
+ transform: [{ translateX: 100 }, { rotate: '15deg' }, { scale: 1.1 }],
+ },
+
+ // Text styles
+ buttonText: {
+ color: 'white',
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ longText: {
+ color: 'white',
+ fontWeight: 'bold',
+ textAlign: 'center',
+ fontSize: 16,
+ },
+});
diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/RNGestureHandlerPackage.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/RNGestureHandlerPackage.kt
index 95f071671f..d5c7001e20 100644
--- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/RNGestureHandlerPackage.kt
+++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/RNGestureHandlerPackage.kt
@@ -11,6 +11,7 @@ import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import com.facebook.react.uimanager.ViewManager
import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager
+import com.swmansion.gesturehandler.react.RNGestureHandlerButtonWrapperViewManager
import com.swmansion.gesturehandler.react.RNGestureHandlerDetectorViewManager
import com.swmansion.gesturehandler.react.RNGestureHandlerModule
import com.swmansion.gesturehandler.react.RNGestureHandlerRootViewManager
@@ -34,6 +35,9 @@ class RNGestureHandlerPackage :
RNGestureHandlerDetectorViewManager.REACT_CLASS to ModuleSpec.viewManagerSpec {
RNGestureHandlerDetectorViewManager()
},
+ RNGestureHandlerButtonWrapperViewManager.REACT_CLASS to ModuleSpec.viewManagerSpec {
+ RNGestureHandlerButtonWrapperViewManager()
+ },
)
}
@@ -41,6 +45,7 @@ class RNGestureHandlerPackage :
RNGestureHandlerRootViewManager(),
RNGestureHandlerButtonViewManager(),
RNGestureHandlerDetectorViewManager(),
+ RNGestureHandlerButtonWrapperViewManager(),
)
override fun getViewManagerNames(reactContext: ReactApplicationContext) = viewManagers.keys.toList()
diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonWrapperViewManager.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonWrapperViewManager.kt
new file mode 100644
index 0000000000..7137243bd7
--- /dev/null
+++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonWrapperViewManager.kt
@@ -0,0 +1,30 @@
+package com.swmansion.gesturehandler.react
+
+import com.facebook.react.module.annotations.ReactModule
+import com.facebook.react.uimanager.ThemedReactContext
+import com.facebook.react.uimanager.ViewGroupManager
+import com.facebook.react.uimanager.ViewManagerDelegate
+import com.facebook.react.viewmanagers.RNGestureHandlerButtonWrapperManagerDelegate
+import com.facebook.react.viewmanagers.RNGestureHandlerButtonWrapperManagerInterface
+import com.facebook.react.views.view.ReactViewGroup
+
+@ReactModule(name = RNGestureHandlerButtonWrapperViewManager.REACT_CLASS)
+class RNGestureHandlerButtonWrapperViewManager :
+ ViewGroupManager(),
+ RNGestureHandlerButtonWrapperManagerInterface {
+ private val mDelegate: ViewManagerDelegate =
+ RNGestureHandlerButtonWrapperManagerDelegate<
+ ReactViewGroup,
+ RNGestureHandlerButtonWrapperViewManager,
+ >(this)
+
+ override fun getDelegate(): ViewManagerDelegate = mDelegate
+
+ override fun getName() = REACT_CLASS
+
+ override fun createViewInstance(reactContext: ThemedReactContext) = ReactViewGroup(reactContext)
+
+ companion object {
+ const val REACT_CLASS = "RNGestureHandlerButtonWrapper"
+ }
+}
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.h
new file mode 100644
index 0000000000..c94ecf946e
--- /dev/null
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.h
@@ -0,0 +1,19 @@
+#if !TARGET_OS_OSX
+#import
+#else
+#import
+#endif
+
+#import
+
+#import
+
+using namespace facebook::react;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RNGestureHandlerButtonWrapper : RCTViewComponentView
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.mm
new file mode 100644
index 0000000000..526c7743d6
--- /dev/null
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.mm
@@ -0,0 +1,46 @@
+#import "RNGestureHandlerButtonWrapper.h"
+#import "RNGestureHandlerButtonWrapperComponentDescriptor.h"
+#import "RNGestureHandlerModule.h"
+
+#import
+#import
+
+#import
+#import
+#import
+
+@interface RNGestureHandlerButtonWrapper ()
+@end
+
+@implementation RNGestureHandlerButtonWrapper
+
+#if TARGET_OS_OSX
++ (BOOL)shouldBeRecycled
+{
+ return NO;
+}
+#endif
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+ if (self = [super initWithFrame:frame]) {
+ static const auto defaultProps = std::make_shared();
+ _props = defaultProps;
+ }
+
+ return self;
+}
+
+#pragma mark - RCTComponentViewProtocol
+
++ (ComponentDescriptorProvider)componentDescriptorProvider
+{
+ return concreteComponentDescriptorProvider();
+}
+
+@end
+
+Class RNGestureHandlerButtonWrapperCls(void)
+{
+ return RNGestureHandlerButtonWrapper.class;
+}
diff --git a/packages/react-native-gesture-handler/package.json b/packages/react-native-gesture-handler/package.json
index e6bdb0fd70..e0e7269ee5 100644
--- a/packages/react-native-gesture-handler/package.json
+++ b/packages/react-native-gesture-handler/package.json
@@ -129,7 +129,8 @@
"ios": {
"componentProvider": {
"RNGestureHandlerButton": "RNGestureHandlerButtonComponentView",
- "RNGestureHandlerDetector": "RNGestureHandlerDetector"
+ "RNGestureHandlerDetector": "RNGestureHandlerDetector",
+ "RNGestureHandlerButtonWrapper": "RNGestureHandlerButtonWrapper"
}
}
},
diff --git a/packages/react-native-gesture-handler/react-native.config.js b/packages/react-native-gesture-handler/react-native.config.js
index 4ec41154cd..46e3de4682 100644
--- a/packages/react-native-gesture-handler/react-native.config.js
+++ b/packages/react-native-gesture-handler/react-native.config.js
@@ -2,7 +2,10 @@ module.exports = {
dependency: {
platforms: {
android: {
- componentDescriptors: ['RNGestureHandlerDetectorComponentDescriptor'],
+ componentDescriptors: [
+ 'RNGestureHandlerDetectorComponentDescriptor',
+ 'RNGestureHandlerButtonWrapperComponentDescriptor',
+ ],
cmakeListsPath: './CMakeLists.txt',
},
},
diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/ComponentDescriptors.h b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/ComponentDescriptors.h
index d9f3374c48..5621db20e7 100644
--- a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/ComponentDescriptors.h
+++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/ComponentDescriptors.h
@@ -15,12 +15,14 @@
#include
#include
+#include
#include
namespace facebook::react {
using RNGestureHandlerButtonComponentDescriptor =
ConcreteComponentDescriptor;
+
using RNGestureHandlerRootViewComponentDescriptor =
ConcreteComponentDescriptor;
diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperComponentDescriptor.h b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperComponentDescriptor.h
new file mode 100644
index 0000000000..c3323ae823
--- /dev/null
+++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperComponentDescriptor.h
@@ -0,0 +1,32 @@
+
+/**
+ * This code was generated by
+ * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
+ *
+ * Do not edit this file as changes may cause incorrect behavior and will be
+ * lost once the code is regenerated.
+ *
+ * @generated by codegen project: GenerateComponentDescriptorH.js
+ */
+
+#pragma once
+
+#include
+
+#include "RNGestureHandlerButtonWrapperShadowNode.h"
+
+namespace facebook::react {
+
+class RNGestureHandlerButtonWrapperComponentDescriptor final
+ : public ConcreteComponentDescriptor<
+ RNGestureHandlerButtonWrapperShadowNode> {
+ using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
+ void adopt(ShadowNode &shadowNode) const override {
+ react_native_assert(
+ dynamic_cast(&shadowNode));
+
+ ConcreteComponentDescriptor::adopt(shadowNode);
+ }
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.cpp b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.cpp
new file mode 100644
index 0000000000..9b327dad51
--- /dev/null
+++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.cpp
@@ -0,0 +1,78 @@
+
+#include
+
+#include "RNGestureHandlerButtonWrapperShadowNode.h"
+
+namespace facebook::react {
+
+extern const char RNGestureHandlerButtonWrapperComponentName[] =
+ "RNGestureHandlerButtonWrapper";
+
+void RNGestureHandlerButtonWrapperShadowNode::initialize() {
+ // When the button wrapper is cloned and has a child node, the child node
+ // should be cloned as well to ensure it is mutable.
+ if (!getChildren().empty()) {
+ prepareChildren();
+ }
+}
+
+void RNGestureHandlerButtonWrapperShadowNode::prepareChildren() {
+ const auto &children = getChildren();
+ react_native_assert(
+ children.size() == 1 &&
+ "RNGestureHandlerButtonWrapper received more than one child");
+
+ const auto directChild = children[0];
+ react_native_assert(
+ directChild->getChildren().size() == 1 &&
+ "RNGestureHandlerButtonWrapper received more than one grandchild");
+
+ const auto clonedChild = directChild->clone({});
+
+ const auto childWithProtectedAccess =
+ std::static_pointer_cast(
+ clonedChild);
+ childWithProtectedAccess->traits_.unset(ShadowNodeTraits::ForceFlattenView);
+
+ replaceChild(*directChild, clonedChild);
+
+ const auto grandChild = clonedChild->getChildren()[0];
+ const auto clonedGrandChild = grandChild->clone({});
+ clonedChild->replaceChild(*grandChild, clonedGrandChild);
+}
+
+void RNGestureHandlerButtonWrapperShadowNode::appendChild(
+ const std::shared_ptr &child) {
+ YogaLayoutableShadowNode::appendChild(child);
+ prepareChildren();
+}
+
+void RNGestureHandlerButtonWrapperShadowNode::layout(
+ LayoutContext layoutContext) {
+ YogaLayoutableShadowNode::layout(layoutContext);
+ react_native_assert(getChildren().size() == 1);
+ react_native_assert(getChildren()[0]->getChildren().size() == 1);
+
+ auto child = std::static_pointer_cast(
+ getChildren()[0]);
+ auto grandChild = std::static_pointer_cast(
+ child->getChildren()[0]);
+
+ child->ensureUnsealed();
+ grandChild->ensureUnsealed();
+
+ auto mutableChild = std::const_pointer_cast(child);
+ auto mutableGrandChild =
+ std::const_pointer_cast(grandChild);
+
+ // TODO: figure out the correct way to setup metrics between button wrapper
+ // and the child
+ auto metrics = grandChild->getLayoutMetrics();
+ mutableChild->setLayoutMetrics(metrics);
+
+ auto metricsNoOrigin = grandChild->getLayoutMetrics();
+ metricsNoOrigin.frame.origin = Point{};
+ mutableGrandChild->setLayoutMetrics(metricsNoOrigin);
+}
+
+} // namespace facebook::react
diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.h b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.h
new file mode 100644
index 0000000000..79c87ea6bd
--- /dev/null
+++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#include "RNGestureHandlerButtonWrapperState.h"
+
+namespace facebook::react {
+
+JSI_EXPORT extern const char RNGestureHandlerButtonWrapperComponentName[];
+
+/*
+ * `ShadowNode` for component.
+ */
+class RNGestureHandlerButtonWrapperShadowNode final
+ : public ConcreteViewShadowNode<
+ RNGestureHandlerButtonWrapperComponentName,
+ RNGestureHandlerButtonWrapperProps,
+ RNGestureHandlerButtonWrapperEventEmitter,
+ RNGestureHandlerButtonWrapperState> {
+ public:
+ RNGestureHandlerButtonWrapperShadowNode(
+ const ShadowNodeFragment &fragment,
+ const ShadowNodeFamily::Shared &family,
+ ShadowNodeTraits traits)
+ : ConcreteViewShadowNode(fragment, family, traits) {
+ initialize();
+ }
+
+ RNGestureHandlerButtonWrapperShadowNode(
+ const ShadowNode &sourceShadowNode,
+ const ShadowNodeFragment &fragment)
+ : ConcreteViewShadowNode(sourceShadowNode, fragment) {
+ initialize();
+ }
+
+ void layout(LayoutContext layoutContext) override;
+ void appendChild(const std::shared_ptr &child) override;
+
+ private:
+ void initialize();
+ void prepareChildren();
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperState.h b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperState.h
new file mode 100644
index 0000000000..7d9fa242e0
--- /dev/null
+++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperState.h
@@ -0,0 +1,32 @@
+/**
+ * This code was generated by
+ * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
+ *
+ * Do not edit this file as changes may cause incorrect behavior and will be
+ * lost once the code is regenerated.
+ *
+ * @generated by codegen project: GenerateStateH.js
+ */
+#pragma once
+
+#ifdef ANDROID
+#include
+#endif
+
+namespace facebook::react {
+
+class RNGestureHandlerButtonWrapperState {
+ public:
+ RNGestureHandlerButtonWrapperState() = default;
+
+#ifdef ANDROID
+ RNGestureHandlerButtonWrapperState(
+ RNGestureHandlerButtonWrapperState const &previousState,
+ folly::dynamic data){};
+ folly::dynamic getDynamic() const {
+ return {};
+ };
+#endif
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx b/packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx
index b6f0c391c0..a3e1a6026a 100644
--- a/packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx
+++ b/packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx
@@ -1,5 +1,147 @@
-import { HostComponent } from 'react-native';
+import { HostComponent, StyleSheet, View } from 'react-native';
import type { RawButtonProps } from './GestureButtonsProps';
import RNGestureHandlerButtonNativeComponent from '../specs/RNGestureHandlerButtonNativeComponent';
+import RNGestureHandlerButtonWrapperNativeComponent from '../specs/RNGestureHandlerButtonWrapperNativeComponent';
+import { useMemo } from 'react';
-export default RNGestureHandlerButtonNativeComponent as HostComponent;
+const ButtonComponent =
+ RNGestureHandlerButtonNativeComponent as HostComponent;
+
+export default function GestureHandlerButton({
+ style,
+ ...rest
+}: RawButtonProps) {
+ const flattenedStyle = useMemo(() => StyleSheet.flatten(style), [style]);
+
+ const {
+ // Layout properties
+ display,
+ width,
+ height,
+ minWidth,
+ maxWidth,
+ minHeight,
+ maxHeight,
+ flex,
+ flexGrow,
+ flexShrink,
+ flexBasis,
+ flexDirection,
+ flexWrap,
+ justifyContent,
+ alignItems,
+ alignContent,
+ alignSelf,
+ aspectRatio,
+ gap,
+ rowGap,
+ columnGap,
+ margin,
+ marginTop,
+ marginBottom,
+ marginLeft,
+ marginRight,
+ marginVertical,
+ marginHorizontal,
+ marginStart,
+ marginEnd,
+ padding,
+ paddingTop,
+ paddingBottom,
+ paddingLeft,
+ paddingRight,
+ paddingVertical,
+ paddingHorizontal,
+ paddingStart,
+ paddingEnd,
+ position,
+ top,
+ right,
+ bottom,
+ left,
+ start,
+ end,
+ overflow,
+
+ // Visual properties
+ ...restStyle
+ } = flattenedStyle;
+
+ // Layout styles for ButtonComponent
+ const layoutStyle = useMemo(
+ () => ({
+ display,
+ width,
+ height,
+ minWidth,
+ maxWidth,
+ minHeight,
+ maxHeight,
+ flex,
+ flexGrow,
+ flexShrink,
+ flexBasis,
+ flexDirection,
+ flexWrap,
+ justifyContent,
+ alignItems,
+ alignContent,
+ alignSelf,
+ aspectRatio,
+ gap,
+ rowGap,
+ columnGap,
+ margin,
+ marginTop,
+ marginBottom,
+ marginLeft,
+ marginRight,
+ marginVertical,
+ marginHorizontal,
+ marginStart,
+ marginEnd,
+ padding,
+ paddingTop,
+ paddingBottom,
+ paddingLeft,
+ paddingRight,
+ paddingVertical,
+ paddingHorizontal,
+ paddingStart,
+ paddingEnd,
+ position,
+ top,
+ right,
+ bottom,
+ left,
+ start,
+ end,
+ overflow,
+ }),
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [flattenedStyle]
+ );
+
+ return (
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ contents: {
+ display: 'contents',
+ },
+ overflowHidden: {
+ overflow: 'hidden',
+ },
+});
diff --git a/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonWrapperNativeComponent.ts b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonWrapperNativeComponent.ts
new file mode 100644
index 0000000000..d84e610aaa
--- /dev/null
+++ b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonWrapperNativeComponent.ts
@@ -0,0 +1,11 @@
+import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
+import type { ViewProps } from 'react-native';
+
+interface NativeProps extends ViewProps {}
+
+export default codegenNativeComponent(
+ 'RNGestureHandlerButtonWrapper',
+ {
+ interfaceOnly: true,
+ }
+);