entregas_app/docs/SISTEMA_ESTILIZACAO_UI.md

831 lines
19 KiB
Markdown
Raw Permalink Normal View History

# Sistema de Estilização e Componentes UI
## Visão Geral
O aplicativo utiliza um sistema de estilização híbrido, combinando StyleSheet nativo do React Native com Tailwind CSS para componentes web, além de uma biblioteca completa de componentes UI baseados no shadcn/ui.
## Sistema de Cores e Tema
### Paleta de Cores Principal
**Arquivo**: `src/constants/theme.ts`
```typescript
export const COLORS = {
primary: "#0A1E63", // Azul escuro principal
secondary: "#F5F5F5", // Cinza claro
background: "#FFFFFF", // Branco de fundo
text: "#333333", // Cinza escuro para texto
textLight: "#777777", // Cinza médio para texto secundário
success: "#4CAF50", // Verde para sucesso
warning: "#FFC107", // Amarelo para avisos
danger: "#F44336", // Vermelho para erros
info: "#2196F3", // Azul para informações
border: "#E0E0E0", // Cinza claro para bordas
card: "#FFFFFF", // Branco para cards
shadow: "#000000", // Preto para sombras
error: "#FF3B30", // Vermelho para erros
}
```
### Sistema de Tamanhos
```typescript
export const SIZES = {
base: 8, // Unidade base (8px)
small: 12, // Pequeno
font: 14, // Tamanho de fonte padrão
medium: 16, // Médio
large: 18, // Grande
extraLarge: 24, // Extra grande
}
```
### Tipografia
```typescript
export const FONTS = {
regular: "Roboto-Regular",
medium: "Roboto-Medium",
bold: "Roboto-Bold",
}
```
### Sistema de Sombras
```typescript
export const SHADOWS = {
small: {
shadowColor: COLORS.shadow,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 2,
},
medium: {
shadowColor: COLORS.shadow,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 5.84,
elevation: 5,
},
}
```
## Configuração Tailwind CSS
### Arquivo de Configuração
**Arquivo**: `tailwind.config.ts`
```typescript
const config: Config = {
darkMode: ["class"],
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"*.{js,ts,jsx,tsx,mdx}"
],
theme: {
extend: {
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
// ... outras cores
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
keyframes: {
'accordion-down': {
from: { height: '0' },
to: { height: 'var(--radix-accordion-content-height)' }
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' }
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
}
}
},
plugins: [require("tailwindcss-animate")],
};
```
## Componentes UI Principais
### 1. Componente Icon
**Arquivo**: `components/Icon.tsx`
```typescript
interface IconProps {
type: 'material' | 'font-awesome' | 'ionicons';
name: string;
size?: number;
color?: string;
style?: any;
}
const Icon: React.FC<IconProps> = ({ type, name, size = 24, color, style }) => {
switch (type) {
case 'material':
return <MaterialIcons name={name} size={size} color={color} style={style} />;
case 'font-awesome':
return <FontAwesome name={name} size={size} color={color} style={style} />;
case 'ionicons':
return <Ionicons name={name} size={size} color={color} style={style} />;
default:
return <MaterialIcons name={name} size={size} color={color} style={style} />;
}
};
```
**Uso**:
```typescript
<Icon type="material" name="home" size={24} color={COLORS.primary} />
<Icon type="ionicons" name="location" size={20} color={COLORS.textLight} />
```
### 2. Componente FloatingPanicButton
**Arquivo**: `components/FloatingPanicButton.tsx`
```typescript
interface FloatingPanicButtonProps {
onPanic: (location?: { latitude: number; longitude: number } | null) => void;
}
const FloatingPanicButton: React.FC<FloatingPanicButtonProps> = ({ onPanic }) => {
const [location, setLocation] = useState<Location | null>(null);
const handlePanic = async () => {
try {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status === 'granted') {
const location = await Location.getCurrentPositionAsync({});
setLocation(location.coords);
onPanic(location.coords);
} else {
onPanic(null);
}
} catch (error) {
onPanic(null);
}
};
return (
<TouchableOpacity style={styles.panicButton} onPress={handlePanic}>
<Ionicons name="warning" size={24} color="white" />
</TouchableOpacity>
);
};
```
### 3. Componente DeliveryMap
**Arquivo**: `src/components/DeliveryMap.tsx`
```typescript
interface DeliveryMapProps {
deliveries: Delivery[];
onRouteCalculated?: (optimizedDeliveries: Delivery[]) => void;
showRoute?: boolean;
}
const DeliveryMap: React.FC<DeliveryMapProps> = ({
deliveries,
onRouteCalculated,
showRoute = true
}) => {
const [mapRef, setMapRef] = useState<MapView | null>(null);
const [routeCoordinates, setRouteCoordinates] = useState<LatLng[]>([]);
// Implementação do mapa com marcadores e rotas
return (
<MapView
ref={setMapRef}
style={styles.map}
initialRegion={initialRegion}
onMapReady={handleMapReady}
>
{/* Marcadores das entregas */}
{deliveries.map((delivery, index) => (
<Marker
key={delivery.id}
coordinate={{
latitude: delivery.lat || 0,
longitude: delivery.lng || 0
}}
title={delivery.customerName}
description={`Entrega ${delivery.deliverySeq}`}
/>
))}
{/* Polilinha da rota */}
{showRoute && routeCoordinates.length > 0 && (
<Polyline
coordinates={routeCoordinates}
strokeColor={COLORS.primary}
strokeWidth={3}
/>
)}
</MapView>
);
};
```
### 4. Componente MobileSignalIndicator
**Arquivo**: `src/components/MobileSignalIndicator.tsx`
```typescript
interface MobileSignalIndicatorProps {
signalInfo: MobileSignalInfo;
onOfflineModeChange?: (isOffline: boolean) => void;
}
const MobileSignalIndicator: React.FC<MobileSignalIndicatorProps> = ({
signalInfo,
onOfflineModeChange
}) => {
const getSignalColor = (strength: number) => {
if (strength >= 70) return COLORS.success;
if (strength >= 40) return COLORS.warning;
return COLORS.danger;
};
const getSignalIcon = (strength: number) => {
if (strength >= 70) return 'signal-cellular-4-bar';
if (strength >= 40) return 'signal-cellular-2-bar';
return 'signal-cellular-0-bar';
};
return (
<View style={styles.container}>
<Ionicons
name={getSignalIcon(signalInfo.signalStrength)}
size={16}
color={getSignalColor(signalInfo.signalStrength)}
/>
<Text style={styles.text}>
{signalInfo.signalStrength}% - {signalInfo.connectionType}
</Text>
{signalInfo.shouldUseOffline && (
<View style={styles.offlineBadge}>
<Text style={styles.offlineText}>OFFLINE</Text>
</View>
)}
</View>
);
};
```
## Componentes shadcn/ui
### Estrutura dos Componentes
**Pasta**: `components/ui/`
Os componentes seguem o padrão shadcn/ui com adaptações para React Native:
#### 1. Button Component
**Arquivo**: `components/ui/button.tsx`
```typescript
interface ButtonProps {
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
size?: 'default' | 'sm' | 'lg' | 'icon';
children: React.ReactNode;
onPress?: () => void;
disabled?: boolean;
}
const Button: React.FC<ButtonProps> = ({
variant = 'default',
size = 'default',
children,
onPress,
disabled = false
}) => {
const buttonStyles = [
styles.base,
styles[variant],
styles[size],
disabled && styles.disabled
];
return (
<TouchableOpacity
style={buttonStyles}
onPress={onPress}
disabled={disabled}
>
{children}
</TouchableOpacity>
);
};
```
#### 2. Card Component
**Arquivo**: `components/ui/card.tsx`
```typescript
interface CardProps {
children: React.ReactNode;
style?: any;
}
const Card: React.FC<CardProps> = ({ children, style }) => (
<View style={[styles.card, style]}>
{children}
</View>
);
const CardHeader: React.FC<CardProps> = ({ children, style }) => (
<View style={[styles.cardHeader, style]}>
{children}
</View>
);
const CardContent: React.FC<CardProps> = ({ children, style }) => (
<View style={[styles.cardContent, style]}>
{children}
</View>
);
```
#### 3. Input Component
**Arquivo**: `components/ui/input.tsx`
```typescript
interface InputProps {
placeholder?: string;
value?: string;
onChangeText?: (text: string) => void;
secureTextEntry?: boolean;
keyboardType?: KeyboardTypeOptions;
style?: any;
}
const Input: React.FC<InputProps> = ({
placeholder,
value,
onChangeText,
secureTextEntry = false,
keyboardType = 'default',
style
}) => (
<TextInput
style={[styles.input, style]}
placeholder={placeholder}
value={value}
onChangeText={onChangeText}
secureTextEntry={secureTextEntry}
keyboardType={keyboardType}
placeholderTextColor={COLORS.textLight}
/>
);
```
## Estilos das Telas Principais
### 1. LoginScreen Styles
```typescript
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
scrollContent: {
flexGrow: 1,
justifyContent: "center",
padding: 20,
},
logoContainer: {
alignItems: "center",
marginBottom: 20,
},
headerContainer: {
alignItems: "center",
marginBottom: 30,
},
headerTitle: {
fontSize: 28,
fontWeight: "bold",
color: COLORS.text,
marginBottom: 8,
},
formContainer: {
width: "100%",
maxWidth: 400,
alignSelf: "center",
},
input: {
backgroundColor: COLORS.secondary,
borderRadius: 12,
padding: 15,
fontSize: 16,
color: COLORS.text,
width: "100%",
},
loginButton: {
backgroundColor: COLORS.primary,
borderRadius: 12,
padding: 16,
alignItems: "center",
marginBottom: 24,
},
loginButtonText: {
color: "#FFFFFF",
fontSize: 16,
fontWeight: "bold",
},
});
```
### 2. HomeScreen Styles
```typescript
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
headerGradient: {
paddingBottom: 16,
},
header: {
flexDirection: "row",
alignItems: "flex-start",
justifyContent: "space-between",
paddingHorizontal: 20,
paddingVertical: 16,
},
statsContainer: {
flexDirection: "row",
backgroundColor: COLORS.card,
paddingVertical: 20,
paddingHorizontal: 20,
marginHorizontal: 16,
marginTop: -8,
borderRadius: 16,
...SHADOWS.medium,
},
statCard: {
flex: 1,
alignItems: "center",
},
statNumber: {
fontSize: 24,
fontWeight: "bold",
color: COLORS.primary,
marginBottom: 4,
},
nextDeliveryCard: {
backgroundColor: COLORS.card,
borderRadius: 20,
padding: 20,
marginHorizontal: 16,
marginBottom: 16,
...SHADOWS.medium,
borderWidth: 2,
borderColor: COLORS.primary + "20",
},
});
```
### 3. RoutesScreen Styles
```typescript
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
mapContainer: {
flex: 1,
borderRadius: 16,
overflow: 'hidden',
margin: 16,
...SHADOWS.medium,
},
map: {
flex: 1,
},
controlsContainer: {
position: 'absolute',
top: 20,
right: 20,
flexDirection: 'row',
gap: 10,
},
controlButton: {
backgroundColor: COLORS.card,
borderRadius: 8,
padding: 12,
...SHADOWS.small,
},
statsContainer: {
position: 'absolute',
bottom: 20,
left: 20,
right: 20,
backgroundColor: COLORS.card,
borderRadius: 16,
padding: 16,
...SHADOWS.medium,
},
});
```
## Sistema de Gradientes
### LinearGradient Usage
```typescript
import { LinearGradient } from 'expo-linear-gradient';
// Header gradient
<LinearGradient
colors={[COLORS.primary, "#3B82F6"]}
style={styles.headerGradient}
>
{/* Header content */}
</LinearGradient>
// Button gradient
<LinearGradient
colors={[COLORS.primary, "#3B82F6"]}
style={styles.buttonGradient}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
>
<Text style={styles.buttonText}>Entrar</Text>
</LinearGradient>
// Card gradient
<LinearGradient
colors={["#D1FAE5", "#F0FDF4"]}
style={styles.successCard}
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}
>
{/* Success content */}
</LinearGradient>
```
## Animações e Transições
### Animated Components
```typescript
import { Animated } from 'react-native';
// Fade animation
const fadeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
}, []);
<Animated.View style={{ opacity: fadeAnim }}>
{/* Animated content */}
</Animated.View>
// Scale animation
const scaleAnim = useRef(new Animated.Value(0)).current;
const animatePress = () => {
Animated.sequence([
Animated.timing(scaleAnim, {
toValue: 0.95,
duration: 100,
useNativeDriver: true,
}),
Animated.timing(scaleAnim, {
toValue: 1,
duration: 100,
useNativeDriver: true,
}),
]).start();
};
```
## Responsividade e Adaptação
### Dimensions Usage
```typescript
import { Dimensions } from 'react-native';
const { width, height } = Dimensions.get('window');
// Responsive styles
const styles = StyleSheet.create({
container: {
width: width * 0.9,
height: height * 0.8,
},
card: {
width: width > 768 ? width * 0.3 : width * 0.9,
},
});
```
### Platform-specific Styles
```typescript
import { Platform } from 'react-native';
const styles = StyleSheet.create({
header: {
paddingTop: Platform.OS === 'ios' ? 44 : 24,
},
button: {
borderRadius: Platform.OS === 'ios' ? 8 : 4,
},
});
```
## Temas e Modo Escuro
### Theme Provider
```typescript
// components/theme-provider.tsx
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType>({
theme: 'light',
toggleTheme: () => {},
});
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
```
### Dark Mode Colors
```typescript
export const DARK_COLORS = {
primary: "#1E40AF",
secondary: "#1F2937",
background: "#111827",
text: "#F9FAFB",
textLight: "#9CA3AF",
card: "#1F2937",
border: "#374151",
// ... outras cores
};
```
## Componentes de Formulário
### Form Components
```typescript
// components/ui/form.tsx
interface FormFieldProps {
label: string;
error?: string;
children: React.ReactNode;
}
const FormField: React.FC<FormFieldProps> = ({ label, error, children }) => (
<View style={styles.formField}>
<Text style={styles.label}>{label}</Text>
{children}
{error && <Text style={styles.errorText}>{error}</Text>}
</View>
);
// Usage
<FormField label="Nome de Usuário" error={usernameError}>
<Input
placeholder="Digite seu nome de usuário"
value={username}
onChangeText={setUsername}
/>
</FormField>
```
## Componentes de Feedback
### Toast Component
```typescript
// components/ui/toast.tsx
interface ToastProps {
message: string;
type: 'success' | 'error' | 'warning' | 'info';
visible: boolean;
onHide: () => void;
}
const Toast: React.FC<ToastProps> = ({ message, type, visible, onHide }) => {
useEffect(() => {
if (visible) {
const timer = setTimeout(onHide, 3000);
return () => clearTimeout(timer);
}
}, [visible, onHide]);
if (!visible) return null;
return (
<Animated.View style={[styles.toast, styles[type]]}>
<Ionicons name={getIcon(type)} size={20} color="white" />
<Text style={styles.toastText}>{message}</Text>
</Animated.View>
);
};
```
### Loading Component
```typescript
// components/ui/loading.tsx
interface LoadingProps {
visible: boolean;
message?: string;
}
const Loading: React.FC<LoadingProps> = ({ visible, message = "Carregando..." }) => {
if (!visible) return null;
return (
<View style={styles.overlay}>
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={COLORS.primary} />
<Text style={styles.loadingText}>{message}</Text>
</View>
</View>
);
};
```
## Considerações para Sincronização Offline
### 1. Indicadores de Status
```typescript
// Componente para mostrar status de sincronização
const SyncStatusIndicator: React.FC<{ status: 'synced' | 'pending' | 'error' }> = ({ status }) => {
const getStatusColor = (status: string) => {
switch (status) {
case 'synced': return COLORS.success;
case 'pending': return COLORS.warning;
case 'error': return COLORS.danger;
default: return COLORS.textLight;
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'synced': return 'checkmark-circle';
case 'pending': return 'time';
case 'error': return 'close-circle';
default: return 'help-circle';
}
};
return (
<View style={styles.statusContainer}>
<Ionicons
name={getStatusIcon(status)}
size={16}
color={getStatusColor(status)}
/>
<Text style={[styles.statusText, { color: getStatusColor(status) }]}>
{status.toUpperCase()}
</Text>
</View>
);
};
```
### 2. Componentes Offline
```typescript
// Componente para modo offline
const OfflineBanner: React.FC<{ visible: boolean }> = ({ visible }) => {
if (!visible) return null;
return (
<View style={styles.offlineBanner}>
<Ionicons name="cloud-offline" size={20} color="white" />
<Text style={styles.offlineText}>Modo Offline Ativo</Text>
</View>
);
};
```
Esta documentação fornece uma visão completa do sistema de estilização e componentes UI do aplicativo, incluindo padrões de design, componentes reutilizáveis e considerações para implementação de funcionalidades offline.