mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-06-14 03:11:02 +00:00
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:
@@ -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,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user