react-native-nitro-modal 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 (117) hide show
  1. package/LICENSE +20 -0
  2. package/NitroModal.podspec +29 -0
  3. package/README.md +39 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +124 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/cpp-adapter.cpp +11 -0
  8. package/android/src/main/java/com/margelo/nitro/nitromodal/BottomSheetPresenter.kt +284 -0
  9. package/android/src/main/java/com/margelo/nitro/nitromodal/HybridNitroModal.kt +312 -0
  10. package/android/src/main/java/com/margelo/nitro/nitromodal/ModalConfig.kt +50 -0
  11. package/android/src/main/java/com/margelo/nitro/nitromodal/ModalContentRoot.kt +115 -0
  12. package/android/src/main/java/com/margelo/nitro/nitromodal/ModalPresenter.kt +253 -0
  13. package/android/src/main/java/com/margelo/nitro/nitromodal/NitroModalHostView.kt +52 -0
  14. package/android/src/main/java/com/margelo/nitro/nitromodal/NitroModalPackage.kt +28 -0
  15. package/android/src/main/java/com/margelo/nitro/nitromodal/NitroModalViewManager.kt +67 -0
  16. package/android/src/main/java/com/margelo/nitro/nitromodal/PopupPresenter.kt +156 -0
  17. package/android/src/main/res/values/styles.xml +17 -0
  18. package/ios/BackdropView.swift +64 -0
  19. package/ios/HybridNitroModal.swift +329 -0
  20. package/ios/HybridNitroModalComponent+Children.mm +77 -0
  21. package/ios/ModalConfig.swift +73 -0
  22. package/ios/NitroModalHostView.swift +91 -0
  23. package/ios/PopupViewController.swift +223 -0
  24. package/ios/SheetViewController.swift +244 -0
  25. package/lib/module/NitroModal.js +188 -0
  26. package/lib/module/NitroModal.js.map +1 -0
  27. package/lib/module/NitroModal.nitro.js +4 -0
  28. package/lib/module/NitroModal.nitro.js.map +1 -0
  29. package/lib/module/NitroModalView.js +17 -0
  30. package/lib/module/NitroModalView.js.map +1 -0
  31. package/lib/module/NitroModalView.native.js +6 -0
  32. package/lib/module/NitroModalView.native.js.map +1 -0
  33. package/lib/module/index.js +4 -0
  34. package/lib/module/index.js.map +1 -0
  35. package/lib/module/package.json +1 -0
  36. package/lib/typescript/package.json +1 -0
  37. package/lib/typescript/src/NitroModal.d.ts +59 -0
  38. package/lib/typescript/src/NitroModal.d.ts.map +1 -0
  39. package/lib/typescript/src/NitroModal.nitro.d.ts +78 -0
  40. package/lib/typescript/src/NitroModal.nitro.d.ts.map +1 -0
  41. package/lib/typescript/src/NitroModalView.d.ts +4 -0
  42. package/lib/typescript/src/NitroModalView.d.ts.map +1 -0
  43. package/lib/typescript/src/NitroModalView.native.d.ts +3 -0
  44. package/lib/typescript/src/NitroModalView.native.d.ts.map +1 -0
  45. package/lib/typescript/src/index.d.ts +3 -0
  46. package/lib/typescript/src/index.d.ts.map +1 -0
  47. package/nitro.json +23 -0
  48. package/nitrogen/generated/android/c++/JDismissReason.hpp +64 -0
  49. package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  50. package/nitrogen/generated/android/c++/JFunc_void_DismissReason.hpp +77 -0
  51. package/nitrogen/generated/android/c++/JFunc_void_ModalContentArea.hpp +77 -0
  52. package/nitrogen/generated/android/c++/JFunc_void_double.hpp +75 -0
  53. package/nitrogen/generated/android/c++/JHybridNitroModalSpec.cpp +344 -0
  54. package/nitrogen/generated/android/c++/JHybridNitroModalSpec.hpp +106 -0
  55. package/nitrogen/generated/android/c++/JKeyboardBehavior.hpp +61 -0
  56. package/nitrogen/generated/android/c++/JModalContentArea.hpp +61 -0
  57. package/nitrogen/generated/android/c++/JModalMode.hpp +58 -0
  58. package/nitrogen/generated/android/c++/JPopupAnimation.hpp +61 -0
  59. package/nitrogen/generated/android/c++/JSheetDetent.hpp +64 -0
  60. package/nitrogen/generated/android/c++/views/JHybridNitroModalStateUpdater.cpp +174 -0
  61. package/nitrogen/generated/android/c++/views/JHybridNitroModalStateUpdater.hpp +54 -0
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/DismissReason.kt +25 -0
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/Func_void.kt +78 -0
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/Func_void_DismissReason.kt +78 -0
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/Func_void_ModalContentArea.kt +78 -0
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/Func_void_double.kt +78 -0
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/HybridNitroModalSpec.kt +237 -0
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/KeyboardBehavior.kt +24 -0
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/ModalContentArea.kt +56 -0
  70. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/ModalMode.kt +23 -0
  71. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/PopupAnimation.kt +24 -0
  72. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/SheetDetent.kt +25 -0
  73. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/nitromodalOnLoad.kt +35 -0
  74. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/views/HybridNitroModalManager.kt +96 -0
  75. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/views/HybridNitroModalStateUpdater.kt +23 -0
  76. package/nitrogen/generated/android/nitromodal+autolinking.cmake +83 -0
  77. package/nitrogen/generated/android/nitromodal+autolinking.gradle +27 -0
  78. package/nitrogen/generated/android/nitromodalOnLoad.cpp +64 -0
  79. package/nitrogen/generated/android/nitromodalOnLoad.hpp +34 -0
  80. package/nitrogen/generated/ios/NitroModal+autolinking.rb +62 -0
  81. package/nitrogen/generated/ios/NitroModal-Swift-Cxx-Bridge.cpp +65 -0
  82. package/nitrogen/generated/ios/NitroModal-Swift-Cxx-Bridge.hpp +237 -0
  83. package/nitrogen/generated/ios/NitroModal-Swift-Cxx-Umbrella.hpp +64 -0
  84. package/nitrogen/generated/ios/NitroModalAutolinking.mm +33 -0
  85. package/nitrogen/generated/ios/NitroModalAutolinking.swift +26 -0
  86. package/nitrogen/generated/ios/c++/HybridNitroModalSpecSwift.cpp +11 -0
  87. package/nitrogen/generated/ios/c++/HybridNitroModalSpecSwift.hpp +247 -0
  88. package/nitrogen/generated/ios/c++/views/HybridNitroModalComponent.mm +264 -0
  89. package/nitrogen/generated/ios/swift/DismissReason.swift +48 -0
  90. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  91. package/nitrogen/generated/ios/swift/Func_void_DismissReason.swift +46 -0
  92. package/nitrogen/generated/ios/swift/Func_void_ModalContentArea.swift +46 -0
  93. package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
  94. package/nitrogen/generated/ios/swift/HybridNitroModalSpec.swift +77 -0
  95. package/nitrogen/generated/ios/swift/HybridNitroModalSpec_cxx.swift +554 -0
  96. package/nitrogen/generated/ios/swift/KeyboardBehavior.swift +44 -0
  97. package/nitrogen/generated/ios/swift/ModalContentArea.swift +34 -0
  98. package/nitrogen/generated/ios/swift/ModalMode.swift +40 -0
  99. package/nitrogen/generated/ios/swift/PopupAnimation.swift +44 -0
  100. package/nitrogen/generated/ios/swift/SheetDetent.swift +48 -0
  101. package/nitrogen/generated/shared/c++/DismissReason.hpp +84 -0
  102. package/nitrogen/generated/shared/c++/HybridNitroModalSpec.cpp +65 -0
  103. package/nitrogen/generated/shared/c++/HybridNitroModalSpec.hpp +124 -0
  104. package/nitrogen/generated/shared/c++/KeyboardBehavior.hpp +80 -0
  105. package/nitrogen/generated/shared/c++/ModalContentArea.hpp +87 -0
  106. package/nitrogen/generated/shared/c++/ModalMode.hpp +76 -0
  107. package/nitrogen/generated/shared/c++/PopupAnimation.hpp +80 -0
  108. package/nitrogen/generated/shared/c++/SheetDetent.hpp +84 -0
  109. package/nitrogen/generated/shared/c++/views/HybridNitroModalComponent.cpp +74 -0
  110. package/nitrogen/generated/shared/c++/views/HybridNitroModalComponent.hpp +151 -0
  111. package/nitrogen/generated/shared/json/NitroModalConfig.json +30 -0
  112. package/package.json +183 -0
  113. package/src/NitroModal.nitro.ts +92 -0
  114. package/src/NitroModal.tsx +292 -0
  115. package/src/NitroModalView.native.tsx +8 -0
  116. package/src/NitroModalView.tsx +24 -0
  117. package/src/index.tsx +12 -0
