From 7b1aeae0fa00d4cb716bd7c86348a421d46ebf72 Mon Sep 17 00:00:00 2001 From: Xavier Costa Date: Tue, 19 May 2026 16:32:56 +0100 Subject: [PATCH] feat: add showScrollIndicators prop to inline in app message --- .../InlineInAppMessageViewManager.kt | 5 +++++ .../ReactInlineInAppMessageView.kt | 22 +++++++++++++++++++ .../customerio-reactnative.api.md | 4 +++- .../inapp/inline/RCTInlineMessageNative.mm | 7 ++++++ .../inapp/inline/ReactInlineMessageView.h | 3 +++ .../inapp/inline/ReactInlineMessageView.swift | 19 ++++++++++++++++ src/components/InlineInAppMessageView.tsx | 4 +++- .../InlineMessageNativeComponent.ts | 2 ++ 8 files changed, 64 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt index c976e0e9..83531f48 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt @@ -50,6 +50,11 @@ class InlineInAppMessageViewManager : view?.elementId = value } + @ReactProp(name = "showScrollIndicators", defaultBoolean = true) + override fun setShowScrollIndicators(view: ReactInlineInAppMessageView?, value: Boolean) { + view?.showScrollIndicators = value + } + companion object { internal const val NAME = "InlineMessageNative" } diff --git a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/ReactInlineInAppMessageView.kt b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/ReactInlineInAppMessageView.kt index 7b3b8682..140cf7cd 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/ReactInlineInAppMessageView.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/ReactInlineInAppMessageView.kt @@ -2,6 +2,8 @@ package io.customer.reactnative.sdk.messaginginapp import android.content.Context import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup import androidx.annotation.AttrRes import androidx.annotation.StyleRes import io.customer.messaginginapp.type.InAppMessage @@ -29,6 +31,26 @@ class ReactInlineInAppMessageView @JvmOverloads constructor( setActionListener(this) } + var showScrollIndicators: Boolean = true + set(value) { + if (field == value) return + field = value + applyScrollIndicators(this) + } + + override fun onViewAdded(child: View) { + super.onViewAdded(child) + applyScrollIndicators(child) + } + + private fun applyScrollIndicators(view: View) { + view.isVerticalScrollBarEnabled = showScrollIndicators + view.isHorizontalScrollBarEnabled = showScrollIndicators + if (view is ViewGroup) { + for (i in 0 until view.childCount) applyScrollIndicators(view.getChildAt(i)) + } + } + override fun onActionClick(message: InAppMessage, actionValue: String, actionName: String) { val payload = mapOf( "message" to mapOf( diff --git a/api-extractor-output/customerio-reactnative.api.md b/api-extractor-output/customerio-reactnative.api.md index c17a5e49..0e6dc2ee 100644 --- a/api-extractor-output/customerio-reactnative.api.md +++ b/api-extractor-output/customerio-reactnative.api.md @@ -14,6 +14,7 @@ import { TurboModule } from 'react-native'; import type { UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes'; import type { ViewProps } from 'react-native'; import { ViewStyle } from 'react-native'; +import type { WithDefault } from 'react-native/Libraries/Types/CodegenTypes'; // @public export type CioConfig = { @@ -204,13 +205,14 @@ export const InlineInAppMessageView: React_2.FC; // Warning: (ae-forgotten-export) The symbol "NativeProps" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export interface InlineInAppMessageViewProps extends Omit { +export interface InlineInAppMessageViewProps extends Omit { loadingComponent?: React_2.ReactNode; loadingContainerStyle?: ViewStyle; loadingIndicatorProps?: ActivityIndicatorProps & { minimumHeight?: number; }; onActionClick?: (message: InAppMessage, actionValue: string, actionName: string) => void; + showScrollIndicators?: boolean; } // @public diff --git a/ios/wrappers/inapp/inline/RCTInlineMessageNative.mm b/ios/wrappers/inapp/inline/RCTInlineMessageNative.mm index 3c522f2f..1942cbd1 100644 --- a/ios/wrappers/inapp/inline/RCTInlineMessageNative.mm +++ b/ios/wrappers/inapp/inline/RCTInlineMessageNative.mm @@ -51,9 +51,11 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & // On updates, diff against the parameter rather than _props so we never rely on // the ivar's runtime type. BOOL elementIdChanged = YES; + BOOL showScrollIndicatorsChanged = YES; if (oldProps) { const auto &oldViewProps = *std::static_pointer_cast(oldProps); elementIdChanged = oldViewProps.elementId != newViewProps.elementId; + showScrollIndicatorsChanged = oldViewProps.showScrollIndicators != newViewProps.showScrollIndicators; } if (elementIdChanged) { @@ -62,6 +64,11 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & [self.bridge setElementId:elementId]; } + if (showScrollIndicatorsChanged) { + [self assertBridgeAvailable:@"updating showScrollIndicators prop"]; + [self.bridge setShowScrollIndicators:newViewProps.showScrollIndicators]; + } + [super updateProps:props oldProps:oldProps]; } diff --git a/ios/wrappers/inapp/inline/ReactInlineMessageView.h b/ios/wrappers/inapp/inline/ReactInlineMessageView.h index 53a6e01a..98151f72 100644 --- a/ios/wrappers/inapp/inline/ReactInlineMessageView.h +++ b/ios/wrappers/inapp/inline/ReactInlineMessageView.h @@ -28,6 +28,9 @@ NS_ASSUME_NONNULL_BEGIN /// Sets the element ID for the inline message - (void)setElementId:(NSString *)elementId; +/// Sets container scroll indicators visibility +- (void)setShowScrollIndicators:(BOOL)showScrollIndicators; + /// Prepares the view for reuse in React Native lifecycle - (void)setupForReuse; diff --git a/ios/wrappers/inapp/inline/ReactInlineMessageView.swift b/ios/wrappers/inapp/inline/ReactInlineMessageView.swift index eb77f36b..2fbf6b7b 100644 --- a/ios/wrappers/inapp/inline/ReactInlineMessageView.swift +++ b/ios/wrappers/inapp/inline/ReactInlineMessageView.swift @@ -1,6 +1,7 @@ import CioMessagingInApp import Foundation import UIKit +import WebKit /// React Native wrapper for inline message display with content view lifecycle management. /// @@ -29,6 +30,14 @@ class ReactInlineMessageView: NSObject { contentView.elementId = elementId } + private var showScrollIndicators: Bool = true + + @objc + func setShowScrollIndicators(_ showScrollIndicators: Bool) { + self.showScrollIndicators = showScrollIndicators + applyScrollIndicators(in: contentView) + } + @objc func setupForReuse() { contentView.onViewAttached() @@ -106,6 +115,7 @@ extension ReactInlineMessageView: InlineMessageBridgeViewDelegate { } func onMessageSizeChanged(width: CGFloat, height: CGFloat) { + applyScrollIndicators(in: contentView) sendOnSizeChangeEvent(width: width, height: height) } @@ -114,6 +124,7 @@ extension ReactInlineMessageView: InlineMessageBridgeViewDelegate { } func onStartLoading(onComplete: @escaping () -> Void) { + applyScrollIndicators(in: contentView) sendOnStateChangeEvent(state: .loadingStarted) onComplete() } @@ -121,4 +132,12 @@ extension ReactInlineMessageView: InlineMessageBridgeViewDelegate { func onFinishLoading() { sendOnStateChangeEvent(state: .loadingFinished) } + + private func applyScrollIndicators(in view: UIView) { + if let webView = view as? WKWebView { + webView.scrollView.showsVerticalScrollIndicator = showScrollIndicators + webView.scrollView.showsHorizontalScrollIndicator = showScrollIndicators + } + view.subviews.forEach { applyScrollIndicators(in: $0) } + } } diff --git a/src/components/InlineInAppMessageView.tsx b/src/components/InlineInAppMessageView.tsx index 9f7c7d31..28b334eb 100644 --- a/src/components/InlineInAppMessageView.tsx +++ b/src/components/InlineInAppMessageView.tsx @@ -22,7 +22,7 @@ import type { InAppMessage } from '../types'; /** @public */ export interface InlineInAppMessageViewProps extends Omit< NativeProps, - 'onSizeChange' | 'onStateChange' | 'onActionClick' + 'onSizeChange' | 'onStateChange' | 'onActionClick' | 'showScrollIndicators' > { /** Custom loading component to display while message is loading */ loadingComponent?: React.ReactNode; @@ -33,6 +33,8 @@ export interface InlineInAppMessageViewProps extends Omit< }; /** Custom styles for the loading container */ loadingContainerStyle?: ViewStyle; + /** Controls whether the inline message's scroll indicators are visible. (default: true) */ + showScrollIndicators?: boolean; /** Callback triggered when a user clicks an action button in the inline message */ onActionClick?: ( message: InAppMessage, diff --git a/src/specs/components/InlineMessageNativeComponent.ts b/src/specs/components/InlineMessageNativeComponent.ts index 6ac66ba0..645c833c 100644 --- a/src/specs/components/InlineMessageNativeComponent.ts +++ b/src/specs/components/InlineMessageNativeComponent.ts @@ -8,6 +8,7 @@ import type { HostComponent, ViewProps } from 'react-native'; import type { DirectEventHandler, Double, + WithDefault, } from 'react-native/Libraries/Types/CodegenTypes'; import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; @@ -46,6 +47,7 @@ export interface ActionClickEvent { export interface NativeProps extends ViewProps { /** Required element ID for retrieving inline message content. */ elementId: string; + showScrollIndicators?: WithDefault; onSizeChange: DirectEventHandler; onStateChange?: DirectEventHandler; onActionClick?: DirectEventHandler;