Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

theme app #9

Merged
merged 2 commits into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
},
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# dependencies
node_modules/
yarn.lock

# Expo
.expo/
Expand Down
21 changes: 14 additions & 7 deletions App.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
<StatusBar style="auto" />
</View>
<ThemeProvider>
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
<StatusBar style="auto" />
<SwitchTheme />
<PrimaryButton onPress={() => {}} title="dasdsa" />
</View>
</ThemeProvider>
);
};

const styles = StyleSheet.create({
container: {
...globalStyles.horizontalFlex,
...globalStyles.centerFlex,
color: 'red',
height: '100%',
width: '100%',
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center',
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_
Expand Down
9 changes: 9 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
},
"web": {
"favicon": "./assets/favicon.png"
},
"expo": {
"userInterfaceStyle": "automatic",
"ios": {
"userInterfaceStyle": "automatic"
},
"android": {
"userInterfaceStyle": "automatic"
}
}
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Binary file added readme/themeUsage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added readme/themedColors.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file removed src/components/.placeholder
Empty file.
12 changes: 12 additions & 0 deletions src/components/SwitchTheme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { Switch } from 'react-native';

import { useTheme } from '@/hooks';

const SwitchTheme = () => {
const { theme, switchTheme } = useTheme();

return <Switch value={theme === 'dark'} onChange={switchTheme} />;
};

export default SwitchTheme;
2 changes: 2 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as PrimaryButton } from './PrimaryButton';
export { default as SwitchTheme } from './SwitchTheme';
Empty file removed src/hooks/.placeholder
Empty file.
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useTheme';
84 changes: 84 additions & 0 deletions src/hooks/useTheme.tsx
Original file line number Diff line number Diff line change
@@ -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<string, string>;
switchTheme: () => void;
};

const ThemeContext = React.createContext<Context | undefined>(undefined);

const getThemedStyles = (theme: Theme): Record<string, string> => {
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<Theme>(
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 (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
};

export const useTheme = () => {
const context = useContext(ThemeContext);

if (!context) {
throw new Error('use Theme must be used within provider');
}

return context;
};
Empty file removed src/types/.placeholder
Empty file.
5 changes: 5 additions & 0 deletions src/types/theme.ts
Original file line number Diff line number Diff line change
@@ -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);
10 changes: 8 additions & 2 deletions style.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { StyleSheet } from 'react-native';

const globalStyles = StyleSheet.create({
export const globalStyles = StyleSheet.create({
verticalFlex: {
display: 'flex',
flexDirection: 'column',
Expand All @@ -14,4 +14,10 @@ const globalStyles = StyleSheet.create({
},
});

export { globalStyles };
export const darkColors = {
primary: 'blue',
};

export const lightColors = {
primary: 'green',
};
19 changes: 19 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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/[email protected]":
version "11.3.7"
resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-11.3.7.tgz#cb4c2f225f78593412c2d191b55b8570f409a48f"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -6712,6 +6724,13 @@ [email protected]:
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"
Expand Down