Theme
Система тем с поддержкой light/dark mode.
Структура
src/core/theme/
├── colors.ts # HEX цвета + RGB переменные
├── store.ts # Zustand store для темы
├── provider.tsx # ThemeProvider с CSS vars
└── index.ts
Цветовая палитра
// src/core/theme/colors.ts
export const colors = {
// Brand
primary: '#C85400', // Orange
secondary: '#7c3aed', // Violet
accent: '#22c55e', // Green
// Light mode
light: {
background: '#FFFFFF',
foreground: '#111827',
card: '#FFFFFF',
cardForeground: '#111827',
border: '#E5E7EB',
muted: '#F3F4F6',
mutedForeground: '#6B7280',
},
// Dark mode
dark: {
background: '#111827',
foreground: '#F9FAFB',
card: '#1F2937',
cardForeground: '#F9FAFB',
border: '#374151',
muted: '#374151',
mutedForeground: '#9CA3AF',
},
// Gradients
gradients: {
authHeader: ['#C85400', '#E67300'],
primary: ['#C85400', '#FF6B00'],
},
};
Zustand Store
// src/core/theme/store.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Appearance } from 'react-native';
type ThemeMode = 'light' | 'dark' | 'system';
interface ThemeStore {
mode: ThemeMode;
setMode: (mode: ThemeMode) => void;
}
export const useThemeStore = create<ThemeStore>()(
persist(
(set) => ({
mode: 'system',
setMode: (mode) => set({ mode }),
}),
{
name: 'theme-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
// Convenience hook
export function useTheme() {
const mode = useThemeStore((s) => s.mode);
const setMode = useThemeStore((s) => s.setMode);
const resolvedTheme = mode === 'system'
? Appearance.getColorScheme() || 'light'
: mode;
return {
mode,
setMode,
resolvedTheme,
isDark: resolvedTheme === 'dark',
};
}
ThemeProvider
// src/core/theme/provider.tsx
import { vars } from 'nativewind';
import { View } from 'react-native';
import { colors } from './colors';
import { useTheme } from './store';
export function ThemeProvider({ children }: PropsWithChildren) {
const { isDark } = useTheme();
const palette = isDark ? colors.dark : colors.light;
const themeVars = vars({
'--color-background': palette.background,
'--color-foreground': palette.foreground,
'--color-card': palette.card,
'--color-card-foreground': palette.cardForeground,
'--color-border': palette.border,
'--color-muted': palette.muted,
'--color-muted-foreground': palette.mutedForeground,
'--color-primary': colors.primary,
'--color-secondary': colors.secondary,
'--color-accent': colors.accent,
});
return (
<View style={themeVars} className="flex-1 bg-background">
{children}
</View>
);
}
Использование в компонентах
// Цвета через CSS переменные (автоматически меняются с темой)
<View className="bg-background" />
<View className="bg-card border-border" />
<Text className="text-foreground" />
<Text className="text-muted-foreground" />
// Brand цвета (не меняются)
<View className="bg-primary" />
<Button className="bg-secondary" />
<Badge className="bg-accent" />
Переключение темы
// settings/theme.tsx
import { useTheme } from '@/core/theme';
function ThemeSettings() {
const { mode, setMode } = useTheme();
return (
<View>
<RadioButton
selected={mode === 'light'}
onPress={() => setMode('light')}
label="Светлая"
/>
<RadioButton
selected={mode === 'dark'}
onPress={() => setMode('dark')}
label="Тёмная"
/>
<RadioButton
selected={mode === 'system'}
onPress={() => setMode('system')}
label="Системная"
/>
</View>
);
}
tailwind.config.js
module.exports = {
content: ['./app/**/*.{ts,tsx}', './src/**/*.{ts,tsx}'],
presets: [require('nativewind/preset')],
theme: {
extend: {
colors: {
background: 'var(--color-background)',
foreground: 'var(--color-foreground)',
card: 'var(--color-card)',
'card-foreground': 'var(--color-card-foreground)',
border: 'var(--color-border)',
muted: 'var(--color-muted)',
'muted-foreground': 'var(--color-muted-foreground)',
primary: 'var(--color-primary)',
secondary: 'var(--color-secondary)',
accent: 'var(--color-accent)',
},
},
},
};
Доступ к цветам в JS
import { colors } from '@/core/theme';
import { useTheme } from '@/core/theme';
function Chart() {
const { isDark } = useTheme();
return (
<LineChart
lineColor={colors.primary}
backgroundColor={isDark ? colors.dark.card : colors.light.card}
/>
);
}