@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.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -55
  3. package/index.js +4 -0
  4. package/package.json +9 -3
  5. package/scripts/askPackageName.js +10 -5
  6. package/template/.env.development +8 -6
  7. package/template/.env.example +15 -5
  8. package/template/.env.production +8 -6
  9. package/template/.env.staging +8 -6
  10. package/template/.eslintrc.js +14 -0
  11. package/template/.husky/pre-commit +1 -0
  12. package/template/App.tsx +47 -16
  13. package/template/__tests__/App.test.tsx +28 -10
  14. package/template/babel.config.js +20 -1
  15. package/template/docs/ARCHITECTURE.md +40 -10
  16. package/template/docs/BEST_PRACTICES.md +10 -1
  17. package/template/docs/CUSTOMIZATION.md +118 -5
  18. package/template/docs/design-system.html +1164 -0
  19. package/template/docs/wireframes.html +411 -0
  20. package/template/index.js +10 -0
  21. package/template/jest.config.js +16 -1
  22. package/template/jest.setup.js +61 -0
  23. package/template/package-lock.json +12178 -8293
  24. package/template/package.json +53 -19
  25. package/template/react-native.config.js +3 -0
  26. package/template/resources/fonts/.gitkeep +0 -0
  27. package/template/scripts/ci-sync-env.cjs +71 -0
  28. package/template/src/assets/brand/logo-mark.svg +8 -0
  29. package/template/src/assets/brand/logo-mono.svg +9 -0
  30. package/template/src/assets/brand/logo-primary.svg +15 -0
  31. package/template/src/assets/brand/wordmark-dark.svg +18 -0
  32. package/template/src/common/components/AppBottomSheet.tsx +87 -0
  33. package/template/src/common/components/AppSwitch.tsx +75 -0
  34. package/template/src/common/components/AppTextInput.tsx +161 -0
  35. package/template/src/common/components/Avatar.tsx +75 -0
  36. package/template/src/common/components/Badge.tsx +66 -0
  37. package/template/src/common/components/CardScroller.tsx +58 -0
  38. package/template/src/common/components/Cards.tsx +13 -7
  39. package/template/src/common/components/Carousel.tsx +196 -0
  40. package/template/src/common/components/Checkbox.tsx +85 -0
  41. package/template/src/common/components/Chip.tsx +55 -0
  42. package/template/src/common/components/Dropdown.tsx +202 -0
  43. package/template/src/common/components/ErrorBoundary.tsx +82 -0
  44. package/template/src/common/components/FlatListWrapper.tsx +8 -8
  45. package/template/src/common/components/ListItem.tsx +90 -0
  46. package/template/src/common/components/LoadingComponent.tsx +8 -2
  47. package/template/src/common/components/Logo.tsx +77 -0
  48. package/template/src/common/components/ModalDialog.tsx +141 -0
  49. package/template/src/common/components/NetworkBanner.tsx +47 -0
  50. package/template/src/common/components/OTPInput.tsx +0 -1
  51. package/template/src/common/components/PrimaryButton.tsx +0 -14
  52. package/template/src/common/components/PrimaryTextInput.tsx +66 -130
  53. package/template/src/common/components/RadioGroup.tsx +95 -0
  54. package/template/src/common/components/SafeText.tsx +4 -3
  55. package/template/src/common/components/SearchBar.tsx +7 -5
  56. package/template/src/common/components/SegmentedControl.tsx +77 -0
  57. package/template/src/common/components/Skeleton.tsx +47 -0
  58. package/template/src/common/components/TryAgain.tsx +4 -2
  59. package/template/src/common/helpers/arrayHelpers.ts +2 -2
  60. package/template/src/common/helpers/defaultKeyIdExtractor.ts +1 -1
  61. package/template/src/common/helpers/regexHelpers.ts +1 -2
  62. package/template/src/common/helpers/stringsHelpers.ts +0 -1
  63. package/template/src/common/hooks/useBackHandler.ts +5 -2
  64. package/template/src/common/hooks/useEventRegister.ts +1 -1
  65. package/template/src/common/hooks/useFlatListActions.ts +1 -1
  66. package/template/src/common/hooks/useWhyDidYouUpdate.ts +1 -1
  67. package/template/src/common/localization/LocalizationProvider.tsx +1 -1
  68. package/template/src/common/localization/RTLInitializer.tsx +1 -1
  69. package/template/src/common/localization/dateFormatter.ts +0 -1
  70. package/template/src/common/localization/intlFormatter.ts +0 -1
  71. package/template/src/common/localization/localization.ts +2 -2
  72. package/template/src/common/localization/translations/homeLocalization.ts +14 -0
  73. package/template/src/common/localization/translations/loginLocalization.ts +8 -0
  74. package/template/src/common/localization/translations/mainNavigationLocalization.ts +2 -0
  75. package/template/src/common/localization/translations/profileLocalization.ts +16 -0
  76. package/template/src/common/utils/index.tsx +0 -6
  77. package/template/src/common/validations/commonValidations.ts +2 -2
  78. package/template/src/core/api/errorHandler.ts +1 -1
  79. package/template/src/core/api/responseHandlers.ts +1 -3
  80. package/template/src/core/api/serverHeaders.ts +61 -12
  81. package/template/src/core/notifications/notificationAuth.ts +6 -0
  82. package/template/src/core/notifications/notificationService.ts +125 -0
  83. package/template/src/core/notifications/routeFromNotificationData.ts +32 -0
  84. package/template/src/core/store/categories/categoriesActions.ts +25 -0
  85. package/template/src/core/store/categories/categoriesSlice.ts +51 -0
  86. package/template/src/core/store/categories/categoriesState.ts +19 -0
  87. package/template/src/core/store/rootReducer.ts +2 -0
  88. package/template/src/core/store/store.tsx +6 -1
  89. package/template/src/core/store/user/userActions.ts +75 -14
  90. package/template/src/core/store/user/userSlice.ts +49 -26
  91. package/template/src/core/store/user/userState.ts +6 -4
  92. package/template/src/core/theme/ThemeProvider.tsx +5 -3
  93. package/template/src/core/theme/brand.ts +50 -0
  94. package/template/src/core/theme/colors.ts +113 -99
  95. package/template/src/core/theme/commonConsts.ts +2 -2
  96. package/template/src/core/theme/commonStyles.ts +1 -1
  97. package/template/src/core/theme/themes.ts +2 -0
  98. package/template/src/core/theme/types.ts +4 -2
  99. package/template/src/core/utils/stringUtils.ts +1 -1
  100. package/template/src/design-system/index.ts +2 -0
  101. package/template/src/design-system/tokens/brand.ts +6 -0
  102. package/template/src/design-system/tokens/index.ts +3 -0
  103. package/template/src/design-system/tokens/palette.ts +4 -0
  104. package/template/src/design-system/tokens/typography-spacing.ts +2 -0
  105. package/template/src/navigation/AuthStack.tsx +1 -4
  106. package/template/src/navigation/HeaderComponents.tsx +6 -3
  107. package/template/src/navigation/MainStack.tsx +18 -6
  108. package/template/src/navigation/RootNavigation.tsx +4 -7
  109. package/template/src/navigation/TabBar.tsx +7 -6
  110. package/template/src/navigation/types.ts +10 -31
  111. package/template/src/screens/Login/Login.tsx +47 -47
  112. package/template/src/screens/OTP/OTPScreen.tsx +6 -9
  113. package/template/src/screens/components/ComponentsScreen.tsx +301 -0
  114. package/template/src/screens/home/HomeScreen.tsx +143 -1
  115. package/template/src/screens/home/hooks/useHomeData.ts +19 -5
  116. package/template/src/screens/index.tsx +1 -0
  117. package/template/src/screens/profile/Profile.tsx +139 -2
  118. package/template/src/screens/splash/Splash.tsx +44 -11
  119. package/template/src/sheetManager/sheets.tsx +1 -1
  120. package/template/tsconfig.json +14 -2
  121. package/template/types/globals.d.ts +43 -0
  122. package/template/types/index.ts +2 -6
  123. package/template/types/modules.d.ts +9 -0
  124. package/template/types/react-native-config.d.ts +0 -2
  125. package/.vscode/settings.json +0 -8
  126. package/CHANGELOG.md +0 -119
  127. package/CODE_OF_CONDUCT.md +0 -83
  128. package/CONTRIBUTING.md +0 -60
  129. package/local.properties +0 -1
  130. package/template/src/common/components/ImageCropPickerButton.tsx +0 -107
  131. package/template/src/common/components/PhotoTakingButton.tsx +0 -94
  132. package/template/src/common/helpers/imageHelpers.ts +0 -5
  133. package/template/src/common/helpers/inAppReviewHelper.ts +0 -30
  134. package/template/src/common/helpers/orientationHelpers.ts +0 -25
  135. package/template/src/common/helpers/shareHelpers.ts +0 -47
  136. package/template/src/common/utils/FeesCaalculation.tsx +0 -37
  137. package/template/src/common/utils/printData.tsx +0 -161
  138. 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', ['accessToken', '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, getState, dispatch}: any,
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 { createSlice } from '@reduxjs/toolkit';
2
- import { cloneDeep, uniqBy } from 'lodash';
3
- import { LoadState } from '../../../../types';
4
- import { newState } from '../../../common/utils/newState';
5
- import { handleErrorResponse } from '../../api/responseHandlers';
6
- import {
7
- userLogin
8
- } from './userActions';
9
- import { UserInitialState, UserState } from './userState';
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
- loginLoading: LoadState['allIsLoaded'],
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['pullToRefresh'],
20
+ loginLoading: LoadState.pullToRefresh,
19
21
  });
