diff --git a/.eslintrc.js b/.eslintrc.js
index 023dd08..543c72b 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -35,7 +35,7 @@ module.exports = {
'react-hooks/exhaustive-deps': 'warn',
'simple-import-sort/imports': 'warn',
'simple-import-sort/exports': 'warn',
- indent: ['warn', 2],
+ indent: ['warn', 2, { SwitchCase: 1 }],
quotes: ['warn', 'single'],
semi: ['warn', 'always'],
},
diff --git a/.gitignore b/.gitignore
index 05647d5..a72314d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
# dependencies
node_modules/
+yarn.lock
# Expo
.expo/
diff --git a/App.tsx b/App.tsx
index b8f7370..bed543d 100644
--- a/App.tsx
+++ b/App.tsx
@@ -1,20 +1,27 @@
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
-import { globalStyles } from 'style';
+
+import { PrimaryButton, SwitchTheme } from '@/components';
+import { ThemeProvider } from '@/hooks';
const App = () => {
return (
-
- Open up App.tsx to start working on your app!
-
-
+
+
+ Open up App.tsx to start working on your app!
+
+
+ {}} title="dasdsa" />
+
+
);
};
const styles = StyleSheet.create({
container: {
- ...globalStyles.horizontalFlex,
- ...globalStyles.centerFlex,
+ color: 'red',
+ height: '100%',
+ width: '100%',
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center',
diff --git a/README.md b/README.md
index 543a975..1bae7d1 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,16 @@
`You should declare all reusable stylings there`
+## Theming
+
+![themed colors example](./readme/themedColors.png)
+
+### Example of how do we declare colors for different themes
+
+![usage of themes](./readme/themeUsage.png)
+
+### Example of how do we use themes in component
+
## Form
### When we want to create form, we use external library called _react-hook-form_
diff --git a/app.json b/app.json
index 0c595bc..1d5ad68 100644
--- a/app.json
+++ b/app.json
@@ -30,6 +30,15 @@
},
"web": {
"favicon": "./assets/favicon.png"
+ },
+ "expo": {
+ "userInterfaceStyle": "automatic",
+ "ios": {
+ "userInterfaceStyle": "automatic"
+ },
+ "android": {
+ "userInterfaceStyle": "automatic"
+ }
}
}
}
diff --git a/package.json b/package.json
index 489223c..f452873 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"lint": "eslint --ext .js,.jsx,.ts,.tsx"
},
"dependencies": {
+ "@react-native-async-storage/async-storage": "^1.21.0",
"@react-native/metro-config": "^0.73.3",
"expo": "~49.0.15",
"expo-status-bar": "~1.6.0",
diff --git a/readme/themeUsage.png b/readme/themeUsage.png
new file mode 100644
index 0000000..42b2bbc
Binary files /dev/null and b/readme/themeUsage.png differ
diff --git a/readme/themedColors.png b/readme/themedColors.png
new file mode 100644
index 0000000..e15e95a
Binary files /dev/null and b/readme/themedColors.png differ
diff --git a/src/components/.placeholder b/src/components/.placeholder
deleted file mode 100644
index e69de29..0000000
diff --git a/src/components/SwitchTheme.tsx b/src/components/SwitchTheme.tsx
new file mode 100644
index 0000000..a199af9
--- /dev/null
+++ b/src/components/SwitchTheme.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import { Switch } from 'react-native';
+
+import { useTheme } from '@/hooks';
+
+const SwitchTheme = () => {
+ const { theme, switchTheme } = useTheme();
+
+ return ;
+};
+
+export default SwitchTheme;
diff --git a/src/components/index.ts b/src/components/index.ts
new file mode 100644
index 0000000..946e549
--- /dev/null
+++ b/src/components/index.ts
@@ -0,0 +1,2 @@
+export { default as PrimaryButton } from './PrimaryButton';
+export { default as SwitchTheme } from './SwitchTheme';
diff --git a/src/hooks/.placeholder b/src/hooks/.placeholder
deleted file mode 100644
index e69de29..0000000
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
new file mode 100644
index 0000000..7baad7d
--- /dev/null
+++ b/src/hooks/index.ts
@@ -0,0 +1 @@
+export * from './useTheme';
diff --git a/src/hooks/useTheme.tsx b/src/hooks/useTheme.tsx
new file mode 100644
index 0000000..6bad2d4
--- /dev/null
+++ b/src/hooks/useTheme.tsx
@@ -0,0 +1,84 @@
+import AsyncStorage from '@react-native-async-storage/async-storage';
+import React, { useContext, useEffect, useState } from 'react';
+import { useColorScheme } from 'react-native';
+import { darkColors, lightColors } from 'style';
+
+import { isTypeOfTheme, Theme, THEMES } from '@/types/theme';
+
+type Props = {
+ children: React.ReactNode;
+};
+
+type Context = {
+ theme: Theme;
+ themedStyles: Record;
+ switchTheme: () => void;
+};
+
+const ThemeContext = React.createContext(undefined);
+
+const getThemedStyles = (theme: Theme): Record => {
+ switch (theme) {
+ case 'dark':
+ return darkColors;
+ case 'light':
+ return lightColors;
+ default:
+ return lightColors;
+ }
+};
+
+export const ThemeProvider = ({ children }: Props) => {
+ const colorScheme = useColorScheme();
+
+ const [theme, setTheme] = useState(
+ isTypeOfTheme(colorScheme) ? colorScheme : THEMES[0],
+ );
+
+ const switchTheme = () => {
+ const newTheme = theme === 'dark' ? 'light' : 'dark';
+
+ setTheme(newTheme);
+
+ AsyncStorage.setItem('theme', newTheme);
+ };
+
+ useEffect(() => {
+ // Load saved theme from storage
+ console.log('xd');
+ const getTheme = async () => {
+ try {
+ const savedTheme = await AsyncStorage.getItem('theme');
+ if (savedTheme && isTypeOfTheme(savedTheme)) {
+ setTheme(savedTheme);
+ }
+ } catch (error) {
+ // TODO - log error to db?
+ }
+ };
+
+ getTheme();
+ }, []);
+
+ const themedStyles = getThemedStyles(theme);
+
+ const value: Context = {
+ theme,
+ themedStyles,
+ switchTheme,
+ };
+
+ return (
+ {children}
+ );
+};
+
+export const useTheme = () => {
+ const context = useContext(ThemeContext);
+
+ if (!context) {
+ throw new Error('use Theme must be used within provider');
+ }
+
+ return context;
+};
diff --git a/src/types/.placeholder b/src/types/.placeholder
deleted file mode 100644
index e69de29..0000000
diff --git a/src/types/theme.ts b/src/types/theme.ts
new file mode 100644
index 0000000..d44ab87
--- /dev/null
+++ b/src/types/theme.ts
@@ -0,0 +1,5 @@
+export const THEMES = ['light', 'dark'] as const;
+export type Theme = (typeof THEMES)[number];
+
+export const isTypeOfTheme = (value: any): value is Theme =>
+ THEMES.includes(value);
diff --git a/style.ts b/style.ts
index d36a146..c79ba8c 100644
--- a/style.ts
+++ b/style.ts
@@ -1,6 +1,6 @@
import { StyleSheet } from 'react-native';
-const globalStyles = StyleSheet.create({
+export const globalStyles = StyleSheet.create({
verticalFlex: {
display: 'flex',
flexDirection: 'column',
@@ -14,4 +14,10 @@ const globalStyles = StyleSheet.create({
},
});
-export { globalStyles };
+export const darkColors = {
+ primary: 'blue',
+};
+
+export const lightColors = {
+ primary: 'green',
+};
diff --git a/yarn.lock b/yarn.lock
index e828b0f..b352261 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1787,6 +1787,13 @@
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.0.tgz#7d8dacb7fdef0e4387caf7396cbd77f179867d06"
integrity sha512-Zwq5OCzuwJC2jwqmpEQt7Ds1DTi6BWSwoGkbb1n9pO3hzb35BoJELx7c0T23iDkBGkh2e7tvOtjF3tr3OaQHDQ==
+"@react-native-async-storage/async-storage@^1.21.0":
+ version "1.21.0"
+ resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.21.0.tgz#d7e370028e228ab84637016ceeb495878b7a44c8"
+ integrity sha512-JL0w36KuFHFCvnbOXRekqVAUplmOyT/OuCQkogo6X98MtpSaJOKEAeZnYO8JB0U/RIEixZaGI5px73YbRm/oag==
+ dependencies:
+ merge-options "^3.0.4"
+
"@react-native-community/cli-clean@11.3.7":
version "11.3.7"
resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-11.3.7.tgz#cb4c2f225f78593412c2d191b55b8570f409a48f"
@@ -5980,6 +5987,11 @@ is-path-inside@^3.0.2, is-path-inside@^3.0.3:
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+is-plain-obj@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
+ integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
+
is-plain-obj@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7"
@@ -6712,6 +6724,13 @@ merge-descriptors@1.0.1:
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
+merge-options@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7"
+ integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==
+ dependencies:
+ is-plain-obj "^2.1.0"
+
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"