From a6f1e7b27c6ea157ef54a90574a15c124ccacd88 Mon Sep 17 00:00:00 2001 From: John Haupenthal Date: Mon, 20 Jan 2025 07:31:11 -0800 Subject: [PATCH 1/3] feat: add color theme --- App.tsx | 5 ++- src/navigation/BottomTabs.tsx | 3 ++ src/styles/darkThemeColors.ts | 45 ++++++++++++++++++++++++++ src/styles/index.ts | 3 ++ src/styles/lightThemeColors.ts | 45 ++++++++++++++++++++++++++ src/styles/palette.ts | 54 ++++++++++++++++++++++++++++++++ src/styles/useAppColorScheme.ts | 10 ++++++ src/styles/useNavigationTheme.ts | 37 ++++++++++++++++++++++ src/styles/useThemeColors.ts | 26 +++++++++++++++ 9 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 src/styles/darkThemeColors.ts create mode 100644 src/styles/index.ts create mode 100644 src/styles/lightThemeColors.ts create mode 100644 src/styles/palette.ts create mode 100644 src/styles/useAppColorScheme.ts create mode 100644 src/styles/useNavigationTheme.ts create mode 100644 src/styles/useThemeColors.ts diff --git a/App.tsx b/App.tsx index 9c69689..b5cea80 100644 --- a/App.tsx +++ b/App.tsx @@ -1,13 +1,16 @@ import { createStaticNavigation } from "@react-navigation/native"; import { RootStack } from "./src/navigation/RootStack"; import { ApiClientProvider } from "./src/services/client/Provider"; +import { useNavigationTheme } from "./src/styles"; const Navigation = createStaticNavigation(RootStack); export function App() { + const theme = useNavigationTheme(); + return ( - + ); } diff --git a/src/navigation/BottomTabs.tsx b/src/navigation/BottomTabs.tsx index 997225f..8225665 100644 --- a/src/navigation/BottomTabs.tsx +++ b/src/navigation/BottomTabs.tsx @@ -3,10 +3,13 @@ import { SearchStack } from "./SearchStack"; import { AccountStack } from "./AccountStack"; import SearchSvg from "../../assets/svgs/search.svg"; import UserSvg from "../../assets/svgs/user.svg"; +import { palette } from "../styles/palette"; export const BottomTabs = createBottomTabNavigator({ screenOptions: { tabBarShowLabel: false, + tabBarActiveTintColor: palette.primary500, + tabBarInactiveTintColor: palette.greyscale500, tabBarStyle: { paddingTop: 4, }, diff --git a/src/styles/darkThemeColors.ts b/src/styles/darkThemeColors.ts new file mode 100644 index 0000000..4a08828 --- /dev/null +++ b/src/styles/darkThemeColors.ts @@ -0,0 +1,45 @@ +import { palette } from "./palette"; + +export const darkThemeColors = { + buttonDisabled: palette.disabledButton, + buttonDisabledText: palette.white, + buttonPrimary: palette.primary500, + buttonPrimaryText: palette.white, + buttonSecondary: palette.dark3, + buttonSecondaryText: palette.white, + checkboxCheck: palette.white, + checkboxHightLight: palette.primary500, + chipHighlight: palette.primary500, + chipText: palette.white, + inputBlurredBackground: palette.dark2, + inputFocusedBackground: palette.dark3, + inputFocusedBorder: palette.greyscale400, + inputPlaceholderText: palette.greyscale500, + inputTextColor: palette.white, + textDefault: palette.white, + toggleThumb: palette.white, + toggleTrackFalse: palette.dark2, + toggleTrackTrue: palette.primary500, + inActiveRing: palette.greyscale200, + activeRing: palette.primary500, + wheelActive: palette.primary500, + accordionBackground: palette.greyscale900, + accordionBottomBorder: palette.greyscale800, + accordionSubtext: palette.primary400, + screenBackground: palette.dark1, + navigationBorder: palette.greyscale800, + navigationPrimary: palette.primary500, + sliderActive: palette.primary500, + sliderInactive: palette.greyscale200, + textLight: palette.greyscale500, + primary: palette.primary500, + cardBackground: palette.dark3, + comboSectionBackground: palette.greyscale900, + imageBorder: palette.greyscale800, + translucent: palette.black_50, + cardText: palette.white, + cardGradient: palette.greyscale900, + tabBarActiveTintColor: palette.primary500, + tabBarInactiveTintColor: palette.greyscale500, + error: palette.error, +}; diff --git a/src/styles/index.ts b/src/styles/index.ts new file mode 100644 index 0000000..8582833 --- /dev/null +++ b/src/styles/index.ts @@ -0,0 +1,3 @@ +export * from "./useAppColorScheme"; +export * from "./useNavigationTheme"; +export * from "./useThemeColors"; diff --git a/src/styles/lightThemeColors.ts b/src/styles/lightThemeColors.ts new file mode 100644 index 0000000..c0bf618 --- /dev/null +++ b/src/styles/lightThemeColors.ts @@ -0,0 +1,45 @@ +import { palette } from "./palette"; + +export const lightThemeColors = { + buttonDisabled: palette.disabledButton, + buttonDisabledText: palette.white, + buttonPrimary: palette.primary500, + buttonPrimaryText: palette.white, + buttonSecondary: palette.primary100, + buttonSecondaryText: palette.primary500, + checkboxCheck: palette.white, + checkboxHightLight: palette.primary500, + chipHighlight: palette.primary500, + chipText: palette.white, + inputBlurredBackground: palette.greyscale200, + inputFocusedBackground: palette.greyscale100, + inputFocusedBorder: palette.greyscale800, + inputPlaceholderText: palette.greyscale500, + inputTextColor: palette.greyscale900, + textDefault: palette.greyscale900, + toggleThumb: palette.white, + toggleTrackFalse: palette.greyscale200, + toggleTrackTrue: palette.primary500, + inActiveRing: palette.greyscale400, + activeRing: palette.primary500, + wheelActive: palette.primary500, + accordionBackground: palette.greyscale50, + accordionBottomBorder: palette.greyscale200, + accordionSubtext: palette.primary400, + screenBackground: palette.white, + navigationBorder: palette.greyscale400, + navigationPrimary: palette.primary500, + sliderActive: palette.primary500, + sliderInactive: palette.greyscale200, + textLight: palette.greyscale700, + primary: palette.primary500, + cardBackground: palette.white, + comboSectionBackground: palette.greyscale100, + imageBorder: palette.greyscale300, + translucent: palette.white_50, + cardText: palette.white, + cardGradient: palette.greyscale500, + tabBarActiveTintColor: palette.primary500, + tabBarInactiveTintColor: palette.greyscale500, + error: palette.error, +}; diff --git a/src/styles/palette.ts b/src/styles/palette.ts new file mode 100644 index 0000000..f624c5f --- /dev/null +++ b/src/styles/palette.ts @@ -0,0 +1,54 @@ +export const palette = { + amber: "#FFC107", + black: "#000000", + blue: "#2196F3", + blueGrey: "#607D8B", + brown: "#795548", + cyan: "#00BCD4", + dark1: "#181A20", + dark2: "#1F222A", + dark3: "#35383F", + deepOrange: "#FF5722", + deepPurple: "#673AB3", + disabled: "#D8D8D8", + disabledButton: "#C3B3FF", + error: "#F75555", + grey: "#9E9E9E", + greyscale100: "#F5F5F5", + greyscale200: "#EEEEEE", + greyscale300: "#E0E0E0", + greyscale400: "#BDBDBD", + greyscale50: "#FAFAFA", + greyscale500: "#9E9E9E", + greyscale600: "#757575", + greyscale700: "#616161", + greyscale800: "#424242", + greyscale900: "#212121", + indigo: "#3F51B5", + info: "#246BFD", + lightBlue: "#03A9F4", + lightGreen: "#8BC34A", + lime: "#CDDC39", + orange: "#FF9800", + pink: "#EA1E61", + primary100: "#F0ECFF", + primary200: "#C3B3FF", + primary300: "#A48EFF", + primary400: "#8668FF", + primary500: "#6842FF", + primary500_08: "rgba(104, 66, 255, 0.08)", + purple: "#9D28AC", + red: "#F54336", + secondary100: "#FFFBE6", + secondary200: "#FFED99", + secondary300: "#FFE566", + secondary400: "#FFDC33", + secondary500: "#FFD300", + success: "#4ADE80", + teal: "#009688", + warning: "#FACC15", + white: "#FFFFFF", + yellow: "#FFEB3B", + black_50: "rgba(0, 0, 0, 0.5)", + white_50: "rgba(255, 255, 255, 0.5)", +}; diff --git a/src/styles/useAppColorScheme.ts b/src/styles/useAppColorScheme.ts new file mode 100644 index 0000000..9fa4a59 --- /dev/null +++ b/src/styles/useAppColorScheme.ts @@ -0,0 +1,10 @@ +import { useColorScheme } from "react-native"; + +// TODO Add setting to override +export function useAppColorScheme() { + const systemColorScheme = useColorScheme(); + + const colorScheme = systemColorScheme; + + return colorScheme; +} diff --git a/src/styles/useNavigationTheme.ts b/src/styles/useNavigationTheme.ts new file mode 100644 index 0000000..68faea9 --- /dev/null +++ b/src/styles/useNavigationTheme.ts @@ -0,0 +1,37 @@ +import type { Theme } from "@react-navigation/native"; +import { DarkTheme, DefaultTheme } from "@react-navigation/native"; +import { useMemo } from "react"; +import { useAppColorScheme } from "./useAppColorScheme"; +import { useThemeColors } from "./useThemeColors"; + +export function useNavigationTheme(): Theme { + const colorScheme = useAppColorScheme(); + const [background, text, border, primary] = useThemeColors([ + "screenBackground", + "textDefault", + "navigationBorder", + "navigationPrimary", + ]); + + const darkTheme = useMemo( + () => ({ + ...DarkTheme, + colors: { + ...DarkTheme.colors, + background, + card: background, + text, + border, + primary, + }, + }), + [background, border, primary, text], + ); + + const value = useMemo( + () => (colorScheme === "dark" ? darkTheme : DefaultTheme), + [colorScheme, darkTheme], + ); + + return value; +} diff --git a/src/styles/useThemeColors.ts b/src/styles/useThemeColors.ts new file mode 100644 index 0000000..4419e96 --- /dev/null +++ b/src/styles/useThemeColors.ts @@ -0,0 +1,26 @@ +import { useMemo } from "react"; +import { darkThemeColors } from "./darkThemeColors"; +import { lightThemeColors } from "./lightThemeColors"; +import { useAppColorScheme } from "./useAppColorScheme"; + +export type ThemeColor = keyof typeof lightThemeColors; + +/** + * @param {ThemeColor[]} keys ThemeColor keys + * @description Takes in a set of ThemeColor keys and returns an array of colors, in order of the keys + * @returns {String[]} Array of string colors + */ +export function useThemeColors(keys: ThemeColor[]) { + const colorScheme = useAppColorScheme(); + + const themeColors = useMemo( + () => (colorScheme === "dark" ? darkThemeColors : lightThemeColors), + [colorScheme], + ); + const colors = useMemo( + () => keys.map((key) => themeColors[key]), + [keys, themeColors], + ); + + return colors; +} From 80cd80f349a8d1b73ff90a13833b9132c58c61dc Mon Sep 17 00:00:00 2001 From: John Haupenthal Date: Mon, 20 Jan 2025 08:04:30 -0800 Subject: [PATCH 2/3] feat: add StyledText --- src/components/StyledText.tsx | 163 ++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 src/components/StyledText.tsx diff --git a/src/components/StyledText.tsx b/src/components/StyledText.tsx new file mode 100644 index 0000000..89d5fb7 --- /dev/null +++ b/src/components/StyledText.tsx @@ -0,0 +1,163 @@ +import React from "react"; +import type { TextProps } from "react-native"; +import { StyleSheet, Text } from "react-native"; +import type { ThemeColor } from "../styles"; +import { useThemeColors } from "../styles"; + +interface Props extends TextProps { + variant: keyof typeof styledTextStyles; + themeColor?: ThemeColor; +} + +export function StyledText({ + themeColor = "textDefault", + style, + variant, + ...rest +}: Props) { + const variantStyle = styledTextStyles[variant]; + const [color] = useThemeColors([themeColor]); + + return ; +} + +const bodyStyles = { + xl: { + fontSize: 18, + lineHeight: 25, + letterSpacing: 0.002, + }, + lg: { + fontSize: 16, + lineHeight: 22, + letterSpacing: 0.002, + }, + md: { + fontSize: 14, + lineHeight: 20, + letterSpacing: 0.002, + }, + sm: { + fontSize: 12, + lineHeight: 16, + letterSpacing: 0.002, + }, + xs: { + fontSize: 10, + lineHeight: 14, + letterSpacing: 0.002, + }, +}; + +export const styledTextStyles = StyleSheet.create({ + heading1: { + fontWeight: 700, + fontSize: 48, + lineHeight: 53, + }, + heading2: { + fontWeight: 700, + fontSize: 40, + lineHeight: 44, + }, + heading3: { + fontWeight: 700, + fontSize: 32, + lineHeight: 35, + }, + heading4: { + fontWeight: 700, + fontSize: 24, + lineHeight: 29, + }, + heading5: { + fontWeight: 700, + fontSize: 20, + lineHeight: 24, + }, + heading6: { + fontWeight: 700, + fontSize: 18, + lineHeight: 22, + }, + bodyXLRegular: { + ...bodyStyles.xl, + }, + bodyXLBold: { + fontWeight: 700, + ...bodyStyles.xl, + }, + bodyXLSemiBold: { + fontWeight: 600, + ...bodyStyles.xl, + }, + bodyXLMedium: { + fontWeight: 500, + ...bodyStyles.xl, + }, + bodyLGRegular: { + ...bodyStyles.lg, + }, + bodyLGBold: { + fontWeight: 700, + ...bodyStyles.lg, + }, + bodyLGSemiBold: { + fontWeight: 600, + ...bodyStyles.lg, + }, + bodyLGMedium: { + fontWeight: 500, + ...bodyStyles.lg, + }, + bodyMDRegular: { + ...bodyStyles.md, + }, + bodyMDBold: { + fontWeight: 700, + ...bodyStyles.md, + }, + bodyMDSemiBold: { + fontWeight: 600, + ...bodyStyles.md, + }, + bodyMDMedium: { + fontWeight: 500, + ...bodyStyles.md, + }, + bodySMRegular: { + ...bodyStyles.sm, + }, + bodySMBold: { + fontWeight: 700, + ...bodyStyles.sm, + }, + bodySMSemiBold: { + fontWeight: 600, + ...bodyStyles.sm, + }, + bodySMMedium: { + fontWeight: 500, + ...bodyStyles.sm, + }, + bodyXSRegular: { + ...bodyStyles.xs, + }, + bodyXSBold: { + fontWeight: 700, + ...bodyStyles.xs, + }, + bodyXSSemiBold: { + fontWeight: 600, + ...bodyStyles.xs, + }, + bodyXSMedium: { + fontWeight: 500, + ...bodyStyles.xs, + }, + button: { + fontWeight: 700, + ...bodyStyles.md, + textTransform: "uppercase", + }, +}); From 9f66f500ac380b7ca561da4194948ff274ca79d2 Mon Sep 17 00:00:00 2001 From: John Haupenthal Date: Mon, 20 Jan 2025 08:04:41 -0800 Subject: [PATCH 3/3] feat: add TextButton --- src/components/TextButton/index.tsx | 61 ++++++++++++++++++++++ src/components/TextButton/types.ts | 1 + src/components/TextButton/useTextButton.ts | 48 +++++++++++++++++ src/screens/Search/index.tsx | 14 ++--- 4 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 src/components/TextButton/index.tsx create mode 100644 src/components/TextButton/types.ts create mode 100644 src/components/TextButton/useTextButton.ts diff --git a/src/components/TextButton/index.tsx b/src/components/TextButton/index.tsx new file mode 100644 index 0000000..c4411a2 --- /dev/null +++ b/src/components/TextButton/index.tsx @@ -0,0 +1,61 @@ +import React from "react"; +import type { ViewStyle } from "react-native"; +import { Pressable, StyleSheet } from "react-native"; +import { StyledText } from "../StyledText"; +import type { VariantKey } from "./types"; +import { useTextButton } from "./useTextButton"; + +interface Props { + variant?: VariantKey; + text: string; + onPress: () => void; + disabled?: boolean; + style?: ViewStyle; +} + +export function TextButton({ + variant = "primary", + text, + disabled = false, + onPress, + style, +}: Props) { + const { borderColor, color, backgroundColor } = useTextButton({ + variant, + disabled, + }); + + return ( + { + return [ + styles.base, + style, + { + backgroundColor, + borderColor, + opacity: pressed ? 0.9 : 1, + transform: [{ scale: pressed ? 0.995 : 1 }], + }, + ]; + }} + > + + {text} + + + ); +} + +const styles = StyleSheet.create({ + base: { + borderRadius: 8, + alignItems: "center", + justifyContent: "center", + paddingVertical: 12, + paddingHorizontal: 16, + borderWidth: 1, + }, +}); diff --git a/src/components/TextButton/types.ts b/src/components/TextButton/types.ts new file mode 100644 index 0000000..c5a711b --- /dev/null +++ b/src/components/TextButton/types.ts @@ -0,0 +1 @@ +export type VariantKey = "primary" | "secondary"; diff --git a/src/components/TextButton/useTextButton.ts b/src/components/TextButton/useTextButton.ts new file mode 100644 index 0000000..4f78266 --- /dev/null +++ b/src/components/TextButton/useTextButton.ts @@ -0,0 +1,48 @@ +import { useMemo } from "react"; +import type { ThemeColor } from "../../styles"; +import { useThemeColors } from "../../styles"; +import type { VariantKey } from "./types"; + +const colorMap: { + [key in VariantKey | "disabled"]: { + backgroundColorKey: ThemeColor; + textColorKey: ThemeColor; + }; +} = { + primary: { + backgroundColorKey: "buttonPrimary", + textColorKey: "buttonPrimaryText", + }, + secondary: { + backgroundColorKey: "buttonSecondary", + textColorKey: "buttonSecondaryText", + }, + disabled: { + backgroundColorKey: "buttonDisabled", + textColorKey: "buttonDisabledText", + }, +}; + +interface Props { + variant: VariantKey; + disabled: boolean; +} + +export const useTextButton = ({ disabled, variant }: Props) => { + const variantKey = disabled ? "disabled" : variant; + const { backgroundColorKey, textColorKey } = colorMap[variantKey]; + const [backgroundColor, color] = useThemeColors([ + backgroundColorKey, + textColorKey, + ]); + const borderColor = useMemo( + () => (variantKey === "secondary" ? color : backgroundColor), + [variantKey, color, backgroundColor], + ); + + return { + borderColor, + color, + backgroundColor, + }; +}; diff --git a/src/screens/Search/index.tsx b/src/screens/Search/index.tsx index 35b8502..d6b89d2 100644 --- a/src/screens/Search/index.tsx +++ b/src/screens/Search/index.tsx @@ -1,6 +1,8 @@ import { useNavigation } from "@react-navigation/native"; import { useCallback } from "react"; -import { View, Text, TouchableOpacity } from "react-native"; +import { View } from "react-native"; +import { StyledText } from "../../components/StyledText"; +import { TextButton } from "../../components/TextButton"; export function SearchScreen() { const navigation = useNavigation(); @@ -11,14 +13,8 @@ export function SearchScreen() { return ( - Search - - - Go to Search Details - + Search + ); }