Стилизация
Приложение использует NativeWind v4 — Tailwind CSS для React Native.
Основы NativeWind
import { View, Text } from 'react-native';
export function Card() {
return (
<View className="bg-white rounded-xl p-4 shadow-md">
<Text className="text-lg font-semibold text-gray-900">
Заголовок
</Text>
</View>
);
}
Цветовая палитра
Цвета определены в src/core/theme/colors.ts:
| Цвет | HEX | Использование |
|---|---|---|
| Primary (Orange) | #C85400 | CTA кнопки, акценты |
| Secondary (Violet) | #7c3aed | Вторичные элементы |
| Accent (Green) | #22c55e | Успех, прогресс |
| Gray-900 | #111827 | Default кнопки, текст |
| Gray-50 | #F9FAFB | Surface, контейнеры |
Использование в NativeWind
// Кастомные цвета через CSS переменные
<View className="bg-primary" /> // Orange
<View className="bg-secondary" /> // Violet
<View className="bg-accent" /> // Green
Система тем (Light/Dark)
ThemeProvider
// src/core/theme/provider.tsx
import { vars } from 'nativewind';
import { colors } from './colors';
export function ThemeProvider({ children }) {
const { theme } = useTheme();
const isDark = theme === 'dark';
const themeVars = vars({
'--color-background': isDark ? colors.dark.background : colors.light.background,
'--color-text': isDark ? colors.dark.text : colors.light.text,
'--color-card': isDark ? colors.dark.card : colors.light.card,
// ...
});
return (
<View style={themeVars} className="flex-1">
{children}
</View>
);
}
Использование тем
// Автоматически адаптируется к теме
<View className="bg-background" />
<Text className="text-foreground" />
<View className="bg-card border-border" />
Zustand Store для темы
// src/core/theme/store.ts
export const useTheme = () => {
const theme = useThemeStore((state) => state.theme);
const setTheme = useThemeStore((state) => state.setTheme);
return { theme, setTheme };
};
UI Компоненты
Button
// src/components/ui/button.tsx
import { cva } from 'class-variance-authority';
const buttonVariants = cva(
'flex-row items-center justify-center rounded-xl shadow-sm',
{
variants: {
variant: {
default: 'bg-[#1F2937] dark:bg-white', // Gray-900, white in dark
primary: 'bg-[#F97316]', // Orange (rare, for CTAs)
secondary: 'bg-[#7F56D9]', // Violet
accent: 'bg-[#10B981]', // Green
destructive: 'bg-[#EF4444]', // Red
outline: 'border border-[#E5E7EB] dark:border-[#374151] bg-transparent',
ghost: 'bg-transparent shadow-none',
},
size: {
sm: 'h-10 px-4',
default: 'h-12 px-6',
lg: 'h-14 px-8',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
const buttonTextVariants = cva('font-semibold', {
variants: {
variant: {
default: 'text-white dark:text-black',
primary: 'text-white',
secondary: 'text-white',
accent: 'text-white',
destructive: 'text-white',
outline: 'text-[#374151] dark:text-[#E5E7EB]',
ghost: 'text-[#374151] dark:text-[#E5E7EB]',
},
},
});
Использование
// Default (gray-900 / white in dark mode) - most common
<Button>Sign In</Button>
// Primary orange - ONLY for special CTAs (subscriptions, upgrades)
<Button variant="primary">Upgrade Plan</Button>
// Outline
<Button variant="outline">Cancel</Button>
Button Guidelines
- Default (gray-900): Login, Register, Submit, Confirm, Next, Continue, Save
- Primary (orange): ONLY subscription upgrades, special offers
- When in doubt, use default
Градиенты
// Auth header gradient
import { LinearGradient } from 'expo-linear-gradient';
import { colors } from '@/core/theme';
<LinearGradient
colors={colors.gradients.authHeader}
className="h-64 rounded-b-3xl"
/>
Шрифты
Inter font family загружается через src/core/fonts/:
// src/core/fonts/index.ts
import {
useFonts,
Inter_400Regular,
Inter_500Medium,
Inter_600SemiBold,
Inter_700Bold,
} from '@expo-google-fonts/inter';
export function useAppFonts() {
return useFonts({
Inter_400Regular,
Inter_500Medium,
Inter_600SemiBold,
Inter_700Bold,
});
}
// Использование
<Text className="font-normal" /> // Inter 400
<Text className="font-medium" /> // Inter 500
<Text className="font-semibold" /> // Inter 600
<Text className="font-bold" /> // Inter 700
tailwind.config.js
module.exports = {
content: [
'./app/**/*.{js,jsx,ts,tsx}',
'./src/**/*.{js,jsx,ts,tsx}',
],
presets: [require('nativewind/preset')],
theme: {
extend: {
colors: {
primary: 'var(--color-primary)',
secondary: 'var(--color-secondary)',
accent: 'var(--color-accent)',
background: 'var(--color-background)',
foreground: 'var(--color-foreground)',
card: 'var(--color-card)',
border: 'var(--color-border)',
},
fontFamily: {
sans: ['Inter'],
},
},
},
plugins: [],
};
Анимации
Используем react-native-reanimated:
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring
} from 'react-native-reanimated';
function FlipCard() {
const rotation = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ rotateY: `${rotation.value}deg` }],
}));
const flip = () => {
rotation.value = withSpring(rotation.value === 0 ? 180 : 0);
};
return (
<Animated.View style={animatedStyle}>
<Card />
</Animated.View>
);
}