@@ -0,0 +1,292 @@
1
+ import {
2
+ useImperativeHandle,
3
+ useLayoutEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ type ReactNode,
8
+ type Ref,
9
+ } from 'react';
10
+ import {
11
+ processColor,
12
+ StyleSheet,
13
+ useWindowDimensions,
14
+ View,
15
+ type ColorValue,
16
+ type StyleProp,
17
+ type ViewStyle,
18
+ } from 'react-native';
19
+ import { callback } from 'react-native-nitro-modules';
20
+ import type {
21
+ DismissReason,
22
+ KeyboardBehavior,
23
+ ModalContentArea,
24
+ ModalMode,
25
+ NitroModal as NitroModalHybridView,
26
+ PopupAnimation,
27
+ SheetDetent,
28
+ } from './NitroModal.nitro';
29
+ import { NitroModalView } from './NitroModalView';
30
+
31
+ export interface NitroModalRef {
32
+ /** Opens the modal. Only for uncontrolled usage (no `isOpen` prop). */
33
+ present(): void;
34
+ /** Closes the modal. `onDismiss` then fires with `'programmatic'`. */
35
+ dismiss(): void;
36
+ /** Animates a `bottomSheet` to `detents[index]`. */
37
+ snapToDetent(index: number): void;
38
+ }
39
+
40
+ export interface NitroModalProps {
41
+ /**
42
+ * Controls visibility. Leave undefined to drive the modal through `ref`
43
+ * instead. When the user dismisses it natively (swipe, backdrop, back
44
+ * button), `onDismiss` fires and you should set this back to `false`.
45
+ */
46
+ isOpen?: boolean;
47
+ /** @default 'bottomSheet' */
48
+ mode?: ModalMode;
49
+ /** Sheet heights, smallest first. Android uses at most three. @default ['fitContent'] */
50
+ detents?: SheetDetent[];
51
+ /** @default 0 */
52
+ initialDetentIndex?: number;
53
+ /** @default 'black' */
54
+ backdropColor?: ColorValue;
55
+ /** @default 0.4 */
56
+ backdropOpacity?: number;
57
+ /** Blur radius (dp/pt) behind the modal. Android 12+; approximated on iOS. @default 0 */
58
+ backdropBlur?: number;
59
+ /** @default true */
60
+ dismissOnBackdropPress?: boolean;
61
+ /** Swipe down to dismiss a `bottomSheet`. @default true */
62
+ dismissOnSwipe?: boolean;
63
+ /** Android back button/gesture dismisses the modal. @default true */
64
+ dismissOnBackButton?: boolean;
65
+ /** @default false */
66
+ showGrabber?: boolean;
67
+ /** Corner radius of the sheet/card. Defaults to the platform style. */
68
+ cornerRadius?: number;
69
+ /** Sheet/card background. Defaults to the system surface color. */
70
+ backgroundColor?: ColorValue;
71
+ /** @default 'pan' */
72
+ keyboardBehavior?: KeyboardBehavior;
73
+ /** @default 'scale' */
74
+ popupAnimation?: PopupAnimation;
75
+ /** Style of the view that wraps `children` inside the modal. */
76
+ contentContainerStyle?: StyleProp<ViewStyle>;
77
+ onPresent?: () => void;
78
+ onDismiss?: (reason: DismissReason) => void;
79
+ onDetentChange?: (index: number) => void;
80
+ onBackdropPress?: () => void;
81
+ onBackButtonPress?: () => void;
82
+ children?: ReactNode;
83
+ testID?: string;
84
+ ref?: Ref<NitroModalRef>;
85
+ }
86
+
87
+ const DEFAULT_DETENTS: SheetDetent[] = ['fitContent'];
88
+ const BLACK = 0xff000000;
89
+
90
+ function toNativeColor(color: ColorValue | undefined): number | undefined {
91
+ if (color == null) return undefined;
92
+ const processed = processColor(color);
93
+ // PlatformColor / DynamicColorIOS objects are not supported.
94
+ return typeof processed === 'number' ? processed : undefined;
95
+ }
96
+
97
+ export function NitroModal({
98
+ isOpen,
99
+ mode = 'bottomSheet',
100
+ detents = DEFAULT_DETENTS,
101
+ initialDetentIndex = 0,
102
+ backdropColor = 'black',
103
+ backdropOpacity = 0.4,
104
+ backdropBlur = 0,
105
+ dismissOnBackdropPress = true,
106
+ dismissOnSwipe = true,
107
+ dismissOnBackButton = true,
108
+ showGrabber = false,
109
+ cornerRadius,
110
+ backgroundColor,
111
+ keyboardBehavior = 'pan',
112
+ popupAnimation = 'scale',
113
+ contentContainerStyle,
114
+ onPresent,
115
+ onDismiss,
116
+ onDetentChange,
117
+ onBackdropPress,
118
+ onBackButtonPress,
119
+ children,
120
+ testID,
121
+ ref,
122
+ }: NitroModalProps) {
123
+ const isControlled = isOpen !== undefined;
124
+ const [internalOpen, setInternalOpen] = useState(false);
125
+ const open = isControlled ? isOpen : internalOpen;
126
+
127
+ // Children mount when the modal opens and stay mounted until native reports
128
+ // that it is fully gone, so the exit animation still shows them.
129
+ const [sessionActive, setSessionActive] = useState(open);
130
+ const [prevOpen, setPrevOpen] = useState(open);
131
+ if (open !== prevOpen) {
132
+ setPrevOpen(open);
133
+ if (open) setSessionActive(true);
134
+ }
135
+ const renderContent = open || sessionActive;
136
+
137
+ const [area, setArea] = useState<ModalContentArea | null>(null);
138
+ const hybridRef = useRef<NitroModalHybridView | null>(null);
139
+
140
+ // Native callbacks are created once (stable props, no native updates on
141
+ // re-render) and read the latest handlers from this ref.
142
+ const latest = useRef({
143
+ isControlled,
144
+ onPresent,
145
+ onDismiss,
146
+ onDetentChange,
147
+ onBackdropPress,
148
+ onBackButtonPress,
149
+ });
150
+ useLayoutEffect(() => {
151
+ latest.current = {
152
+ isControlled,
153
+ onPresent,
154
+ onDismiss,
155
+ onDetentChange,
156
+ onBackdropPress,
157
+ onBackButtonPress,
158
+ };
159
+ });
160
+
161
+ const nativeCallbacks = useMemo(
162
+ () => ({
163
+ hybridRef: callback((instance: NitroModalHybridView) => {
164
+ hybridRef.current = instance;
165
+ }),
166
+ onPresent: callback(() => {
167
+ setSessionActive(true);
168
+ latest.current.onPresent?.();
169
+ }),
170
+ onDismiss: callback((reason: DismissReason) => {
171
+ setSessionActive(false);
172
+ if (!latest.current.isControlled) setInternalOpen(false);
173
+ latest.current.onDismiss?.(reason);
174
+ }),
175
+ onDetentChange: callback((index: number) => {
176
+ latest.current.onDetentChange?.(index);
177
+ }),
178
+ onBackdropPress: callback(() => {
179
+ latest.current.onBackdropPress?.();
180
+ }),
181
+ onBackButtonPress: callback(() => {
182
+ latest.current.onBackButtonPress?.();
183
+ }),
184
+ onContentAreaChange: callback((next: ModalContentArea) => {
185
+ setArea((current) =>
186
+ current?.width === next.width && current.height === next.height
187
+ ? current
188
+ : next
189
+ );
190
+ }),
191
+ }),
192
+ []
193
+ );
194
+
195
+ useImperativeHandle(
196
+ ref,
197
+ () => ({
198
+ present: () => {
199
+ if (latest.current.isControlled) {
200
+ console.warn(
201
+ 'NitroModal: present() is ignored while `isOpen` is controlled.'
202
+ );
203
+ return;
204
+ }
205
+ setInternalOpen(true);
206
+ },
207
+ dismiss: () => {
208
+ if (latest.current.isControlled) {
209
+ hybridRef.current?.dismiss();
210
+ } else {
211
+ setInternalOpen(false);
212
+ }
213
+ },
214
+ snapToDetent: (index: number) => {
215
+ hybridRef.current?.snapToDetent(index);
216
+ },
217
+ }),
218
+ []
219
+ );
220
+
221
+ // Arrays are diffed by identity, so keep one instance per distinct value.
222
+ const detentsKey = detents.join(',');
223
+ const nativeDetents = useMemo(
224
+ () => [...new Set(detentsKey.split(','))] as SheetDetent[],
225
+ [detentsKey]
226
+ );
227
+
228
+ // Native reports the exact area; this only covers the first frame.
229
+ const windowSize = useWindowDimensions();
230
+ const contentArea = area ?? {
231
+ width: mode === 'popup' ? windowSize.width - 32 : windowSize.width,
232
+ height:
233
+ mode === 'popup' ? windowSize.height - 160 : windowSize.height * 0.85,
234
+ };
235
+ const containerStyle: ViewStyle =
236
+ mode === 'popup'
237
+ ? { maxWidth: contentArea.width, maxHeight: contentArea.height }
238
+ : nativeDetents.includes('fitContent')
239
+ ? { width: contentArea.width, maxHeight: contentArea.height }
240
+ : { width: contentArea.width, height: contentArea.height };
241
+
242
+ return (
243
+ <NitroModalView
244
+ style={styles.host}
245
+ isOpen={open}
246
+ mode={mode}
247
+ detents={nativeDetents}
248
+ initialDetentIndex={initialDetentIndex}
249
+ backdropColor={toNativeColor(backdropColor) ?? BLACK}
250
+ backdropOpacity={backdropOpacity}
251
+ backdropBlurRadius={backdropBlur}
252
+ dismissOnBackdropPress={dismissOnBackdropPress}
253
+ dismissOnSwipe={dismissOnSwipe}
254
+ dismissOnBackButton={dismissOnBackButton}
255
+ grabberVisible={showGrabber}
256
+ cornerRadius={cornerRadius ?? -1}
257
+ contentBackgroundColor={toNativeColor(backgroundColor)}
258
+ keyboardBehavior={keyboardBehavior}
259
+ popupAnimation={popupAnimation}
260
+ {...nativeCallbacks}
261
+ >
262
+ {renderContent ? (
263
+ <View
264
+ collapsable={false}
265
+ testID={testID}
266
+ style={[styles.container, containerStyle, contentContainerStyle]}
267
+ >
268
+ {children}
269
+ </View>
270
+ ) : null}
271
+ </NitroModalView>
272
+ );
273
+ }
274
+
275
+ const styles = StyleSheet.create({
276
+ // Zero-size placeholder in the React tree; the content renders natively in
277
+ // the modal's own window / view controller.
278
+ host: {
279
+ position: 'absolute',
280
+ left: 0,
281
+ top: 0,
282
+ width: 0,
283
+ height: 0,
284
+ },
285
+ // Pinned at the origin: native hosts it at the top of the sheet / the
286
+ // card, and uses its measured size for `fitContent` and the card size.
287
+ container: {
288
+ position: 'absolute',
289
+ left: 0,
290
+ top: 0,
291
+ },
292
+ });
@@ -0,0 +1,8 @@
1
+ import { getHostComponent } from 'react-native-nitro-modules';
2
+ const NitroModalConfig = require('../nitrogen/generated/shared/json/NitroModalConfig.json');
3
+ import type { NitroModalMethods, NitroModalProps } from './NitroModal.nitro';
4
+
5
+ export const NitroModalView = getHostComponent<
6
+ NitroModalProps,
7
+ NitroModalMethods
8
+ >('NitroModal', () => NitroModalConfig);
@@ -0,0 +1,24 @@
1
+ import type { ReactNativeView } from 'react-native-nitro-modules';
2
+ import type { NitroModalMethods, NitroModalProps } from './NitroModal.nitro';
3
+
4
+ let warned = false;
5
+
6
+ /**
7
+ * Platforms without a native implementation (e.g. web) render nothing.
8
+ * iOS and Android use `NitroModalView.native.tsx`.
9
+ */
10
+ function UnsupportedNitroModalView() {
11
+ if (!warned) {
12
+ warned = true;
13
+ console.warn(
14
+ 'react-native-nitro-modal is only implemented on iOS and Android.'
15
+ );
16
+ }
17
+ return null;
18
+ }
19
+
20
+ export const NitroModalView =
21
+ UnsupportedNitroModalView as unknown as ReactNativeView<
22
+ NitroModalProps,
23
+ NitroModalMethods
24
+ >;
package/src/index.tsx ADDED
@@ -0,0 +1,12 @@
1
+ export {
2
+ NitroModal,
3
+ type NitroModalProps,
4
+ type NitroModalRef,
5
+ } from './NitroModal';
6
+ export type {
7
+ DismissReason,
8
+ KeyboardBehavior,
9
+ ModalMode,
10
+ PopupAnimation,
11
+ SheetDetent,
12
+ } from './NitroModal.nitro';