20
22
  }
21
23
 
22
- function loginErrorHandler(
23
- state: UserState,
24
- payload: {payload: {message: string}},
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['error'],
28
+ loginLoading: LoadState.error,
30
29
  });
31
30
  }
32
31
 
33
- function logoutHandler(state: UserState) {
34
- return newState(state, UserInitialState);
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
- loginLoading: LoadState['needLoad'],
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 || systemColorScheme || 'light',
45
+ initialTheme || resolvedSystem,
44
46
  );
45
47
 
46
48
  useEffect(() => {
47
- if (!initialTheme && systemColorScheme) {
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
- PlatinateBlue_0 = '#E9EDFF',
6
- PlatinateBlue_25 = '#D3DAFF',
7
- PlatinateBlue_50 = '#BDC8FF',
8
- PlatinateBlue_100 = '#A7B5FF',
9
- PlatinateBlue_200 = '#7C91FF',
10
- PlatinateBlue_300 = '#506CFF',
11
- PlatinateBlue_400 = '#2447FF',
12
- PlatinateBlue_500 = '#1D39CC',
13
- PlatinateBlue_600 = '#1D39CC',
14
- PlatinateBlue_700 = '#0E1C66',
15
- cetaceanBlue_0 = '#4C7CEA',
16
- cetaceanBlue_25 = '#436FD5',
17
- cetaceanBlue_50 = '#3B63BF',
18
- cetaceanBlue_100 = '#3257AA',
19
- cetaceanBlue_200 = '#223E80',
20
- cetaceanBlue_300 = '#112655',
21
- cetaceanBlue_400 = '#000D2B',
22
- cetaceanBlue_500 = '#000A22',
23
- cetaceanBlue_600 = '#00081A',
24
- cetaceanBlue_700 = '#000511',
25
- strokeDeactive = '#D3DAFF',
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
- background_2 = '#F9F9F9',
30
- grayScale_0 = '#FAFAFA',
31
- grayScale_25 = '#A3AAAD',
32
- grayScale_50 = '#949C9E',
33
- grayScale_100 = '#858D8F',
34
- grayScale_200 = '#686F72',
35
- grayScale_300 = '#4A5254',
36
- grayScale_400 = '#2D3436',
37
- grayScale_500 = '#242A2B',
38
- grayScale_600 = '#1B1F20',
39
- grayScale_700 = '#121516',
40
- naturalColor_0 = '#9C97B3',
41
- naturalColor_25 = '#8E89A4',
42
- naturalColor_50 = '#7F7B94',
43
- naturalColor_100 = '#716D85',
44
- naturalColor_200 = '#555265',
45
- naturalColor_300 = '#383646',
46
- naturalColor_400 = '#1C1A27',
47
- naturalColor_500 = '#16151F',
48
- naturalColor_600 = '#111017',
49
- naturalColor_700 = '#0B0A10',
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
- links_0 = '#E8F2FF',
54
- links_25 = '#D2E4FE',
55
- links_50 = '#BBD7FE',
56
- links_100 = '#A5CAFE',
57
- links_200 = '#77AFFD',
58
- links_300 = '#4A95FD',
59
- links_400 = '#1D7AFC',
60
- links_500 = '#1762CA',
61
- links_600 = '#114997',
62
- links_700 = '#0C3165',
63
- success_0 = '#C6FFE5',
64
- success_25 = '#8FE6BE',
65
- success_50 = '#7DDAB0',
66
- success_100 = '#6CCEA1',
67
- success_200 = '#48B684',
68
- success_300 = '#259E67',
69
- success_400 = '#02864A',
70
- success_500 = '#026B3B',
71
- success_600 = '#01502C',
72
- success_700 = '#01361E',
73
- warning_0 = '#FFF4E8',
74
- warning_25 = '#FEE8D1',
75
- warning_50 = '#FEDDBA',
76
- warning_100 = '#FDD1A3',
77
- warning_200 = '#FDBB76',
78
- warning_300 = '#FCA448',
79
- warning_400 = '#FB8D1A',
80
- warning_500 = '#C97115',
81
- warning_600 = '#975510',
82
- warning_700 = '#64380A',
83
- error_0 = '#FDE6EC',
84
- error_25 = '#FACED8',
85
- error_50 = '#F8B5C5',
86
- error_100 = '#F69CB2',
87
- error_200 = '#F16B8B',
88
- error_300 = '#ED3965',
89
- error_400 = '#E8083E',
90
- error_500 = '#BA0632',
91
- error_600 = '#8B0525',
92
- error_700 = '#5D0319',
93
- discover_0 = '#F1ECF6',
94
- discover_25 = '#E4D9EC',
95
- discover_50 = '#D6C5E3',
96
- discover_100 = '#C8B2D9',
97
- discover_200 = '#AD8CC7',
98
- discover_300 = '#9165B4',
99
- discover_400 = '#763FA1',
100
- discover_500 = '#5E3281',
101
- discover_600 = '#472661',
102
- discover_700 = '#2F1940',
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 = '#000000',
108
- gray = '#808080',
109
- red = '#FF0000',
110
- green = '#00FF00',
111
- blue = '#0000FF',
112
- yellow = '#FFFF00',
113
- purple = '#800080',
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 == 'ios';
16
- export const isAndroid = Platform.OS == 'android';
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;
@@ -282,4 +282,4 @@ export const CommonStyles = createThemedStyles({
282
282
  lg: 16,
283
283
  xl: 24,
284
284
  },
285
- });
285
+ } as unknown as Theme);
@@ -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
- colors: typeof PrimaryColors & typeof NaturalColors & typeof AlertColors;
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
@@ -20,7 +20,7 @@ export const ensureString = (value: any): string => {
20
20
 
21
21
  // Otherwise stringify the object
22
22
  return JSON.stringify(value);
23
- } catch (e) {
23
+ } catch {
24
24
  return '[Object]';
25
25
  }
26
26
  }
@@ -0,0 +1,2 @@
1
+ export * from './tokens';
2
+ export { useTheme, ThemeProvider } from '../core/theme/ThemeProvider';
@@ -0,0 +1,6 @@
1
+ export {
2
+ BrandColors,
3
+ BrandGradients,
4
+ GradientDirection,
5
+ Glow,
6
+ } from '../../core/theme/brand';