@whop/elements-react-native 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.
- package/README.md +179 -0
- package/android/build.gradle +64 -0
- package/android/consumer-rules.pro +3 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/java/com/whop/elements/WhopAddressSuggestionsModule.kt +102 -0
- package/android/src/main/java/com/whop/elements/WhopAuthSessionModule.kt +126 -0
- package/android/src/main/java/com/whop/elements/WhopElementsPackage.kt +39 -0
- package/android/src/main/java/com/whop/elements/WhopPayButtonManager.kt +74 -0
- package/android/src/main/java/com/whop/elements/WhopPayButtonPressEvent.kt +17 -0
- package/android/src/main/java/com/whop/elements/WhopPayButtonView.kt +178 -0
- package/android/src/main/java/com/whop/elements/WhopPlatformPayModule.kt +212 -0
- package/assets/whop-wordmark-dark.png +0 -0
- package/assets/whop-wordmark-dark@2x.png +0 -0
- package/assets/whop-wordmark-dark@3x.png +0 -0
- package/assets/whop-wordmark-light.png +0 -0
- package/assets/whop-wordmark-light@2x.png +0 -0
- package/assets/whop-wordmark-light@3x.png +0 -0
- package/ios/Package.swift +59 -0
- package/ios/Sources/ElementsReactNative/WhopAddressSuggestionsImpl.swift +189 -0
- package/ios/Sources/ElementsReactNative/WhopAuthSessionImpl.swift +106 -0
- package/ios/Sources/ElementsReactNative/WhopPayButtonImpl.swift +89 -0
- package/ios/Sources/ElementsReactNative/WhopPlatformPayImpl.swift +181 -0
- package/ios/generated/WhopAddressSuggestionsModule.h +11 -0
- package/ios/generated/WhopAddressSuggestionsModule.mm +46 -0
- package/ios/generated/WhopAuthSessionModule.h +11 -0
- package/ios/generated/WhopAuthSessionModule.mm +45 -0
- package/ios/generated/WhopElementsShims.h +35 -0
- package/ios/generated/WhopPayButtonComponentView.h +12 -0
- package/ios/generated/WhopPayButtonComponentView.mm +82 -0
- package/ios/generated/WhopPlatformPayModule.h +11 -0
- package/ios/generated/WhopPlatformPayModule.mm +51 -0
- package/lib/module/api/client.js +139 -0
- package/lib/module/api/client.js.map +1 -0
- package/lib/module/api/client.test.js +137 -0
- package/lib/module/api/client.test.js.map +1 -0
- package/lib/module/api/types.js +2 -0
- package/lib/module/api/types.js.map +1 -0
- package/lib/module/controller/actions.js +148 -0
- package/lib/module/controller/actions.js.map +1 -0
- package/lib/module/controller/actions.test.js +102 -0
- package/lib/module/controller/actions.test.js.map +1 -0
- package/lib/module/controller/store.js +158 -0
- package/lib/module/controller/store.js.map +1 -0
- package/lib/module/core/address/country-data.js +1370 -0
- package/lib/module/core/address/country-data.js.map +1 -0
- package/lib/module/core/address/country-name.js +38 -0
- package/lib/module/core/address/country-name.js.map +1 -0
- package/lib/module/core/address/country-names.generated.js +249 -0
- package/lib/module/core/address/country-names.generated.js.map +1 -0
- package/lib/module/core/address/engine.js +245 -0
- package/lib/module/core/address/engine.js.map +1 -0
- package/lib/module/core/address/engine.test.js +216 -0
- package/lib/module/core/address/engine.test.js.map +1 -0
- package/lib/module/core/address/form-state.js +409 -0
- package/lib/module/core/address/form-state.js.map +1 -0
- package/lib/module/core/address/form-state.test.js +516 -0
- package/lib/module/core/address/form-state.test.js.map +1 -0
- package/lib/module/core/address/places.js +36 -0
- package/lib/module/core/address/places.js.map +1 -0
- package/lib/module/core/address/required-coverage.test.js +57 -0
- package/lib/module/core/address/required-coverage.test.js.map +1 -0
- package/lib/module/core/address/suggest.js +102 -0
- package/lib/module/core/address/suggest.js.map +1 -0
- package/lib/module/core/address/suggest.test.js +156 -0
- package/lib/module/core/address/suggest.test.js.map +1 -0
- package/lib/module/core/address/use-suggestions.js +33 -0
- package/lib/module/core/address/use-suggestions.js.map +1 -0
- package/lib/module/core/payments/address-change.js +91 -0
- package/lib/module/core/payments/address-change.js.map +1 -0
- package/lib/module/core/payments/billing-merge.js +62 -0
- package/lib/module/core/payments/billing-merge.js.map +1 -0
- package/lib/module/core/payments/browser-info.js +42 -0
- package/lib/module/core/payments/browser-info.js.map +1 -0
- package/lib/module/core/payments/bt.js +151 -0
- package/lib/module/core/payments/bt.js.map +1 -0
- package/lib/module/core/payments/bt.test.js +23 -0
- package/lib/module/core/payments/bt.test.js.map +1 -0
- package/lib/module/core/payments/currency.js +37 -0
- package/lib/module/core/payments/currency.js.map +1 -0
- package/lib/module/core/payments/detail.js +159 -0
- package/lib/module/core/payments/detail.js.map +1 -0
- package/lib/module/core/payments/detail.test.js +225 -0
- package/lib/module/core/payments/detail.test.js.map +1 -0
- package/lib/module/core/payments/errors.js +115 -0
- package/lib/module/core/payments/errors.js.map +1 -0
- package/lib/module/core/payments/mint.js +89 -0
- package/lib/module/core/payments/mint.js.map +1 -0
- package/lib/module/core/payments/plan.js +131 -0
- package/lib/module/core/payments/plan.js.map +1 -0
- package/lib/module/core/payments/refusals.js +17 -0
- package/lib/module/core/payments/refusals.js.map +1 -0
- package/lib/module/core/payments/secure-fields.js +87 -0
- package/lib/module/core/payments/secure-fields.js.map +1 -0
- package/lib/module/core/payments/tax-id-catalog.js +616 -0
- package/lib/module/core/payments/tax-id-catalog.js.map +1 -0
- package/lib/module/core/payments/wallet-contact.js +42 -0
- package/lib/module/core/payments/wallet-contact.js.map +1 -0
- package/lib/module/core/payments/wallet-contact.test.js +77 -0
- package/lib/module/core/payments/wallet-contact.test.js.map +1 -0
- package/lib/module/elements/address.js +467 -0
- package/lib/module/elements/address.js.map +1 -0
- package/lib/module/elements/branding.js +114 -0
- package/lib/module/elements/branding.js.map +1 -0
- package/lib/module/elements/card-fields/context.js +254 -0
- package/lib/module/elements/card-fields/context.js.map +1 -0
- package/lib/module/elements/card-fields/fields.js +309 -0
- package/lib/module/elements/card-fields/fields.js.map +1 -0
- package/lib/module/elements/card-fields/index.js +30 -0
- package/lib/module/elements/card-fields/index.js.map +1 -0
- package/lib/module/elements/card.js +107 -0
- package/lib/module/elements/card.js.map +1 -0
- package/lib/module/elements/email.js +84 -0
- package/lib/module/elements/email.js.map +1 -0
- package/lib/module/elements/payment/declared-fields.js +186 -0
- package/lib/module/elements/payment/declared-fields.js.map +1 -0
- package/lib/module/elements/payment/index.js +448 -0
- package/lib/module/elements/payment/index.js.map +1 -0
- package/lib/module/elements/payment/wallet.js +132 -0
- package/lib/module/elements/payment/wallet.js.map +1 -0
- package/lib/module/elements/tax-id.js +100 -0
- package/lib/module/elements/tax-id.js.map +1 -0
- package/lib/module/i18n/catalogs.js +103 -0
- package/lib/module/i18n/catalogs.js.map +1 -0
- package/lib/module/i18n/index.js +45 -0
- package/lib/module/i18n/index.js.map +1 -0
- package/lib/module/index.js +27 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/namespace.js +174 -0
- package/lib/module/namespace.js.map +1 -0
- package/lib/module/native/index.js +128 -0
- package/lib/module/native/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/provider.js +56 -0
- package/lib/module/provider.js.map +1 -0
- package/lib/module/specs/NativeWhopAddressSuggestions.js +6 -0
- package/lib/module/specs/NativeWhopAddressSuggestions.js.map +1 -0
- package/lib/module/specs/NativeWhopAuthSession.js +10 -0
- package/lib/module/specs/NativeWhopAuthSession.js.map +1 -0
- package/lib/module/specs/NativeWhopPlatformPay.js +10 -0
- package/lib/module/specs/NativeWhopPlatformPay.js.map +1 -0
- package/lib/module/specs/WhopPayButtonNativeComponent.ts +22 -0
- package/lib/module/theme/appearance.js +85 -0
- package/lib/module/theme/appearance.js.map +1 -0
- package/lib/module/theme/appearance.test.js +124 -0
- package/lib/module/theme/appearance.test.js.map +1 -0
- package/lib/module/theme/provider.js +25 -0
- package/lib/module/theme/provider.js.map +1 -0
- package/lib/module/theme/resolve.js +69 -0
- package/lib/module/theme/resolve.js.map +1 -0
- package/lib/module/theme/scales.generated.js +419 -0
- package/lib/module/theme/scales.generated.js.map +1 -0
- package/lib/module/ui/element-frame.js +83 -0
- package/lib/module/ui/element-frame.js.map +1 -0
- package/lib/module/ui/payment-method-icon.js +69 -0
- package/lib/module/ui/payment-method-icon.js.map +1 -0
- package/lib/module/ui/primitives.js +266 -0
- package/lib/module/ui/primitives.js.map +1 -0
- package/lib/module/ui/select.js +281 -0
- package/lib/module/ui/select.js.map +1 -0
- package/lib/module/ui/suggestions.js +71 -0
- package/lib/module/ui/suggestions.js.map +1 -0
- package/lib/module/ui/whop-logo.js +42 -0
- package/lib/module/ui/whop-logo.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/api/client.d.ts +87 -0
- package/lib/typescript/src/api/client.d.ts.map +1 -0
- package/lib/typescript/src/api/types.d.ts +175 -0
- package/lib/typescript/src/api/types.d.ts.map +1 -0
- package/lib/typescript/src/controller/actions.d.ts +42 -0
- package/lib/typescript/src/controller/actions.d.ts.map +1 -0
- package/lib/typescript/src/controller/store.d.ts +90 -0
- package/lib/typescript/src/controller/store.d.ts.map +1 -0
- package/lib/typescript/src/core/address/country-data.d.ts +4 -0
- package/lib/typescript/src/core/address/country-data.d.ts.map +1 -0
- package/lib/typescript/src/core/address/country-name.d.ts +2 -0
- package/lib/typescript/src/core/address/country-name.d.ts.map +1 -0
- package/lib/typescript/src/core/address/country-names.generated.d.ts +2 -0
- package/lib/typescript/src/core/address/country-names.generated.d.ts.map +1 -0
- package/lib/typescript/src/core/address/engine.d.ts +97 -0
- package/lib/typescript/src/core/address/engine.d.ts.map +1 -0
- package/lib/typescript/src/core/address/form-state.d.ts +144 -0
- package/lib/typescript/src/core/address/form-state.d.ts.map +1 -0
- package/lib/typescript/src/core/address/places.d.ts +23 -0
- package/lib/typescript/src/core/address/places.d.ts.map +1 -0
- package/lib/typescript/src/core/address/suggest.d.ts +47 -0
- package/lib/typescript/src/core/address/suggest.d.ts.map +1 -0
- package/lib/typescript/src/core/address/use-suggestions.d.ts +3 -0
- package/lib/typescript/src/core/address/use-suggestions.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/address-change.d.ts +52 -0
- package/lib/typescript/src/core/payments/address-change.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/billing-merge.d.ts +39 -0
- package/lib/typescript/src/core/payments/billing-merge.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/browser-info.d.ts +12 -0
- package/lib/typescript/src/core/payments/browser-info.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/bt.d.ts +55 -0
- package/lib/typescript/src/core/payments/bt.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/currency.d.ts +7 -0
- package/lib/typescript/src/core/payments/currency.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/detail.d.ts +53 -0
- package/lib/typescript/src/core/payments/detail.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/errors.d.ts +62 -0
- package/lib/typescript/src/core/payments/errors.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/mint.d.ts +74 -0
- package/lib/typescript/src/core/payments/mint.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/plan.d.ts +69 -0
- package/lib/typescript/src/core/payments/plan.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/refusals.d.ts +12 -0
- package/lib/typescript/src/core/payments/refusals.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/secure-fields.d.ts +33 -0
- package/lib/typescript/src/core/payments/secure-fields.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/tax-id-catalog.d.ts +481 -0
- package/lib/typescript/src/core/payments/tax-id-catalog.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/wallet-contact.d.ts +40 -0
- package/lib/typescript/src/core/payments/wallet-contact.d.ts.map +1 -0
- package/lib/typescript/src/elements/address.d.ts +55 -0
- package/lib/typescript/src/elements/address.d.ts.map +1 -0
- package/lib/typescript/src/elements/branding.d.ts +18 -0
- package/lib/typescript/src/elements/branding.d.ts.map +1 -0
- package/lib/typescript/src/elements/card-fields/context.d.ts +67 -0
- package/lib/typescript/src/elements/card-fields/context.d.ts.map +1 -0
- package/lib/typescript/src/elements/card-fields/fields.d.ts +15 -0
- package/lib/typescript/src/elements/card-fields/fields.d.ts.map +1 -0
- package/lib/typescript/src/elements/card-fields/index.d.ts +21 -0
- package/lib/typescript/src/elements/card-fields/index.d.ts.map +1 -0
- package/lib/typescript/src/elements/card.d.ts +21 -0
- package/lib/typescript/src/elements/card.d.ts.map +1 -0
- package/lib/typescript/src/elements/email.d.ts +31 -0
- package/lib/typescript/src/elements/email.d.ts.map +1 -0
- package/lib/typescript/src/elements/payment/declared-fields.d.ts +43 -0
- package/lib/typescript/src/elements/payment/declared-fields.d.ts.map +1 -0
- package/lib/typescript/src/elements/payment/index.d.ts +37 -0
- package/lib/typescript/src/elements/payment/index.d.ts.map +1 -0
- package/lib/typescript/src/elements/payment/wallet.d.ts +39 -0
- package/lib/typescript/src/elements/payment/wallet.d.ts.map +1 -0
- package/lib/typescript/src/elements/tax-id.d.ts +41 -0
- package/lib/typescript/src/elements/tax-id.d.ts.map +1 -0
- package/lib/typescript/src/i18n/catalogs.d.ts +7 -0
- package/lib/typescript/src/i18n/catalogs.d.ts.map +1 -0
- package/lib/typescript/src/i18n/index.d.ts +17 -0
- package/lib/typescript/src/i18n/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +42 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/namespace.d.ts +36 -0
- package/lib/typescript/src/namespace.d.ts.map +1 -0
- package/lib/typescript/src/native/index.d.ts +62 -0
- package/lib/typescript/src/native/index.d.ts.map +1 -0
- package/lib/typescript/src/provider.d.ts +37 -0
- package/lib/typescript/src/provider.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeWhopAddressSuggestions.d.ts +13 -0
- package/lib/typescript/src/specs/NativeWhopAddressSuggestions.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeWhopAuthSession.d.ts +20 -0
- package/lib/typescript/src/specs/NativeWhopAuthSession.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeWhopPlatformPay.d.ts +24 -0
- package/lib/typescript/src/specs/NativeWhopPlatformPay.d.ts.map +1 -0
- package/lib/typescript/src/specs/WhopPayButtonNativeComponent.d.ts +21 -0
- package/lib/typescript/src/specs/WhopPayButtonNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/theme/appearance.d.ts +71 -0
- package/lib/typescript/src/theme/appearance.d.ts.map +1 -0
- package/lib/typescript/src/theme/provider.d.ts +11 -0
- package/lib/typescript/src/theme/provider.d.ts.map +1 -0
- package/lib/typescript/src/theme/resolve.d.ts +19 -0
- package/lib/typescript/src/theme/resolve.d.ts.map +1 -0
- package/lib/typescript/src/theme/scales.generated.d.ts +18 -0
- package/lib/typescript/src/theme/scales.generated.d.ts.map +1 -0
- package/lib/typescript/src/ui/element-frame.d.ts +32 -0
- package/lib/typescript/src/ui/element-frame.d.ts.map +1 -0
- package/lib/typescript/src/ui/payment-method-icon.d.ts +21 -0
- package/lib/typescript/src/ui/payment-method-icon.d.ts.map +1 -0
- package/lib/typescript/src/ui/primitives.d.ts +94 -0
- package/lib/typescript/src/ui/primitives.d.ts.map +1 -0
- package/lib/typescript/src/ui/select.d.ts +25 -0
- package/lib/typescript/src/ui/select.d.ts.map +1 -0
- package/lib/typescript/src/ui/suggestions.d.ts +15 -0
- package/lib/typescript/src/ui/suggestions.d.ts.map +1 -0
- package/lib/typescript/src/ui/whop-logo.d.ts +4 -0
- package/lib/typescript/src/ui/whop-logo.d.ts.map +1 -0
- package/package.json +121 -0
- package/scripts/check-swift.sh +27 -0
- package/scripts/codegen-shims.mjs +409 -0
- package/scripts/gen-country-names.mjs +58 -0
- package/scripts/gen-theme.mjs +124 -0
- package/scripts/gen-wordmark.mjs +68 -0
- package/scripts/mirror.mjs +112 -0
- package/src/api/client.ts +202 -0
- package/src/api/types.ts +166 -0
- package/src/controller/actions.ts +197 -0
- package/src/controller/store.ts +201 -0
- package/src/core/address/country-data.ts +927 -0
- package/src/core/address/country-name.ts +36 -0
- package/src/core/address/country-names.generated.ts +246 -0
- package/src/core/address/engine.ts +349 -0
- package/src/core/address/form-state.ts +479 -0
- package/src/core/address/places.ts +39 -0
- package/src/core/address/suggest.ts +153 -0
- package/src/core/address/use-suggestions.ts +37 -0
- package/src/core/payments/address-change.ts +121 -0
- package/src/core/payments/billing-merge.ts +91 -0
- package/src/core/payments/browser-info.ts +43 -0
- package/src/core/payments/bt.ts +176 -0
- package/src/core/payments/currency.ts +54 -0
- package/src/core/payments/detail.ts +204 -0
- package/src/core/payments/errors.ts +171 -0
- package/src/core/payments/mint.ts +130 -0
- package/src/core/payments/plan.ts +198 -0
- package/src/core/payments/refusals.ts +24 -0
- package/src/core/payments/secure-fields.ts +85 -0
- package/src/core/payments/tax-id-catalog.ts +320 -0
- package/src/core/payments/wallet-contact.ts +56 -0
- package/src/elements/address.tsx +570 -0
- package/src/elements/branding.tsx +87 -0
- package/src/elements/card-fields/context.tsx +310 -0
- package/src/elements/card-fields/fields.tsx +281 -0
- package/src/elements/card-fields/index.tsx +37 -0
- package/src/elements/card.tsx +101 -0
- package/src/elements/email.tsx +100 -0
- package/src/elements/payment/declared-fields.tsx +238 -0
- package/src/elements/payment/index.tsx +497 -0
- package/src/elements/payment/wallet.tsx +155 -0
- package/src/elements/tax-id.tsx +143 -0
- package/src/i18n/catalogs.ts +100 -0
- package/src/i18n/index.tsx +46 -0
- package/src/index.ts +68 -0
- package/src/namespace.tsx +231 -0
- package/src/native/index.ts +173 -0
- package/src/provider.tsx +76 -0
- package/src/specs/NativeWhopAddressSuggestions.ts +14 -0
- package/src/specs/NativeWhopAuthSession.ts +21 -0
- package/src/specs/NativeWhopPlatformPay.ts +25 -0
- package/src/specs/WhopPayButtonNativeComponent.ts +22 -0
- package/src/theme/appearance.ts +150 -0
- package/src/theme/provider.tsx +28 -0
- package/src/theme/resolve.ts +68 -0
- package/src/theme/scales.generated.ts +427 -0
- package/src/ui/element-frame.tsx +94 -0
- package/src/ui/payment-method-icon.tsx +55 -0
- package/src/ui/primitives.tsx +269 -0
- package/src/ui/select.tsx +255 -0
- package/src/ui/suggestions.tsx +69 -0
- package/src/ui/whop-logo.tsx +37 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import type { ParsedPlaceAddress } from '../core/address/places';
|
|
4
|
+
import type { AddressSuggestion } from '../core/address/suggest';
|
|
5
|
+
import { parseComponents, parseSuggestions } from '../core/address/suggest';
|
|
6
|
+
import type { Spec as AddressSuggestionsSpec } from '../specs/NativeWhopAddressSuggestions';
|
|
7
|
+
import type { Spec as AuthSessionSpec } from '../specs/NativeWhopAuthSession';
|
|
8
|
+
import type { Spec as PlatformPaySpec } from '../specs/NativeWhopPlatformPay';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The three native units, resolved lazily and tolerantly.
|
|
12
|
+
*
|
|
13
|
+
* A missing module is not a crash: it means the app has not rebuilt since adding this package, and
|
|
14
|
+
* the honest consequence is that redirect steps and wallet tiles are unavailable while everything
|
|
15
|
+
* else keeps working. Throwing at import time would take the whole checkout down instead.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
function optional<T>(load: () => T | null): T | null {
|
|
19
|
+
try {
|
|
20
|
+
return load() ?? null;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let authSession: AuthSessionSpec | null | undefined;
|
|
27
|
+
let platformPay: PlatformPaySpec | null | undefined;
|
|
28
|
+
let addressSuggestions: AddressSuggestionsSpec | null | undefined;
|
|
29
|
+
|
|
30
|
+
function auth(): AuthSessionSpec | null {
|
|
31
|
+
if (authSession === undefined) {
|
|
32
|
+
authSession = optional(() => require('../specs/NativeWhopAuthSession').default as AuthSessionSpec);
|
|
33
|
+
}
|
|
34
|
+
return authSession;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function pay(): PlatformPaySpec | null {
|
|
38
|
+
if (platformPay === undefined) {
|
|
39
|
+
platformPay = optional(() => require('../specs/NativeWhopPlatformPay').default as PlatformPaySpec);
|
|
40
|
+
}
|
|
41
|
+
return platformPay;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const WhopAuthSession = {
|
|
45
|
+
isAvailable(): boolean {
|
|
46
|
+
return auth()?.isAvailable() ?? false;
|
|
47
|
+
},
|
|
48
|
+
/** Resolves the callback URL, or null when the buyer dismissed the browser. */
|
|
49
|
+
present(url: string, options?: { callbackScheme?: string; prefersEphemeral?: boolean }): Promise<string | null> {
|
|
50
|
+
const module = auth();
|
|
51
|
+
if (!module) {
|
|
52
|
+
return Promise.reject(
|
|
53
|
+
new Error(
|
|
54
|
+
'whop elements: the native module is not linked — rebuild the app after installing @whop/elements-react-native.',
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return module.present(url, options?.callbackScheme ?? null, options?.prefersEphemeral ?? false);
|
|
59
|
+
},
|
|
60
|
+
dismiss(): void {
|
|
61
|
+
auth()?.dismiss();
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export interface WalletSheetConfig {
|
|
66
|
+
networks: string[];
|
|
67
|
+
allowedCardNetworks?: string[];
|
|
68
|
+
allowedAuthMethods?: string[];
|
|
69
|
+
environment?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface WalletSheetRequest extends WalletSheetConfig {
|
|
73
|
+
merchantIdentifier: string;
|
|
74
|
+
merchantName?: string;
|
|
75
|
+
countryCode: string;
|
|
76
|
+
currencyCode: string;
|
|
77
|
+
/** Major units as a decimal string; PassKit and Google Pay both price in major units. */
|
|
78
|
+
totalPrice: string;
|
|
79
|
+
lineItems: Array<{ label: string; amount: string }>;
|
|
80
|
+
requiresBillingAddress: boolean;
|
|
81
|
+
requiresEmail: boolean;
|
|
82
|
+
gateway?: string;
|
|
83
|
+
gatewayMerchantId?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type WalletCapability = 'apple' | 'google' | 'none';
|
|
87
|
+
|
|
88
|
+
export const WhopPlatformPay = {
|
|
89
|
+
capability(): WalletCapability {
|
|
90
|
+
const value = pay()?.capability();
|
|
91
|
+
return value === 'apple' || value === 'google' ? value : 'none';
|
|
92
|
+
},
|
|
93
|
+
async canPay(config: WalletSheetConfig): Promise<boolean> {
|
|
94
|
+
const module = pay();
|
|
95
|
+
if (!module) return false;
|
|
96
|
+
try {
|
|
97
|
+
return await module.canPay(JSON.stringify(config));
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
/** Resolves the raw network payload, or null when the buyer dismissed the sheet. */
|
|
103
|
+
present(request: WalletSheetRequest): Promise<string | null> {
|
|
104
|
+
const module = pay();
|
|
105
|
+
if (!module) return Promise.resolve(null);
|
|
106
|
+
return module.present(JSON.stringify(request));
|
|
107
|
+
},
|
|
108
|
+
/** Apple's sheet stays open until the confirm outcome is known; Google's dismisses itself. */
|
|
109
|
+
complete(success: boolean): void {
|
|
110
|
+
pay()?.complete(success);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const WALLET_TYPE_FOR_PLATFORM = Platform.OS === 'ios' ? 'apple_pay' : 'google_pay';
|
|
115
|
+
|
|
116
|
+
export { default as WhopPayButton } from '../specs/WhopPayButtonNativeComponent';
|
|
117
|
+
|
|
118
|
+
let warnedMissingSuggestions = false;
|
|
119
|
+
|
|
120
|
+
function suggestions(): AddressSuggestionsSpec | null {
|
|
121
|
+
if (addressSuggestions === undefined) {
|
|
122
|
+
addressSuggestions = optional(
|
|
123
|
+
() => require('../specs/NativeWhopAddressSuggestions').default as AddressSuggestionsSpec,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
// Absence here is silent by design at runtime — a checkout must never break over suggestions —
|
|
127
|
+
// but for whoever is building it, "no autocomplete" and "module not linked" look identical.
|
|
128
|
+
// Say which, once, in development only.
|
|
129
|
+
if (addressSuggestions === null && !warnedMissingSuggestions && typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
130
|
+
warnedMissingSuggestions = true;
|
|
131
|
+
console.warn(
|
|
132
|
+
'whop elements: address autocomplete is off because the WhopAddressSuggestions native module is not linked. ' +
|
|
133
|
+
'Rebuild the app after installing or updating @whop/elements-react-native — in the example, `node scripts/setup-native.mjs --force` then `pnpm run ios`.',
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return addressSuggestions;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Street suggestions from the platform geocoder: MapKit on iOS, android.location.Geocoder on
|
|
141
|
+
* Android. No key, no account, no third-party SDK on either side.
|
|
142
|
+
*
|
|
143
|
+
* Two calls, because MapKit's as-you-type API answers with display strings only and the components
|
|
144
|
+
* come from a second lookup. Android resolves from its own cache, so the shape costs it nothing.
|
|
145
|
+
*
|
|
146
|
+
* Every failure path answers empty rather than throwing. A geocoder that is missing, throttled,
|
|
147
|
+
* offline or simply unhelpful should leave the buyer typing the address by hand, which is where
|
|
148
|
+
* they started — it must never take down a checkout.
|
|
149
|
+
*/
|
|
150
|
+
export const WhopAddressSuggestions = {
|
|
151
|
+
isAvailable(): boolean {
|
|
152
|
+
return suggestions()?.isAvailable() ?? false;
|
|
153
|
+
},
|
|
154
|
+
async suggest(query: string): Promise<AddressSuggestion[]> {
|
|
155
|
+
const module = suggestions();
|
|
156
|
+
if (!module) return [];
|
|
157
|
+
try {
|
|
158
|
+
return parseSuggestions(await module.suggest(query));
|
|
159
|
+
} catch {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
/** `formCountry` is not sent to the geocoder; it backfills a result that comes back without one. */
|
|
164
|
+
async resolve(id: string, formCountry?: string): Promise<ParsedPlaceAddress | null> {
|
|
165
|
+
const module = suggestions();
|
|
166
|
+
if (!module) return null;
|
|
167
|
+
try {
|
|
168
|
+
return parseComponents(await module.details(id), formCountry);
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
};
|
package/src/provider.tsx
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createContext, type ReactNode, useContext, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { WhopEnvironment } from './api/client';
|
|
4
|
+
import { type Appearance, assertNativeAppearance } from './theme/appearance';
|
|
5
|
+
import { ThemeProvider } from './theme/provider';
|
|
6
|
+
import { LocaleProvider } from './i18n';
|
|
7
|
+
|
|
8
|
+
export interface WhopElementsConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Which Whop API the elements talk to. An ENUM over two Whop-owned origins, never a URL: a
|
|
11
|
+
* consumer-supplied host would let the embedding app read everything a buyer types.
|
|
12
|
+
*/
|
|
13
|
+
environment?: WhopEnvironment;
|
|
14
|
+
appearance?: Appearance | null;
|
|
15
|
+
locale?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a buyer or account token per request. There is no cookie in an app, so this is how a
|
|
18
|
+
* surface that needs credentials gets them. Called again before every call, so fetch rather than
|
|
19
|
+
* cache.
|
|
20
|
+
*/
|
|
21
|
+
getToken?: () => Promise<string | null | undefined> | string | null | undefined;
|
|
22
|
+
/** Opt in to the same event names and property allowlists the web elements emit. */
|
|
23
|
+
analytics?: { writeKey: string } | false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface WhopElementsProps extends WhopElementsConfig {
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
/**
|
|
29
|
+
* @deprecated There is no hosted script to load on React Native — the elements are in this
|
|
30
|
+
* package. Remove the prop.
|
|
31
|
+
*/
|
|
32
|
+
elements?: never;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface RootValue {
|
|
36
|
+
config: WhopElementsConfig;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const RootContext = createContext<RootValue | null>(null);
|
|
40
|
+
|
|
41
|
+
export function useRootConfig(): WhopElementsConfig {
|
|
42
|
+
const root = useContext(RootContext);
|
|
43
|
+
if (!root) throw new Error('Whop Elements components must be used within <WhopElements>');
|
|
44
|
+
return root.config;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The root. Holds the environment, the appearance, the locale, and the token resolver, and puts a
|
|
49
|
+
* resolved theme in context for every element beneath it.
|
|
50
|
+
*/
|
|
51
|
+
export function WhopElements(props: WhopElementsProps): ReactNode {
|
|
52
|
+
const { children, environment = 'production', appearance, locale, getToken, analytics } = props;
|
|
53
|
+
|
|
54
|
+
// `elements={loadWhop()}` is the single most-copied line in the web docs. Failing loudly on it
|
|
55
|
+
// is kinder than ignoring a prop someone believed was load-bearing.
|
|
56
|
+
if ((props as unknown as Record<string, unknown>).elements !== undefined) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
'whop elements: `<WhopElements elements={…}>` is a web-only prop — React Native has no hosted script to load. Remove it.',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
assertNativeAppearance(appearance ?? undefined);
|
|
62
|
+
|
|
63
|
+
const config = useMemo<WhopElementsConfig>(
|
|
64
|
+
() => ({ environment, appearance, locale, getToken, analytics }),
|
|
65
|
+
[environment, appearance, locale, getToken, analytics],
|
|
66
|
+
);
|
|
67
|
+
const value = useMemo<RootValue>(() => ({ config }), [config]);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<RootContext.Provider value={value}>
|
|
71
|
+
<LocaleProvider locale={locale}>
|
|
72
|
+
<ThemeProvider appearance={appearance}>{children}</ThemeProvider>
|
|
73
|
+
</LocaleProvider>
|
|
74
|
+
</RootContext.Provider>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface Spec extends TurboModule {
|
|
5
|
+
/** Whether the platform has a geocoder to ask. Android devices can ship without a backend. */
|
|
6
|
+
isAvailable(): boolean;
|
|
7
|
+
/** Candidates for a partial address, as a JSON array of `{ id, label }`. Display only. */
|
|
8
|
+
suggest(query: string): Promise<string>;
|
|
9
|
+
/** One candidate's raw components as JSON, by the `id` its `suggest` row carried. */
|
|
10
|
+
details(suggestionId: string): Promise<string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** `get`, not `getEnforcing`: an app that has not rebuilt loses suggestions, it does not crash. */
|
|
14
|
+
export default TurboModuleRegistry.get<Spec>('WhopAddressSuggestions');
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface Spec extends TurboModule {
|
|
5
|
+
isAvailable(): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Presents `url` in the system browser — ASWebAuthenticationSession on iOS, a Chrome Custom
|
|
8
|
+
* Tab on Android. Resolves with the callback URL when one arrives, or null when the buyer
|
|
9
|
+
* dismissed it. Never an in-app WebView: the page runs out of process, so the host app can
|
|
10
|
+
* neither read nor script it.
|
|
11
|
+
*/
|
|
12
|
+
present(url: string, callbackScheme: string | null, prefersEphemeral: boolean): Promise<string | null>;
|
|
13
|
+
dismiss(): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `get`, not `getEnforcing`: a host app that has not rebuilt since adding this package should lose
|
|
18
|
+
* the redirect and wallet surfaces, not crash on import. The wrappers in src/native treat null as
|
|
19
|
+
* "unavailable".
|
|
20
|
+
*/
|
|
21
|
+
export default TurboModuleRegistry.get<Spec>('WhopAuthSession');
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface Spec extends TurboModule {
|
|
5
|
+
/** 'apple' on iOS, 'google' on Android, 'none' where neither is usable. */
|
|
6
|
+
capability(): string;
|
|
7
|
+
/** `configJson` is a WalletSheetConfig. Answers whether the OS will present a sheet at all. */
|
|
8
|
+
canPay(configJson: string): Promise<boolean>;
|
|
9
|
+
/**
|
|
10
|
+
* Presents the OS wallet sheet for a WalletSheetRequest. Resolves with the raw network payload
|
|
11
|
+
* as a JSON string — PKPaymentToken.paymentData on iOS, PaymentData's tokenization token on
|
|
12
|
+
* Android — or null when the buyer dismissed it. The payload goes straight to Basis Theory as a
|
|
13
|
+
* token intent; it never enters the SDK's own state.
|
|
14
|
+
*/
|
|
15
|
+
present(requestJson: string): Promise<string | null>;
|
|
16
|
+
/** Settles a presented sheet once the confirm outcome is known. */
|
|
17
|
+
complete(success: boolean): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `get`, not `getEnforcing`: a host app that has not rebuilt since adding this package should lose
|
|
22
|
+
* the redirect and wallet surfaces, not crash on import. The wrappers in src/native treat null as
|
|
23
|
+
* "unavailable".
|
|
24
|
+
*/
|
|
25
|
+
export default TurboModuleRegistry.get<Spec>('WhopPlatformPay');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CodegenTypes, HostComponent, ViewProps } from 'react-native';
|
|
2
|
+
import { codegenNativeComponent } from 'react-native';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The platform's own pay button. Apple and Google both require their branded control rather than a
|
|
6
|
+
* drawn imitation, so this is the one element surface that must be a native view.
|
|
7
|
+
*/
|
|
8
|
+
export interface NativeProps extends ViewProps {
|
|
9
|
+
variant?: CodegenTypes.WithDefault<'buy' | 'pay' | 'checkout' | 'book' | 'subscribe' | 'order', 'pay'>;
|
|
10
|
+
buttonAppearance?: CodegenTypes.WithDefault<'auto' | 'light' | 'dark', 'auto'>;
|
|
11
|
+
cornerRadius?: CodegenTypes.Double;
|
|
12
|
+
disabled?: CodegenTypes.WithDefault<boolean, false>;
|
|
13
|
+
/**
|
|
14
|
+
* NOT `onPress`. Codegen derives the native event name by replacing the `on` prefix with `top`,
|
|
15
|
+
* and React Native already registers `topPress` as a BUBBLING event for Pressable — a name
|
|
16
|
+
* registered as both direct and bubbling fails the view-config invariant at render time, which
|
|
17
|
+
* takes down the whole screen rather than just this button.
|
|
18
|
+
*/
|
|
19
|
+
onPayPress?: CodegenTypes.DirectEventHandler<null>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default codegenNativeComponent<NativeProps>('WhopPayButton') as HostComponent<NativeProps>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { TextStyle, ViewStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { ACCENT_NAMES, GRAY_NAMES } from './scales.generated';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Copied from frontend/packages/elements-framework/src/builder/appearance/types.ts so an appearance
|
|
7
|
+
* object written for the web elements types identically here.
|
|
8
|
+
*/
|
|
9
|
+
export type AccentColor = (typeof ACCENT_NAMES)[number];
|
|
10
|
+
export type GrayColor = (typeof GRAY_NAMES)[number] | 'auto';
|
|
11
|
+
|
|
12
|
+
export type Theme = {
|
|
13
|
+
appearance?: 'light' | 'dark';
|
|
14
|
+
accentColor?: AccentColor;
|
|
15
|
+
grayColor?: GrayColor;
|
|
16
|
+
dangerColor?: 'ruby' | 'red' | 'tomato';
|
|
17
|
+
warningColor?: 'yellow' | 'amber';
|
|
18
|
+
successColor?: 'green' | 'teal' | 'jade' | 'grass';
|
|
19
|
+
infoColor?: 'sky' | 'blue';
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The web's `variables` are CSS custom properties, which React Native has no analogue for. `tokens`
|
|
24
|
+
* is the closed, typed replacement: numbers rather than `"8px"` strings, because there are no units
|
|
25
|
+
* here.
|
|
26
|
+
*/
|
|
27
|
+
export interface Tokens {
|
|
28
|
+
radius: number;
|
|
29
|
+
spacing: number;
|
|
30
|
+
fontSize: number;
|
|
31
|
+
labelFontSize: number;
|
|
32
|
+
borderWidth: number;
|
|
33
|
+
controlHeight: number;
|
|
34
|
+
fontFamily: string | undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The web's `classes` are CSS declarations keyed by `whop-*` part names. `parts` is the same idea
|
|
39
|
+
* with React Native style objects instead: flat per-part overrides applied after the theme, with no
|
|
40
|
+
* cascade, no pseudo-classes and no combinators.
|
|
41
|
+
*
|
|
42
|
+
* Names follow the web's wherever the two surfaces mean the same thing — `whop-PaymentMethodTile`,
|
|
43
|
+
* `whop-PaymentMethodDetail`, `whop-PaymentMethodRowSelected`, `whop-CardFieldGroup`. They diverge
|
|
44
|
+
* only where the surfaces genuinely differ, in three ways:
|
|
45
|
+
*
|
|
46
|
+
* - This surface is MORE granular in places: `whop-CardNumberField`, `whop-CardExpiryField` and
|
|
47
|
+
* `whop-CardCvcField` are separately themeable where the web has one `whop-CardFieldInput`.
|
|
48
|
+
* - It is generic where the web is per-element: one `whop-Input` / `whop-Label` / `whop-ErrorText`
|
|
49
|
+
* serves every field, where the web publishes `whop-PaymentFieldInput`, `whop-EmailInput`,
|
|
50
|
+
* `whop-TaxIdInput` and so on. Matching that would mean adding parts, not renaming them.
|
|
51
|
+
* - Some parts have no web counterpart at all: `whop-Sheet` and `whop-SheetRow` (the web uses a
|
|
52
|
+
* dropdown, not a bottom sheet) and `whop-AddressSuggestions`.
|
|
53
|
+
*
|
|
54
|
+
* So a web appearance object does not port across wholesale, and the parts that do not match are
|
|
55
|
+
* the ones where the web has nothing to match.
|
|
56
|
+
*/
|
|
57
|
+
export type Parts = Partial<Record<PartName, ViewStyle & TextStyle>>;
|
|
58
|
+
|
|
59
|
+
export type PartName =
|
|
60
|
+
| 'whop-Root'
|
|
61
|
+
| 'whop-Label'
|
|
62
|
+
| 'whop-Input'
|
|
63
|
+
| 'whop-InputFocused'
|
|
64
|
+
| 'whop-InputInvalid'
|
|
65
|
+
| 'whop-ErrorText'
|
|
66
|
+
| 'whop-HelperText'
|
|
67
|
+
| 'whop-Select'
|
|
68
|
+
| 'whop-SelectValue'
|
|
69
|
+
| 'whop-Sheet'
|
|
70
|
+
| 'whop-SheetRow'
|
|
71
|
+
| 'whop-CardNumberField'
|
|
72
|
+
| 'whop-CardExpiryField'
|
|
73
|
+
| 'whop-CardCvcField'
|
|
74
|
+
| 'whop-CardFieldGroup'
|
|
75
|
+
| 'whop-AddressCountrySelect'
|
|
76
|
+
| 'whop-AddressSuggestions'
|
|
77
|
+
| 'whop-AddressSuggestion'
|
|
78
|
+
| 'whop-AddressRow'
|
|
79
|
+
| 'whop-PaymentMethodTile'
|
|
80
|
+
| 'whop-PaymentMethodRowSelected'
|
|
81
|
+
| 'whop-MethodTileLabel'
|
|
82
|
+
| 'whop-PaymentMethodDetail'
|
|
83
|
+
| 'whop-Branding'
|
|
84
|
+
| 'whop-BrandingLink'
|
|
85
|
+
| 'whop-Skeleton';
|
|
86
|
+
|
|
87
|
+
export type Appearance = {
|
|
88
|
+
theme?: Theme | null;
|
|
89
|
+
tokens?: Partial<Tokens> | null;
|
|
90
|
+
parts?: Parts | null;
|
|
91
|
+
/** @deprecated CSS custom properties do not exist on React Native. Use `tokens`. */
|
|
92
|
+
variables?: never;
|
|
93
|
+
/** @deprecated CSS declarations do not exist on React Native. Use `parts`. */
|
|
94
|
+
classes?: never;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const ACCENT_SET: ReadonlySet<string> = new Set(ACCENT_NAMES);
|
|
98
|
+
const GRAY_SET: ReadonlySet<string> = new Set([...GRAY_NAMES, 'auto']);
|
|
99
|
+
const VALUES = {
|
|
100
|
+
appearance: new Set(['light', 'dark']),
|
|
101
|
+
dangerColor: new Set(['ruby', 'red', 'tomato']),
|
|
102
|
+
warningColor: new Set(['yellow', 'amber']),
|
|
103
|
+
successColor: new Set(['green', 'teal', 'jade', 'grass']),
|
|
104
|
+
infoColor: new Set(['sky', 'blue']),
|
|
105
|
+
} as const;
|
|
106
|
+
|
|
107
|
+
/** An unknown value falls back rather than throwing, matching `parse-theme.ts`. */
|
|
108
|
+
export function parseTheme(theme?: Theme | null): Theme {
|
|
109
|
+
if (!theme) return {};
|
|
110
|
+
const pick = <K extends keyof typeof VALUES>(key: K, value: unknown): Theme[K] =>
|
|
111
|
+
typeof value === 'string' && VALUES[key].has(value) ? (value as Theme[K]) : undefined;
|
|
112
|
+
return {
|
|
113
|
+
appearance: pick('appearance', theme.appearance),
|
|
114
|
+
accentColor: typeof theme.accentColor === 'string' && ACCENT_SET.has(theme.accentColor) ? theme.accentColor : undefined,
|
|
115
|
+
grayColor: typeof theme.grayColor === 'string' && GRAY_SET.has(theme.grayColor) ? theme.grayColor : undefined,
|
|
116
|
+
dangerColor: pick('dangerColor', theme.dangerColor),
|
|
117
|
+
warningColor: pick('warningColor', theme.warningColor),
|
|
118
|
+
successColor: pick('successColor', theme.successColor),
|
|
119
|
+
infoColor: pick('infoColor', theme.infoColor),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* A silently-ignored style object is the worst failure available here, so a web appearance passed
|
|
125
|
+
* unchanged is refused by name rather than dropped.
|
|
126
|
+
*/
|
|
127
|
+
export function assertNativeAppearance(appearance: Appearance | undefined): void {
|
|
128
|
+
if (!appearance) return;
|
|
129
|
+
const bag = appearance as Record<string, unknown>;
|
|
130
|
+
if (bag.variables !== undefined) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
'whop elements: `appearance.variables` is a CSS concept with no React Native equivalent — use `appearance.tokens` (numbers, not "8px" strings).',
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (bag.classes !== undefined) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
'whop elements: `appearance.classes` is a CSS concept with no React Native equivalent — use `appearance.parts` (the same whop-* part names, React Native style objects).',
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const DEFAULT_TOKENS: Tokens = {
|
|
143
|
+
radius: 8,
|
|
144
|
+
spacing: 4,
|
|
145
|
+
fontSize: 15,
|
|
146
|
+
labelFontSize: 13,
|
|
147
|
+
borderWidth: 1,
|
|
148
|
+
controlHeight: 44,
|
|
149
|
+
fontFamily: undefined,
|
|
150
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createContext, type ReactNode, useContext, useMemo } from 'react';
|
|
2
|
+
import { useColorScheme } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import type { Appearance } from './appearance';
|
|
5
|
+
import { type ResolvedTheme, resolveTheme } from './resolve';
|
|
6
|
+
|
|
7
|
+
const ThemeContext = createContext<ResolvedTheme | null>(null);
|
|
8
|
+
|
|
9
|
+
export function ThemeProvider({
|
|
10
|
+
appearance,
|
|
11
|
+
children,
|
|
12
|
+
}: {
|
|
13
|
+
appearance?: Appearance | null;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
}): ReactNode {
|
|
16
|
+
const system = useColorScheme();
|
|
17
|
+
const resolved = useMemo(() => resolveTheme(appearance ?? undefined, system ?? 'light'), [appearance, system]);
|
|
18
|
+
return <ThemeContext.Provider value={resolved}>{children}</ThemeContext.Provider>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useTheme(): ResolvedTheme {
|
|
22
|
+
const theme = useContext(ThemeContext);
|
|
23
|
+
if (!theme) throw new Error('Whop Elements components must be used within <WhopElements>');
|
|
24
|
+
return theme;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { alphaStep, resolveTheme, step } from './resolve';
|
|
28
|
+
export type { ResolvedTheme } from './resolve';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Appearance,
|
|
3
|
+
DEFAULT_TOKENS,
|
|
4
|
+
type Parts,
|
|
5
|
+
parseTheme,
|
|
6
|
+
type Theme,
|
|
7
|
+
type Tokens,
|
|
8
|
+
} from './appearance';
|
|
9
|
+
import { ACCENT_SCALES, type ColorScales, GRAY_SCALES } from './scales.generated';
|
|
10
|
+
|
|
11
|
+
export interface ResolvedTheme {
|
|
12
|
+
mode: 'light' | 'dark';
|
|
13
|
+
accent: ColorScales;
|
|
14
|
+
gray: ColorScales;
|
|
15
|
+
danger: ColorScales;
|
|
16
|
+
warning: ColorScales;
|
|
17
|
+
success: ColorScales;
|
|
18
|
+
info: ColorScales;
|
|
19
|
+
tokens: Tokens;
|
|
20
|
+
parts: Parts;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** frosted's pairing: a neutral carrying a hint of the accent unless one is named. */
|
|
24
|
+
const GRAY_FOR_ACCENT: Record<string, string> = {
|
|
25
|
+
tomato: 'mauve', red: 'mauve', ruby: 'mauve', crimson: 'mauve', pink: 'mauve', plum: 'mauve',
|
|
26
|
+
purple: 'mauve', violet: 'mauve', iris: 'slate', indigo: 'slate', blue: 'slate', sky: 'slate',
|
|
27
|
+
cyan: 'slate', teal: 'sage', jade: 'sage', mint: 'sage', green: 'sage', grass: 'olive',
|
|
28
|
+
lime: 'olive', yellow: 'sand', lemon: 'sand', amber: 'sand', orange: 'sand', brown: 'sand',
|
|
29
|
+
gold: 'sand', bronze: 'sand', magenta: 'mauve', gray: 'gray',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function resolveTheme(appearance: Appearance | undefined, system: 'light' | 'dark'): ResolvedTheme {
|
|
33
|
+
const theme: Theme = parseTheme(appearance?.theme);
|
|
34
|
+
// An unset appearance follows the device, which is what a native buyer expects.
|
|
35
|
+
const mode = theme.appearance ?? system;
|
|
36
|
+
|
|
37
|
+
const accentName = theme.accentColor ?? 'blue';
|
|
38
|
+
const grayName =
|
|
39
|
+
theme.grayColor === 'auto' || theme.grayColor === undefined
|
|
40
|
+
? (GRAY_FOR_ACCENT[accentName] ?? 'gray')
|
|
41
|
+
: theme.grayColor;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
mode,
|
|
45
|
+
accent: pickAccent(accentName, mode),
|
|
46
|
+
gray: GRAY_SCALES[grayName]?.[mode] ?? GRAY_SCALES.gray![mode],
|
|
47
|
+
danger: pickAccent(theme.dangerColor ?? 'red', mode),
|
|
48
|
+
warning: pickAccent(theme.warningColor ?? 'amber', mode),
|
|
49
|
+
success: pickAccent(theme.successColor ?? 'green', mode),
|
|
50
|
+
info: pickAccent(theme.infoColor ?? 'blue', mode),
|
|
51
|
+
tokens: { ...DEFAULT_TOKENS, ...appearance?.tokens },
|
|
52
|
+
parts: appearance?.parts ?? {},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function pickAccent(name: string, mode: 'light' | 'dark'): ColorScales {
|
|
57
|
+
return ACCENT_SCALES[name]?.[mode] ?? ACCENT_SCALES.blue![mode];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Radix scales are 1-indexed in their own docs; keeping that here makes call sites read like the
|
|
61
|
+
* design tokens they mirror. */
|
|
62
|
+
export function step(scale: ColorScales, n: number): string {
|
|
63
|
+
return scale.solid[n - 1] ?? scale.solid[0]!;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function alphaStep(scale: ColorScales, n: number): string {
|
|
67
|
+
return scale.alpha[n - 1] ?? scale.alpha[0]!;
|
|
68
|
+
}
|