@moonpay/platform-sdk-react-native 1.1.0 → 1.2.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.
package/README.md CHANGED
@@ -61,6 +61,8 @@ export function App() {
61
61
 
62
62
  `MoonPayProvider` also accepts optional `apiBaseUrl` and `frameBaseUrl` props that point the SDK at a different MoonPay environment — useful if MoonPay gives you test endpoints during onboarding. Leave them unset to use production.
63
63
 
64
+ Pass an optional `theme={{ appearance: 'light' | 'dark' }}` to render every MoonPay frame in light or dark mode. Omit it to follow the customer's system preference. The connect flow can override it per call (`client.connect({ theme })` or `<MoonPayConnect theme={...} />`).
65
+
64
66
  **3. Check the connection and connect the customer:**
65
67
 
66
68
  ```tsx
package/dist/index.cjs CHANGED
@@ -220,7 +220,9 @@ var connectSpec = {
220
220
  buildParams: (ctx, props, publicKey) => ({
221
221
  sessionToken: ctx.sessionToken,
222
222
  publicKey,
223
- ...props.theme?.appearance && { appearance: props.theme.appearance }
223
+ // Per-call override; `applyThemeParam` in the engine fills the client
224
+ // default for connect (and every other visible frame) when this is absent.
225
+ ...(0, import_platform_sdk_core.themeParams)(props.theme)
224
226
  }),
225
227
  onMessageEffect: applyCredentialsOnComplete,
226
228
  mapMessage: (msg) => {
@@ -521,11 +523,12 @@ function createFrameSession(spec, ctx, props, overrides = {}) {
521
523
  const generated = `${spec.channelPrefix}-${Date.now()}`;
522
524
  const channelId = spec.resolveChannelId ? spec.resolveChannelId(props, generated) : generated;
523
525
  const keyPair = spec.needsKeyPair ? (0, import_platform_sdk_core2.generateKeyPair)() : void 0;
524
- const url = spec.buildUrl ? spec.buildUrl(ctx, props, channelId, keyPair?.publicKey) : (0, import_platform_sdk_core2.buildFrameUrl)(
526
+ const builtUrl = spec.buildUrl ? spec.buildUrl(ctx, props, channelId, keyPair?.publicKey) : (0, import_platform_sdk_core2.buildFrameUrl)(
525
527
  spec.path,
526
528
  { ...spec.buildParams?.(ctx, props, keyPair?.publicKey), channelId },
527
529
  { frameBaseUrl: ctx.frameBaseUrl }
528
530
  );
531
+ const url = spec.hidden ? builtUrl : (0, import_platform_sdk_core2.applyThemeParam)(builtUrl, ctx.theme);
529
532
  const transport = (overrides.createTransport ?? (() => new WebViewTransport()))();
530
533
  let resolvedHandle = null;
531
534
  let rejectDisposed;
@@ -948,6 +951,38 @@ function VisibleFrameSlotSheet({
948
951
  onCloseRef.current();
949
952
  });
950
953
  }, [translateY, sheetHeight]);
954
+ const panResponder = (0, import_react2.useMemo)(
955
+ () => import_react_native2.PanResponder.create({
956
+ // Claim the gesture only for a clearly downward drag — never on a tap
957
+ // (so the handle's onPress still fires) or a mostly-horizontal swipe.
958
+ onMoveShouldSetPanResponder: (_evt, { dy, dx }) => dy > 5 && dy > Math.abs(dx),
959
+ onPanResponderGrant: () => {
960
+ translateY.stopAnimation(() => {
961
+ translateY.extractOffset();
962
+ });
963
+ },
964
+ // Track the finger downward; clamp upward travel to the open position.
965
+ onPanResponderMove: (_evt, { dy }) => {
966
+ translateY.setValue(Math.max(0, dy));
967
+ },
968
+ onPanResponderRelease: (_evt, { dy, vy }) => {
969
+ translateY.flattenOffset();
970
+ const draggedPastThreshold = dy > sheetHeight * 0.35;
971
+ const flungDownFast = vy > 0.5;
972
+ if (draggedPastThreshold || flungDownFast) {
973
+ close();
974
+ } else {
975
+ animateSheet(translateY, 0);
976
+ }
977
+ },
978
+ // Another responder stole the gesture: settle to a known state.
979
+ onPanResponderTerminate: () => {
980
+ translateY.flattenOffset();
981
+ animateSheet(translateY, 0);
982
+ }
983
+ }),
984
+ [translateY, sheetHeight, close]
985
+ );
951
986
  (0, import_react2.useEffect)(() => {
952
987
  animateSheet(translateY, 0);
953
988
  }, [translateY]);
@@ -978,17 +1013,16 @@ function VisibleFrameSlotSheet({
978
1013
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Animated.View, { style: [styles.sheet, { height: sheetHeight, transform: [{ translateY }] }], children: [
979
1014
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MoonPayFrame, { transport: slot.transport }),
980
1015
  !frameReady && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles.loading, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.ActivityIndicator, { size: "large", color: "#000" }) }),
981
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1016
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles.handleArea, ...panResponder.panHandlers, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
982
1017
  import_react_native2.Pressable,
983
1018
  {
984
1019
  onPress: close,
985
1020
  accessibilityRole: "button",
986
1021
  accessibilityLabel: "Close",
987
- hitSlop: 8,
988
- style: styles.closeButton,
989
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles.closeButtonText, children: "\u2715" })
1022
+ hitSlop: 12,
1023
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles.handleBar })
990
1024
  }
991
- )
1025
+ ) })
992
1026
  ] })
993
1027
  ] }) });
994
1028
  }
@@ -1018,25 +1052,25 @@ var styles = import_react_native2.StyleSheet.create({
1018
1052
  alignItems: "center",
1019
1053
  justifyContent: "center"
1020
1054
  },
1021
- // Floating X in the top-right corner, over the frame.
1022
- closeButton: {
1055
+ // Drag-to-dismiss strip pinned across the top of the sheet, floating over the
1056
+ // frame. Tall enough to be an easy grab target without eating much of the UI.
1057
+ handleArea: {
1023
1058
  position: "absolute",
1024
- top: 12,
1025
- right: 12,
1026
- width: 32,
1027
- height: 32,
1028
- borderRadius: 16,
1029
- backgroundColor: "rgba(0, 0, 0, 0.06)",
1059
+ top: 0,
1060
+ left: 0,
1061
+ right: 0,
1062
+ height: 28,
1030
1063
  alignItems: "center",
1031
1064
  justifyContent: "center",
1032
1065
  zIndex: 1,
1033
1066
  elevation: 2
1034
1067
  },
1035
- closeButtonText: {
1036
- fontSize: 17,
1037
- lineHeight: 20,
1038
- color: "#000",
1039
- fontWeight: "500"
1068
+ // The visible pill.
1069
+ handleBar: {
1070
+ width: 36,
1071
+ height: 5,
1072
+ borderRadius: 3,
1073
+ backgroundColor: "rgba(0, 0, 0, 0.2)"
1040
1074
  }
1041
1075
  });
1042
1076
 
@@ -1047,6 +1081,7 @@ function MoonPayProvider({
1047
1081
  sessionToken,
1048
1082
  apiBaseUrl,
1049
1083
  frameBaseUrl,
1084
+ theme,
1050
1085
  children
1051
1086
  }) {
1052
1087
  const [frameSlots, setFrameSlots] = (0, import_react3.useState)([]);
@@ -1066,13 +1101,13 @@ function MoonPayProvider({
1066
1101
  }
1067
1102
  const core = coreRef.current;
1068
1103
  const client = (0, import_react3.useMemo)(() => {
1069
- const specCtx = { sessionToken, core, frameBaseUrl };
1104
+ const specCtx = { sessionToken, core, frameBaseUrl, theme };
1070
1105
  return createRNClient({
1071
1106
  mountSession: createMountSession(specCtx, { addSlot, removeSlot }),
1072
1107
  core
1073
1108
  });
1074
- }, [sessionToken, frameBaseUrl, core, addSlot, removeSlot]);
1075
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MoonPayContext.Provider, { value: { client, sessionToken, core, frameBaseUrl }, children: [
1109
+ }, [sessionToken, frameBaseUrl, core, addSlot, removeSlot, theme?.appearance]);
1110
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MoonPayContext.Provider, { value: { client, sessionToken, core, frameBaseUrl, theme }, children: [
1076
1111
  children,
1077
1112
  frameSlots.filter((slot) => slot.hidden).map((slot) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoonPayFrame, { transport: slot.transport, hidden: true }, slot.id)),
1078
1113
  frameSlots.filter((slot) => !slot.hidden).map((slot) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -1117,7 +1152,7 @@ function MoonPayFrameView({
1117
1152
  defaultStyle,
1118
1153
  onEvent
1119
1154
  }) {
1120
- const { sessionToken, core, frameBaseUrl } = useMoonPayContext();
1155
+ const { sessionToken, core, frameBaseUrl, theme } = useMoonPayContext();
1121
1156
  const [transport, setTransport] = (0, import_react4.useState)(null);
1122
1157
  const handleRef = (0, import_react4.useRef)(null);
1123
1158
  const onEventRef = (0, import_react4.useRef)(onEvent);
@@ -1143,7 +1178,11 @@ function MoonPayFrameView({
1143
1178
  }
1144
1179
  appliedRef.current = applied;
1145
1180
  try {
1146
- session = createFrameSession(spec, { sessionToken, core, frameBaseUrl }, propsRef.current);
1181
+ session = createFrameSession(
1182
+ spec,
1183
+ { sessionToken, core, frameBaseUrl, theme },
1184
+ propsRef.current
1185
+ );
1147
1186
  } catch (e) {
1148
1187
  onEventRef.current?.(
1149
1188
  spec.errorEvent(e instanceof Error ? e.message : "Failed to create frame")