@fadyshawky/react-native-magic 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +90 -56
- package/index.js +4 -0
- package/package.json +8 -2
- package/scripts/askPackageName.js +10 -5
- package/template/.env.development +8 -6
- package/template/.env.example +15 -5
- package/template/.env.production +8 -6
- package/template/.env.staging +8 -6
- package/template/.eslintrc.js +14 -0
- package/template/.husky/pre-commit +1 -0
- package/template/App.tsx +47 -16
- package/template/__tests__/App.test.tsx +28 -10
- package/template/babel.config.js +20 -1
- package/template/docs/ARCHITECTURE.md +40 -10
- package/template/docs/BEST_PRACTICES.md +10 -1
- package/template/docs/CUSTOMIZATION.md +118 -5
- package/template/docs/design-system.html +1164 -0
- package/template/docs/wireframes.html +411 -0
- package/template/index.js +10 -0
- package/template/jest.config.js +16 -1
- package/template/jest.setup.js +61 -0
- package/template/package-lock.json +12178 -8293
- package/template/package.json +53 -20
- package/template/react-native.config.js +3 -0
- package/template/resources/fonts/.gitkeep +0 -0
- package/template/scripts/ci-sync-env.cjs +71 -0
- package/template/src/assets/brand/logo-mark.svg +8 -0
- package/template/src/assets/brand/logo-mono.svg +9 -0
- package/template/src/assets/brand/logo-primary.svg +15 -0
- package/template/src/assets/brand/wordmark-dark.svg +18 -0
- package/template/src/common/components/AppBottomSheet.tsx +87 -0
- package/template/src/common/components/AppSwitch.tsx +75 -0
- package/template/src/common/components/AppTextInput.tsx +161 -0
- package/template/src/common/components/Avatar.tsx +75 -0
- package/template/src/common/components/Badge.tsx +66 -0
- package/template/src/common/components/CardScroller.tsx +58 -0
- package/template/src/common/components/Cards.tsx +13 -7
- package/template/src/common/components/Carousel.tsx +196 -0
- package/template/src/common/components/Checkbox.tsx +85 -0
- package/template/src/common/components/Chip.tsx +55 -0
- package/template/src/common/components/Dropdown.tsx +202 -0
- package/template/src/common/components/ErrorBoundary.tsx +82 -0
- package/template/src/common/components/FlatListWrapper.tsx +4 -5
- package/template/src/common/components/ListItem.tsx +90 -0
- package/template/src/common/components/LoadingComponent.tsx +8 -2
- package/template/src/common/components/Logo.tsx +77 -0
- package/template/src/common/components/ModalDialog.tsx +141 -0
- package/template/src/common/components/NetworkBanner.tsx +47 -0
- package/template/src/common/components/OTPInput.tsx +0 -1
- package/template/src/common/components/PrimaryButton.tsx +0 -14
- package/template/src/common/components/PrimaryTextInput.tsx +66 -130
- package/template/src/common/components/RadioGroup.tsx +95 -0
- package/template/src/common/components/SafeText.tsx +4 -3
- package/template/src/common/components/SearchBar.tsx +7 -5
- package/template/src/common/components/SegmentedControl.tsx +77 -0
- package/template/src/common/components/Skeleton.tsx +47 -0
- package/template/src/common/components/TryAgain.tsx +4 -2
- package/template/src/common/helpers/arrayHelpers.ts +2 -2
- package/template/src/common/helpers/defaultKeyIdExtractor.ts +1 -1
- package/template/src/common/helpers/regexHelpers.ts +1 -2
- package/template/src/common/helpers/stringsHelpers.ts +0 -1
- package/template/src/common/hooks/useBackHandler.ts +5 -2
- package/template/src/common/hooks/useEventRegister.ts +1 -1
- package/template/src/common/hooks/useFlatListActions.ts +1 -1
- package/template/src/common/hooks/useWhyDidYouUpdate.ts +1 -1
- package/template/src/common/localization/LocalizationProvider.tsx +1 -1
- package/template/src/common/localization/RTLInitializer.tsx +1 -1
- package/template/src/common/localization/dateFormatter.ts +0 -1
- package/template/src/common/localization/intlFormatter.ts +0 -1
- package/template/src/common/localization/localization.ts +2 -2
- package/template/src/common/localization/translations/homeLocalization.ts +14 -0
- package/template/src/common/localization/translations/loginLocalization.ts +8 -0
- package/template/src/common/localization/translations/mainNavigationLocalization.ts +2 -0
- package/template/src/common/localization/translations/profileLocalization.ts +16 -0
- package/template/src/common/utils/index.tsx +0 -6
- package/template/src/common/validations/commonValidations.ts +2 -2
- package/template/src/core/api/errorHandler.ts +1 -1
- package/template/src/core/api/responseHandlers.ts +1 -3
- package/template/src/core/api/serverHeaders.ts +61 -12
- package/template/src/core/notifications/notificationAuth.ts +6 -0
- package/template/src/core/notifications/notificationService.ts +125 -0
- package/template/src/core/notifications/routeFromNotificationData.ts +32 -0
- package/template/src/core/store/categories/categoriesActions.ts +25 -0
- package/template/src/core/store/categories/categoriesSlice.ts +51 -0
- package/template/src/core/store/categories/categoriesState.ts +19 -0
- package/template/src/core/store/rootReducer.ts +2 -0
- package/template/src/core/store/store.tsx +6 -1
- package/template/src/core/store/user/userActions.ts +75 -14
- package/template/src/core/store/user/userSlice.ts +49 -26
- package/template/src/core/store/user/userState.ts +6 -4
- package/template/src/core/theme/ThemeProvider.tsx +5 -3
- package/template/src/core/theme/brand.ts +50 -0
- package/template/src/core/theme/colors.ts +113 -99
- package/template/src/core/theme/commonConsts.ts +2 -2
- package/template/src/core/theme/commonStyles.ts +1 -1
- package/template/src/core/theme/themes.ts +2 -0
- package/template/src/core/theme/types.ts +4 -2
- package/template/src/core/utils/stringUtils.ts +1 -1
- package/template/src/design-system/index.ts +2 -0
- package/template/src/design-system/tokens/brand.ts +6 -0
- package/template/src/design-system/tokens/index.ts +3 -0
- package/template/src/design-system/tokens/palette.ts +4 -0
- package/template/src/design-system/tokens/typography-spacing.ts +2 -0
- package/template/src/navigation/AuthStack.tsx +1 -4
- package/template/src/navigation/HeaderComponents.tsx +6 -3
- package/template/src/navigation/MainStack.tsx +18 -6
- package/template/src/navigation/RootNavigation.tsx +4 -7
- package/template/src/navigation/TabBar.tsx +7 -6
- package/template/src/navigation/types.ts +10 -31
- package/template/src/screens/Login/Login.tsx +47 -47
- package/template/src/screens/OTP/OTPScreen.tsx +6 -9
- package/template/src/screens/components/ComponentsScreen.tsx +301 -0
- package/template/src/screens/home/HomeScreen.tsx +143 -1
- package/template/src/screens/home/hooks/useHomeData.ts +19 -5
- package/template/src/screens/index.tsx +1 -0
- package/template/src/screens/profile/Profile.tsx +139 -2
- package/template/src/screens/splash/Splash.tsx +44 -11
- package/template/src/sheetManager/sheets.tsx +1 -1
- package/template/tsconfig.json +14 -2
- package/template/types/globals.d.ts +43 -0
- package/template/types/index.ts +2 -6
- package/template/types/modules.d.ts +9 -0
- package/template/types/react-native-config.d.ts +0 -2
- package/.vscode/settings.json +0 -8
- package/CHANGELOG.md +0 -119
- package/CODE_OF_CONDUCT.md +0 -83
- package/CONTRIBUTING.md +0 -60
- package/local.properties +0 -1
- package/template/src/common/components/ImageCropPickerButton.tsx +0 -107
- package/template/src/common/components/PhotoTakingButton.tsx +0 -94
- package/template/src/common/helpers/imageHelpers.ts +0 -5
- package/template/src/common/helpers/inAppReviewHelper.ts +0 -30
- package/template/src/common/helpers/orientationHelpers.ts +0 -25
- package/template/src/common/helpers/shareHelpers.ts +0 -47
- package/template/src/common/utils/FeesCaalculation.tsx +0 -37
- package/template/src/common/utils/printData.tsx +0 -161
- package/template/src/common/validations/examples/TextInputWithValidation.tsx +0 -229
package/template/package.json
CHANGED
|
@@ -12,38 +12,59 @@
|
|
|
12
12
|
"ios": "react-native run-ios",
|
|
13
13
|
"lint": "eslint .",
|
|
14
14
|
"start": "react-native start",
|
|
15
|
-
"test": "jest"
|
|
15
|
+
"test": "jest",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"postinstall": "patch-package",
|
|
18
|
+
"prepare": "husky"
|
|
19
|
+
},
|
|
20
|
+
"lint-staged": {
|
|
21
|
+
"*.{ts,tsx,js,jsx}": [
|
|
22
|
+
"eslint --fix",
|
|
23
|
+
"prettier --write"
|
|
24
|
+
],
|
|
25
|
+
"*.{json,md,yml,yaml}": [
|
|
26
|
+
"prettier --write"
|
|
27
|
+
]
|
|
16
28
|
},
|
|
17
29
|
"dependencies": {
|
|
18
|
-
"@
|
|
30
|
+
"@d11/react-native-fast-image": "8.13.0",
|
|
31
|
+
"@gorhom/bottom-sheet": "5.2.10",
|
|
19
32
|
"@react-native-async-storage/async-storage": "2.2.0",
|
|
33
|
+
"@react-native-community/netinfo": "12.0.1",
|
|
34
|
+
"@react-native-firebase/analytics": "24.0.0",
|
|
35
|
+
"@react-native-firebase/app": "24.0.0",
|
|
36
|
+
"@react-native-firebase/messaging": "24.0.0",
|
|
20
37
|
"@react-native-masked-view/masked-view": "0.3.2",
|
|
21
|
-
"@react-navigation/bottom-tabs": "7.15.
|
|
22
|
-
"@react-navigation/native": "7.
|
|
23
|
-
"@react-navigation/native-stack": "7.14.
|
|
38
|
+
"@react-navigation/bottom-tabs": "7.15.9",
|
|
39
|
+
"@react-navigation/native": "7.2.2",
|
|
40
|
+
"@react-navigation/native-stack": "7.14.11",
|
|
24
41
|
"@reduxjs/toolkit": "2.11.2",
|
|
25
|
-
"@shopify/flash-list": "
|
|
26
|
-
"axios": "1.
|
|
42
|
+
"@shopify/flash-list": "2.3.1",
|
|
43
|
+
"axios": "^1.18.1",
|
|
27
44
|
"babel-plugin-transform-remove-console": "6.9.4",
|
|
28
|
-
"dayjs": "1.11.
|
|
45
|
+
"dayjs": "1.11.20",
|
|
29
46
|
"intl": "1.2.5",
|
|
30
|
-
"lodash": "4.
|
|
47
|
+
"lodash": "^4.18.1",
|
|
31
48
|
"react": "19.2.3",
|
|
32
49
|
"react-dom": "19.2.3",
|
|
33
|
-
"react-native": "0.
|
|
50
|
+
"react-native": "0.85.2",
|
|
34
51
|
"react-native-actions-sheet": "10.1.2",
|
|
35
52
|
"react-native-config": "1.6.1",
|
|
36
53
|
"react-native-device-info": "15.0.2",
|
|
37
|
-
"react-native-gesture-handler": "2.
|
|
54
|
+
"react-native-gesture-handler": "2.31.1",
|
|
55
|
+
"react-native-haptic-feedback": "2.3.3",
|
|
38
56
|
"react-native-keyboard-aware-scroll-view": "0.9.5",
|
|
39
57
|
"react-native-linear-gradient": "2.8.3",
|
|
40
58
|
"react-native-localization": "2.3.2",
|
|
41
|
-
"react-native-
|
|
59
|
+
"react-native-permissions": "5.5.1",
|
|
60
|
+
"react-native-reanimated": "4.3.0",
|
|
42
61
|
"react-native-restart": "0.0.27",
|
|
43
62
|
"react-native-safe-area-context": "5.7.0",
|
|
44
63
|
"react-native-screens": "4.24.0",
|
|
45
64
|
"react-native-sfsymbols": "1.2.2",
|
|
65
|
+
"react-native-size-matters": "0.4.2",
|
|
46
66
|
"react-native-snackbar": "3.0.1",
|
|
67
|
+
"react-native-svg": "15.15.4",
|
|
47
68
|
"react-native-vector-icons": "10.3.0",
|
|
48
69
|
"react-native-worklets": "0.7.4",
|
|
49
70
|
"react-redux": "9.2.0",
|
|
@@ -52,26 +73,38 @@
|
|
|
52
73
|
"redux-persist-transform-filter": "0.0.22"
|
|
53
74
|
},
|
|
54
75
|
"devDependencies": {
|
|
55
|
-
"@babel/core": "7.29.
|
|
56
|
-
"@babel/preset-env": "7.29.
|
|
76
|
+
"@babel/core": "^7.29.7",
|
|
77
|
+
"@babel/preset-env": "7.29.2",
|
|
57
78
|
"@babel/runtime": "7.28.6",
|
|
58
79
|
"@react-native-community/cli": "20.1.2",
|
|
59
80
|
"@react-native-community/cli-platform-android": "20.1.2",
|
|
60
81
|
"@react-native-community/cli-platform-ios": "20.1.2",
|
|
61
|
-
"@react-native/babel-preset": "0.
|
|
62
|
-
"@react-native/eslint-config": "0.
|
|
63
|
-
"@react-native/
|
|
64
|
-
"@react-native/
|
|
82
|
+
"@react-native/babel-preset": "0.85.2",
|
|
83
|
+
"@react-native/eslint-config": "0.85.2",
|
|
84
|
+
"@react-native/jest-preset": "0.85.2",
|
|
85
|
+
"@react-native/metro-config": "0.85.2",
|
|
86
|
+
"@react-native/typescript-config": "0.85.2",
|
|
65
87
|
"@types/jest": "30.0.0",
|
|
66
88
|
"@types/react": "19.2.14",
|
|
67
89
|
"@types/react-test-renderer": "19.1.0",
|
|
68
|
-
"
|
|
69
|
-
"
|
|
90
|
+
"babel-plugin-module-resolver": "5.0.3",
|
|
91
|
+
"eslint": "^8.57.1",
|
|
92
|
+
"eslint-plugin-import": "^2.31.0",
|
|
93
|
+
"husky": "9.1.7",
|
|
94
|
+
"jest": "^30.4.2",
|
|
95
|
+
"lint-staged": "16.4.0",
|
|
96
|
+
"patch-package": "8.0.1",
|
|
70
97
|
"prettier": "3.8.1",
|
|
71
98
|
"react-test-renderer": "19.2.3",
|
|
72
99
|
"typescript": "5.9.3"
|
|
73
100
|
},
|
|
74
101
|
"engines": {
|
|
75
102
|
"node": ">=20"
|
|
103
|
+
},
|
|
104
|
+
"overrides": {
|
|
105
|
+
"jest-mock": "^30.0.0",
|
|
106
|
+
"jest-runtime": "^30.0.0",
|
|
107
|
+
"jest-snapshot": "^30.0.0",
|
|
108
|
+
"jest-environment-node": "^30.0.0"
|
|
76
109
|
}
|
|
77
110
|
}
|
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CI helper: write/update KEY=VALUE pairs in an env file.
|
|
5
|
+
* Used in Codemagic / Bitrise / GitHub Actions to sync version codes,
|
|
6
|
+
* build numbers, or any other env var into .env.<environment> before build.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node scripts/ci-sync-env.cjs <envFile> KEY=value [KEY=value ...]
|
|
10
|
+
*
|
|
11
|
+
* Example (bump Android version code to next Play Store value):
|
|
12
|
+
* node scripts/ci-sync-env.cjs .env.production ANDROID_VERSION_CODE=42 IOS_BUILD_NUMBER=42
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
function parseArgs(argv) {
|
|
19
|
+
const [, , envFile, ...pairs] = argv;
|
|
20
|
+
if (!envFile) {
|
|
21
|
+
console.error(
|
|
22
|
+
'Usage: node scripts/ci-sync-env.cjs <envFile> KEY=value [KEY=value ...]',
|
|
23
|
+
);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
const updates = {};
|
|
27
|
+
for (const pair of pairs) {
|
|
28
|
+
const eq = pair.indexOf('=');
|
|
29
|
+
if (eq === -1) {
|
|
30
|
+
console.error(`Skipping invalid pair (no '='): ${pair}`);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const key = pair.slice(0, eq).trim();
|
|
34
|
+
const value = pair.slice(eq + 1);
|
|
35
|
+
updates[key] = value;
|
|
36
|
+
}
|
|
37
|
+
return { envFile: path.resolve(process.cwd(), envFile), updates };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function applyUpdates(envFile, updates) {
|
|
41
|
+
let content = '';
|
|
42
|
+
if (fs.existsSync(envFile)) {
|
|
43
|
+
content = fs.readFileSync(envFile, 'utf8');
|
|
44
|
+
}
|
|
45
|
+
const lines = content.split('\n');
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
const output = lines.map((line) => {
|
|
48
|
+
const eq = line.indexOf('=');
|
|
49
|
+
if (eq === -1) return line;
|
|
50
|
+
const key = line.slice(0, eq).trim();
|
|
51
|
+
if (key in updates) {
|
|
52
|
+
seen.add(key);
|
|
53
|
+
return `${key}=${updates[key]}`;
|
|
54
|
+
}
|
|
55
|
+
return line;
|
|
56
|
+
});
|
|
57
|
+
for (const key of Object.keys(updates)) {
|
|
58
|
+
if (!seen.has(key)) {
|
|
59
|
+
if (output.length && output[output.length - 1] !== '') output.push('');
|
|
60
|
+
output.push(`${key}=${updates[key]}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
fs.writeFileSync(envFile, output.join('\n'), 'utf8');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { envFile, updates } = parseArgs(process.argv);
|
|
67
|
+
applyUpdates(envFile, updates);
|
|
68
|
+
console.log(`Updated ${envFile}:`);
|
|
69
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
70
|
+
console.log(` ${k}=${v}`);
|
|
71
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120" fill="none">
|
|
2
|
+
<g fill="#2F6BFF">
|
|
3
|
+
<rect x="25" y="24" width="13" height="72" rx="3"/>
|
|
4
|
+
<rect x="25" y="24" width="33" height="13" rx="3"/>
|
|
5
|
+
<rect x="25" y="52" width="26" height="12" rx="3"/>
|
|
6
|
+
<path d="M101 42 C101 32 91 28 83 28 C73 28 67 35 67 44 C67 52 75 56 84 59 C93 62 101 66 101 76 C101 86 91 91 83 91 C73 91 67 85 66 77" fill="none" stroke="#2F6BFF" stroke-width="13" stroke-linecap="round" stroke-linejoin="round"/>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120" fill="none">
|
|
2
|
+
<rect width="120" height="120" rx="30" fill="#0D1124"/>
|
|
3
|
+
<g fill="#FFFFFF">
|
|
4
|
+
<rect x="25" y="24" width="13" height="72" rx="3"/>
|
|
5
|
+
<rect x="25" y="24" width="33" height="13" rx="3"/>
|
|
6
|
+
<rect x="25" y="52" width="26" height="12" rx="3"/>
|
|
7
|
+
<path d="M101 42 C101 32 91 28 83 28 C73 28 67 35 67 44 C67 52 75 56 84 59 C93 62 101 66 101 76 C101 86 91 91 83 91 C73 91 67 85 66 77" fill="none" stroke="#FFFFFF" stroke-width="13" stroke-linecap="round" stroke-linejoin="round"/>
|
|
8
|
+
</g>
|
|
9
|
+
</svg>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="chip" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#0A1230"/>
|
|
5
|
+
<stop offset="1" stop-color="#1B45B8"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="120" height="120" rx="30" fill="url(#chip)"/>
|
|
9
|
+
<g fill="#6BA0FF">
|
|
10
|
+
<rect x="25" y="24" width="13" height="72" rx="3"/>
|
|
11
|
+
<rect x="25" y="24" width="33" height="13" rx="3"/>
|
|
12
|
+
<rect x="25" y="52" width="26" height="12" rx="3"/>
|
|
13
|
+
<path d="M101 42 C101 32 91 28 83 28 C73 28 67 35 67 44 C67 52 75 56 84 59 C93 62 101 66 101 76 C101 86 91 91 83 91 C73 91 67 85 66 77" fill="none" stroke="#6BA0FF" stroke-width="13" stroke-linecap="round" stroke-linejoin="round"/>
|
|
14
|
+
</g>
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="460" height="140" viewBox="0 0 460 140" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="chip" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#0A1230"/>
|
|
5
|
+
<stop offset="1" stop-color="#1B45B8"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="460" height="140" rx="22" fill="#06080F"/>
|
|
9
|
+
<rect x="28" y="22" width="96" height="96" rx="24" fill="url(#chip)"/>
|
|
10
|
+
<g fill="#6BA0FF" transform="translate(28,22) scale(0.8)">
|
|
11
|
+
<rect x="25" y="24" width="13" height="72" rx="3"/>
|
|
12
|
+
<rect x="25" y="24" width="33" height="13" rx="3"/>
|
|
13
|
+
<rect x="25" y="52" width="26" height="12" rx="3"/>
|
|
14
|
+
<path d="M101 42 C101 32 91 28 83 28 C73 28 67 35 67 44 C67 52 75 56 84 59 C93 62 101 66 101 76 C101 86 91 91 83 91 C73 91 67 85 66 77" fill="none" stroke="#6BA0FF" stroke-width="13" stroke-linecap="round" stroke-linejoin="round"/>
|
|
15
|
+
</g>
|
|
16
|
+
<text x="150" y="68" font-family="Verdana,Arial,sans-serif" font-size="30" font-weight="700" fill="#EEF2FF" letter-spacing="4">FADY SHAWKY</text>
|
|
17
|
+
<text x="152" y="94" font-family="Verdana,Arial,sans-serif" font-size="13" fill="#5E6A98" letter-spacing="3">react native magic</text>
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Modal, Pressable, StyleSheet, View, ViewStyle} from 'react-native';
|
|
3
|
+
import {useTheme} from '../../core/theme/ThemeProvider';
|
|
4
|
+
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
5
|
+
import {RTLAwareText} from './RTLAwareText';
|
|
6
|
+
|
|
7
|
+
interface AppBottomSheetProps {
|
|
8
|
+
visible: boolean;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
title?: string;
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A bottom-anchored sheet built on react-native's Modal.
|
|
16
|
+
* Slides up from the bottom with a dimmed midnight backdrop; tapping the
|
|
17
|
+
* backdrop closes it while taps on the card are absorbed.
|
|
18
|
+
*/
|
|
19
|
+
export function AppBottomSheet({
|
|
20
|
+
visible,
|
|
21
|
+
onClose,
|
|
22
|
+
title,
|
|
23
|
+
children,
|
|
24
|
+
}: AppBottomSheetProps): JSX.Element {
|
|
25
|
+
const {theme} = useTheme();
|
|
26
|
+
|
|
27
|
+
const cardStyle: ViewStyle = {
|
|
28
|
+
backgroundColor: theme.colors.grayScale_0,
|
|
29
|
+
borderTopLeftRadius: CommonSizes.borderRadius.xLarge,
|
|
30
|
+
borderTopRightRadius: CommonSizes.borderRadius.xLarge,
|
|
31
|
+
padding: CommonSizes.spacing.large,
|
|
32
|
+
paddingBottom: CommonSizes.spacing.xxxLarge,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleStyle: ViewStyle = {
|
|
36
|
+
width: 40,
|
|
37
|
+
height: 4,
|
|
38
|
+
borderRadius: CommonSizes.borderRadius.full,
|
|
39
|
+
backgroundColor: theme.colors.grayScale_50,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Modal
|
|
44
|
+
transparent
|
|
45
|
+
visible={visible}
|
|
46
|
+
animationType="slide"
|
|
47
|
+
onRequestClose={onClose}>
|
|
48
|
+
<View style={styles.container}>
|
|
49
|
+
<Pressable
|
|
50
|
+
style={styles.backdrop}
|
|
51
|
+
onPress={onClose}
|
|
52
|
+
accessibilityRole="button"
|
|
53
|
+
accessibilityLabel="Close"
|
|
54
|
+
/>
|
|
55
|
+
<Pressable style={cardStyle} onPress={event => event.stopPropagation()}>
|
|
56
|
+
<View style={styles.handleWrapper}>
|
|
57
|
+
<View style={handleStyle} />
|
|
58
|
+
</View>
|
|
59
|
+
{title ? (
|
|
60
|
+
<RTLAwareText style={[theme.text.bodyXLargeBold, styles.title]}>
|
|
61
|
+
{title}
|
|
62
|
+
</RTLAwareText>
|
|
63
|
+
) : null}
|
|
64
|
+
{children}
|
|
65
|
+
</Pressable>
|
|
66
|
+
</View>
|
|
67
|
+
</Modal>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const styles = StyleSheet.create({
|
|
72
|
+
container: {
|
|
73
|
+
flex: 1,
|
|
74
|
+
justifyContent: 'flex-end',
|
|
75
|
+
},
|
|
76
|
+
backdrop: {
|
|
77
|
+
...StyleSheet.absoluteFill,
|
|
78
|
+
backgroundColor: 'rgba(6,8,15,0.6)',
|
|
79
|
+
},
|
|
80
|
+
handleWrapper: {
|
|
81
|
+
alignItems: 'center',
|
|
82
|
+
marginBottom: CommonSizes.spacing.large,
|
|
83
|
+
},
|
|
84
|
+
title: {
|
|
85
|
+
marginBottom: CommonSizes.spacing.medium,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React, {useEffect, useRef} from 'react';
|
|
2
|
+
import {Animated, Pressable, StyleSheet, ViewStyle} from 'react-native';
|
|
3
|
+
import {useTheme} from '../../core/theme/ThemeProvider';
|
|
4
|
+
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
5
|
+
|
|
6
|
+
interface AppSwitchProps {
|
|
7
|
+
value: boolean;
|
|
8
|
+
onValueChange: (v: boolean) => void;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const TRACK_WIDTH = 48;
|
|
13
|
+
const TRACK_HEIGHT = 28;
|
|
14
|
+
const THUMB_SIZE = 24;
|
|
15
|
+
const PADDING = (TRACK_HEIGHT - THUMB_SIZE) / 2;
|
|
16
|
+
const TRAVEL = TRACK_WIDTH - THUMB_SIZE - PADDING * 2;
|
|
17
|
+
|
|
18
|
+
export function AppSwitch(props: AppSwitchProps): JSX.Element {
|
|
19
|
+
const {value, onValueChange, disabled} = props;
|
|
20
|
+
const {theme} = useTheme();
|
|
21
|
+
const anim = useRef(new Animated.Value(value ? 1 : 0)).current;
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
Animated.timing(anim, {
|
|
25
|
+
toValue: value ? 1 : 0,
|
|
26
|
+
duration: 180,
|
|
27
|
+
useNativeDriver: true,
|
|
28
|
+
}).start();
|
|
29
|
+
}, [value, anim]);
|
|
30
|
+
|
|
31
|
+
const translateX = anim.interpolate({
|
|
32
|
+
inputRange: [0, 1],
|
|
33
|
+
outputRange: [0, TRAVEL],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const trackStyle: ViewStyle = {
|
|
37
|
+
width: TRACK_WIDTH,
|
|
38
|
+
height: TRACK_HEIGHT,
|
|
39
|
+
borderRadius: CommonSizes.borderRadius.full,
|
|
40
|
+
padding: PADDING,
|
|
41
|
+
justifyContent: 'center',
|
|
42
|
+
backgroundColor: value
|
|
43
|
+
? theme.colors.PlatinateBlue_400
|
|
44
|
+
: theme.colors.grayScale_50,
|
|
45
|
+
opacity: disabled ? 0.5 : 1,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Pressable
|
|
50
|
+
onPress={() => !disabled && onValueChange(!value)}
|
|
51
|
+
disabled={disabled}
|
|
52
|
+
hitSlop={8}
|
|
53
|
+
accessibilityRole="switch"
|
|
54
|
+
accessibilityState={{checked: value, disabled}}
|
|
55
|
+
style={trackStyle}>
|
|
56
|
+
<Animated.View
|
|
57
|
+
style={[styles.thumb, {transform: [{translateX}]}]}
|
|
58
|
+
/>
|
|
59
|
+
</Pressable>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const styles = StyleSheet.create({
|
|
64
|
+
thumb: {
|
|
65
|
+
width: THUMB_SIZE,
|
|
66
|
+
height: THUMB_SIZE,
|
|
67
|
+
borderRadius: THUMB_SIZE / 2,
|
|
68
|
+
backgroundColor: '#FFFFFF',
|
|
69
|
+
shadowColor: '#06080F',
|
|
70
|
+
shadowOpacity: 0.2,
|
|
71
|
+
shadowRadius: 3,
|
|
72
|
+
shadowOffset: {width: 0, height: 1},
|
|
73
|
+
elevation: 3,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import React, {useMemo, useState} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
KeyboardTypeOptions,
|
|
4
|
+
Pressable,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
TextInput,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import Svg, {Path} from 'react-native-svg';
|
|
10
|
+
import {useTheme} from '../../core/theme/ThemeProvider';
|
|
11
|
+
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
12
|
+
import {RTLAwareText} from './RTLAwareText';
|
|
13
|
+
import {RTLAwareView} from './RTLAwareView';
|
|
14
|
+
|
|
15
|
+
interface AppTextInputProps {
|
|
16
|
+
label?: string;
|
|
17
|
+
value: string;
|
|
18
|
+
onChangeText: (t: string) => void;
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
error?: string | null;
|
|
21
|
+
secureTextEntry?: boolean;
|
|
22
|
+
multiline?: boolean;
|
|
23
|
+
keyboardType?: KeyboardTypeOptions;
|
|
24
|
+
editable?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function EyeIcon({open, color}: {open: boolean; color: string}): JSX.Element {
|
|
28
|
+
return (
|
|
29
|
+
<Svg width={22} height={22} viewBox="0 0 24 24" fill="none">
|
|
30
|
+
<Path
|
|
31
|
+
d="M2 12C3.7 7.6 7.5 5 12 5C16.5 5 20.3 7.6 22 12C20.3 16.4 16.5 19 12 19C7.5 19 3.7 16.4 2 12Z"
|
|
32
|
+
stroke={color}
|
|
33
|
+
strokeWidth={1.8}
|
|
34
|
+
strokeLinecap="round"
|
|
35
|
+
strokeLinejoin="round"
|
|
36
|
+
/>
|
|
37
|
+
<Path
|
|
38
|
+
d="M12 15C13.66 15 15 13.66 15 12C15 10.34 13.66 9 12 9C10.34 9 9 10.34 9 12C9 13.66 10.34 15 12 15Z"
|
|
39
|
+
stroke={color}
|
|
40
|
+
strokeWidth={1.8}
|
|
41
|
+
strokeLinecap="round"
|
|
42
|
+
strokeLinejoin="round"
|
|
43
|
+
/>
|
|
44
|
+
{!open ? (
|
|
45
|
+
<Path
|
|
46
|
+
d="M4 4L20 20"
|
|
47
|
+
stroke={color}
|
|
48
|
+
strokeWidth={1.8}
|
|
49
|
+
strokeLinecap="round"
|
|
50
|
+
/>
|
|
51
|
+
) : null}
|
|
52
|
+
</Svg>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function AppTextInput(props: AppTextInputProps): JSX.Element {
|
|
57
|
+
const {
|
|
58
|
+
label,
|
|
59
|
+
value,
|
|
60
|
+
onChangeText,
|
|
61
|
+
placeholder,
|
|
62
|
+
error,
|
|
63
|
+
secureTextEntry,
|
|
64
|
+
multiline,
|
|
65
|
+
keyboardType,
|
|
66
|
+
editable = true,
|
|
67
|
+
} = props;
|
|
68
|
+
const {theme} = useTheme();
|
|
69
|
+
const [isFocused, setFocused] = useState(false);
|
|
70
|
+
const [hidden, setHidden] = useState(true);
|
|
71
|
+
|
|
72
|
+
const borderColor = useMemo(() => {
|
|
73
|
+
if (error) {
|
|
74
|
+
return theme.colors.error_400;
|
|
75
|
+
}
|
|
76
|
+
if (isFocused) {
|
|
77
|
+
return theme.colors.PlatinateBlue_400;
|
|
78
|
+
}
|
|
79
|
+
return theme.colors.grayScale_50;
|
|
80
|
+
}, [error, isFocused, theme.colors]);
|
|
81
|
+
|
|
82
|
+
const rowStyle: ViewStyle = {
|
|
83
|
+
flexDirection: 'row',
|
|
84
|
+
alignItems: multiline ? 'flex-start' : 'center',
|
|
85
|
+
backgroundColor: theme.colors.grayScale_0,
|
|
86
|
+
borderColor,
|
|
87
|
+
borderWidth: CommonSizes.borderWidth.medium,
|
|
88
|
+
borderRadius: CommonSizes.borderRadius.large,
|
|
89
|
+
paddingHorizontal: CommonSizes.spacing.xLarge,
|
|
90
|
+
paddingVertical: multiline
|
|
91
|
+
? CommonSizes.spacing.large
|
|
92
|
+
: CommonSizes.spacing.medium,
|
|
93
|
+
minHeight: multiline ? 96 : undefined,
|
|
94
|
+
opacity: editable ? 1 : 0.6,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const inputDynamicStyle = {
|
|
98
|
+
color: theme.colors.grayScale_700,
|
|
99
|
+
textAlignVertical: multiline ? ('top' as const) : ('center' as const),
|
|
100
|
+
minHeight: multiline ? 72 : undefined,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<RTLAwareView style={styles.container}>
|
|
105
|
+
{label ? (
|
|
106
|
+
<RTLAwareText
|
|
107
|
+
style={[theme.text.bodyMediumBold, {color: theme.colors.grayScale_700}]}>
|
|
108
|
+
{label}
|
|
109
|
+
</RTLAwareText>
|
|
110
|
+
) : null}
|
|
111
|
+
<RTLAwareView style={rowStyle}>
|
|
112
|
+
<TextInput
|
|
113
|
+
style={[styles.input, theme.text.bodyLargeRegular, inputDynamicStyle]}
|
|
114
|
+
value={value}
|
|
115
|
+
onChangeText={onChangeText}
|
|
116
|
+
placeholder={placeholder}
|
|
117
|
+
placeholderTextColor={theme.colors.grayScale_200}
|
|
118
|
+
secureTextEntry={secureTextEntry ? hidden : false}
|
|
119
|
+
multiline={multiline}
|
|
120
|
+
keyboardType={keyboardType}
|
|
121
|
+
editable={editable}
|
|
122
|
+
onFocus={() => setFocused(true)}
|
|
123
|
+
onBlur={() => setFocused(false)}
|
|
124
|
+
selectionColor={theme.colors.PlatinateBlue_400}
|
|
125
|
+
/>
|
|
126
|
+
{secureTextEntry ? (
|
|
127
|
+
<Pressable
|
|
128
|
+
onPress={() => setHidden(prev => !prev)}
|
|
129
|
+
hitSlop={8}
|
|
130
|
+
accessibilityRole="button"
|
|
131
|
+
accessibilityLabel={hidden ? 'Show password' : 'Hide password'}
|
|
132
|
+
style={styles.eyeButton}>
|
|
133
|
+
<EyeIcon open={!hidden} color={theme.colors.grayScale_200} />
|
|
134
|
+
</Pressable>
|
|
135
|
+
) : null}
|
|
136
|
+
</RTLAwareView>
|
|
137
|
+
{error ? (
|
|
138
|
+
<RTLAwareText
|
|
139
|
+
style={[theme.text.bodySmallRegular, {color: theme.colors.error_400}]}>
|
|
140
|
+
{error}
|
|
141
|
+
</RTLAwareText>
|
|
142
|
+
) : null}
|
|
143
|
+
</RTLAwareView>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const styles = StyleSheet.create({
|
|
148
|
+
container: {
|
|
149
|
+
width: '100%',
|
|
150
|
+
flexDirection: 'column',
|
|
151
|
+
gap: CommonSizes.spacing.medium,
|
|
152
|
+
} as ViewStyle,
|
|
153
|
+
input: {
|
|
154
|
+
flex: 1,
|
|
155
|
+
padding: 0,
|
|
156
|
+
},
|
|
157
|
+
eyeButton: {
|
|
158
|
+
paddingStart: CommonSizes.spacing.medium,
|
|
159
|
+
alignSelf: 'center',
|
|
160
|
+
},
|
|
161
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Image, StyleSheet} from 'react-native';
|
|
3
|
+
import LinearGradient from 'react-native-linear-gradient';
|
|
4
|
+
import {BrandGradients, GradientDirection} from '../../core/theme/brand';
|
|
5
|
+
import {RTLAwareText} from './RTLAwareText';
|
|
6
|
+
|
|
7
|
+
interface AvatarProps {
|
|
8
|
+
name?: string;
|
|
9
|
+
uri?: string;
|
|
10
|
+
size?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const DEFAULT_SIZE = 44;
|
|
14
|
+
|
|
15
|
+
function getInitials(name?: string): string {
|
|
16
|
+
if (!name) {
|
|
17
|
+
return '?';
|
|
18
|
+
}
|
|
19
|
+
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
20
|
+
if (parts.length === 0) {
|
|
21
|
+
return '?';
|
|
22
|
+
}
|
|
23
|
+
if (parts.length === 1) {
|
|
24
|
+
return parts[0].charAt(0).toUpperCase();
|
|
25
|
+
}
|
|
26
|
+
return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A circular avatar. With `uri` it renders the image; otherwise it shows the
|
|
31
|
+
* name's initials (1–2 letters, '?' when absent) in white on a gradient circle.
|
|
32
|
+
* Initials scale with `size`.
|
|
33
|
+
*/
|
|
34
|
+
export function Avatar({name, uri, size = DEFAULT_SIZE}: AvatarProps): JSX.Element {
|
|
35
|
+
const circleStyle = {
|
|
36
|
+
width: size,
|
|
37
|
+
height: size,
|
|
38
|
+
borderRadius: size / 2,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (uri) {
|
|
42
|
+
return <Image source={{uri}} style={[circleStyle, styles.image]} />;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<LinearGradient
|
|
47
|
+
colors={BrandGradients.primary}
|
|
48
|
+
start={GradientDirection.start}
|
|
49
|
+
end={GradientDirection.end}
|
|
50
|
+
style={[circleStyle, styles.gradient]}>
|
|
51
|
+
<RTLAwareText
|
|
52
|
+
style={[
|
|
53
|
+
styles.initials,
|
|
54
|
+
{fontSize: size * 0.4, lineHeight: size * 0.5},
|
|
55
|
+
]}>
|
|
56
|
+
{getInitials(name)}
|
|
57
|
+
</RTLAwareText>
|
|
58
|
+
</LinearGradient>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const styles = StyleSheet.create({
|
|
63
|
+
image: {
|
|
64
|
+
resizeMode: 'cover',
|
|
65
|
+
},
|
|
66
|
+
gradient: {
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
justifyContent: 'center',
|
|
69
|
+
},
|
|
70
|
+
initials: {
|
|
71
|
+
color: '#FFFFFF',
|
|
72
|
+
fontWeight: 'bold',
|
|
73
|
+
textAlign: 'center',
|
|
74
|
+
},
|
|
75
|
+
});
|