react-native-unified-action-sheet 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/LICENSE +20 -0
  3. package/README.md +167 -0
  4. package/android/build.gradle +51 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/com/unifiedactionsheet/ActionSheetOptions.kt +134 -0
  7. package/android/src/main/java/com/unifiedactionsheet/SheetContentBuilder.kt +159 -0
  8. package/android/src/main/java/com/unifiedactionsheet/SheetPresenters.kt +214 -0
  9. package/android/src/main/java/com/unifiedactionsheet/SheetTheming.kt +56 -0
  10. package/android/src/main/java/com/unifiedactionsheet/UnifiedActionSheetModule.kt +126 -0
  11. package/android/src/main/java/com/unifiedactionsheet/UnifiedActionSheetPackage.kt +30 -0
  12. package/ios/UnifiedActionSheet.h +5 -0
  13. package/ios/UnifiedActionSheet.mm +96 -0
  14. package/ios/UnifiedActionSheetImpl.swift +240 -0
  15. package/jest/index.d.ts +12 -0
  16. package/jest/index.js +46 -0
  17. package/lib/commonjs/NativeUnifiedActionSheet.js +9 -0
  18. package/lib/commonjs/NativeUnifiedActionSheet.js.map +1 -0
  19. package/lib/commonjs/action-sheet-options.interface.js +2 -0
  20. package/lib/commonjs/action-sheet-options.interface.js.map +1 -0
  21. package/lib/commonjs/index.js +104 -0
  22. package/lib/commonjs/index.js.map +1 -0
  23. package/lib/commonjs/package.json +1 -0
  24. package/lib/module/NativeUnifiedActionSheet.js +5 -0
  25. package/lib/module/NativeUnifiedActionSheet.js.map +1 -0
  26. package/lib/module/action-sheet-options.interface.js +2 -0
  27. package/lib/module/action-sheet-options.interface.js.map +1 -0
  28. package/lib/module/index.js +97 -0
  29. package/lib/module/index.js.map +1 -0
  30. package/lib/module/package.json +1 -0
  31. package/lib/typescript/commonjs/package.json +1 -0
  32. package/lib/typescript/commonjs/src/NativeUnifiedActionSheet.d.ts +29 -0
  33. package/lib/typescript/commonjs/src/NativeUnifiedActionSheet.d.ts.map +1 -0
  34. package/lib/typescript/commonjs/src/action-sheet-options.interface.d.ts +29 -0
  35. package/lib/typescript/commonjs/src/action-sheet-options.interface.d.ts.map +1 -0
  36. package/lib/typescript/commonjs/src/index.d.ts +6 -0
  37. package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
  38. package/lib/typescript/module/package.json +1 -0
  39. package/lib/typescript/module/src/NativeUnifiedActionSheet.d.ts +29 -0
  40. package/lib/typescript/module/src/NativeUnifiedActionSheet.d.ts.map +1 -0
  41. package/lib/typescript/module/src/action-sheet-options.interface.d.ts +29 -0
  42. package/lib/typescript/module/src/action-sheet-options.interface.d.ts.map +1 -0
  43. package/lib/typescript/module/src/index.d.ts +6 -0
  44. package/lib/typescript/module/src/index.d.ts.map +1 -0
  45. package/package.json +155 -0
  46. package/react-native-unified-action-sheet.podspec +23 -0
  47. package/src/NativeUnifiedActionSheet.ts +29 -0
  48. package/src/action-sheet-options.interface.ts +43 -0
  49. package/src/index.tsx +131 -0
