@fadyshawky/react-native-magic 2.2.0 → 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 -55
- package/index.js +4 -0
- package/package.json +9 -3
- 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 -19
- 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 +8 -8
- 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
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {combineReducers} from '@reduxjs/toolkit';
|
|
2
2
|
import {AppReducer} from './app/appSlice';
|
|
3
|
+
import {CategoriesReducer} from './categories/categoriesSlice';
|
|
3
4
|
import {UserReducer} from './user/userSlice';
|
|
4
5
|
export const rootReducer = combineReducers({
|
|
5
6
|
app: AppReducer,
|
|
6
7
|
user: UserReducer,
|
|
8
|
+
categories: CategoriesReducer,
|
|
7
9
|
});
|
|
8
10
|
|
|
9
11
|
export type RootState = ReturnType<typeof rootReducer>;
|
|
@@ -13,7 +13,12 @@ const persistConfig: PersistConfig<RootState> = {
|
|
|
13
13
|
version: 1,
|
|
14
14
|
timeout: 1000,
|
|
15
15
|
transforms: [
|
|
16
|
-
createWhitelistFilter('user', [
|
|
16
|
+
createWhitelistFilter('user', [
|
|
17
|
+
'accessToken',
|
|
18
|
+
'refreshToken',
|
|
19
|
+
'fcmToken',
|
|
20
|
+
'user',
|
|
21
|
+
]),
|
|
17
22
|
createWhitelistFilter('app', ['language', 'isRTL']),
|
|
18
23
|
],
|
|
19
24
|
};
|
|
@@ -1,35 +1,62 @@
|
|
|
1
1
|
import {createAsyncThunk} from '@reduxjs/toolkit';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import {API_BASE_URL} from '../../config';
|
|
4
|
+
import {extractServerError} from '../../api/errorHandler';
|
|
2
5
|
import {handleFetchJsonResponse} from '../../api/responseHandlers';
|
|
3
6
|
import {post} from '../../api/serverHeaders';
|
|
4
|
-
import {RootState} from '../rootReducer';
|
|
5
|
-
import {extractServerError} from '../../api/errorHandler';
|
|
6
7
|
import {ensureString} from '../../utils/stringUtils';
|
|
8
|
+
import {RootState} from '../rootReducer';
|
|
7
9
|
|
|
8
10
|
export const userLogin = createAsyncThunk(
|
|
9
11
|
'user/login',
|
|
10
12
|
async (
|
|
11
13
|
{phone, password}: {phone: string; password: string},
|
|
12
|
-
{rejectWithValue
|
|
14
|
+
{rejectWithValue}: any,
|
|
13
15
|
) => {
|
|
14
16
|
try {
|
|
15
|
-
const data: {
|
|
16
|
-
mobile_number: string;
|
|
17
|
-
mpin: string;
|
|
18
|
-
scheme_id: number;
|
|
19
|
-
} = {
|
|
20
|
-
mobile_number: phone,
|
|
21
|
-
mpin: password,
|
|
22
|
-
scheme_id: 1,
|
|
23
|
-
};
|
|
24
17
|
const response = await post({
|
|
25
18
|
url: '/login',
|
|
26
|
-
data
|
|
19
|
+
data: {
|
|
20
|
+
mobile_number: phone,
|
|
21
|
+
mpin: password,
|
|
22
|
+
scheme_id: 1,
|
|
23
|
+
},
|
|
27
24
|
});
|
|
28
|
-
|
|
29
25
|
return handleFetchJsonResponse(response);
|
|
30
26
|
} catch (e: any) {
|
|
31
27
|
const serverError = extractServerError(e);
|
|
28
|
+
return rejectWithValue({
|
|
29
|
+
...serverError,
|
|
30
|
+
message: ensureString(serverError.message),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
);
|
|
32
35
|
|
|
36
|
+
export const verifyOTP = createAsyncThunk(
|
|
37
|
+
'user/verifyOTP',
|
|
38
|
+
async (
|
|
39
|
+
{
|
|
40
|
+
verification_code,
|
|
41
|
+
mobile_number,
|
|
42
|
+
device_token,
|
|
43
|
+
scheme_id,
|
|
44
|
+
}: {
|
|
45
|
+
verification_code?: string;
|
|
46
|
+
mobile_number?: string;
|
|
47
|
+
device_token?: string;
|
|
48
|
+
scheme_id?: number;
|
|
49
|
+
},
|
|
50
|
+
{rejectWithValue}: any,
|
|
51
|
+
) => {
|
|
52
|
+
try {
|
|
53
|
+
const response = await post({
|
|
54
|
+
url: '/verify-otp',
|
|
55
|
+
data: {verification_code, mobile_number, device_token, scheme_id},
|
|
56
|
+
});
|
|
57
|
+
return handleFetchJsonResponse(response);
|
|
58
|
+
} catch (e: any) {
|
|
59
|
+
const serverError = extractServerError(e);
|
|
33
60
|
return rejectWithValue({
|
|
34
61
|
...serverError,
|
|
35
62
|
message: ensureString(serverError.message),
|
|
@@ -37,3 +64,37 @@ export const userLogin = createAsyncThunk(
|
|
|
37
64
|
}
|
|
38
65
|
},
|
|
39
66
|
);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Token refresh thunk.
|
|
70
|
+
* Uses a bare axios call (no interceptor) so a failing refresh doesn't loop.
|
|
71
|
+
* Returns the new tokens; userSlice handles persisting them.
|
|
72
|
+
*/
|
|
73
|
+
export const refreshUserToken = createAsyncThunk<
|
|
74
|
+
{accessToken: string; refreshToken?: string},
|
|
75
|
+
void,
|
|
76
|
+
{state: RootState; rejectValue: {message: string}}
|
|
77
|
+
>('user/refreshToken', async (_arg, {getState, rejectWithValue}) => {
|
|
78
|
+
try {
|
|
79
|
+
const refreshToken = getState().user.refreshToken;
|
|
80
|
+
if (!refreshToken) {
|
|
81
|
+
return rejectWithValue({message: 'Missing refresh token'});
|
|
82
|
+
}
|
|
83
|
+
const response = await axios.post(
|
|
84
|
+
`${API_BASE_URL}/auth/refresh`,
|
|
85
|
+
{refreshToken},
|
|
86
|
+
{headers: {'Content-Type': 'application/json'}},
|
|
87
|
+
);
|
|
88
|
+
const data = response.data?.data ?? response.data ?? {};
|
|
89
|
+
if (!data.accessToken) {
|
|
90
|
+
return rejectWithValue({message: 'Refresh response missing accessToken'});
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
accessToken: data.accessToken,
|
|
94
|
+
refreshToken: data.refreshToken,
|
|
95
|
+
};
|
|
96
|
+
} catch (e: any) {
|
|
97
|
+
const serverError = extractServerError(e);
|
|
98
|
+
return rejectWithValue({message: ensureString(serverError.message)});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
@@ -1,37 +1,50 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function loginHandler(state: UserState, payload: {payload: any}) {
|
|
1
|
+
import {createSlice, PayloadAction} from '@reduxjs/toolkit';
|
|
2
|
+
import {LoadState} from '../../../../types';
|
|
3
|
+
import {newState} from '../../../common/utils/newState';
|
|
4
|
+
import {handleErrorResponse} from '../../api/responseHandlers';
|
|
5
|
+
import {refreshUserToken, userLogin, verifyOTP} from './userActions';
|
|
6
|
+
import {UserInitialState, UserState} from './userState';
|
|
7
|
+
|
|
8
|
+
function loginHandler(state: UserState, action: PayloadAction<any>) {
|
|
9
|
+
const payload = action.payload ?? {};
|
|
12
10
|
return newState(state, {
|
|
13
|
-
|
|
11
|
+
accessToken: payload.accessToken ?? state.accessToken,
|
|
12
|
+
refreshToken: payload.refreshToken ?? state.refreshToken,
|
|
13
|
+
user: payload.user ?? state.user,
|
|
14
|
+
loginLoading: LoadState.allIsLoaded,
|
|
14
15
|
});
|
|
15
16
|
}
|
|
17
|
+
|
|
16
18
|
function loginLoadingHandler(state: UserState) {
|
|
17
19
|
return newState(state, {
|
|
18
|
-
loginLoading: LoadState
|
|
20
|
+
loginLoading: LoadState.pullToRefresh,
|
|
19
21
|
});
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
function loginErrorHandler(
|
|
23
|
-
|
|
24
|
-
payload
|
|
25
|
-
) {
|
|
26
|
-
console.error('payload: ', JSON.stringify(payload));
|
|
27
|
-
handleErrorResponse((payload.payload.message as string) || 'Login failed');
|
|
24
|
+
function loginErrorHandler(state: UserState, action: any) {
|
|
25
|
+
console.error('payload: ', JSON.stringify(action.payload));
|
|
26
|
+
handleErrorResponse((action.payload?.message as string) || 'Login failed');
|
|
28
27
|
return newState(state, {
|
|
29
|
-
loginLoading: LoadState
|
|
28
|
+
loginLoading: LoadState.error,
|
|
30
29
|
});
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
function logoutHandler(
|
|
34
|
-
return
|
|
32
|
+
function logoutHandler() {
|
|
33
|
+
return UserInitialState;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function setFcmTokenHandler(state: UserState, action: PayloadAction<string>) {
|
|
37
|
+
return newState(state, {fcmToken: action.payload});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function setTokensHandler(
|
|
41
|
+
state: UserState,
|
|
42
|
+
action: PayloadAction<{accessToken: string; refreshToken?: string}>,
|
|
43
|
+
) {
|
|
44
|
+
return newState(state, {
|
|
45
|
+
accessToken: action.payload.accessToken,
|
|
46
|
+
refreshToken: action.payload.refreshToken ?? state.refreshToken,
|
|
47
|
+
});
|
|
35
48
|
}
|
|
36
49
|
|
|
37
50
|
export const {reducer: UserReducer, actions} = createSlice({
|
|
@@ -39,15 +52,25 @@ export const {reducer: UserReducer, actions} = createSlice({
|
|
|
39
52
|
initialState: UserInitialState,
|
|
40
53
|
reducers: {
|
|
41
54
|
setLogout: logoutHandler,
|
|
55
|
+
updateFcmToken: setFcmTokenHandler,
|
|
56
|
+
setTokens: setTokensHandler,
|
|
42
57
|
},
|
|
43
58
|
extraReducers: builder => {
|
|
44
59
|
builder
|
|
45
60
|
.addCase(userLogin.fulfilled, loginHandler)
|
|
46
61
|
.addCase(userLogin.rejected, loginErrorHandler)
|
|
47
|
-
.addCase(userLogin.pending, loginLoadingHandler)
|
|
62
|
+
.addCase(userLogin.pending, loginLoadingHandler)
|
|
63
|
+
.addCase(verifyOTP.fulfilled, loginHandler)
|
|
64
|
+
.addCase(verifyOTP.rejected, loginErrorHandler)
|
|
65
|
+
.addCase(verifyOTP.pending, loginLoadingHandler)
|
|
66
|
+
.addCase(refreshUserToken.fulfilled, (state, action) => {
|
|
67
|
+
const payload = action.payload ?? {};
|
|
68
|
+
return newState(state, {
|
|
69
|
+
accessToken: payload.accessToken ?? state.accessToken,
|
|
70
|
+
refreshToken: payload.refreshToken ?? state.refreshToken,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
48
73
|
},
|
|
49
74
|
});
|
|
50
75
|
|
|
51
|
-
export const {
|
|
52
|
-
setLogout,
|
|
53
|
-
} = actions;
|
|
76
|
+
export const {setLogout, updateFcmToken, setTokens} = actions;
|
|
@@ -3,8 +3,11 @@ import {LoadState} from '../../../../types';
|
|
|
3
3
|
export interface UserState {
|
|
4
4
|
user: User;
|
|
5
5
|
accessToken: string;
|
|
6
|
+
refreshToken: string;
|
|
7
|
+
fcmToken: string;
|
|
6
8
|
loginLoading: string;
|
|
7
9
|
}
|
|
10
|
+
|
|
8
11
|
export interface User {
|
|
9
12
|
type: string;
|
|
10
13
|
mobile_number: string;
|
|
@@ -12,7 +15,6 @@ export interface User {
|
|
|
12
15
|
status: string;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
|
|
16
18
|
export enum UserStatus {
|
|
17
19
|
ACTIVE = 'Active',
|
|
18
20
|
REGISTERED = 'Registered',
|
|
@@ -20,8 +22,6 @@ export enum UserStatus {
|
|
|
20
22
|
PENDING = 'Pending',
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
25
|
export const UserInitialState: UserState = {
|
|
26
26
|
user: {
|
|
27
27
|
type: '',
|
|
@@ -30,7 +30,9 @@ export const UserInitialState: UserState = {
|
|
|
30
30
|
status: UserStatus.PENDING,
|
|
31
31
|
},
|
|
32
32
|
accessToken: '',
|
|
33
|
-
|
|
33
|
+
refreshToken: '',
|
|
34
|
+
fcmToken: '',
|
|
35
|
+
loginLoading: LoadState.needLoad,
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
export interface UserEntity {
|
|
@@ -39,13 +39,15 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
39
39
|
initialTheme,
|
|
40
40
|
}) => {
|
|
41
41
|
const systemColorScheme = useColorScheme();
|
|
42
|
+
const resolvedSystem: ThemeMode =
|
|
43
|
+
systemColorScheme === 'dark' ? 'dark' : 'light';
|
|
42
44
|
const [themeMode, setThemeMode] = useState<ThemeMode>(
|
|
43
|
-
initialTheme ||
|
|
45
|
+
initialTheme || resolvedSystem,
|
|
44
46
|
);
|
|
45
47
|
|
|
46
48
|
useEffect(() => {
|
|
47
|
-
if (!initialTheme
|
|
48
|
-
setThemeMode(systemColorScheme);
|
|
49
|
+
if (!initialTheme) {
|
|
50
|
+
setThemeMode(systemColorScheme === 'dark' ? 'dark' : 'light');
|
|
49
51
|
}
|
|
50
52
|
}, [systemColorScheme, initialTheme]);
|
|
51
53
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand tokens that pair with the color ramps in `colors.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Premium "Midnight" black + blue. Gradients are color tuples for
|
|
5
|
+
* react-native-linear-gradient:
|
|
6
|
+
* <LinearGradient colors={BrandGradients.primary} start={GradientDirection.start} end={GradientDirection.end} />
|
|
7
|
+
* Glow values are ready-to-spread ViewStyle shadows for the elevated, premium look.
|
|
8
|
+
*/
|
|
9
|
+
import {ViewStyle} from 'react-native';
|
|
10
|
+
|
|
11
|
+
export const BrandColors = {
|
|
12
|
+
primary: '#2F6BFF',
|
|
13
|
+
primaryDeep: '#1B45B8',
|
|
14
|
+
cyan: '#6193FF', // light-blue accent (legacy key name)
|
|
15
|
+
cyanSoft: '#A6C4FF',
|
|
16
|
+
violet: '#6353F2',
|
|
17
|
+
ink: '#06080F',
|
|
18
|
+
inkSurface: '#0D1124',
|
|
19
|
+
nearWhite: '#F4F6FE',
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
export const BrandGradients = {
|
|
23
|
+
primary: ['#1B45B8', '#3E7BFF'] as string[], // deep blue → bright blue
|
|
24
|
+
midnight: ['#0A1230', '#1B45B8'] as string[], // blue-black → royal (logo chip)
|
|
25
|
+
mark: ['#3E7BFF', '#A6C4FF'] as string[], // bright blue → light (logo mark)
|
|
26
|
+
ink: ['#06080F', '#0D1124'] as string[],
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
export const GradientDirection = {
|
|
30
|
+
start: {x: 0, y: 0},
|
|
31
|
+
end: {x: 1, y: 1},
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
export const Glow: Record<'primary' | 'cyan' | 'none', ViewStyle> = {
|
|
35
|
+
primary: {
|
|
36
|
+
shadowColor: '#2F6BFF',
|
|
37
|
+
shadowOpacity: 0.5,
|
|
38
|
+
shadowRadius: 18,
|
|
39
|
+
shadowOffset: {width: 0, height: 8},
|
|
40
|
+
elevation: 12,
|
|
41
|
+
},
|
|
42
|
+
cyan: {
|
|
43
|
+
shadowColor: '#3E7BFF',
|
|
44
|
+
shadowOpacity: 0.4,
|
|
45
|
+
shadowRadius: 14,
|
|
46
|
+
shadowOffset: {width: 0, height: 4},
|
|
47
|
+
elevation: 8,
|
|
48
|
+
},
|
|
49
|
+
none: {},
|
|
50
|
+
};
|
|
@@ -1,114 +1,128 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Customize for your brand – this is the only place to change app colors.
|
|
3
|
+
*
|
|
4
|
+
* Premium "Midnight" black + blue system. Token KEYS are stable (used across the
|
|
5
|
+
* app); only the hex values define the brand. Recolor here to rebrand the whole app.
|
|
6
|
+
* Signature: primary blue #2F6BFF, deep black #06080F, surfaces #0D1124.
|
|
7
|
+
* See `brand.ts` for the gradient + glow tokens that pair with these.
|
|
3
8
|
*/
|
|
4
9
|
export enum PrimaryColors {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
10
|
+
// Blue ramp (primary). PlatinateBlue_400 is THE primary.
|
|
11
|
+
PlatinateBlue_0 = '#EAF1FF',
|
|
12
|
+
PlatinateBlue_25 = '#D6E4FF',
|
|
13
|
+
PlatinateBlue_50 = '#B6D0FF',
|
|
14
|
+
PlatinateBlue_100 = '#8FB4FF',
|
|
15
|
+
PlatinateBlue_200 = '#6193FF',
|
|
16
|
+
PlatinateBlue_300 = '#3E7BFF',
|
|
17
|
+
PlatinateBlue_400 = '#2F6BFF',
|
|
18
|
+
PlatinateBlue_500 = '#2356D6',
|
|
19
|
+
PlatinateBlue_600 = '#1B45B8',
|
|
20
|
+
PlatinateBlue_700 = '#143186',
|
|
21
|
+
// Black ramp (deep navy-black → near-black). cetaceanBlue_700 is the darkest "ink".
|
|
22
|
+
cetaceanBlue_0 = '#3C4A86',
|
|
23
|
+
cetaceanBlue_25 = '#344078',
|
|
24
|
+
cetaceanBlue_50 = '#2C3768',
|
|
25
|
+
cetaceanBlue_100 = '#232C55',
|
|
26
|
+
cetaceanBlue_200 = '#1A2142',
|
|
27
|
+
cetaceanBlue_300 = '#131934',
|
|
28
|
+
cetaceanBlue_400 = '#0E1228',
|
|
29
|
+
cetaceanBlue_500 = '#0A0E1F',
|
|
30
|
+
cetaceanBlue_600 = '#080B16',
|
|
31
|
+
cetaceanBlue_700 = '#06080F',
|
|
32
|
+
strokeDeactive = '#C9D6FF',
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
export enum NaturalColors {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
36
|
+
// Cool neutral ramp. grayScale_0 = white surface, grayScale_700 = black app bg.
|
|
37
|
+
background_2 = '#F4F6FE',
|
|
38
|
+
grayScale_0 = '#FFFFFF',
|
|
39
|
+
grayScale_25 = '#EAEDF7',
|
|
40
|
+
grayScale_50 = '#D7DCEC',
|
|
41
|
+
grayScale_100 = '#B8C0DA',
|
|
42
|
+
grayScale_200 = '#828BB0',
|
|
43
|
+
grayScale_300 = '#59618A',
|
|
44
|
+
grayScale_400 = '#343C63',
|
|
45
|
+
grayScale_500 = '#1A2142',
|
|
46
|
+
grayScale_600 = '#0D1124',
|
|
47
|
+
grayScale_700 = '#06080F',
|
|
48
|
+
// Cool blue-gray tints.
|
|
49
|
+
naturalColor_0 = '#AAB2DA',
|
|
50
|
+
naturalColor_25 = '#959ECB',
|
|
51
|
+
naturalColor_50 = '#808ABA',
|
|
52
|
+
naturalColor_100 = '#6A74A1',
|
|
53
|
+
naturalColor_200 = '#4F587E',
|
|
54
|
+
naturalColor_300 = '#363D5F',
|
|
55
|
+
naturalColor_400 = '#212744',
|
|
56
|
+
naturalColor_500 = '#161B36',
|
|
57
|
+
naturalColor_600 = '#0D1124',
|
|
58
|
+
naturalColor_700 = '#06080F',
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
export enum AlertColors {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
62
|
+
// Links → blue (matches primary).
|
|
63
|
+
links_0 = '#EAF1FF',
|
|
64
|
+
links_25 = '#D6E4FF',
|
|
65
|
+
links_50 = '#B6D0FF',
|
|
66
|
+
links_100 = '#8FB4FF',
|
|
67
|
+
links_200 = '#6193FF',
|
|
68
|
+
links_300 = '#3E7BFF',
|
|
69
|
+
links_400 = '#2F6BFF',
|
|
70
|
+
links_500 = '#2356D6',
|
|
71
|
+
links_600 = '#1B45B8',
|
|
72
|
+
links_700 = '#143186',
|
|
73
|
+
// Success → mint/teal.
|
|
74
|
+
success_0 = '#D8FBF1',
|
|
75
|
+
success_25 = '#A9F2DC',
|
|
76
|
+
success_50 = '#7FE8C8',
|
|
77
|
+
success_100 = '#54DEB3',
|
|
78
|
+
success_200 = '#2ECE9C',
|
|
79
|
+
success_300 = '#1DB083',
|
|
80
|
+
success_400 = '#0E9C72',
|
|
81
|
+
success_500 = '#0B7D5B',
|
|
82
|
+
success_600 = '#085E45',
|
|
83
|
+
success_700 = '#05402F',
|
|
84
|
+
// Warning → amber.
|
|
85
|
+
warning_0 = '#FFF5E2',
|
|
86
|
+
warning_25 = '#FEE6BC',
|
|
87
|
+
warning_50 = '#FCD795',
|
|
88
|
+
warning_100 = '#FAC76E',
|
|
89
|
+
warning_200 = '#F7B845',
|
|
90
|
+
warning_300 = '#EFA52A',
|
|
91
|
+
warning_400 = '#D98E1E',
|
|
92
|
+
warning_500 = '#AE7116',
|
|
93
|
+
warning_600 = '#835410',
|
|
94
|
+
warning_700 = '#583809',
|
|
95
|
+
// Error → vivid red.
|
|
96
|
+
error_0 = '#FFE7EC',
|
|
97
|
+
error_25 = '#FFC9D4',
|
|
98
|
+
error_50 = '#FFA9BB',
|
|
99
|
+
error_100 = '#FF87A0',
|
|
100
|
+
error_200 = '#FB5E7F',
|
|
101
|
+
error_300 = '#ED3D63',
|
|
102
|
+
error_400 = '#DC2A52',
|
|
103
|
+
error_500 = '#B11E42',
|
|
104
|
+
error_600 = '#851632',
|
|
105
|
+
error_700 = '#590E22',
|
|
106
|
+
// Discover → blue-violet.
|
|
107
|
+
discover_0 = '#ECEBFF',
|
|
108
|
+
discover_25 = '#D6D3FF',
|
|
109
|
+
discover_50 = '#B7B2FF',
|
|
110
|
+
discover_100 = '#9990FF',
|
|
111
|
+
discover_200 = '#7C70FF',
|
|
112
|
+
discover_300 = '#6353F2',
|
|
113
|
+
discover_400 = '#4E3FCC',
|
|
114
|
+
discover_500 = '#3D31A3',
|
|
115
|
+
discover_600 = '#2C2476',
|
|
116
|
+
discover_700 = '#1C174D',
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
export enum BaseColors {
|
|
106
120
|
white = '#FFFFFF',
|
|
107
|
-
black = '#
|
|
108
|
-
gray = '#
|
|
109
|
-
red = '#
|
|
110
|
-
green = '#
|
|
111
|
-
blue = '#
|
|
112
|
-
yellow = '#
|
|
113
|
-
purple = '#
|
|
121
|
+
black = '#06080F',
|
|
122
|
+
gray = '#828BB0',
|
|
123
|
+
red = '#DC2A52',
|
|
124
|
+
green = '#0E9C72',
|
|
125
|
+
blue = '#2F6BFF',
|
|
126
|
+
yellow = '#D98E1E',
|
|
127
|
+
purple = '#6353F2',
|
|
114
128
|
}
|
|
@@ -12,8 +12,8 @@ import DeviceInfo from 'react-native-device-info';
|
|
|
12
12
|
import {createPerfectSize} from '../../common/utils/createPerfectSize';
|
|
13
13
|
|
|
14
14
|
const windowDimensions = Dimensions.get('window');
|
|
15
|
-
export const isIos = Platform.OS
|
|
16
|
-
export const isAndroid = Platform.OS
|
|
15
|
+
export const isIos = Platform.OS === 'ios';
|
|
16
|
+
export const isAndroid = Platform.OS === 'android';
|
|
17
17
|
export const hasDynamicIsland = DeviceInfo.hasDynamicIsland();
|
|
18
18
|
export const hasNotch = DeviceInfo.hasNotch() || hasDynamicIsland;
|
|
19
19
|
export const isIpad = isIos && (Platform as PlatformIOSStatic).isPad;
|
|
@@ -212,6 +212,7 @@ export const lightTheme: Theme = {
|
|
|
212
212
|
...PrimaryColors,
|
|
213
213
|
...NaturalColors,
|
|
214
214
|
...AlertColors,
|
|
215
|
+
...lightThemeColors,
|
|
215
216
|
},
|
|
216
217
|
text: {
|
|
217
218
|
...commonTextStyles,
|
|
@@ -371,6 +372,7 @@ export const darkTheme: Theme = {
|
|
|
371
372
|
...PrimaryColors,
|
|
372
373
|
...NaturalColors,
|
|
373
374
|
...AlertColors,
|
|
375
|
+
...darkThemeColors,
|
|
374
376
|
},
|
|
375
377
|
text: {
|
|
376
378
|
...commonTextStyles,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {TextStyle} from 'react-native';
|
|
2
2
|
import {ElevationShadow} from './shadows';
|
|
3
|
-
import {PrimaryColors, NaturalColors, AlertColors} from './colors';
|
|
4
3
|
|
|
5
4
|
export type ThemeMode = 'light' | 'dark';
|
|
6
5
|
|
|
@@ -133,7 +132,10 @@ export interface ISize {
|
|
|
133
132
|
|
|
134
133
|
export interface Theme {
|
|
135
134
|
mode: ThemeMode;
|
|
136
|
-
|
|
135
|
+
// Raw color-scale keys (PlatinateBlue_*, grayScale_*, …) plus the semantic
|
|
136
|
+
// aliases (white, indigoBlue, surface, card, shadow, background, …) the
|
|
137
|
+
// themes spread in. Indexed as string so both kinds of key are accessible.
|
|
138
|
+
colors: Record<string, string>;
|
|
137
139
|
text: {
|
|
138
140
|
// Almarai Design System Typography
|
|
139
141
|
// Headings
|