mojmajstor/components/theme.tsx
2026-06-04 20:37:09 +02:00

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);
}