Перейти к основному содержимому

Стилизация

Приложение использует 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)#C85400CTA кнопки, акценты
Secondary (Violet)#7c3aedВторичные элементы
Accent (Green)#22c55eУспех, прогресс
Gray-900#111827Default кнопки, текст
Gray-50#F9FAFBSurface, контейнеры

Использование в 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>
);
}