@@ -0,0 +1,214 @@
1
+ package com.unifiedactionsheet
2
+
3
+ import android.app.Activity
4
+ import android.app.Dialog
5
+ import android.content.Context
6
+ import android.graphics.Color
7
+ import android.graphics.Rect
8
+ import android.graphics.drawable.ColorDrawable
9
+ import android.graphics.drawable.GradientDrawable
10
+ import android.view.Gravity
11
+ import android.view.View
12
+ import android.view.View.MeasureSpec
13
+ import android.view.Window
14
+ import android.view.WindowManager
15
+ import android.widget.FrameLayout
16
+ import androidx.appcompat.app.AppCompatDialog
17
+ import androidx.core.graphics.Insets
18
+ import androidx.core.view.ViewCompat
19
+ import androidx.core.view.WindowInsetsCompat
20
+
21
+ private const val MAX_HEIGHT_PERCENT = 90
22
+
23
+ internal interface SheetPresenter {
24
+ fun build(activity: Activity, options: ActionSheetOptions, onSelect: (Dialog, Int) -> Unit): Dialog
25
+ }
26
+
27
+ internal object CenteredDialogPresenter : SheetPresenter {
28
+ private const val CENTERED_CORNER_RADIUS_DP = 28
29
+ private const val CENTERED_MARGIN_DP = 24
30
+ private const val CENTERED_MIN_WIDTH_DP = 280
31
+ private const val CENTERED_MAX_WIDTH_DP = 560
32
+
33
+ override fun build(
34
+ activity: Activity,
35
+ options: ActionSheetOptions,
36
+ onSelect: (Dialog, Int) -> Unit,
37
+ ): Dialog {
38
+ val isDark = isDarkAppearance(activity, options.userInterfaceStyle)
39
+ val palette = paletteFor(isDark)
40
+ val dialog = AppCompatDialog(activity, dialogTheme(isDark))
41
+ dialog.supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
42
+
43
+ val context: Context = dialog.context
44
+ val container = buildContent(context, options, palette) { index -> onSelect(dialog, index) }
45
+
46
+ dialog.setContentView(container)
47
+ dialog.setCanceledOnTouchOutside(true)
48
+
49
+ val background = GradientDrawable().apply {
50
+ cornerRadius = dp(context, CENTERED_CORNER_RADIUS_DP).toFloat()
51
+ setColor(palette.surface)
52
+ }
53
+ dialog.window?.setBackgroundDrawable(background)
54
+ container.background = background.constantState?.newDrawable() ?: background
55
+ container.clipToOutline = true
56
+
57
+ val metrics = context.resources.displayMetrics
58
+ val width = (metrics.widthPixels - 2 * dp(context, CENTERED_MARGIN_DP))
59
+ .coerceAtMost(dp(context, CENTERED_MAX_WIDTH_DP))
60
+ .coerceAtLeast(minOf(dp(context, CENTERED_MIN_WIDTH_DP), metrics.widthPixels))
61
+ dialog.window?.setLayout(width, WindowManager.LayoutParams.WRAP_CONTENT)
62
+
63
+ val maxHeight = (metrics.heightPixels * MAX_HEIGHT_PERCENT) / 100
64
+ container.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
65
+ override fun onLayoutChange(
66
+ view: View,
67
+ left: Int,
68
+ top: Int,
69
+ right: Int,
70
+ bottom: Int,
71
+ oldLeft: Int,
72
+ oldTop: Int,
73
+ oldRight: Int,
74
+ oldBottom: Int,
75
+ ) {
76
+ if (container.height <= maxHeight) return
77
+ container.removeOnLayoutChangeListener(this)
78
+ dialog.window?.setLayout(width, maxHeight)
79
+ }
80
+ })
81
+
82
+ return dialog
83
+ }
84
+ }
85
+
86
+ internal class AnchoredDialogPresenter(private val anchorRect: Rect) : SheetPresenter {
87
+ override fun build(
88
+ activity: Activity,
89
+ options: ActionSheetOptions,
90
+ onSelect: (Dialog, Int) -> Unit,
91
+ ): Dialog {
92
+ val isDark = isDarkAppearance(activity, options.userInterfaceStyle)
93
+ val palette = paletteFor(isDark)
94
+ val dialog = AppCompatDialog(activity, dialogTheme(isDark))
95
+ dialog.supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
96
+
97
+ val context: Context = dialog.context
98
+ val container = buildContent(context, options, palette, includeCancelRow = false) { index ->
99
+ onSelect(dialog, index)
100
+ }
101
+
102
+ container.background = GradientDrawable().apply {
103
+ cornerRadius = dp(context, ANCHORED_CORNER_RADIUS_DP).toFloat()
104
+ setColor(palette.surface)
105
+ }
106
+ container.clipToOutline = true
107
+ container.elevation = dp(context, ANCHORED_ELEVATION_DP).toFloat()
108
+
109
+ val pad = dp(context, SHADOW_PADDING_DP)
110
+ val wrapper = FrameLayout(context).apply {
111
+ setPadding(pad, pad, pad, pad)
112
+ clipChildren = false
113
+ clipToPadding = false
114
+ addView(
115
+ container,
116
+ FrameLayout.LayoutParams(
117
+ FrameLayout.LayoutParams.MATCH_PARENT,
118
+ FrameLayout.LayoutParams.MATCH_PARENT,
119
+ ),
120
+ )
121
+ }
122
+ dialog.setContentView(wrapper)
123
+ dialog.setCanceledOnTouchOutside(true)
124
+
125
+ val window = dialog.window ?: return dialog
126
+ window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
127
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
128
+ window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
129
+ window.setGravity(Gravity.TOP or Gravity.LEFT)
130
+
131
+ // The rect arrives in the activity content view's space, which sits below
132
+ // the status bar unless the app is edge to edge, while this window is laid
133
+ // out in screen space. Offset by the content view's position to line up.
134
+ val anchorOnScreen = Rect(anchorRect).apply {
135
+ val offset = IntArray(2)
136
+ activity.findViewById<View>(android.R.id.content)?.getLocationOnScreen(offset)
137
+ offset(offset[0], offset[1])
138
+ }
139
+
140
+ val decor = activity.window.decorView
141
+ val insets = ViewCompat.getRootWindowInsets(decor)?.getInsets(
142
+ WindowInsetsCompat.Type.systemBars() or
143
+ WindowInsetsCompat.Type.displayCutout() or
144
+ WindowInsetsCompat.Type.ime(),
145
+ ) ?: Insets.NONE
146
+ val margin = dp(context, EDGE_MARGIN_DP)
147
+ val usable = Rect(
148
+ insets.left + margin,
149
+ insets.top + margin,
150
+ decor.width - insets.right - margin,
151
+ decor.height - insets.bottom - margin,
152
+ )
153
+
154
+ val maxWidth = minOf(dp(context, ANCHORED_MAX_WIDTH_DP), usable.width())
155
+ container.measure(
156
+ MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
157
+ MeasureSpec.makeMeasureSpec(usable.height(), MeasureSpec.AT_MOST),
158
+ )
159
+ val width = container.measuredWidth
160
+ .coerceIn(minOf(dp(context, ANCHORED_MIN_WIDTH_DP), maxWidth), maxWidth)
161
+ container.measure(
162
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
163
+ MeasureSpec.makeMeasureSpec(usable.height(), MeasureSpec.AT_MOST),
164
+ )
165
+ val desiredHeight = container.measuredHeight
166
+
167
+ val gap = dp(context, ANCHOR_GAP_DP)
168
+ val spaceBelow = usable.bottom - (anchorOnScreen.bottom + gap)
169
+ val spaceAbove = (anchorOnScreen.top - gap) - usable.top
170
+ var height = desiredHeight
171
+ var y = anchorOnScreen.bottom + gap
172
+ if (desiredHeight > spaceBelow) {
173
+ if (desiredHeight <= spaceAbove) {
174
+ y = anchorOnScreen.top - gap - desiredHeight
175
+ } else {
176
+ height = minOf(desiredHeight, maxOf(spaceBelow, spaceAbove))
177
+ .coerceAtLeast(minOf(desiredHeight, dp(context, ANCHORED_MIN_HEIGHT_DP)))
178
+ y = if (spaceBelow >= spaceAbove) {
179
+ anchorOnScreen.bottom + gap
180
+ } else {
181
+ anchorOnScreen.top - gap - height
182
+ }
183
+ }
184
+ }
185
+ y = y.coerceIn(usable.top, maxOf(usable.top, usable.bottom - height))
186
+
187
+ val rtl =
188
+ context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
189
+ val x = when {
190
+ options.anchorAlignment == AnchorAlignment.CENTER -> anchorOnScreen.centerX() - width / 2
191
+ rtl -> anchorOnScreen.right - width
192
+ else -> anchorOnScreen.left
193
+ }.coerceIn(usable.left, maxOf(usable.left, usable.right - width))
194
+
195
+ val attributes = window.attributes
196
+ attributes.x = x - pad
197
+ attributes.y = y - pad
198
+ window.attributes = attributes
199
+ window.setLayout(width + 2 * pad, height + 2 * pad)
200
+
201
+ return dialog
202
+ }
203
+
204
+ private companion object {
205
+ const val ANCHORED_MIN_WIDTH_DP = 180
206
+ const val ANCHORED_MAX_WIDTH_DP = 280
207
+ const val ANCHORED_MIN_HEIGHT_DP = 96
208
+ const val ANCHORED_CORNER_RADIUS_DP = 8
209
+ const val ANCHORED_ELEVATION_DP = 3
210
+ const val SHADOW_PADDING_DP = 8
211
+ const val EDGE_MARGIN_DP = 8
212
+ const val ANCHOR_GAP_DP = 4
213
+ }
214
+ }
@@ -0,0 +1,56 @@
1
+ package com.unifiedactionsheet
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import android.content.res.Configuration
6
+ import android.util.TypedValue
7
+ import androidx.appcompat.R as AppCompatR
8
+
9
+ internal data class SheetPalette(
10
+ val primaryText: Int,
11
+ val secondaryText: Int,
12
+ val error: Int,
13
+ val surface: Int,
14
+ )
15
+
16
+ private val LIGHT_PALETTE = SheetPalette(
17
+ primaryText = 0xFF1D1B20.toInt(),
18
+ secondaryText = 0xFF49454F.toInt(),
19
+ error = 0xFFB3261E.toInt(),
20
+ surface = 0xFFECE6F0.toInt(),
21
+ )
22
+
23
+ private val DARK_PALETTE = SheetPalette(
24
+ primaryText = 0xFFE6E0E9.toInt(),
25
+ secondaryText = 0xFFCAC4D0.toInt(),
26
+ error = 0xFFF2B8B5.toInt(),
27
+ surface = 0xFF2B2930.toInt(),
28
+ )
29
+
30
+ internal fun isDarkAppearance(activity: Activity, appearance: ForcedAppearance): Boolean =
31
+ when (appearance) {
32
+ ForcedAppearance.LIGHT -> false
33
+ ForcedAppearance.DARK -> true
34
+ ForcedAppearance.SYSTEM -> {
35
+ val nightMode = activity.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
36
+ nightMode == Configuration.UI_MODE_NIGHT_YES
37
+ }
38
+ }
39
+
40
+ internal fun dialogTheme(isDark: Boolean): Int = if (isDark) {
41
+ AppCompatR.style.Theme_AppCompat_Dialog
42
+ } else {
43
+ AppCompatR.style.Theme_AppCompat_Light_Dialog
44
+ }
45
+
46
+ internal fun paletteFor(isDark: Boolean): SheetPalette = if (isDark) DARK_PALETTE else LIGHT_PALETTE
47
+
48
+ internal fun selectableItemBackgroundRes(context: Context): Int {
49
+ val outValue = TypedValue()
50
+ context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true)
51
+
52
+ return outValue.resourceId
53
+ }
54
+
55
+ internal fun dp(context: Context, value: Int): Int =
56
+ (value * context.resources.displayMetrics.density).toInt()
@@ -0,0 +1,126 @@
1
+ package com.unifiedactionsheet
2
+
3
+ import android.app.Activity
4
+ import android.app.Dialog
5
+ import com.facebook.react.bridge.LifecycleEventListener
6
+ import com.facebook.react.bridge.Promise
7
+ import com.facebook.react.bridge.ReactApplicationContext
8
+ import com.facebook.react.bridge.ReadableMap
9
+ import com.facebook.react.bridge.UiThreadUtil
10
+ import java.util.concurrent.atomic.AtomicBoolean
11
+
12
+ class UnifiedActionSheetModule(reactContext: ReactApplicationContext) :
13
+ NativeUnifiedActionSheetSpec(reactContext), LifecycleEventListener {
14
+
15
+ init {
16
+ reactContext.addLifecycleEventListener(this)
17
+ }
18
+
19
+ private val openDialogs = mutableListOf<Dialog>()
20
+
21
+ private val dismissedByApi = mutableSetOf<Dialog>()
22
+
23
+ override fun showActionSheetWithOptions(options: ReadableMap, promise: Promise) {
24
+ val parsed =
25
+ ActionSheetOptions.fromReadableMap(
26
+ options,
27
+ reactApplicationContext.resources.displayMetrics.density,
28
+ )
29
+ val activity = reactApplicationContext.currentActivity
30
+ ?: return promise.reject(
31
+ "E_NO_ACTIVITY",
32
+ "No current activity to attach the action sheet to.",
33
+ )
34
+
35
+ UiThreadUtil.runOnUiThread {
36
+ presentSheet(activity, parsed, promise)
37
+ }
38
+ }
39
+
40
+ override fun dismissActionSheet() {
41
+ UiThreadUtil.runOnUiThread {
42
+ openDialogs.lastOrNull()?.let { dialog ->
43
+ dismissedByApi.add(dialog)
44
+ dialog.dismiss()
45
+ }
46
+ }
47
+ }
48
+
49
+ override fun dismissAllActionSheets() {
50
+ UiThreadUtil.runOnUiThread {
51
+ openDialogs.toList().forEach { dialog ->
52
+ dismissedByApi.add(dialog)
53
+ dialog.dismiss()
54
+ }
55
+ }
56
+ }
57
+
58
+ override fun onHostResume() = Unit
59
+
60
+ override fun onHostPause() = Unit
61
+
62
+ override fun onHostDestroy() {
63
+ UiThreadUtil.runOnUiThread { dismissAllOpenDialogs() }
64
+ }
65
+
66
+ override fun invalidate() {
67
+ reactApplicationContext.removeLifecycleEventListener(this)
68
+ UiThreadUtil.runOnUiThread { dismissAllOpenDialogs() }
69
+ super.invalidate()
70
+ }
71
+
72
+ private fun dismissAllOpenDialogs() {
73
+ openDialogs.toList().forEach { it.dismiss() }
74
+ openDialogs.clear()
75
+ dismissedByApi.clear()
76
+ }
77
+
78
+ private fun presentSheet(
79
+ activity: Activity,
80
+ options: ActionSheetOptions,
81
+ promise: Promise,
82
+ ) {
83
+ val resolved = AtomicBoolean(false)
84
+ val resolveOnce: (Int?) -> Unit = { index ->
85
+ if (resolved.compareAndSet(false, true)) {
86
+ promise.resolve(index ?: -1)
87
+ }
88
+ }
89
+
90
+ val presenter: SheetPresenter = when (options.presentationStyle) {
91
+ PresentationStyle.CENTERED -> CenteredDialogPresenter
92
+ PresentationStyle.ANCHORED -> {
93
+ val rect = options.anchorRect
94
+ if (rect != null && rect.width() > 0 && rect.height() > 0) {
95
+ AnchoredDialogPresenter(rect)
96
+ } else {
97
+ CenteredDialogPresenter
98
+ }
99
+ }
100
+ }
101
+
102
+ val dialog = presenter.build(activity, options) { presented, index ->
103
+ presented.dismiss()
104
+ resolveOnce(index)
105
+ }
106
+ openDialogs.add(dialog)
107
+
108
+ dialog.setOnCancelListener { resolveOnce(options.cancelButtonIndex) }
109
+ dialog.setOnDismissListener {
110
+ openDialogs.remove(dialog)
111
+ if (dismissedByApi.remove(dialog)) {
112
+ resolveOnce(DISMISSED_BY_API)
113
+ } else {
114
+ resolveOnce(options.cancelButtonIndex)
115
+ }
116
+ }
117
+
118
+ dialog.show()
119
+ }
120
+
121
+ companion object {
122
+ const val NAME = NativeUnifiedActionSheetSpec.NAME
123
+
124
+ private const val DISMISSED_BY_API = -2
125
+ }
126
+ }
@@ -0,0 +1,30 @@
1
+ package com.unifiedactionsheet
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+
9
+ class UnifiedActionSheetPackage : BaseReactPackage() {
10
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
11
+ return if (name == UnifiedActionSheetModule.NAME) {
12
+ UnifiedActionSheetModule(reactContext)
13
+ } else {
14
+ null
15
+ }
16
+ }
17
+
18
+ override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
19
+ mapOf(
20
+ UnifiedActionSheetModule.NAME to ReactModuleInfo(
21
+ name = UnifiedActionSheetModule.NAME,
22
+ className = UnifiedActionSheetModule.NAME,
23
+ canOverrideExistingModule = false,
24
+ needsEagerInit = false,
25
+ isCxxModule = false,
26
+ isTurboModule = true
27
+ )
28
+ )
29
+ }
30
+ }
@@ -0,0 +1,5 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface UnifiedActionSheet : NSObject <RCTBridgeModule>
4
+
5
+ @end
@@ -0,0 +1,96 @@
1
+ #import "UnifiedActionSheet.h"
2
+
3
+ #import <RCTTypeSafety/RCTConvertHelpers.h>
4
+ #import <UnifiedActionSheetSpec/UnifiedActionSheetSpec.h>
5
+
6
+ #import "react_native_unified_action_sheet-Swift.h"
7
+
8
+ @interface UnifiedActionSheet () <NativeUnifiedActionSheetSpec>
9
+ @end
10
+
11
+ @implementation UnifiedActionSheet
12
+
13
+ RCT_EXPORT_MODULE()
14
+
15
+ + (BOOL)requiresMainQueueSetup
16
+ {
17
+ return NO;
18
+ }
19
+
20
+ /// Only the keys the iOS presentation understands are forwarded; the
21
+ /// Android-only keys in the shared spec are ignored here.
22
+ RCT_EXPORT_METHOD(showActionSheetWithOptions
23
+ : (JS::NativeUnifiedActionSheet::SpecShowActionSheetWithOptionsOptions &)options resolve
24
+ : (RCTPromiseResolveBlock)resolve reject
25
+ : (RCTPromiseRejectBlock)reject)
26
+ {
27
+ NSMutableDictionary *payload = [NSMutableDictionary new];
28
+
29
+ payload[@"options"] = RCTConvertVecToArray(options.options(), ^id(NSString *element) {
30
+ return element;
31
+ });
32
+
33
+ if (options.cancelButtonIndex()) {
34
+ payload[@"cancelButtonIndex"] = @(*options.cancelButtonIndex());
35
+ }
36
+ if (options.destructiveButtonIndices()) {
37
+ payload[@"destructiveButtonIndices"] =
38
+ RCTConvertVecToArray(*options.destructiveButtonIndices(), ^id(double element) {
39
+ return @(element);
40
+ });
41
+ }
42
+ if (options.disabledButtonIndices()) {
43
+ payload[@"disabledButtonIndices"] = RCTConvertVecToArray(*options.disabledButtonIndices(), ^id(double element) {
44
+ return @(element);
45
+ });
46
+ }
47
+
48
+ payload[@"title"] = options.title();
49
+ payload[@"message"] = options.message();
50
+ payload[@"tintColor"] = options.tintColor();
51
+ payload[@"cancelButtonTintColor"] = options.cancelButtonTintColor();
52
+ payload[@"userInterfaceStyle"] = options.userInterfaceStyle();
53
+ payload[@"destructiveColor"] = options.destructiveColor();
54
+ payload[@"presentationStyle"] = options.presentationStyle();
55
+
56
+ // The anchor arrives already measured from the ref on the JS side, so this
57
+ // module never resolves a view and needs no React Native view API.
58
+ auto anchorRect = options.anchorRect();
59
+ if (anchorRect.has_value()) {
60
+ payload[@"anchorRect"] = @{
61
+ @"x" : @(anchorRect->x()),
62
+ @"y" : @(anchorRect->y()),
63
+ @"width" : @(anchorRect->width()),
64
+ @"height" : @(anchorRect->height()),
65
+ };
66
+ }
67
+
68
+ dispatch_async(dispatch_get_main_queue(), ^{
69
+ [UnifiedActionSheetImpl.shared showWithOptions:payload
70
+ completion:^(NSInteger buttonIndex) {
71
+ resolve(@(buttonIndex));
72
+ }];
73
+ });
74
+ }
75
+
76
+ RCT_EXPORT_METHOD(dismissActionSheet)
77
+ {
78
+ dispatch_async(dispatch_get_main_queue(), ^{
79
+ [UnifiedActionSheetImpl.shared dismiss];
80
+ });
81
+ }
82
+
83
+ RCT_EXPORT_METHOD(dismissAllActionSheets)
84
+ {
85
+ dispatch_async(dispatch_get_main_queue(), ^{
86
+ [UnifiedActionSheetImpl.shared dismissAll];
87
+ });
88
+ }
89
+
90
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
91
+ (const facebook::react::ObjCTurboModule::InitParams &)params
92
+ {
93
+ return std::make_shared<facebook::react::NativeUnifiedActionSheetSpecJSI>(params);
94
+ }
95
+
96
+ @end