187 lines
5.1 KiB
TypeScript
187 lines
5.1 KiB
TypeScript
import { createContext, useContext } from "react";
|
|
import { useColorScheme } from "react-native";
|
|
|
|
type ThemeColors = {
|
|
primary: string;
|
|
primaryLight: string;
|
|
primaryDark: string;
|
|
secondary: string;
|
|
secondaryLight: string;
|
|
background: string;
|
|
surface: string;
|
|
surfaceAlt: string;
|
|
card: string;
|
|
border: string;
|
|
borderLight: string;
|
|
text: string;
|
|
textSecondary: string;
|
|
textTertiary: string;
|
|
textInverse: string;
|
|
error: string;
|
|
errorLight: string;
|
|
success: string;
|
|
successLight: string;
|
|
star: string;
|
|
starLight: string;
|
|
tabActive: string;
|
|
tabInactive: string;
|
|
};
|
|
|
|
type Shadow = {
|
|
shadowColor: string;
|
|
shadowOffset: { width: number; height: number };
|
|
shadowOpacity: number;
|
|
shadowRadius: number;
|
|
elevation: number;
|
|
};
|
|
|
|
type ThemeShadows = {
|
|
sm: Shadow;
|
|
md: Shadow;
|
|
lg: Shadow;
|
|
button: Shadow;
|
|
};
|
|
|
|
type Typography = {
|
|
h1: { fontSize: number; fontWeight: "700"; lineHeight: number };
|
|
h2: { fontSize: number; fontWeight: "600"; lineHeight: number };
|
|
h3: { fontSize: number; fontWeight: "600"; lineHeight: number };
|
|
body: { fontSize: number; fontWeight: "400"; lineHeight: number };
|
|
bodyBold: { fontSize: number; fontWeight: "600"; lineHeight: number };
|
|
caption: { fontSize: number; fontWeight: "400"; lineHeight: number };
|
|
captionBold: { fontSize: number; fontWeight: "600"; lineHeight: number };
|
|
label: { fontSize: number; fontWeight: "500"; lineHeight: number };
|
|
};
|
|
|
|
type Theme = {
|
|
colors: ThemeColors;
|
|
spacing: {
|
|
xs: number;
|
|
sm: number;
|
|
md: number;
|
|
lg: number;
|
|
xl: number;
|
|
xxl: number;
|
|
};
|
|
radius: {
|
|
sm: number;
|
|
md: number;
|
|
lg: number;
|
|
xl: number;
|
|
full: number;
|
|
};
|
|
shadows: ThemeShadows;
|
|
typography: Typography;
|
|
};
|
|
|
|
const makeShadow = (elevation: number, color: string): Shadow => {
|
|
const shadows: Record<number, Shadow> = {
|
|
2: { shadowColor: color, shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 3, elevation: 2 },
|
|
4: { shadowColor: color, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.12, shadowRadius: 8, elevation: 4 },
|
|
8: { shadowColor: color, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.15, shadowRadius: 16, elevation: 8 },
|
|
};
|
|
return shadows[elevation] || shadows[2];
|
|
};
|
|
|
|
const spacing = { xs: 4, sm: 8, md: 16, lg: 24, xl: 32, xxl: 48 };
|
|
const radius = { sm: 6, md: 12, lg: 16, xl: 24, full: 999 };
|
|
|
|
const typography: Typography = {
|
|
h1: { fontSize: 28, fontWeight: "700", lineHeight: 36 },
|
|
h2: { fontSize: 22, fontWeight: "600", lineHeight: 30 },
|
|
h3: { fontSize: 18, fontWeight: "600", lineHeight: 24 },
|
|
body: { fontSize: 15, fontWeight: "400", lineHeight: 22 },
|
|
bodyBold: { fontSize: 15, fontWeight: "600", lineHeight: 22 },
|
|
caption: { fontSize: 13, fontWeight: "400", lineHeight: 18 },
|
|
captionBold: { fontSize: 13, fontWeight: "600", lineHeight: 18 },
|
|
label: { fontSize: 11, fontWeight: "500", lineHeight: 16 },
|
|
};
|
|
|
|
const LightTheme: Theme = {
|
|
colors: {
|
|
primary: "#D4764A",
|
|
primaryLight: "#FDF0EA",
|
|
primaryDark: "#B85E34",
|
|
secondary: "#1A2A3A",
|
|
secondaryLight: "#E8ECF0",
|
|
background: "#FDF8F3",
|
|
surface: "#FFFFFF",
|
|
surfaceAlt: "#F5F0EB",
|
|
card: "#FFFFFF",
|
|
border: "#E8E2DA",
|
|
borderLight: "#F0EBE5",
|
|
text: "#1A1A2E",
|
|
textSecondary: "#8B7E74",
|
|
textTertiary: "#B0A69C",
|
|
textInverse: "#FFFFFF",
|
|
error: "#DC2626",
|
|
errorLight: "#FEE2E2",
|
|
success: "#4A8C6F",
|
|
successLight: "#E8F3EE",
|
|
star: "#F59E0B",
|
|
starLight: "#FEF3C7",
|
|
tabActive: "#D4764A",
|
|
tabInactive: "#B0A69C",
|
|
},
|
|
spacing,
|
|
radius,
|
|
shadows: {
|
|
sm: makeShadow(2, "#000000"),
|
|
md: makeShadow(4, "#000000"),
|
|
lg: makeShadow(8, "#000000"),
|
|
button: { shadowColor: "#D4764A", shadowOffset: { width: 0, height: 3 }, shadowOpacity: 0.3, shadowRadius: 6, elevation: 4 },
|
|
},
|
|
typography,
|
|
};
|
|
|
|
const DarkTheme: Theme = {
|
|
colors: {
|
|
primary: "#E89B78",
|
|
primaryLight: "#2D1F18",
|
|
primaryDark: "#D4764A",
|
|
secondary: "#E8ECF0",
|
|
secondaryLight: "#2A3040",
|
|
background: "#0F0F1A",
|
|
surface: "#1A1A2E",
|
|
surfaceAlt: "#222240",
|
|
card: "#1A1A2E",
|
|
border: "#2A2A45",
|
|
borderLight: "#222240",
|
|
text: "#F0EDE8",
|
|
textSecondary: "#9CA3AF",
|
|
textTertiary: "#6B7280",
|
|
textInverse: "#FFFFFF",
|
|
error: "#F87171",
|
|
errorLight: "#451111",
|
|
success: "#6EC99B",
|
|
successLight: "#1A3A2E",
|
|
star: "#FBBF24",
|
|
starLight: "#3A2E1A",
|
|
tabActive: "#E89B78",
|
|
tabInactive: "#6B7280",
|
|
},
|
|
spacing,
|
|
radius,
|
|
shadows: {
|
|
sm: makeShadow(2, "#000000"),
|
|
md: makeShadow(4, "#000000"),
|
|
lg: makeShadow(8, "#000000"),
|
|
button: { shadowColor: "#E89B78", shadowOffset: { width: 0, height: 3 }, shadowOpacity: 0.3, shadowRadius: 6, elevation: 4 },
|
|
},
|
|
typography,
|
|
};
|
|
|
|
export type { Theme, ThemeColors, ThemeShadows, Typography };
|
|
|
|
const ThemeContext = createContext<Theme>(LightTheme);
|
|
|
|
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
const scheme = useColorScheme();
|
|
const theme = scheme === "dark" ? DarkTheme : LightTheme;
|
|
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
|
|
}
|
|
|
|
export function useTheme() {
|
|
return useContext(ThemeContext);
|
|
}
|