diff --git a/app/jest.config.cjs b/app/jest.config.cjs index b97823593..efc667b63 100644 --- a/app/jest.config.cjs +++ b/app/jest.config.cjs @@ -6,7 +6,7 @@ module.exports = { preset: 'react-native', moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], transformIgnorePatterns: [ - 'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz|@sentry|@anon-aadhaar|react-native-svg|react-native-svg-circle-country-flags)/)', + 'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz|@sentry|@anon-aadhaar|react-native-svg|react-native-svg-circle-country-flags|react-native-webview|react-native-safe-area-context|react-native-haptic-feedback|react-native-localize|lottie-react-native|@tamagui)/)', ], setupFiles: ['/jest.setup.js'], testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$', @@ -19,6 +19,13 @@ module.exports = { '^@tests$': '/tests/src', // Map react-native-svg to app's node_modules for all packages '^react-native-svg$': '/node_modules/react-native-svg', + // Map Tamagui icons to app's node_modules (peer dependency of mobile-sdk-alpha) + '^@tamagui/lucide-icons$': '/node_modules/@tamagui/lucide-icons', + // Map react-native-webview to app's node_modules (peer dependency of mobile-sdk-alpha) + '^react-native-webview$': '/node_modules/react-native-webview', + // Mock PixelRatio for React Native (needed by StyleSheet and SDK components) + '^react-native/Libraries/Utilities/PixelRatio$': + '/tests/__setup__/pixelRatioMock.js', '^@selfxyz/mobile-sdk-alpha$': '/../packages/mobile-sdk-alpha/dist/cjs/index.cjs', '^@selfxyz/mobile-sdk-alpha/components$': diff --git a/app/jest.setup.js b/app/jest.setup.js index 646d693eb..787d2a3c9 100644 --- a/app/jest.setup.js +++ b/app/jest.setup.js @@ -125,6 +125,26 @@ jest.mock( { virtual: true }, ); +// Also mock PixelRatio in the main React Native module for bundled SDK code +jest.mock('react-native/Libraries/Utilities/PixelRatio', () => ({ + get: jest.fn(() => 2), + getFontScale: jest.fn(() => 1), + getPixelSizeForLayoutSize: jest.fn(layoutSize => layoutSize * 2), + roundToNearestPixel: jest.fn(layoutSize => Math.round(layoutSize * 2) / 2), + startDetecting: jest.fn(), +})); + +// Ensure PixelRatio is available in React Native exports for bundled SDK code +const RN = require('react-native'); + +RN.PixelRatio = { + get: jest.fn(() => 2), + getFontScale: jest.fn(() => 1), + getPixelSizeForLayoutSize: jest.fn(layoutSize => layoutSize * 2), + roundToNearestPixel: jest.fn(layoutSize => Math.round(layoutSize * 2) / 2), + startDetecting: jest.fn(), +}; + // Mock mobile-sdk-alpha's StyleSheet module directly jest.mock( '../packages/mobile-sdk-alpha/node_modules/react-native/Libraries/StyleSheet/StyleSheet', @@ -858,6 +878,9 @@ jest.mock('@tamagui/lucide-icons', () => { __esModule: true, ExternalLink: makeIcon('external-link'), X: makeIcon('x'), + ArrowLeft: makeIcon('arrow-left'), + ArrowRight: makeIcon('arrow-right'), + RotateCcw: makeIcon('rotate-ccw'), }; }); diff --git a/app/tests/__setup__/pixelRatioMock.js b/app/tests/__setup__/pixelRatioMock.js new file mode 100644 index 000000000..45cbd63d8 --- /dev/null +++ b/app/tests/__setup__/pixelRatioMock.js @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +// Mock PixelRatio module for Jest tests +module.exports = { + get: jest.fn(() => 2), + getFontScale: jest.fn(() => 1), + getPixelSizeForLayoutSize: jest.fn(layoutSize => layoutSize * 2), + roundToNearestPixel: jest.fn(layoutSize => Math.round(layoutSize * 2) / 2), + startDetecting: jest.fn(), +}; diff --git a/packages/mobile-sdk-alpha/.eslintrc.cjs b/packages/mobile-sdk-alpha/.eslintrc.cjs index d8cb2e125..253e50934 100644 --- a/packages/mobile-sdk-alpha/.eslintrc.cjs +++ b/packages/mobile-sdk-alpha/.eslintrc.cjs @@ -64,7 +64,13 @@ module.exports = { 'import/newline-after-import': 'error', 'import/no-duplicates': 'error', 'import/export': 'off', - 'import/no-unresolved': ['error', { caseSensitive: true }], + 'import/no-unresolved': [ + 'error', + { + caseSensitive: true, + ignore: ['react-native-webview', 'react-native-safe-area-context', '@tamagui/lucide-icons'], + }, + ], 'import/namespace': 'error', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-require-imports': 'error', diff --git a/packages/mobile-sdk-alpha/package.json b/packages/mobile-sdk-alpha/package.json index b97185255..69b659ba2 100644 --- a/packages/mobile-sdk-alpha/package.json +++ b/packages/mobile-sdk-alpha/package.json @@ -123,6 +123,7 @@ "zustand": "^4.5.2" }, "devDependencies": { + "@tamagui/lucide-icons": "^1.135.3", "@testing-library/react": "^14.1.2", "@types/react": "^18.3.4", "@types/react-dom": "^18.3.0", @@ -144,18 +145,23 @@ "react-native": "0.76.9", "react-native-haptic-feedback": "^2.3.3", "react-native-localize": "^3.5.2", + "react-native-svg": "^15.14.0", "react-native-web": "^0.21.1", + "react-native-webview": "^13.16.0", "tsup": "^8.0.1", "typescript": "^5.9.2", "vitest": "^2.1.8" }, "peerDependencies": { + "@tamagui/lucide-icons": "*", "lottie-react-native": "7.2.2", "react": "^18.3.1", "react-native": "0.76.9", "react-native-haptic-feedback": "*", "react-native-localize": "*", - "react-native-svg": "*" + "react-native-safe-area-context": "*", + "react-native-svg": "*", + "react-native-webview": "*" }, "packageManager": "yarn@4.6.0", "publishConfig": { diff --git a/packages/mobile-sdk-alpha/src/components/index.ts b/packages/mobile-sdk-alpha/src/components/index.ts index 3fc20fb86..3baefb39d 100644 --- a/packages/mobile-sdk-alpha/src/components/index.ts +++ b/packages/mobile-sdk-alpha/src/components/index.ts @@ -3,6 +3,9 @@ // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. export type { ViewProps } from './layout/View'; +export type { WebViewFooterProps } from './webview/WebViewFooter'; +export type { WebViewNavBarProps } from './webview/WebViewNavBar'; +export type { WebViewScreenParams, WebViewScreenProps } from './webview/WebViewScreen'; export { default as AbstractButton } from './buttons/AbstractButton'; @@ -40,6 +43,11 @@ export { Title } from './typography/Title'; export { View } from './layout/View'; +// WebView components +export { WebViewFooter } from './webview/WebViewFooter'; +export { WebViewNavBar } from './webview/WebViewNavBar'; +export { WebViewScreen } from './webview/WebViewScreen'; + export { XStack } from './layout/XStack'; // Export types diff --git a/packages/mobile-sdk-alpha/src/components/webview/WebViewFooter.tsx b/packages/mobile-sdk-alpha/src/components/webview/WebViewFooter.tsx new file mode 100644 index 000000000..2f367140e --- /dev/null +++ b/packages/mobile-sdk-alpha/src/components/webview/WebViewFooter.tsx @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import type React from 'react'; + +import { black, slate50, slate400 } from '../../constants/colors'; +import { buttonTap } from '../../haptic'; +import { Button } from '../layout/Button'; +import { XStack } from '../layout/XStack'; +import { YStack } from '../layout/YStack'; + +import { ArrowLeft, ArrowRight, RotateCcw } from '@tamagui/lucide-icons'; + +export interface WebViewFooterProps { + canGoBack: boolean; + canGoForward: boolean; + onGoBack: () => void; + onGoForward: () => void; + onReload: () => void; + onOpenInBrowser: () => void; +} + +const iconSize = 22; +const buttonSize = 36; + +export const WebViewFooter: React.FC = ({ + canGoBack, + canGoForward, + onGoBack, + onGoForward, + onReload, + onOpenInBrowser: _onOpenInBrowser, +}) => { + const renderIconButton = (key: string, icon: React.ReactNode, onPress: () => void, disabled?: boolean) => ( + + ); + + return ( + + + {renderIconButton( + 'back', + , + onGoBack, + !canGoBack, + )} + {renderIconButton('reload', , onReload)} + {renderIconButton( + 'forward', + , + onGoForward, + !canGoForward, + )} + + + ); +}; diff --git a/packages/mobile-sdk-alpha/src/components/webview/WebViewNavBar.tsx b/packages/mobile-sdk-alpha/src/components/webview/WebViewNavBar.tsx new file mode 100644 index 000000000..b6ee6bfa8 --- /dev/null +++ b/packages/mobile-sdk-alpha/src/components/webview/WebViewNavBar.tsx @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import type React from 'react'; +import { StyleSheet, Text } from 'react-native'; + +import { black } from '../../constants/colors'; +import { dinot } from '../../constants/fonts'; +import { buttonTap } from '../../haptic'; +import { Button } from '../layout/Button'; +import { XStack } from '../layout/XStack'; + +import { ExternalLink, X } from '@tamagui/lucide-icons'; + +export interface WebViewNavBarProps { + title?: string; + canGoBack?: boolean; + onBackPress: () => void; + onOpenExternalPress?: () => void; + isOpenExternalDisabled?: boolean; + safeAreaTop?: number; +} + +export const WebViewNavBar: React.FC = ({ + title, + onBackPress, + onOpenExternalPress, + isOpenExternalDisabled, + safeAreaTop = 0, +}) => { + return ( + + {/* Left: Close Button */} +