Урок 06

Темизация и цвета

Светлая и тёмная темы, единая система цветов.

Система цветов

// constants/colors.ts
export const colors = {
    primary: '#c1442e',
    primaryDark: '#a83926',
    text: '#1a1a1a',
    textMuted: '#666',
    background: '#fafafa',
    surface: '#ffffff',
    border: '#e5e5e5',
    success: '#10b981',
    error: '#ef4444',
    warning: '#f59e0b',
};

Использование:

const styles = StyleSheet.create({
    button: { backgroundColor: colors.primary },
    text: { color: colors.text },
});

useColorScheme

Системная тема:

import { useColorScheme } from 'react-native';

function Component() {
    const scheme = useColorScheme(); // 'light' | 'dark' | null

    return (
        <View style={{ backgroundColor: scheme === 'dark' ? '#000' : '#fff' }}>
            {/* контент */}
        </View>
    );
}

Контекст темы

// contexts/ThemeContext.tsx
import { createContext, useContext, ReactNode } from 'react';
import { useColorScheme } from 'react-native';

type Theme = {
    colors: typeof lightColors;
    isDark: boolean;
};

const lightColors = { background: '#fff', text: '#000', primary: '#c1442e' };
const darkColors = { background: '#0a0a0a', text: '#fff', primary: '#ff6b4a' };

const ThemeContext = createContext<Theme | null>(null);

export function ThemeProvider({ children }: { children: ReactNode }) {
    const scheme = useColorScheme();
    const isDark = scheme === 'dark';

    const theme: Theme = {
        colors: isDark ? darkColors : lightColors,
        isDark,
    };

    return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
}

export function useTheme() {
    const context = useContext(ThemeContext);
    if (!context) throw new Error('useTheme must be used within ThemeProvider');
    return context;
}

Использование:

function Component() {
    const { colors, isDark } = useTheme();

    return (
        <View style={{ backgroundColor: colors.background }}>
            <Text style={{ color: colors.text }}>Текст</Text>
        </View>
    );
}

Стили с темой

Создание стилей внутри компонента:

function Component() {
    const { colors } = useTheme();

    const styles = useMemo(() => StyleSheet.create({
        container: { backgroundColor: colors.background },
        text: { color: colors.text },
    }), [colors]);

    return <View style={styles.container} />;
}

useMemo чтобы не создавать стили на каждый рендер.

Переключатель темы

function ThemeToggle() {
    const [mode, setMode] = useState<'system' | 'light' | 'dark'>('system');

    return (
        <View>
            <Pressable onPress={() => setMode('light')}>
                <Text>Светлая</Text>
            </Pressable>
            <Pressable onPress={() => setMode('dark')}>
                <Text>Тёмная</Text>
            </Pressable>
            <Pressable onPress={() => setMode('system')}>
                <Text>Системная</Text>
            </Pressable>
        </View>
    );
}

Сохранение выбора в AsyncStorage.

Expo: userInterfaceStyle

В app.json:

{
    "expo": {
        "userInterfaceStyle": "automatic"
    }
}

Варианты: light, dark, automatic.

automatic — следует системной теме.

Готовые библиотеки

  • React Navigation — встроенная поддержка тем
  • tamagui — дизайн-система
  • restyle — типизированные темы
  • nativewind — Tailwind с dark mode

Если ты уже знаешь React

В вебе — CSS-переменные и prefers-color-scheme. В React Native — контекст и useColorScheme. Логика похожа.

Итоги

  • useColorScheme для системной темы
  • Контекст для общих цветов
  • useMemo для стилей
  • app.json для userInterfaceStyle
  • AsyncStorage для сохранения выбора