Add world-class mobile components and Staking/Governance screens

PHASE 1 & 2 of mobile app transformation completed.

New Modern Component Library:
- Card: Elevated, outlined, filled variants with press states
- Button: 5 variants (primary, secondary, outline, ghost, danger) with Kurdistan colors
- Input: Floating labels, validation, icons, focus states
- BottomSheet: Swipe-to-dismiss modal with smooth animations
- LoadingSkeleton: Shimmer loading states (Skeleton, CardSkeleton, ListItemSkeleton)
- Badge: Status indicators and labels for Tiki roles

New Screens:
1. StakingScreen (504 lines):
   - View staked amount and rewards
   - Live staking data from blockchain
   - Stake/Unstake with bottom sheets
   - Tiki score breakdown
   - Monthly PEZ rewards calculation
   - APY estimation
   - Unbonding status
   - Inspired by Polkadot.js and Argent

2. GovernanceScreen (447 lines):
   - Active proposals list
   - Vote FOR/AGAINST proposals
   - Real-time voting statistics
   - Vote progress visualization
   - Proposal details bottom sheet
   - Democratic participation interface
   - Inspired by modern DAO platforms

Design Principles:
 Kurdistan colors (Kesk, Sor, Zer) throughout
 Material Design 3 inspired
 Smooth animations and transitions
 Clean, modern UI
 Accessibility-first
 RTL support ready

All components use:
- Shared theme from @pezkuwi/theme
- Shared blockchain logic from @pezkuwi/lib
- TypeScript with full type safety
- React Native best practices

Next: DEX/Swap, NFT Gallery, Transaction History
This commit is contained in:
Claude
2025-11-15 01:10:55 +00:00
parent 8c905183fd
commit 3d84b618cf
9 changed files with 1827 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
import React, { useEffect, useRef } from 'react';
import {
View,
Text,
Modal,
Animated,
Pressable,
StyleSheet,
Dimensions,
PanResponder,
} from 'react-native';
import { AppColors } from '../theme/colors';
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
interface BottomSheetProps {
visible: boolean;
onClose: () => void;
title?: string;
children: React.ReactNode;
height?: number;
showHandle?: boolean;
}
/**
* Modern Bottom Sheet Component
* Swipe to dismiss, smooth animations
*/
export const BottomSheet: React.FC<BottomSheetProps> = ({
visible,
onClose,
title,
children,
height = SCREEN_HEIGHT * 0.6,
showHandle = true,
}) => {
const translateY = useRef(new Animated.Value(height)).current;
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: (_, gestureState) => {
return gestureState.dy > 5;
},
onPanResponderMove: (_, gestureState) => {
if (gestureState.dy > 0) {
translateY.setValue(gestureState.dy);
}
},
onPanResponderRelease: (_, gestureState) => {
if (gestureState.dy > 100) {
closeSheet();
} else {
Animated.spring(translateY, {
toValue: 0,
useNativeDriver: true,
}).start();
}
},
})
).current;
useEffect(() => {
if (visible) {
openSheet();
}
}, [visible]);
const openSheet = () => {
Animated.spring(translateY, {
toValue: 0,
useNativeDriver: true,
damping: 20,
}).start();
};
const closeSheet = () => {
Animated.timing(translateY, {
toValue: height,
duration: 250,
useNativeDriver: true,
}).start(() => {
onClose();
});
};
return (
<Modal
visible={visible}
transparent
animationType="fade"
onRequestClose={closeSheet}
>
<View style={styles.overlay}>
<Pressable style={styles.backdrop} onPress={closeSheet} />
<Animated.View
style={[
styles.sheet,
{ height, transform: [{ translateY }] },
]}
{...panResponder.panHandlers}
>
{showHandle && (
<View style={styles.handleContainer}>
<View style={styles.handle} />
</View>
)}
{title && (
<View style={styles.header}>
<Text style={styles.title}>{title}</Text>
</View>
)}
<View style={styles.content}>{children}</View>
</Animated.View>
</View>
</Modal>
);
};
const styles = StyleSheet.create({
overlay: {
flex: 1,
justifyContent: 'flex-end',
},
backdrop: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
sheet: {
backgroundColor: AppColors.surface,
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
paddingBottom: 34, // Safe area
shadowColor: '#000',
shadowOffset: { width: 0, height: -4 },
shadowOpacity: 0.15,
shadowRadius: 12,
elevation: 20,
},
handleContainer: {
alignItems: 'center',
paddingTop: 12,
paddingBottom: 8,
},
handle: {
width: 40,
height: 4,
borderRadius: 2,
backgroundColor: AppColors.border,
},
header: {
paddingHorizontal: 20,
paddingVertical: 16,
borderBottomWidth: 1,
borderBottomColor: AppColors.border,
},
title: {
fontSize: 20,
fontWeight: '700',
color: AppColors.text,
},
content: {
flex: 1,
padding: 20,
},
});