mirror of
https://github.com/luxfi/safe-wallet.git
synced 2026-07-26 22:53:37 +00:00
feat(mobile): upgrade Expo SDK to v55 (#7433)
* feat(mobile): upgrade Expo SDK to v55 Upgrade Expo SDK from v54 to v55, along with React Native 0.83.4, React 19.2, and all related dependencies. Key changes: - Refactored @safe-global/theme to use sub-path exports to fix RN import resolution issues - Added react-native patch for Appearance.setColorScheme bug (facebook/react-native#08d176453) not yet in 0.83.4 release - Replaced Tamagui Skeleton with custom SafeSkeleton component - Updated expo config plugins for SDK 55 compatibility Co-authored-by: Hanzo Dev <dev@hanzo.ai> * fix: downgrade eslint-config-next to v15 to match next.js version eslint-config-next v16 requires next v16 but the project uses next v15.5.8. The v16 package tries to import next/dist/compiled/babel/ eslint-parser which doesn't exist in next v15. Co-authored-by: Hanzo Dev <dev@hanzo.ai> * fix: migrate packages away from eslint-config-next eslint-config-next requires next at runtime for its babel parser, but next is only installed in apps/web. After the Expo SDK v55 upgrade changed dependency hoisting, the parser became unreachable. - Create shared config/eslint/base.mjs using direct plugin imports - Migrate store, theme, utils, and tx-builder to the shared config - Add packageExtensions in .yarnrc.yml so eslint-config-next in apps/web can still resolve next as a peer dependency Co-authored-by: Hanzo Dev <dev@hanzo.ai> * chore: align react 19.2 across workspaces and fix MUI peer dep - Bump react/react-dom to 19.2.0 in web and tx-builder to match mobile - Add @mui/material as optional peer dependency in theme package to avoid duplicate installations causing type conflicts Co-authored-by: Hanzo Dev <dev@hanzo.ai> * chore(mobile): update storybook on mobile to v10 * fix(web): resolve @emotion/react hoisting issue after Expo SDK 55 upgrade Yarn 4's node-modules hoisting shifted after dependency graph changes, leaving @emotion/react only in apps/web/node_modules while @mui/styled-engine remained at root. Add webpack resolve aliases to ensure Next.js finds emotion packages from the web workspace. Co-authored-by: Hanzo Dev <dev@hanzo.ai> * fix(web): resolve @emotion hoisting issue in Jest config Same hoisting fix as next.config.mjs but for Jest's module resolution. Add moduleNameMapper entries for @emotion/react and @emotion/styled. Co-authored-by: Hanzo Dev <dev@hanzo.ai> * fix(tx-builder): prettier * fix: resolve type-check errors after Expo SDK 55 upgrade - Fix BlurView tint prop: map ColorSchemeName to valid BlurTint values - Replace @storybook/test imports with storybook/test (merged in v10) - Align @types/react (~19.2.10) and @types/react-dom (~19.2.0) across all workspaces to prevent duplicate @mui/material type mismatches - Spread visuallyHidden into sx prop for @types/react 19.2.x compat - Add @types/react, @types/react-dom, storybook to yarn version checker Co-authored-by: Hanzo Dev <dev@hanzo.ai> * chore: align more dependencies * chore(web): update storybook snapshot * fix(mobile): normalize color scheme and deduplicate MUI/emotion packages - Normalize useTheme colorScheme to 'light' | 'dark', never 'unspecified' so TamaguiProvider always receives a valid theme name - Update ColorScheme type to exclude 'unspecified' - Remove redundant ?? 'light' fallbacks in SafeThemeProvider - Align @emotion/* and @mui/icons-material versions across workspaces to prevent Yarn from creating duplicate lockfile resolutions - Remove emotion webpack/Jest aliases (no longer needed after dedup) - Add @emotion/react, @emotion/styled, @mui/material, @mui/icons-material to yarn version consistency checker Co-authored-by: Hanzo Dev <dev@hanzo.ai> --------- Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
committed by
GitHub
co-authored by
Hanzo Dev
parent
d167bad14f
commit
defd17df3b
@@ -0,0 +1,18 @@
|
||||
diff --git a/Libraries/Utilities/Appearance.js b/Libraries/Utilities/Appearance.js
|
||||
index 9b7758c3afa3c9874c911045e258abf5219e8af5..56e54167670befc79ee371a871138e4af2528313 100644
|
||||
--- a/Libraries/Utilities/Appearance.js
|
||||
+++ b/Libraries/Utilities/Appearance.js
|
||||
@@ -99,7 +99,12 @@ export function setColorScheme(colorScheme: ColorSchemeName): void {
|
||||
if (NativeAppearance != null) {
|
||||
NativeAppearance.setColorScheme(colorScheme);
|
||||
state.appearance = {
|
||||
- colorScheme,
|
||||
+ // When setting to 'unspecified', get the actual system color scheme.
|
||||
+ // Fall back to the passed value if getColorScheme() returns null.
|
||||
+ colorScheme:
|
||||
+ colorScheme === 'unspecified'
|
||||
+ ? (NativeAppearance.getColorScheme() ?? colorScheme)
|
||||
+ : colorScheme,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,14 @@ enableScripts: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
# eslint-config-next requires next at runtime but doesn't declare it as
|
||||
# a peer dependency. Without this, Yarn hoists eslint-config-next to root
|
||||
# where it can't resolve next (which lives in apps/web/node_modules/).
|
||||
packageExtensions:
|
||||
eslint-config-next@*:
|
||||
peerDependencies:
|
||||
next: '*'
|
||||
|
||||
# Set cool down period for npm packages to 24 hours
|
||||
# This prevents the installation of npm packages if
|
||||
# they are updated within the last 24 hours
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { StorybookConfig as WebStorybookConfig } from '@storybook/react-webpack5'
|
||||
import type { StorybookConfig as RNStorybookConfig } from '@storybook/react-native'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'
|
||||
import { globSync } from 'glob'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
let config: WebStorybookConfig | RNStorybookConfig
|
||||
const isWeb = process.env.STORYBOOK_WEB
|
||||
|
||||
@@ -29,8 +33,6 @@ if (isWeb) {
|
||||
config = {
|
||||
stories: [...getStories()],
|
||||
addons: [
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-interactions',
|
||||
{
|
||||
name: '@storybook/addon-react-native-web',
|
||||
options: {
|
||||
@@ -38,7 +40,6 @@ if (isWeb) {
|
||||
modulesToTranspile: [],
|
||||
},
|
||||
},
|
||||
'@storybook/addon-webpack5-compiler-babel',
|
||||
],
|
||||
/**
|
||||
* Use standard framework configuration instead of path resolution.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext"
|
||||
},
|
||||
"include": ["../src/**/*", "./**/*"]
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ const config: ExpoConfig = {
|
||||
icon: './assets/images/icon.png',
|
||||
scheme: 'myapp',
|
||||
userInterfaceStyle: 'automatic',
|
||||
newArchEnabled: true,
|
||||
ios: {
|
||||
config: {
|
||||
usesNonExemptEncryption: false,
|
||||
@@ -74,7 +73,6 @@ const config: ExpoConfig = {
|
||||
'android.permission.FOREGROUND_SERVICE',
|
||||
'android.permission.WAKE_LOCK',
|
||||
],
|
||||
edgeToEdgeEnabled: true,
|
||||
allowBackup: false,
|
||||
},
|
||||
web: {
|
||||
@@ -159,6 +157,8 @@ const config: ExpoConfig = {
|
||||
enableBase64ShareAndroid: true,
|
||||
},
|
||||
],
|
||||
'@react-native-community/datetimepicker',
|
||||
'expo-image',
|
||||
'expo-task-manager',
|
||||
'expo-web-browser',
|
||||
[
|
||||
@@ -185,13 +185,7 @@ const config: ExpoConfig = {
|
||||
],
|
||||
experiments: {
|
||||
typedRoutes: true,
|
||||
},
|
||||
notification: {
|
||||
icon: './assets/images/icon.png',
|
||||
color: '#FFFFFF',
|
||||
androidMode: 'default',
|
||||
androidCollapsedTitle: 'Updates from Safe Wallet',
|
||||
iosDisplayInForeground: true,
|
||||
reactCompiler: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-undef */
|
||||
const { withPodfile, withAppDelegate, withMainApplication, withDangerousMod } = require('@expo/config-plugins')
|
||||
const { withPodfile, withAppDelegate, withMainApplication, withDangerousMod } = require('expo/config-plugins')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
// SSL Pinning Configuration will be passed from app.config.js
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { withDangerousMod } = require('@expo/config-plugins')
|
||||
const { withDangerousMod } = require('expo/config-plugins')
|
||||
|
||||
const androidFolderPath = ['app', 'src', 'main', 'res', 'drawable']
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { withAndroidManifest } = require('@expo/config-plugins')
|
||||
const { withAndroidManifest } = require('expo/config-plugins')
|
||||
|
||||
function addNotificationIconMetadata(androidManifest) {
|
||||
const application = androidManifest.manifest.application[0]
|
||||
|
||||
@@ -2,26 +2,18 @@ const path = require('path')
|
||||
|
||||
// Learn more https://docs.expo.io/guides/customizing-metro
|
||||
const { getDatadogExpoConfig } = require('@datadog/mobile-react-native/metro')
|
||||
const withStorybook = require('@storybook/react-native/metro/withStorybook')
|
||||
const { withStorybook } = require('@storybook/react-native/metro/withStorybook')
|
||||
|
||||
/** @type {import('expo/metro-config').MetroConfig} */
|
||||
const config = getDatadogExpoConfig(__dirname)
|
||||
|
||||
config.resolver.resolveRequest = (context, moduleName, platform) => {
|
||||
const defaultResolveResult = context.resolveRequest(context, moduleName, platform)
|
||||
|
||||
if (process.env.STORYBOOK_ENABLED !== 'true' && defaultResolveResult?.filePath?.includes?.('.storybook/')) {
|
||||
return {
|
||||
type: 'empty',
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleName === 'crypto') {
|
||||
// when importing crypto, resolve to react-native-quick-crypto
|
||||
return context.resolveRequest(context, 'react-native-quick-crypto', platform)
|
||||
}
|
||||
|
||||
return defaultResolveResult
|
||||
return context.resolveRequest(context, moduleName, platform)
|
||||
}
|
||||
|
||||
if (process.env.RN_SRC_EXT) {
|
||||
@@ -29,8 +21,6 @@ if (process.env.RN_SRC_EXT) {
|
||||
}
|
||||
|
||||
module.exports = withStorybook(config, {
|
||||
// Set to false to remove storybook specific options
|
||||
enabled: true,
|
||||
// Path to your storybook config
|
||||
enabled: process.env.STORYBOOK_ENABLED === 'true',
|
||||
configPath: path.resolve(__dirname, './.storybook'),
|
||||
})
|
||||
|
||||
+47
-52
@@ -25,7 +25,7 @@
|
||||
"type-check": "tsc --noEmit",
|
||||
"prettier": "prettier --check . --config ../../.prettierrc --ignore-path ../../.prettierignore",
|
||||
"prettier:fix": "prettier --write . --config ../../.prettierrc --ignore-path ../../.prettierignore",
|
||||
"storybook-generate": "sb-rn-get-stories",
|
||||
"storybook-generate": "sb-rn-get-stories --config-path ./.storybook",
|
||||
"generate:icons": "node ./scripts/generateIconTypes.js",
|
||||
"prepare": "husky",
|
||||
"build-storybook": "STORYBOOK_WEB='true' storybook build",
|
||||
@@ -41,14 +41,13 @@
|
||||
"@cowprotocol/app-data": "^3.1.0",
|
||||
"@datadog/mobile-react-native": "^3.1.2",
|
||||
"@ethersproject/shims": "^5.7.0",
|
||||
"@expo/config-plugins": "~54.0.1",
|
||||
"@expo/vector-icons": "^15.1.1",
|
||||
"@formatjs/intl-displaynames": "^6.8.11",
|
||||
"@formatjs/intl-getcanonicallocales": "^2.5.4",
|
||||
"@formatjs/intl-locale": "^4.2.10",
|
||||
"@formatjs/intl-numberformat": "^8.15.3",
|
||||
"@formatjs/intl-pluralrules": "^5.4.3",
|
||||
"@gorhom/bottom-sheet": "^5.2.6",
|
||||
"@gorhom/bottom-sheet": "^5.2.8",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
"@ledgerhq/context-module": "^1.7.0",
|
||||
"@ledgerhq/device-management-kit": "^0.9.0",
|
||||
@@ -57,8 +56,8 @@
|
||||
"@notifee/react-native": "^9.1.8",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-native-clipboard/clipboard": "^1.16.3",
|
||||
"@react-native-community/datetimepicker": "8.4.4",
|
||||
"@react-native-community/slider": "5.0.1",
|
||||
"@react-native-community/datetimepicker": "8.6.0",
|
||||
"@react-native-community/slider": "5.1.2",
|
||||
"@react-native-firebase/analytics": "23.3.1",
|
||||
"@react-native-firebase/app": "23.3.1",
|
||||
"@react-native-firebase/crashlytics": "23.3.1",
|
||||
@@ -66,15 +65,16 @@
|
||||
"@react-native-firebase/remote-config": "23.3.1",
|
||||
"@react-native-masked-view/masked-view": "0.3.2",
|
||||
"@react-native-menu/menu": "^2.0.0",
|
||||
"@react-navigation/native": "^7.1.17",
|
||||
"@react-navigation/elements": "^2.9.10",
|
||||
"@react-navigation/native": "^7.1.28",
|
||||
"@react-navigation/native-stack": "^7.14.4",
|
||||
"@reduxjs/toolkit": "^2.11.0",
|
||||
"@safe-global/protocol-kit": "^5.2.25",
|
||||
"@safe-global/store": "workspace:^",
|
||||
"@safe-global/theme": "workspace:^",
|
||||
"@safe-global/utils": "workspace:^",
|
||||
"@shopify/flash-list": "^2.2.0",
|
||||
"@storybook/addon-react-native-web": "^0.0.26",
|
||||
"@storybook/addon-webpack5-compiler-babel": "^3.0.3",
|
||||
"@shopify/flash-list": "^2.3.0",
|
||||
"@storybook/addon-react-native-web": "^0.0.29",
|
||||
"@tamagui/animations-moti": "^1.126.3",
|
||||
"@tamagui/babel-plugin": "^1.126.3",
|
||||
"@tamagui/config": "^1.126.3",
|
||||
@@ -86,56 +86,56 @@
|
||||
"date-fns": "^4.1.0",
|
||||
"deepmerge": "^4.3.1",
|
||||
"ethers": "6.14.3",
|
||||
"expo": "^54.0.0",
|
||||
"expo-application": "~7.0.7",
|
||||
"expo-blur": "~15.0.7",
|
||||
"expo-build-properties": "~1.0.9",
|
||||
"expo-constants": "~18.0.9",
|
||||
"expo-datadog": "^54.0.0",
|
||||
"expo-dev-client": "~6.0.12",
|
||||
"expo-device": "~8.0.8",
|
||||
"expo-document-picker": "~14.0.7",
|
||||
"expo-file-system": "~19.0.15",
|
||||
"expo-font": "~14.0.8",
|
||||
"expo-image": "~3.0.8",
|
||||
"expo-linear-gradient": "~15.0.7",
|
||||
"expo-linking": "~8.0.8",
|
||||
"expo-router": "~6.0.8",
|
||||
"expo-splash-screen": "~31.0.10",
|
||||
"expo-status-bar": "~3.0.8",
|
||||
"expo-system-ui": "~6.0.7",
|
||||
"expo-task-manager": "~14.0.7",
|
||||
"expo-web-browser": "~15.0.7",
|
||||
"expo": "^55.0.6",
|
||||
"expo-application": "~55.0.9",
|
||||
"expo-blur": "~55.0.9",
|
||||
"expo-build-properties": "~55.0.9",
|
||||
"expo-constants": "~55.0.7",
|
||||
"expo-datadog": "^55.0.0",
|
||||
"expo-dev-client": "~55.0.16",
|
||||
"expo-device": "~55.0.9",
|
||||
"expo-document-picker": "~55.0.8",
|
||||
"expo-file-system": "~55.0.10",
|
||||
"expo-font": "~55.0.4",
|
||||
"expo-image": "~55.0.6",
|
||||
"expo-linear-gradient": "~55.0.8",
|
||||
"expo-linking": "~55.0.7",
|
||||
"expo-router": "~55.0.5",
|
||||
"expo-splash-screen": "~55.0.10",
|
||||
"expo-status-bar": "~55.0.4",
|
||||
"expo-system-ui": "~55.0.9",
|
||||
"expo-task-manager": "~55.0.9",
|
||||
"expo-web-browser": "~55.0.9",
|
||||
"freerasp-react-native": "^4.2.4",
|
||||
"lodash": "^4.17.23",
|
||||
"moti": "^0.29.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-native": "0.81.4",
|
||||
"react-native": "patch:react-native@npm%3A0.83.4#~/.yarn/patches/react-native-npm-0.83.4-77634a290c.patch",
|
||||
"react-native-ble-plx": "patch:react-native-ble-plx@npm%3A3.5.0#~/.yarn/patches/react-native-ble-plx+3.5.0.patch",
|
||||
"react-native-capture-protection": "^2.3.1",
|
||||
"react-native-collapsible-tab-view": "v9.0.0-rc.0",
|
||||
"react-native-device-crypto": "patch:react-native-device-crypto@npm%3A0.1.7#~/.yarn/patches/react-native-device-crypto-npm-0.1.7-dbd2698fc4.patch",
|
||||
"react-native-device-info": "^14.0.1",
|
||||
"react-native-draggable-flatlist": "^4.0.1",
|
||||
"react-native-gesture-handler": "~2.28.0",
|
||||
"react-native-keyboard-controller": "1.18.5",
|
||||
"react-native-gesture-handler": "~2.30.0",
|
||||
"react-native-keyboard-controller": "1.20.7",
|
||||
"react-native-keychain": "^10.0.0",
|
||||
"react-native-mmkv": "^3.3.3",
|
||||
"react-native-pager-view": "6.9.1",
|
||||
"react-native-pager-view": "8.0.0",
|
||||
"react-native-permissions": "^5.4.2",
|
||||
"react-native-progress": "^5.0.1",
|
||||
"react-native-qrcode-styled": "^0.4.0",
|
||||
"react-native-quick-crypto": "^0.7.11",
|
||||
"react-native-reanimated": "~4.1.0",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-reanimated": "4.2.1",
|
||||
"react-native-safe-area-context": "~5.6.2",
|
||||
"react-native-screens": "~4.23.0",
|
||||
"react-native-share": "^12.2.0",
|
||||
"react-native-svg": "^15.13.0",
|
||||
"react-native-svg": "15.15.3",
|
||||
"react-native-vision-camera": "^4.7.2",
|
||||
"react-native-web": "^0.21.0",
|
||||
"react-native-worklets": "^0.5.1",
|
||||
"react-native-worklets": "0.7.2",
|
||||
"react-redux": "^9.1.2",
|
||||
"redux": "^5.0.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
@@ -147,7 +147,6 @@
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-env": "^7.26.0",
|
||||
"@babel/preset-react": "^7.26.3",
|
||||
"@datadog/datadog-ci": "^5.9.0",
|
||||
@@ -157,24 +156,20 @@
|
||||
"@rtk-query/codegen-openapi": "^2.0.0",
|
||||
"@safe-global/test": "workspace:^",
|
||||
"@safe-global/types-kit": "^1.0.5",
|
||||
"@storybook/addon-essentials": "^8.4.6",
|
||||
"@storybook/addon-interactions": "^8.4.6",
|
||||
"@storybook/addon-actions": "^9.0.8",
|
||||
"@storybook/addon-onboarding": "^10.2.6",
|
||||
"@storybook/addon-ondevice-actions": "^8.4.4",
|
||||
"@storybook/addon-ondevice-controls": "^8.4.2",
|
||||
"@storybook/addon-webpack5-compiler-swc": "^1.0.5",
|
||||
"@storybook/blocks": "^8.4.6",
|
||||
"@storybook/addon-ondevice-actions": "10.3.0-next.6",
|
||||
"@storybook/addon-ondevice-controls": "10.3.0-next.6",
|
||||
"@storybook/react": "^10.2.6",
|
||||
"@storybook/react-native": "^8.4.2",
|
||||
"@storybook/react-webpack5": "^8.4.6",
|
||||
"@storybook/test": "^8.4.6",
|
||||
"@storybook/react-native": "10.3.0-next.6",
|
||||
"@storybook/react-webpack5": "^10.2.6",
|
||||
"@testing-library/react-native": "^13.2.0",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/lodash": "^4.17.13",
|
||||
"@types/node": "^22.13.1",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "~19.1.10",
|
||||
"@types/react": "~19.2.10",
|
||||
"@types/semver": "^7.7.0",
|
||||
"babel-loader": "^10.0.0",
|
||||
"eslint": "^9.29.0",
|
||||
@@ -182,7 +177,7 @@
|
||||
"glob": "^13.0.6",
|
||||
"globals": "^15.14.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-expo": "~54.0.12",
|
||||
"jest-expo": "~55.0.9",
|
||||
"prettier": "^3.6.2",
|
||||
"redux-devtools-expo-dev-plugin": "^2.0.0",
|
||||
"storybook": "^10.2.7",
|
||||
|
||||
@@ -173,10 +173,6 @@ function RootLayout() {
|
||||
)
|
||||
}
|
||||
|
||||
let AppEntryPoint = RootLayout
|
||||
import StorybookUI from '../../.storybook'
|
||||
|
||||
if (isStorybookEnv) {
|
||||
AppEntryPoint = require('../../.storybook').default
|
||||
}
|
||||
|
||||
export default AppEntryPoint
|
||||
export default isStorybookEnv ? StorybookUI : RootLayout
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ export const BlurredIdenticonBackground = ({ address, height = 125, children }:
|
||||
height: height,
|
||||
},
|
||||
]}
|
||||
tint={colorScheme || 'dark'}
|
||||
tint={colorScheme === 'light' ? 'light' : 'dark'}
|
||||
/>
|
||||
|
||||
{children}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Text, View, XStack, YStack } from 'tamagui'
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
import { Logo } from '@/src/components/Logo'
|
||||
import { Fiat } from '@/src/components/Fiat'
|
||||
import { useAppSelector } from '@/src/store/hooks'
|
||||
@@ -53,7 +53,7 @@ export const ChainIndicator = ({
|
||||
const noChains = !allChains || allChains.length === 0
|
||||
const finalChainConfig = chainConfig || (showUnknown ? fallbackChainConfig : null)
|
||||
if (isLoading || noChains) {
|
||||
return <Skeleton width={onlyLogo ? 24 : 115} height={22} radius={4} />
|
||||
return <SafeSkeleton width={onlyLogo ? 24 : 115} height={22} radius={4} />
|
||||
}
|
||||
|
||||
if (!finalChainConfig) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import { ColorSchemeName, ImageSourcePropType, Platform, StyleSheet } from 'react-native'
|
||||
import { ImageSourcePropType, Platform, StyleSheet } from 'react-native'
|
||||
import { ColorScheme } from '@/src/types/theme'
|
||||
import { H2, Image, Text, getTokenValue, View } from 'tamagui'
|
||||
import { SafeButton } from '@/src/components/SafeButton'
|
||||
import { WINDOW_HEIGHT } from '@/src/store/constants'
|
||||
@@ -23,7 +24,7 @@ interface OptInProps {
|
||||
testID?: string
|
||||
isVisible?: boolean
|
||||
isLoading?: boolean
|
||||
colorScheme: ColorSchemeName
|
||||
colorScheme: ColorScheme
|
||||
infoMessage?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Storybook stories for SafeButton component - test mobile screenshots workflow
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { SafeButton } from '@/src/components/SafeButton'
|
||||
import { action } from '@storybook/addon-actions'
|
||||
import { action } from 'storybook/actions'
|
||||
import { YStack, Text, XStack, ScrollView } from 'tamagui'
|
||||
import { SafeFontIcon } from '@/src/components/SafeFontIcon'
|
||||
import React from 'react'
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import type { ComponentProps } from 'react'
|
||||
import { useTheme } from '@/src/theme/hooks/useTheme'
|
||||
|
||||
type SafeSkeletonProps = Omit<ComponentProps<typeof Skeleton>, 'colorMode'>
|
||||
|
||||
/**
|
||||
* Thin wrapper around Moti's Skeleton that automatically syncs with the app theme.
|
||||
*
|
||||
* Moti only accepts 'light' | 'dark' for colorMode and defaults to 'dark'
|
||||
* when the value is unrecognized. This wrapper maps 'unspecified' to 'light'.
|
||||
*/
|
||||
export function SafeSkeleton(props: SafeSkeletonProps) {
|
||||
const { colorScheme } = useTheme()
|
||||
const colorMode = colorScheme === 'dark' ? 'dark' : 'light'
|
||||
|
||||
return <Skeleton colorMode={colorMode} {...props} />
|
||||
}
|
||||
|
||||
SafeSkeleton.Group = Skeleton.Group
|
||||
@@ -0,0 +1 @@
|
||||
export { SafeSkeleton } from './SafeSkeleton'
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, useMemo } from 'react'
|
||||
import { TabBarProps, Tabs } from 'react-native-collapsible-tab-view'
|
||||
import { safeTabItem } from './types'
|
||||
import { SafeTabBar } from './SafeTabBar'
|
||||
import { Theme } from 'tamagui'
|
||||
import { Theme, useTheme } from 'tamagui'
|
||||
import { StyleProp, ViewStyle } from 'react-native'
|
||||
import { View } from 'tamagui'
|
||||
|
||||
@@ -16,7 +16,7 @@ interface SafeTabProps<T> {
|
||||
rightNode?: (activeTabLabel: string) => React.ReactNode
|
||||
}
|
||||
|
||||
export function SafeTab<T extends object>({
|
||||
function SafeTabInner<T extends object>({
|
||||
renderHeader,
|
||||
headerHeight,
|
||||
items,
|
||||
@@ -25,31 +25,41 @@ export function SafeTab<T extends object>({
|
||||
onIndexChange,
|
||||
rightNode,
|
||||
}: SafeTabProps<T>) {
|
||||
const theme = useTheme()
|
||||
const headerContainerStyle = useMemo(
|
||||
() => ({ backgroundColor: theme.background.get(), shadowColor: 'transparent' }),
|
||||
[theme.background],
|
||||
)
|
||||
|
||||
return (
|
||||
<Tabs.Container
|
||||
containerStyle={containerStyle}
|
||||
renderHeader={renderHeader}
|
||||
headerContainerStyle={headerContainerStyle}
|
||||
headerHeight={headerHeight}
|
||||
renderTabBar={(props) => <SafeTabBar rightNode={rightNode} {...props} />}
|
||||
onIndexChange={onIndexChange}
|
||||
initialTabName={items[0].label}
|
||||
>
|
||||
{items.map(({ label, testID, Component }, index) => (
|
||||
<Tabs.Tab name={label} key={`${label}-${index}`}>
|
||||
<View testID={testID ?? `tab-content-${label}-${index}`} flex={1}>
|
||||
<Component {...(containerProps as T)} />
|
||||
</View>
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs.Container>
|
||||
)
|
||||
}
|
||||
|
||||
export function SafeTab<T extends object>(props: SafeTabProps<T>) {
|
||||
return (
|
||||
<Theme name={'tab'}>
|
||||
<Tabs.Container
|
||||
containerStyle={containerStyle}
|
||||
renderHeader={renderHeader}
|
||||
headerContainerStyle={headerContainerStyle}
|
||||
headerHeight={headerHeight}
|
||||
renderTabBar={(props) => <SafeTabBar rightNode={rightNode} {...props} />}
|
||||
onIndexChange={onIndexChange}
|
||||
initialTabName={items[0].label}
|
||||
>
|
||||
{items.map(({ label, testID, Component }, index) => (
|
||||
<Tabs.Tab name={label} key={`${label}-${index}`}>
|
||||
<View testID={testID ?? `tab-content-${label}-${index}`} flex={1}>
|
||||
<Component {...(containerProps as T)} />
|
||||
</View>
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs.Container>
|
||||
<SafeTabInner {...props} />
|
||||
</Theme>
|
||||
)
|
||||
}
|
||||
|
||||
const headerContainerStyle = { backgroundColor: '$background', shadowColor: 'transparent' }
|
||||
|
||||
SafeTab.FlashList = Tabs.FlashList
|
||||
SafeTab.FlatList = Tabs.FlatList
|
||||
SafeTab.ScrollView = Tabs.ScrollView
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Badge } from '@/src/components/Badge'
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
import { BadgeThemeTypes } from '@/src/components/Badge/Badge'
|
||||
import { useAppSelector } from '@/src/store/hooks'
|
||||
import { selectSignerByAddress } from '@/src/store/signersSlice'
|
||||
@@ -28,7 +28,7 @@ export const SignerTypeBadge = ({
|
||||
const signer = useAppSelector((state) => selectSignerByAddress(state, address))
|
||||
|
||||
if (isLoading) {
|
||||
return <Skeleton colorMode="dark" radius="round" height={size} width={size} />
|
||||
return <SafeSkeleton radius="round" height={size} width={size} />
|
||||
}
|
||||
|
||||
if (signer?.type === 'ledger') {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { PendingTransactions } from '@/src/components/StatusBanners/PendingTransactions'
|
||||
import { action } from '@storybook/addon-actions'
|
||||
import { action } from 'storybook/actions'
|
||||
|
||||
const meta: Meta<typeof PendingTransactions> = {
|
||||
title: 'StatusBanners/PendingTransactions',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Badge } from '@/src/components/Badge'
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
|
||||
interface ThresholdBadgeProps {
|
||||
threshold: number
|
||||
@@ -20,7 +20,7 @@ export const ThresholdBadge = ({
|
||||
testID,
|
||||
}: ThresholdBadgeProps) => {
|
||||
if (isLoading) {
|
||||
return <Skeleton colorMode="dark" radius="round" height={size} width={size} />
|
||||
return <SafeSkeleton radius="round" height={size} width={size} />
|
||||
}
|
||||
|
||||
const content = `${threshold}/${ownersCount}`
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React from 'react'
|
||||
import { View } from 'tamagui'
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
import { Container } from '@/src/components/Container'
|
||||
import random from 'lodash/random'
|
||||
import { useTheme } from '@/src/theme/hooks/useTheme'
|
||||
|
||||
interface TransactionSkeletonProps {
|
||||
count?: number
|
||||
@@ -12,8 +11,6 @@ interface TransactionSkeletonProps {
|
||||
}
|
||||
|
||||
export const TransactionSkeletonItem = () => {
|
||||
const { colorScheme } = useTheme()
|
||||
|
||||
// Memoize random widths to prevent re-renders and maintain consistent skeleton appearance
|
||||
const widths = React.useMemo(
|
||||
() => ({
|
||||
@@ -29,20 +26,20 @@ export const TransactionSkeletonItem = () => {
|
||||
<View flexDirection="row" width="100%" alignItems="center" justifyContent="space-between">
|
||||
<View flexDirection="row" maxWidth="55%" alignItems="center" gap="$3">
|
||||
{/* Left icon skeleton */}
|
||||
<Skeleton colorMode={colorScheme} radius="round" height={32} width={32} />
|
||||
<SafeSkeleton radius="round" height={32} width={32} />
|
||||
|
||||
<View flex={1} gap="$2">
|
||||
{/* Transaction type skeleton */}
|
||||
<Skeleton colorMode={colorScheme} height={10} width={widths.transactionType} />
|
||||
<SafeSkeleton height={10} width={widths.transactionType} />
|
||||
|
||||
{/* Transaction label skeleton */}
|
||||
<Skeleton colorMode={colorScheme} height={18} width={widths.transactionLabel} />
|
||||
<SafeSkeleton height={18} width={widths.transactionLabel} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Right side skeleton - value, status, or buttons */}
|
||||
<View alignItems="flex-end" gap="$2">
|
||||
<Skeleton colorMode={colorScheme} height={16} width={widths.rightSide} />
|
||||
<SafeSkeleton height={16} width={widths.rightSide} />
|
||||
</View>
|
||||
</View>
|
||||
</Container>
|
||||
@@ -54,26 +51,20 @@ export const TransactionSkeleton = ({
|
||||
showSections = true,
|
||||
sectionTitles = ['Recent transactions'],
|
||||
}: TransactionSkeletonProps) => {
|
||||
const { colorScheme } = useTheme()
|
||||
|
||||
// For pending transactions, we typically have 2 sections (Next, In queue)
|
||||
// For history, we typically have date-based sections
|
||||
const sections = showSections ? sectionTitles : ['']
|
||||
const itemsPerSection = Math.ceil(count / sections.length)
|
||||
|
||||
return (
|
||||
<Skeleton.Group show={true}>
|
||||
<SafeSkeleton.Group show={true}>
|
||||
<View>
|
||||
{sections.map((sectionTitle, sectionIndex) => (
|
||||
<View key={sectionIndex} gap="$4">
|
||||
{/* Section header skeleton - only show if we have a title */}
|
||||
{showSections && sectionTitle && (
|
||||
<View marginBottom="$2">
|
||||
<Skeleton
|
||||
colorMode={colorScheme}
|
||||
height={20}
|
||||
width={sectionTitle === 'Recent transactions' ? 120 : random(80, 120)}
|
||||
/>
|
||||
<SafeSkeleton height={20} width={sectionTitle === 'Recent transactions' ? 120 : random(80, 120)} />
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -84,6 +75,6 @@ export const TransactionSkeleton = ({
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</Skeleton.Group>
|
||||
</SafeSkeleton.Group>
|
||||
)
|
||||
}
|
||||
|
||||
+3
@@ -109,6 +109,7 @@ exports[`StakingTxDepositCard renders correctly 1`] = `
|
||||
onLoadStart={[Function]}
|
||||
onProgress={[Function]}
|
||||
placeholder={[]}
|
||||
sfEffect={null}
|
||||
source={
|
||||
[
|
||||
{
|
||||
@@ -122,6 +123,8 @@ exports[`StakingTxDepositCard renders correctly 1`] = `
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
symbolSize={null}
|
||||
symbolWeight={null}
|
||||
testID="logo-image"
|
||||
transition={null}
|
||||
/>
|
||||
|
||||
+3
@@ -109,6 +109,7 @@ exports[`StakingTxExitCard matches snapshot 1`] = `
|
||||
onLoadStart={[Function]}
|
||||
onProgress={[Function]}
|
||||
placeholder={[]}
|
||||
sfEffect={null}
|
||||
source={
|
||||
[
|
||||
{
|
||||
@@ -122,6 +123,8 @@ exports[`StakingTxExitCard matches snapshot 1`] = `
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
symbolSize={null}
|
||||
symbolWeight={null}
|
||||
testID="logo-image"
|
||||
transition={null}
|
||||
/>
|
||||
|
||||
+3
@@ -109,6 +109,7 @@ exports[`StakingTxWithdrawCard matches snapshot 1`] = `
|
||||
onLoadStart={[Function]}
|
||||
onProgress={[Function]}
|
||||
placeholder={[]}
|
||||
sfEffect={null}
|
||||
source={
|
||||
[
|
||||
{
|
||||
@@ -122,6 +123,8 @@ exports[`StakingTxWithdrawCard matches snapshot 1`] = `
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
symbolSize={null}
|
||||
symbolWeight={null}
|
||||
testID="logo-image"
|
||||
transition={null}
|
||||
/>
|
||||
|
||||
+3
@@ -100,6 +100,7 @@ exports[`VaultTxDepositCard renders correctly 1`] = `
|
||||
onLoadStart={[Function]}
|
||||
onProgress={[Function]}
|
||||
placeholder={[]}
|
||||
sfEffect={null}
|
||||
source={
|
||||
[
|
||||
{
|
||||
@@ -113,6 +114,8 @@ exports[`VaultTxDepositCard renders correctly 1`] = `
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
symbolSize={null}
|
||||
symbolWeight={null}
|
||||
testID="logo-image"
|
||||
transition={null}
|
||||
/>
|
||||
|
||||
+3
@@ -100,6 +100,7 @@ exports[`VaultTxRedeemCard renders correctly 1`] = `
|
||||
onLoadStart={[Function]}
|
||||
onProgress={[Function]}
|
||||
placeholder={[]}
|
||||
sfEffect={null}
|
||||
source={
|
||||
[
|
||||
{
|
||||
@@ -113,6 +114,8 @@ exports[`VaultTxRedeemCard renders correctly 1`] = `
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
symbolSize={null}
|
||||
symbolWeight={null}
|
||||
testID="logo-image"
|
||||
transition={null}
|
||||
/>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { AccountItem } from './AccountItem'
|
||||
import { Meta, StoryObj } from '@storybook/react'
|
||||
import { mockedActiveSafeInfo, mockedChains } from '@/src/store/constants'
|
||||
import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains'
|
||||
import { action } from '@storybook/addon-actions'
|
||||
import { action } from 'storybook/actions'
|
||||
import { Address } from '@/src/types/address'
|
||||
|
||||
const meta: Meta<typeof AccountItem> = {
|
||||
|
||||
@@ -2,10 +2,9 @@ import React from 'react'
|
||||
import { View } from 'tamagui'
|
||||
|
||||
import { Fiat } from '@/src/components/Fiat'
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
import { useAppSelector } from '@/src/store/hooks'
|
||||
import { selectCurrency } from '@/src/store/settingsSlice'
|
||||
import { useTheme } from '@/src/theme/hooks/useTheme'
|
||||
|
||||
interface BalanceProps {
|
||||
isLoading: boolean
|
||||
@@ -13,20 +12,19 @@ interface BalanceProps {
|
||||
}
|
||||
|
||||
export function Balance({ isLoading, balanceAmount }: BalanceProps) {
|
||||
const { colorScheme } = useTheme()
|
||||
const currency = useAppSelector(selectCurrency)
|
||||
|
||||
const showSkeleton = isLoading || !balanceAmount
|
||||
|
||||
return (
|
||||
<View alignItems="center" justifyContent="center" paddingVertical="$4" width="100%">
|
||||
<Skeleton.Group show={showSkeleton}>
|
||||
<Skeleton colorMode={colorScheme} width={220}>
|
||||
<SafeSkeleton.Group show={showSkeleton}>
|
||||
<SafeSkeleton width={220}>
|
||||
<View alignItems="center">
|
||||
<Fiat value={balanceAmount} currency={currency} precise />
|
||||
</View>
|
||||
</Skeleton>
|
||||
</Skeleton.Group>
|
||||
</SafeSkeleton>
|
||||
</SafeSkeleton.Group>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
+3
@@ -106,6 +106,7 @@ exports[`PositionItem renders correctly 1`] = `
|
||||
onLoadStart={[Function]}
|
||||
onProgress={[Function]}
|
||||
placeholder={[]}
|
||||
sfEffect={null}
|
||||
source={
|
||||
[
|
||||
{
|
||||
@@ -119,6 +120,8 @@ exports[`PositionItem renders correctly 1`] = `
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
symbolSize={null}
|
||||
symbolWeight={null}
|
||||
testID="logo-image"
|
||||
transition={null}
|
||||
/>
|
||||
|
||||
+3
@@ -132,6 +132,7 @@ exports[`StakingDeposit matches snapshot 1`] = `
|
||||
onLoadStart={[Function]}
|
||||
onProgress={[Function]}
|
||||
placeholder={[]}
|
||||
sfEffect={null}
|
||||
source={
|
||||
[
|
||||
{
|
||||
@@ -145,6 +146,8 @@ exports[`StakingDeposit matches snapshot 1`] = `
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
symbolSize={null}
|
||||
symbolWeight={null}
|
||||
testID="logo-image"
|
||||
transition={null}
|
||||
/>
|
||||
|
||||
+3
@@ -132,6 +132,7 @@ exports[`StakingExit matches snapshot 1`] = `
|
||||
onLoadStart={[Function]}
|
||||
onProgress={[Function]}
|
||||
placeholder={[]}
|
||||
sfEffect={null}
|
||||
source={
|
||||
[
|
||||
{
|
||||
@@ -145,6 +146,8 @@ exports[`StakingExit matches snapshot 1`] = `
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
symbolSize={null}
|
||||
symbolWeight={null}
|
||||
testID="logo-image"
|
||||
transition={null}
|
||||
/>
|
||||
|
||||
+3
@@ -132,6 +132,7 @@ exports[`StakingWithdrawRequest matches snapshot 1`] = `
|
||||
onLoadStart={[Function]}
|
||||
onProgress={[Function]}
|
||||
placeholder={[]}
|
||||
sfEffect={null}
|
||||
source={
|
||||
[
|
||||
{
|
||||
@@ -145,6 +146,8 @@ exports[`StakingWithdrawRequest matches snapshot 1`] = `
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
symbolSize={null}
|
||||
symbolWeight={null}
|
||||
testID="logo-image"
|
||||
transition={null}
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Text, YStack, Image, styled, H2, H5, getTokenValue } from 'tamagui'
|
||||
import { SafeButton } from '@/src/components/SafeButton'
|
||||
import { ColorScheme } from '@/src/types/theme'
|
||||
import TransferOldAppDark from '@/assets/images/transfer-old-app-dark.png'
|
||||
import TransferOldAppLight from '@/assets/images/transfer-old-app-light.png'
|
||||
import { ColorSchemeName } from 'react-native'
|
||||
import { GradientText } from '@/src/components/GradientText'
|
||||
|
||||
const StyledText = styled(Text, {
|
||||
@@ -12,7 +12,7 @@ const StyledText = styled(Text, {
|
||||
})
|
||||
|
||||
interface DataTransferViewProps {
|
||||
colorScheme: ColorSchemeName
|
||||
colorScheme: ColorScheme
|
||||
bottomInset: number
|
||||
onPressTransferData: () => void
|
||||
onPressStartFresh: () => void
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react'
|
||||
import { Text, YStack, Image, styled, H2 } from 'tamagui'
|
||||
import { SafeButton } from '@/src/components/SafeButton'
|
||||
import { ColorScheme } from '@/src/types/theme'
|
||||
import ImportDataSelectFilesDark from '@/assets/images/import-data-select-files-dark.png'
|
||||
import ImportDataSelectFilesLight from '@/assets/images/import-data-select-files-light.png'
|
||||
import { ColorSchemeName, TouchableOpacity } from 'react-native'
|
||||
import { TouchableOpacity } from 'react-native'
|
||||
|
||||
const StyledText = styled(Text, {
|
||||
fontSize: '$4',
|
||||
@@ -12,7 +13,7 @@ const StyledText = styled(Text, {
|
||||
})
|
||||
|
||||
interface FileSelectionViewProps {
|
||||
colorScheme: ColorSchemeName
|
||||
colorScheme: ColorScheme
|
||||
bottomInset: number
|
||||
onFileSelect: () => void
|
||||
onImagePress: () => void
|
||||
|
||||
+2
-4
@@ -1,8 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Text, View } from 'tamagui'
|
||||
import { router } from 'expo-router'
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import { useTheme } from '@/src/theme/hooks/useTheme'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
import { useAppSelector } from '@/src/store/hooks'
|
||||
import { selectActiveChain } from '@/src/store/chains'
|
||||
import { ExecutionMethod } from '@/src/features/HowToExecuteSheet/types'
|
||||
@@ -27,7 +26,6 @@ export const EstimatedNetworkFee = ({
|
||||
willFail,
|
||||
}: EstimatedNetworkFeeProps) => {
|
||||
const chain = useAppSelector(selectActiveChain)
|
||||
const { colorScheme } = useTheme()
|
||||
const activeSafe = useDefinedActiveSafe()
|
||||
|
||||
const { currentData: relaysRemaining, isLoading: isLoadingRelays } = useRelayGetRelaysRemainingV1Query({
|
||||
@@ -47,7 +45,7 @@ export const EstimatedNetworkFee = ({
|
||||
<Text color="$textSecondaryLight">Est. network fee</Text>
|
||||
|
||||
{isLoadingFees || isLoadingRelays ? (
|
||||
<Skeleton colorMode={colorScheme} height={16} width={100} />
|
||||
<SafeSkeleton height={16} width={100} />
|
||||
) : executionMethod === ExecutionMethod.WITH_RELAY ? (
|
||||
<RelayFee
|
||||
willFail={willFail}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { GradientText } from '@/src/components/GradientText'
|
||||
import { useTheme } from '@/src/theme/hooks/useTheme'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
import { RelaysRemaining } from '@safe-global/store/gateway/AUTO_GENERATED/relay'
|
||||
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import { getTokenValue, Text, View } from 'tamagui'
|
||||
import { CanNotEstimate } from '../CanNotEstimate'
|
||||
import { TouchableOpacity } from 'react-native'
|
||||
@@ -15,8 +14,6 @@ interface RelayFeeProps {
|
||||
}
|
||||
|
||||
export const RelayFee = ({ isLoadingRelays, willFail, relaysRemaining, onFailTextPress }: RelayFeeProps) => {
|
||||
const { colorScheme } = useTheme()
|
||||
|
||||
return (
|
||||
<View alignItems="flex-end" flexDirection="row" justifyContent="center" gap="$2">
|
||||
{willFail ? (
|
||||
@@ -32,7 +29,7 @@ export const RelayFee = ({ isLoadingRelays, willFail, relaysRemaining, onFailTex
|
||||
</View>
|
||||
|
||||
{isLoadingRelays ? (
|
||||
<Skeleton colorMode={colorScheme} height={16} width={80} />
|
||||
<SafeSkeleton height={16} width={80} />
|
||||
) : (
|
||||
relaysRemaining && <Text fontWeight={700}>{relaysRemaining.remaining} left / day</Text>
|
||||
)}
|
||||
|
||||
+4
-6
@@ -7,8 +7,7 @@ import { SelectExecutor } from '@/src/components/SelectExecutor'
|
||||
import { EstimatedNetworkFee } from '../EstimatedNetworkFee'
|
||||
import { Container } from '@/src/components/Container'
|
||||
import { ExecutionMethod } from '@/src/features/HowToExecuteSheet/types'
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import { useTheme } from '@/src/theme/hooks/useTheme'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
import { getSubmitButtonText } from './helpers'
|
||||
import { Alert } from '@/src/components/Alert'
|
||||
import { SafeFontIcon } from '@/src/components/SafeFontIcon'
|
||||
@@ -44,7 +43,6 @@ export function ReviewExecuteFooter({
|
||||
onConfirmPress,
|
||||
}: ReviewExecuteFooterProps) {
|
||||
const insets = useSafeAreaInsets()
|
||||
const { colorScheme } = useTheme()
|
||||
|
||||
const isButtonDisabled = !hasSufficientFunds || isExecuting
|
||||
const buttonText = isExecuting ? 'Executing...' : getSubmitButtonText(hasSufficientFunds)
|
||||
@@ -79,9 +77,9 @@ export function ReviewExecuteFooter({
|
||||
</Container>
|
||||
|
||||
{isCheckingFunds ? (
|
||||
<Skeleton.Group show>
|
||||
<Skeleton colorMode={colorScheme} height={44} width="100%" radius={12} />
|
||||
</Skeleton.Group>
|
||||
<SafeSkeleton.Group show>
|
||||
<SafeSkeleton height={44} width="100%" radius={12} />
|
||||
</SafeSkeleton.Group>
|
||||
) : (
|
||||
<SafeButton onPress={onConfirmPress} width="100%" disabled={isButtonDisabled} loading={isExecuting}>
|
||||
{buttonText}
|
||||
|
||||
+8
-10
@@ -1,16 +1,14 @@
|
||||
import React from 'react'
|
||||
import { Stack, View } from 'tamagui'
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
import { Container } from '@/src/components/Container'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useTheme } from '@/src/theme/hooks/useTheme'
|
||||
|
||||
export function ReviewExecuteFooterSkeleton() {
|
||||
const insets = useSafeAreaInsets()
|
||||
const { colorScheme } = useTheme()
|
||||
|
||||
return (
|
||||
<Skeleton.Group show={true}>
|
||||
<SafeSkeleton.Group show={true}>
|
||||
<Stack paddingHorizontal="$4" space="$3" paddingBottom={insets.bottom ? insets.bottom : '$4'}>
|
||||
<Container
|
||||
backgroundColor="transparent"
|
||||
@@ -22,19 +20,19 @@ export function ReviewExecuteFooterSkeleton() {
|
||||
{/* EstimatedNetworkFee skeleton */}
|
||||
<View flexDirection="row" alignItems="center" justifyContent="space-between" paddingHorizontal="$4">
|
||||
<View gap="$2">
|
||||
<Skeleton colorMode={colorScheme} height={14} width={100} />
|
||||
<Skeleton colorMode={colorScheme} height={12} width={140} />
|
||||
<SafeSkeleton height={14} width={100} />
|
||||
<SafeSkeleton height={12} width={140} />
|
||||
</View>
|
||||
<View alignItems="flex-end" gap="$2">
|
||||
<Skeleton colorMode={colorScheme} height={16} width={60} />
|
||||
<Skeleton colorMode={colorScheme} height={12} width={80} />
|
||||
<SafeSkeleton height={16} width={60} />
|
||||
<SafeSkeleton height={12} width={80} />
|
||||
</View>
|
||||
</View>
|
||||
</Container>
|
||||
|
||||
{/* Button skeleton */}
|
||||
<Skeleton colorMode={colorScheme} height={48} width="100%" radius={12} />
|
||||
<SafeSkeleton height={48} width="100%" radius={12} />
|
||||
</Stack>
|
||||
</Skeleton.Group>
|
||||
</SafeSkeleton.Group>
|
||||
)
|
||||
}
|
||||
|
||||
+2
-5
@@ -1,10 +1,9 @@
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import React from 'react'
|
||||
import { View, Text } from 'tamagui'
|
||||
import { RelaysRemaining } from '@safe-global/store/gateway/AUTO_GENERATED/relay'
|
||||
import { ExecutionMethod } from '@/src/features/HowToExecuteSheet/types'
|
||||
import { SafeFontIcon } from '@/src/components/SafeFontIcon'
|
||||
import { useTheme } from '@/src/theme/hooks/useTheme'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
|
||||
interface RelayAvailableProps {
|
||||
isLoadingRelays: boolean
|
||||
@@ -13,8 +12,6 @@ interface RelayAvailableProps {
|
||||
}
|
||||
|
||||
export const RelayAvailable = ({ isLoadingRelays, relaysRemaining, executionMethod }: RelayAvailableProps) => {
|
||||
const { colorScheme } = useTheme()
|
||||
|
||||
return (
|
||||
<View width="100%" flexDirection="row" justifyContent="space-between" alignItems="center">
|
||||
<View flex={1}>
|
||||
@@ -26,7 +23,7 @@ export const RelayAvailable = ({ isLoadingRelays, relaysRemaining, executionMeth
|
||||
We pay transactions fees for you
|
||||
</Text>
|
||||
{isLoadingRelays ? (
|
||||
<Skeleton colorMode={colorScheme} height={16} width={80} />
|
||||
<SafeSkeleton height={16} width={80} />
|
||||
) : (
|
||||
relaysRemaining && <Text fontSize="$4">{relaysRemaining.remaining} left / day</Text>
|
||||
)}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { View, Text, ScrollView, YStack, getTokenValue } from 'tamagui'
|
||||
import { useScrollableHeader } from '@/src/navigation/useScrollableHeader'
|
||||
import { NavBarTitle } from '@/src/components/Title'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { Controller, useFormContext } from 'react-hook-form'
|
||||
import { Controller, useFormContext, useWatch } from 'react-hook-form'
|
||||
import type { FormValues } from '@/src/features/ImportReadOnly/types'
|
||||
import SafeAccountInput from '@/src/components/SafeAccountInput'
|
||||
|
||||
@@ -21,10 +21,9 @@ export const ImportAccountFormView: React.FC<ImportAccountFormViewProps> = ({ is
|
||||
const {
|
||||
control,
|
||||
formState: { errors, isValid, dirtyFields },
|
||||
watch,
|
||||
} = useFormContext<FormValues>()
|
||||
const { top, bottom } = useSafeAreaInsets()
|
||||
const result = watch('importedSafeResult')
|
||||
const result = useWatch({ name: 'importedSafeResult' })
|
||||
|
||||
const { handleScroll } = useScrollableHeader({
|
||||
children: <NavBarTitle paddingRight={5}>Import Safe account</NavBarTitle>,
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import React from 'react'
|
||||
import { Text, XStack, YStack } from 'tamagui'
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import type { ThreatAnalysisResults } from '@safe-global/utils/features/safe-shield/types'
|
||||
import type { AsyncResult } from '@safe-global/utils/hooks/useAsync'
|
||||
import { InfoSheet } from '@/src/components/InfoSheet'
|
||||
import { SafeFontIcon } from '@/src/components/SafeFontIcon'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
import { BalanceChangeItem } from './BalanceChangeItem'
|
||||
import { Container } from '@/src/components/Container'
|
||||
import { useTheme } from '@/src/theme/hooks/useTheme'
|
||||
|
||||
interface BalanceChangeBlockProps {
|
||||
threat?: AsyncResult<ThreatAnalysisResults>
|
||||
@@ -17,7 +16,6 @@ const BALANCE_CHANGE_INFO =
|
||||
'The balance change gives an overview of the implications of a transaction. You can see which assets will be sent and received after the transaction is executed.'
|
||||
|
||||
export function BalanceChangeBlock({ threat }: BalanceChangeBlockProps) {
|
||||
const { colorScheme } = useTheme()
|
||||
const [threatData, threatError, threatLoading = false] = threat || []
|
||||
|
||||
const balanceChanges = threatData?.BALANCE_CHANGE || []
|
||||
@@ -27,7 +25,7 @@ export function BalanceChangeBlock({ threat }: BalanceChangeBlockProps) {
|
||||
if (threatLoading) {
|
||||
return (
|
||||
<YStack gap="$2" marginTop="$2">
|
||||
<Skeleton colorMode={colorScheme} height={24} radius={4} width="100%" />
|
||||
<SafeSkeleton height={24} radius={4} width="100%" />
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { WidgetAction } from './WidgetAction'
|
||||
import { Severity } from '@safe-global/utils/features/safe-shield/types'
|
||||
import { SEVERITY_TO_TITLE } from '@safe-global/utils/features/safe-shield/constants'
|
||||
import { action } from '@storybook/addon-actions'
|
||||
import { action } from 'storybook/actions'
|
||||
import { View } from 'tamagui'
|
||||
|
||||
const meta: Meta<typeof WidgetAction> = {
|
||||
|
||||
+8
-9
@@ -1,17 +1,16 @@
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import React from 'react'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
import { WidgetDisplayWrapper } from '../WidgetDisplayWrapper'
|
||||
import { useTheme } from '@/src/theme/hooks/useTheme'
|
||||
|
||||
export function LoadingWidget() {
|
||||
const { colorScheme } = useTheme()
|
||||
return (
|
||||
<WidgetDisplayWrapper>
|
||||
<Skeleton.Group show={true}>
|
||||
<Skeleton colorMode={colorScheme} height={20} radius={4} width={'100%'} />
|
||||
<Skeleton colorMode={colorScheme} height={20} radius={4} width={'100%'} />
|
||||
<Skeleton colorMode={colorScheme} height={20} radius={4} width={'100%'} />
|
||||
<Skeleton colorMode={colorScheme} height={60} width={'100%'} />
|
||||
</Skeleton.Group>
|
||||
<SafeSkeleton.Group show={true}>
|
||||
<SafeSkeleton height={20} radius={4} width={'100%'} />
|
||||
<SafeSkeleton height={20} radius={4} width={'100%'} />
|
||||
<SafeSkeleton height={20} radius={4} width={'100%'} />
|
||||
<SafeSkeleton height={60} width={'100%'} />
|
||||
</SafeSkeleton.Group>
|
||||
</WidgetDisplayWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@ import React, { useCallback } from 'react'
|
||||
import { H2, ScrollView, Text, Theme, View, XStack, YStack } from 'tamagui'
|
||||
import { SafeFontIcon } from '@/src/components/SafeFontIcon/SafeFontIcon'
|
||||
import { SafeListItem } from '@/src/components/SafeListItem'
|
||||
import { Skeleton } from 'moti/skeleton'
|
||||
import { SafeSkeleton } from '@/src/components/SafeSkeleton'
|
||||
import { Pressable, TouchableOpacity } from 'react-native'
|
||||
import { useTheme } from '@/src/theme/hooks/useTheme'
|
||||
import { EthAddress } from '@/src/components/EthAddress'
|
||||
import { SafeState } from '@safe-global/store/gateway/AUTO_GENERATED/safes'
|
||||
import { Address } from '@/src/types/address'
|
||||
@@ -44,8 +43,6 @@ export const Settings = ({
|
||||
const activeSafe = useDefinedActiveSafe()
|
||||
const copy = useCopyAndDispatchToast()
|
||||
const { owners = [], threshold, implementation } = data
|
||||
const { colorScheme } = useTheme()
|
||||
|
||||
const onPressAddressCopy = useCallback(() => {
|
||||
copy(activeSafe.address)
|
||||
}, [activeSafe.address])
|
||||
@@ -64,7 +61,7 @@ export const Settings = ({
|
||||
}}
|
||||
>
|
||||
<YStack flex={1} paddingTop={'$10'}>
|
||||
<Skeleton.Group show={!owners.length}>
|
||||
<SafeSkeleton.Group show={!owners.length}>
|
||||
<YStack alignItems="center" space="$3" marginBottom="$6">
|
||||
<BadgeWrapper
|
||||
badge={
|
||||
@@ -105,11 +102,11 @@ export const Settings = ({
|
||||
marginRight={'$2'}
|
||||
>
|
||||
<View width={30}>
|
||||
<Skeleton colorMode={colorScheme}>
|
||||
<SafeSkeleton>
|
||||
<Text fontWeight="bold" textAlign="center" fontSize={'$4'}>
|
||||
{owners.length}
|
||||
</Text>
|
||||
</Skeleton>
|
||||
</SafeSkeleton>
|
||||
</View>
|
||||
<Text color="$colorHover" fontSize={'$3'}>
|
||||
Signers
|
||||
@@ -125,11 +122,11 @@ export const Settings = ({
|
||||
width={80}
|
||||
>
|
||||
<View width={30}>
|
||||
<Skeleton colorMode={colorScheme}>
|
||||
<SafeSkeleton>
|
||||
<Text fontWeight="bold" textAlign="center" fontSize={'$4'}>
|
||||
{threshold}/{owners.length}
|
||||
</Text>
|
||||
</Skeleton>
|
||||
</SafeSkeleton>
|
||||
</View>
|
||||
<Text color="$colorHover" fontSize={'$3'}>
|
||||
Threshold
|
||||
@@ -154,11 +151,11 @@ export const Settings = ({
|
||||
leftNode={<SafeFontIcon name={'owners'} color={'$colorSecondary'} />}
|
||||
rightNode={
|
||||
<View flexDirection={'row'} alignItems={'center'} justifyContent={'center'}>
|
||||
<Skeleton colorMode={colorScheme} height={17}>
|
||||
<SafeSkeleton height={17}>
|
||||
<Text minWidth={15} marginRight={'$3'} color={'$colorSecondary'}>
|
||||
{owners.length}
|
||||
</Text>
|
||||
</Skeleton>
|
||||
</SafeSkeleton>
|
||||
<View>
|
||||
<SafeFontIcon name={'chevron-right'} />
|
||||
</View>
|
||||
@@ -206,7 +203,7 @@ export const Settings = ({
|
||||
</View>
|
||||
)}
|
||||
</YStack>
|
||||
</Skeleton.Group>
|
||||
</SafeSkeleton.Group>
|
||||
|
||||
{/* Footer */}
|
||||
<Pressable
|
||||
|
||||
@@ -274,6 +274,8 @@ jest.mock('@react-native-firebase/crashlytics', () => {
|
||||
jest.mock('@datadog/mobile-react-native', () => require('@datadog/mobile-react-native/jest'))
|
||||
jest.mock('expo-datadog', () => require('@datadog/mobile-react-native/jest'))
|
||||
|
||||
jest.mock('react-native-worklets', () => require('react-native-worklets/src/mock'))
|
||||
|
||||
beforeAll(() => server.listen())
|
||||
afterEach(() => server.resetHandlers())
|
||||
afterAll(() => server.close())
|
||||
|
||||
@@ -60,14 +60,14 @@ describe('useTheme', () => {
|
||||
},
|
||||
)
|
||||
|
||||
it('should return undefined when OS color scheme is null and themePreference is auto', () => {
|
||||
mockUseColorScheme.mockReturnValue(null)
|
||||
it('should normalize unspecified OS color scheme to dark when themePreference is auto', () => {
|
||||
mockUseColorScheme.mockReturnValue('unspecified')
|
||||
mockUseAppSelector.mockReturnValue('auto')
|
||||
|
||||
const { result } = renderHook(() => useTheme())
|
||||
|
||||
expect(result.current.colorScheme).toBe(undefined)
|
||||
expect(result.current.isDark).toBe(false)
|
||||
expect(result.current.colorScheme).toBe('dark')
|
||||
expect(result.current.isDark).toBe(true)
|
||||
expect(result.current.themePreference).toBe('auto')
|
||||
})
|
||||
|
||||
|
||||
@@ -3,14 +3,12 @@ import { useColorScheme } from 'react-native'
|
||||
import { updateSettings } from '@/src/store/settingsSlice'
|
||||
import { selectSettings } from '@/src/store/settingsSlice'
|
||||
import { useAppDispatch, useAppSelector } from '@/src/store/hooks'
|
||||
import { ThemePreference } from '@/src/types/theme'
|
||||
import type { ColorScheme, ThemePreference } from '@/src/types/theme'
|
||||
|
||||
export const useTheme = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
// The logical OR is intentional to ensure colorSchemeOS is never `null`.
|
||||
// This makes it easier to use in the rest of the codebase without having to check for null.
|
||||
const colorSchemeOS = useColorScheme() || undefined
|
||||
const colorSchemeOS = useColorScheme()
|
||||
|
||||
const themePreference = useAppSelector(
|
||||
(state) => selectSettings(state, 'themePreference') ?? 'auto',
|
||||
@@ -23,7 +21,8 @@ export const useTheme = () => {
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
const colorScheme = themePreference === 'auto' ? colorSchemeOS : themePreference
|
||||
const colorSchemeRaw = themePreference === 'auto' ? colorSchemeOS : themePreference
|
||||
const colorScheme: ColorScheme = colorSchemeRaw === 'light' ? 'light' : 'dark'
|
||||
|
||||
return {
|
||||
themePreference,
|
||||
|
||||
@@ -17,12 +17,13 @@ interface SafeThemeProviderProps {
|
||||
export const SafeThemeProvider = ({ children }: SafeThemeProviderProps) => {
|
||||
const { colorScheme, isDark, themePreference } = useTheme()
|
||||
|
||||
// Sync native iOS appearance with the app theme so native components
|
||||
// (RefreshControl, context menus, etc.) match the app's color scheme.
|
||||
// In auto mode, pass null to let the OS control the appearance.
|
||||
// Sync native iOS appearance so native components (RefreshControl, context
|
||||
// menus, etc.) match the app theme. In auto mode, pass 'unspecified' to
|
||||
// follow the OS. Fixed by .yarn/patches/react-native-npm-0.83.4-* which
|
||||
// resolves the actual OS scheme instead of storing 'unspecified' as-is.
|
||||
useEffect(() => {
|
||||
Appearance.setColorScheme(themePreference === 'auto' ? null : (colorScheme ?? null))
|
||||
}, [colorScheme, themePreference])
|
||||
Appearance.setColorScheme(themePreference === 'auto' ? 'unspecified' : themePreference)
|
||||
}, [themePreference])
|
||||
|
||||
const themeProvider = isStorybookEnv ? (
|
||||
<View
|
||||
@@ -37,8 +38,8 @@ export const SafeThemeProvider = ({ children }: SafeThemeProviderProps) => {
|
||||
|
||||
return (
|
||||
<FontProvider>
|
||||
<TamaguiProvider config={config} defaultTheme={colorScheme ?? 'light'}>
|
||||
<View testID={`theme-${colorScheme ?? 'light'}`} style={{ flex: 1 }}>
|
||||
<TamaguiProvider config={config} defaultTheme={colorScheme}>
|
||||
<View testID={`theme-${colorScheme}`} style={{ flex: 1 }}>
|
||||
{themeProvider}
|
||||
</View>
|
||||
</TamaguiProvider>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { createTokens } from 'tamagui'
|
||||
import { zIndex } from '@tamagui/themes'
|
||||
import { generateTamaguiColorTokens, radius, generateTamaguiFontSizes, spacingMobile } from '@safe-global/theme'
|
||||
import { generateTamaguiColorTokens, generateTamaguiFontSizes } from '@safe-global/theme/generators/tamagui'
|
||||
import { radius } from '@safe-global/theme/tokens/radius'
|
||||
import { spacingMobile } from '@safe-global/theme/tokens/spacing'
|
||||
|
||||
// Generate color tokens from unified palettes
|
||||
const colors = generateTamaguiColorTokens()
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export type ThemePreference = 'light' | 'dark' | 'auto'
|
||||
|
||||
export type ColorScheme = 'light' | 'dark'
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"strict": true,
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
"@safe-global/theme/*": ["../../packages/theme/src/*"],
|
||||
"@safe-global/store/*": ["../../packages/store/src/*"],
|
||||
"@safe-global/utils/*": ["../../packages/utils/src/*"],
|
||||
"@safe-global/test/*": ["../../config/test/*"],
|
||||
|
||||
@@ -1,78 +1,11 @@
|
||||
import js from '@eslint/js'
|
||||
import { FlatCompat } from '@eslint/eslintrc'
|
||||
import nextVitals from 'eslint-config-next/core-web-vitals'
|
||||
import typescriptEslint from '@typescript-eslint/eslint-plugin'
|
||||
import baseConfig from '../../config/eslint/base.mjs'
|
||||
import tsParser from '@typescript-eslint/parser'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
})
|
||||
|
||||
const typeScriptRules = {
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-unnecessary-type-constraint': 'warn',
|
||||
}
|
||||
|
||||
const withTypeScriptRules = (configs) => {
|
||||
let hasTypeScriptPlugin = false
|
||||
|
||||
const updatedConfigs = configs.map((config) => {
|
||||
if (!config.plugins || !config.plugins['@typescript-eslint']) {
|
||||
return config
|
||||
}
|
||||
|
||||
hasTypeScriptPlugin = true
|
||||
|
||||
return {
|
||||
...config,
|
||||
languageOptions: {
|
||||
...config.languageOptions,
|
||||
parser: tsParser,
|
||||
ecmaVersion: config.languageOptions?.ecmaVersion ?? 2020,
|
||||
sourceType: config.languageOptions?.sourceType ?? 'module',
|
||||
parserOptions: {
|
||||
...config.languageOptions?.parserOptions,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
...config.rules,
|
||||
...typeScriptRules,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
if (hasTypeScriptPlugin) {
|
||||
return updatedConfigs
|
||||
}
|
||||
|
||||
return [
|
||||
...updatedConfigs,
|
||||
{
|
||||
plugins: {
|
||||
'@typescript-eslint': typescriptEslint,
|
||||
},
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: typeScriptRules,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/node_modules/', '**/build/', '**/vite.config.ts', '**/jest.config.cjs', '**/src/__mocks__/**'],
|
||||
},
|
||||
...withTypeScriptRules(nextVitals),
|
||||
...baseConfig,
|
||||
{
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
@@ -83,12 +16,11 @@ export default [
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-unnecessary-type-constraint': 'warn',
|
||||
'react/display-name': 'off',
|
||||
'react/no-unescaped-entities': 'off',
|
||||
'react-hooks/incompatible-library': 'off',
|
||||
'react-hooks/refs': 'off',
|
||||
'react-hooks/set-state-in-effect': 'off',
|
||||
'react-hooks/use-memo': 'off',
|
||||
'no-console': ['warn', { allow: ['warn', 'error', 'info'] }],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"ethers": "6.14.3",
|
||||
"evm-proxy-detection": "^1.0.0",
|
||||
"localforage": "^1.10.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-is": "^19.1.0",
|
||||
"react-media": "^1.10.0",
|
||||
@@ -43,7 +43,6 @@
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@faker-js/faker": "^9.3.0",
|
||||
"@hookform/devtools": "^4.3.2",
|
||||
@@ -52,21 +51,23 @@
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"@types/react": "~19.2.10",
|
||||
"@types/react-dom": "~19.2.0",
|
||||
"@types/styled-components": "^5.1.34",
|
||||
"@typescript-eslint/eslint-plugin": "^8.31.1",
|
||||
"@typescript-eslint/parser": "^8.31.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-config-next": "^16.0.5",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"msw": "^2.7.3",
|
||||
"prettier": "^3.6.2",
|
||||
"ts-jest": "^29.2.5",
|
||||
"typescript": "~5.9.2",
|
||||
"typescript-eslint": "^8.31.1",
|
||||
"vite": "^6.0.7",
|
||||
"vite-plugin-svgr": "^4.3.0"
|
||||
}
|
||||
|
||||
@@ -57,16 +57,16 @@
|
||||
"@cowprotocol/widget-react": "0.14.6",
|
||||
"@datadog/browser-rum": "^6.24.1",
|
||||
"@ducanh2912/next-pwa": "^10.2.9",
|
||||
"@emotion/cache": "^11.13.5",
|
||||
"@emotion/react": "^11.13.5",
|
||||
"@emotion/cache": "^11.14.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.13.5",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@gnosis.pm/zodiac": "^4.0.3",
|
||||
"@ledgerhq/context-module": "^1.8.0",
|
||||
"@ledgerhq/device-management-kit": "^0.9.1",
|
||||
"@ledgerhq/device-signer-kit-ethereum": "^1.8.0",
|
||||
"@ledgerhq/device-transport-kit-web-hid": "^1.2.0",
|
||||
"@mui/icons-material": "^6.1.6",
|
||||
"@mui/icons-material": "^6.5.0",
|
||||
"@mui/material": "^6.5.0",
|
||||
"@mui/x-date-pickers": "^7.23.3",
|
||||
"@next/third-parties": "^15.2.0",
|
||||
@@ -110,9 +110,9 @@
|
||||
"papaparse": "^5.3.2",
|
||||
"postcss": "^8.5.6",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "19.1.0",
|
||||
"react": "19.2.0",
|
||||
"react-day-picker": "^9.13.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hook-form": "7.41.1",
|
||||
"react-papaparse": "^4.0.2",
|
||||
@@ -169,8 +169,8 @@
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@types/node": "22.13.1",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@types/react": "~19.2.10",
|
||||
"@types/react-dom": "~19.2.0",
|
||||
"@types/semver": "^7.3.10",
|
||||
"@typescript-eslint/eslint-plugin": "^7.6.0",
|
||||
"@typescript-eslint/parser": "^8.18.1",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { fn } from '@storybook/test'
|
||||
import { fn } from 'storybook/test'
|
||||
import { createMockStory } from '@/stories/mocks'
|
||||
import HiddenTokenButton from './index'
|
||||
|
||||
|
||||
+9
-9
@@ -13,8 +13,8 @@ exports[`./DatePickerInput.stories AllowFutureDates 1`] = `
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined mui-style-ll8nw6-MuiFormLabel-root-MuiInputLabel-root"
|
||||
data-shrink="false"
|
||||
for="«r1»"
|
||||
id="«r1»-label"
|
||||
for="_r_1_"
|
||||
id="_r_1_-label"
|
||||
>
|
||||
Expiry date
|
||||
</label>
|
||||
@@ -25,7 +25,7 @@ exports[`./DatePickerInput.stories AllowFutureDates 1`] = `
|
||||
aria-invalid="false"
|
||||
autocomplete="off"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputAdornedEnd mui-style-f423sj-MuiInputBase-input-MuiOutlinedInput-input"
|
||||
id="«r1»"
|
||||
id="_r_1_"
|
||||
inputmode="text"
|
||||
name="expiryDate"
|
||||
placeholder="DD/MM/YYYY"
|
||||
@@ -85,8 +85,8 @@ exports[`./DatePickerInput.stories Default 1`] = `
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined mui-style-ll8nw6-MuiFormLabel-root-MuiInputLabel-root"
|
||||
data-shrink="false"
|
||||
for="«r5»"
|
||||
id="«r5»-label"
|
||||
for="_r_5_"
|
||||
id="_r_5_-label"
|
||||
>
|
||||
Birth date
|
||||
</label>
|
||||
@@ -97,7 +97,7 @@ exports[`./DatePickerInput.stories Default 1`] = `
|
||||
aria-invalid="false"
|
||||
autocomplete="off"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputAdornedEnd mui-style-f423sj-MuiInputBase-input-MuiOutlinedInput-input"
|
||||
id="«r5»"
|
||||
id="_r_5_"
|
||||
inputmode="text"
|
||||
name="birthDate"
|
||||
placeholder="DD/MM/YYYY"
|
||||
@@ -157,8 +157,8 @@ exports[`./DatePickerInput.stories DisableFutureDates 1`] = `
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined mui-style-ll8nw6-MuiFormLabel-root-MuiInputLabel-root"
|
||||
data-shrink="false"
|
||||
for="«r9»"
|
||||
id="«r9»-label"
|
||||
for="_r_9_"
|
||||
id="_r_9_-label"
|
||||
>
|
||||
Created date
|
||||
</label>
|
||||
@@ -169,7 +169,7 @@ exports[`./DatePickerInput.stories DisableFutureDates 1`] = `
|
||||
aria-invalid="false"
|
||||
autocomplete="off"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputAdornedEnd mui-style-f423sj-MuiInputBase-input-MuiOutlinedInput-input"
|
||||
id="«r9»"
|
||||
id="_r_9_"
|
||||
inputmode="text"
|
||||
name="createdDate"
|
||||
placeholder="DD/MM/YYYY"
|
||||
|
||||
@@ -111,7 +111,7 @@ function EnhancedTableHead(props: EnhancedTableHeadProps) {
|
||||
>
|
||||
{headCell.label}
|
||||
{orderBy === headCell.id ? (
|
||||
<Box component="span" sx={visuallyHidden}>
|
||||
<Box component="span" sx={{ ...visuallyHidden }}>
|
||||
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
|
||||
</Box>
|
||||
) : null}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BRAND_NAME, IS_PRODUCTION, IS_BEHIND_IAP } from '@/config/constants'
|
||||
import { ContentSecurityPolicy, StrictTransportSecurity } from '@/config/securityHeaders'
|
||||
import { lightPalette, darkPalette } from '@safe-global/theme'
|
||||
import { lightPalette, darkPalette } from '@safe-global/theme/palettes'
|
||||
|
||||
const descriptionText = `${BRAND_NAME} is the most trusted smart account wallet on Ethereum with over $100B secured.`
|
||||
const titleText = BRAND_NAME
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { fn } from '@storybook/test'
|
||||
import { fn } from 'storybook/test'
|
||||
import { Button, DialogActions, DialogContent, Typography } from '@mui/material'
|
||||
import { StoreDecorator } from '@/stories/storeDecorator'
|
||||
import ModalDialog from './index'
|
||||
|
||||
+12
-12
@@ -13,8 +13,8 @@ exports[`./NameInput.stories Default 1`] = `
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined mui-style-ll8nw6-MuiFormLabel-root-MuiInputLabel-root"
|
||||
data-shrink="false"
|
||||
for="«r1»"
|
||||
id="«r1»-label"
|
||||
for="_r_1_"
|
||||
id="_r_1_-label"
|
||||
>
|
||||
Owner name
|
||||
</label>
|
||||
@@ -24,7 +24,7 @@ exports[`./NameInput.stories Default 1`] = `
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input mui-style-1eoo0u4-MuiInputBase-input-MuiOutlinedInput-input"
|
||||
id="«r1»"
|
||||
id="_r_1_"
|
||||
name="ownerName"
|
||||
type="text"
|
||||
/>
|
||||
@@ -59,8 +59,8 @@ exports[`./NameInput.stories Disabled 1`] = `
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined MuiFormLabel-colorPrimary Mui-disabled MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined mui-style-ll8nw6-MuiFormLabel-root-MuiInputLabel-root"
|
||||
data-shrink="false"
|
||||
for="«r3»"
|
||||
id="«r3»-label"
|
||||
for="_r_3_"
|
||||
id="_r_3_-label"
|
||||
>
|
||||
Owner name
|
||||
</label>
|
||||
@@ -71,7 +71,7 @@ exports[`./NameInput.stories Disabled 1`] = `
|
||||
aria-invalid="false"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input Mui-disabled mui-style-1eoo0u4-MuiInputBase-input-MuiOutlinedInput-input"
|
||||
disabled=""
|
||||
id="«r3»"
|
||||
id="_r_3_"
|
||||
name="ownerName"
|
||||
type="text"
|
||||
/>
|
||||
@@ -106,8 +106,8 @@ exports[`./NameInput.stories Required 1`] = `
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined MuiFormLabel-colorPrimary Mui-required MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined mui-style-ll8nw6-MuiFormLabel-root-MuiInputLabel-root"
|
||||
data-shrink="false"
|
||||
for="«r5»"
|
||||
id="«r5»-label"
|
||||
for="_r_5_"
|
||||
id="_r_5_-label"
|
||||
>
|
||||
Owner name
|
||||
<span
|
||||
@@ -124,7 +124,7 @@ exports[`./NameInput.stories Required 1`] = `
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input mui-style-1eoo0u4-MuiInputBase-input-MuiOutlinedInput-input"
|
||||
id="«r5»"
|
||||
id="_r_5_"
|
||||
name="ownerName"
|
||||
required=""
|
||||
type="text"
|
||||
@@ -162,8 +162,8 @@ exports[`./NameInput.stories WithPlaceholder 1`] = `
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined mui-style-ll8nw6-MuiFormLabel-root-MuiInputLabel-root"
|
||||
data-shrink="false"
|
||||
for="«r7»"
|
||||
id="«r7»-label"
|
||||
for="_r_7_"
|
||||
id="_r_7_-label"
|
||||
>
|
||||
Owner name
|
||||
</label>
|
||||
@@ -173,7 +173,7 @@ exports[`./NameInput.stories WithPlaceholder 1`] = `
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input mui-style-1eoo0u4-MuiInputBase-input-MuiOutlinedInput-input"
|
||||
id="«r7»"
|
||||
id="_r_7_"
|
||||
name="ownerName"
|
||||
placeholder="Enter owner name"
|
||||
type="text"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { Box } from '@mui/system'
|
||||
import { lightPalette } from '@safe-global/theme'
|
||||
import { lightPalette } from '@safe-global/theme/palettes'
|
||||
import css from './styles.module.css'
|
||||
import { Card, LinearProgress, CardHeader, Avatar, Typography, CardContent } from '@mui/material'
|
||||
import type { TxStepperProps } from './useCardStepper'
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { NewSafeFormData } from '@/components/new-safe/create'
|
||||
import { getRedirect } from '@/components/new-safe/create/logic'
|
||||
import StatusMessage from '@/components/new-safe/create/steps/StatusStep/StatusMessage'
|
||||
import useUndeployedSafe from '@/components/new-safe/create/steps/StatusStep/useUndeployedSafe'
|
||||
import { lightPalette } from '@safe-global/theme'
|
||||
import { lightPalette } from '@safe-global/theme/palettes'
|
||||
import { AppRoutes } from '@/config/routes'
|
||||
import { safeCreationPendingStatuses } from '@/features/counterfactual'
|
||||
import { SafeCreationEvent, safeCreationSubscribe, isPredictedSafeProps } from '@/features/counterfactual/services'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { Box, Checkbox, FormControlLabel, Typography } from '@mui/material'
|
||||
import WarningAmberOutlinedIcon from '@mui/icons-material/WarningAmberOutlined'
|
||||
import { lightPalette } from '@safe-global/theme'
|
||||
import { lightPalette } from '@safe-global/theme/palettes'
|
||||
import Domain from './Domain'
|
||||
|
||||
type UnknownAppWarningProps = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { fn } from '@storybook/test'
|
||||
import { fn } from 'storybook/test'
|
||||
import { createMockStory } from '@/stories/mocks'
|
||||
import QrModal from './QrModal'
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { PaletteMode } from '@mui/material'
|
||||
// This import includes MUI type extensions via side-effect
|
||||
import { generateMuiTheme } from '@safe-global/theme'
|
||||
// MUI type extensions (module augmentation for custom palette colors, button variants, etc.)
|
||||
import '@safe-global/theme/generators/mui-extensions'
|
||||
import { generateMuiTheme } from '@safe-global/theme/generators/mui'
|
||||
|
||||
/**
|
||||
* Create Safe-themed MUI theme for the given mode.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@gnosis.pm/zodiac/*": ["../../node_modules/@gnosis.pm/zodiac/*"],
|
||||
"@cowprotocol/app-data": ["../../node_modules/@cowprotocol/app-data"],
|
||||
"@/public/*": ["./public/*"],
|
||||
"@safe-global/theme/*": ["../../packages/theme/src/*"],
|
||||
"@safe-global/store/*": ["../../packages/store/src/*"],
|
||||
"@safe-global/utils/*": ["../../packages/utils/src/*"],
|
||||
"@safe-global/test/*": ["../../config/test/*"],
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import pluginJs from '@eslint/js'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import pluginReact from 'eslint-plugin-react'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
|
||||
/**
|
||||
* Shared ESLint base config for packages that use React + TypeScript
|
||||
* but do not need Next.js-specific rules.
|
||||
*/
|
||||
export default [
|
||||
pluginJs.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
pluginReact.configs.flat.recommended,
|
||||
pluginReact.configs.flat['jsx-runtime'],
|
||||
reactHooks.configs['recommended-latest'],
|
||||
{
|
||||
settings: { react: { version: 'detect' } },
|
||||
rules: {
|
||||
// Disable prop-types — TypeScript handles type checking
|
||||
'react/prop-types': 'off',
|
||||
// Match previous eslint-config-next leniency for these rules
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/no-unused-expressions': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'warn',
|
||||
'@typescript-eslint/no-namespace': 'warn',
|
||||
'@typescript-eslint/no-non-null-asserted-optional-chain': 'warn',
|
||||
'@typescript-eslint/no-unnecessary-type-constraint': 'warn',
|
||||
'no-useless-escape': 'warn',
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
withEntitlementsPlist,
|
||||
withInfoPlist,
|
||||
withXcodeProject,
|
||||
} from '@expo/config-plugins'
|
||||
} from 'expo/config-plugins'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConfigPlugin } from '@expo/config-plugins'
|
||||
import { ConfigPlugin } from 'expo/config-plugins'
|
||||
import { validateProps } from './utils'
|
||||
import { NotifeeExpoPluginProps } from './types'
|
||||
import NotifeeIos from './ios'
|
||||
|
||||
+2
-1
@@ -27,7 +27,8 @@
|
||||
"@ethersproject/signing-key/elliptic": "^6.6.1",
|
||||
"stylus": "0.64.0",
|
||||
"viem": "2.21.55",
|
||||
"webpack": "5.97.1"
|
||||
"webpack": "5.97.1",
|
||||
"@tamagui/static/react-native-web": "0.21.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/builder-webpack5": "^10.2.6",
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
import nextVitals from 'eslint-config-next/core-web-vitals'
|
||||
import baseConfig from '../../config/eslint/base.mjs'
|
||||
|
||||
const eslintConfig = [
|
||||
...nextVitals,
|
||||
{
|
||||
rules: {
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
'react-hooks/set-state-in-effect': 'off',
|
||||
'react-hooks/preserve-manual-memoization': 'off',
|
||||
'react-hooks/refs': 'off',
|
||||
},
|
||||
},
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
ignores: ['**/node_modules/'],
|
||||
},
|
||||
]
|
||||
|
||||
export default eslintConfig
|
||||
|
||||
@@ -17,13 +17,16 @@
|
||||
"build:local": "NODE_ENV=local yarn fetch-schema && yarn generate-api"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@safe-global/test": "workspace:^",
|
||||
"@types/jest": "^29.5.14",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-config-next": "^16.0.5",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "~5.9.2"
|
||||
"typescript": "~5.9.2",
|
||||
"typescript-eslint": "^8.31.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"jest-fixed-jsdom": "^0.0.10",
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import nextVitals from 'eslint-config-next/core-web-vitals'
|
||||
import baseConfig from '../../config/eslint/base.mjs'
|
||||
|
||||
const eslintConfig = [
|
||||
...nextVitals,
|
||||
{
|
||||
rules: {
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
},
|
||||
},
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
ignores: ['**/node_modules/'],
|
||||
},
|
||||
]
|
||||
|
||||
export default eslintConfig
|
||||
|
||||
@@ -17,22 +17,29 @@
|
||||
"prettier:fix": "prettier --write . --config ../../.prettierrc --ignore-path ../../.prettierignore"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^6.5.0",
|
||||
"tamagui": "^1.x"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@mui/material": {
|
||||
"optional": true
|
||||
},
|
||||
"tamagui": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@mui/material": "^6.5.0",
|
||||
"@safe-global/test": "workspace:^",
|
||||
"@types/jest": "^29.5.14",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-config-next": "^16.0.5",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "~5.9.2"
|
||||
"typescript": "~5.9.2",
|
||||
"typescript-eslint": "^8.31.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"jest-fixed-jsdom": "^0.0.10"
|
||||
|
||||
+10
-25
@@ -1,26 +1,11 @@
|
||||
// Main exports for @safe-global/theme package
|
||||
// This package uses sub-path imports exclusively.
|
||||
// Import from specific modules instead of this barrel file:
|
||||
//
|
||||
// import { lightPalette } from '@safe-global/theme/palettes/light'
|
||||
// import { generateMuiTheme } from '@safe-global/theme/generators/mui'
|
||||
// import { generateTamaguiColorTokens } from '@safe-global/theme/generators/tamagui'
|
||||
// import { spacingMobile } from '@safe-global/theme/tokens/spacing'
|
||||
//
|
||||
// See package.json "exports" field for available sub-paths.
|
||||
|
||||
// MUI type extensions (side-effect import for TypeScript module augmentation)
|
||||
import './generators/mui-extensions'
|
||||
|
||||
// Palettes
|
||||
export { default as lightPalette } from './palettes/light'
|
||||
export { default as darkPalette } from './palettes/dark'
|
||||
export { default as staticColors } from './palettes/static'
|
||||
export type { ColorPalette, StaticColors } from './palettes/types'
|
||||
|
||||
// Tokens
|
||||
export * from './tokens'
|
||||
|
||||
// Generators
|
||||
export { generateMuiTheme } from './generators/mui'
|
||||
export {
|
||||
generateTamaguiColorTokens,
|
||||
generateTamaguiTokens,
|
||||
generateTamaguiThemes,
|
||||
generateTamaguiFontSizes,
|
||||
} from './generators/tamagui'
|
||||
export { generateCSSVars } from './generators/css-vars'
|
||||
|
||||
// Utilities
|
||||
export { flattenPalette } from './utils/flatten'
|
||||
export {}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export { default as lightPalette } from './light'
|
||||
export { default as darkPalette } from './dark'
|
||||
export { default as staticColors } from './static'
|
||||
export type { ColorPalette, StaticColors } from './types'
|
||||
@@ -1,14 +1,9 @@
|
||||
import nextVitals from 'eslint-config-next/core-web-vitals'
|
||||
import baseConfig from '../../config/eslint/base.mjs'
|
||||
|
||||
const eslintConfig = [
|
||||
...nextVitals,
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
rules: {
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
'react-hooks/set-state-in-effect': 'off',
|
||||
'react-hooks/preserve-manual-memoization': 'off',
|
||||
'react-hooks/refs': 'off',
|
||||
|
||||
'react-hooks/exhaustive-deps': [
|
||||
'warn',
|
||||
{
|
||||
@@ -21,5 +16,3 @@ const eslintConfig = [
|
||||
ignores: ['**/node_modules/', '**/src/types/contracts/'],
|
||||
},
|
||||
]
|
||||
|
||||
export default eslintConfig
|
||||
|
||||
@@ -13,14 +13,17 @@
|
||||
"test:coverage": "jest --coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@faker-js/faker": "^9.0.3",
|
||||
"@types/jest": "^29.5.14",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-config-next": "^16.0.5",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"ethers": "6.14.3",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "~5.9.2"
|
||||
"typescript": "~5.9.2",
|
||||
"typescript-eslint": "^8.31.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cowprotocol/app-data": "^3.1.0",
|
||||
|
||||
@@ -17,6 +17,13 @@ const DEPS_TO_CHECK = [
|
||||
'@ledgerhq/device-management-kit',
|
||||
'@ledgerhq/device-signer-kit-ethereum',
|
||||
'@types/jest',
|
||||
'@types/react',
|
||||
'@types/react-dom',
|
||||
'storybook',
|
||||
'@emotion/react',
|
||||
'@emotion/styled',
|
||||
'@mui/material',
|
||||
'@mui/icons-material',
|
||||
'@safe-global/protocol-kit',
|
||||
'@safe-global/safe-apps-sdk',
|
||||
'@safe-global/safe-deployments',
|
||||
|
||||
Reference in New Issue
Block a user