Skip to content

Commit 57569b0

Browse files
committed
feat: Revamp card components and theme for Gen Z engagement with glassmorphism and enhanced UX
- Introduced advanced card components: GlassCard, ExpenseCard, GroupSummaryCard, and QuickActionCard. - Enhanced GroupCard for backward compatibility with new design. - Updated theme colors and typography to align with "Expressive Minimalism" and Gen Z preferences. - Implemented glassmorphism effects and improved haptic feedback interactions. - Refactored styles and animations for a cohesive user experience.
1 parent afde178 commit 57569b0

File tree

8 files changed

+3010
-782
lines changed

8 files changed

+3010
-782
lines changed

frontend/A Design Blueprint for a Modern, Minimalist Expens.md

Lines changed: 339 additions & 0 deletions
Large diffs are not rendered by default.

frontend/components/core/Button.js

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Core Button Component - Following Blueprint Specifications
2+
// Implements the 8-second rule and haptic feedback for Gen Z engagement
3+
4+
import * as Haptics from 'expo-haptics';
5+
import { LinearGradient } from 'expo-linear-gradient';
6+
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
7+
import { borderRadius, colors, shadows, spacing } from '../../utils/theme';
8+
9+
const Button = ({
10+
title,
11+
variant = 'primary', // primary, secondary, outline, ghost, destructive
12+
size = 'medium', // small, medium, large
13+
onPress,
14+
disabled = false,
15+
loading = false,
16+
icon,
17+
fullWidth = false,
18+
style,
19+
textStyle,
20+
...props
21+
}) => {
22+
const handlePress = async () => {
23+
if (disabled || loading) return;
24+
25+
// Haptic feedback for engagement (Gen Z preference for tactile response)
26+
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
27+
28+
if (onPress) {
29+
onPress();
30+
}
31+
};
32+
33+
// Size configurations following minimum touch target of 44px
34+
const sizeConfig = {
35+
small: {
36+
paddingVertical: spacing.sm,
37+
paddingHorizontal: spacing.md,
38+
minHeight: 36,
39+
fontSize: 14,
40+
fontWeight: '500',
41+
},
42+
medium: {
43+
paddingVertical: spacing.md,
44+
paddingHorizontal: spacing.lg,
45+
minHeight: 44, // Accessibility minimum
46+
fontSize: 16,
47+
fontWeight: '600',
48+
},
49+
large: {
50+
paddingVertical: spacing.lg,
51+
paddingHorizontal: spacing.xl,
52+
minHeight: 52,
53+
fontSize: 18,
54+
fontWeight: '600',
55+
},
56+
};
57+
58+
// Variant configurations for different button types
59+
const variantConfig = {
60+
primary: {
61+
useGradient: true,
62+
gradientColors: [colors.brand.accent, colors.brand.accentAlt],
63+
textColor: '#FFFFFF',
64+
shadowStyle: shadows.small,
65+
},
66+
secondary: {
67+
backgroundColor: colors.background.secondary,
68+
textColor: colors.text.primary,
69+
borderWidth: 1,
70+
borderColor: colors.border.subtle,
71+
shadowStyle: shadows.subtle,
72+
},
73+
outline: {
74+
backgroundColor: 'transparent',
75+
textColor: colors.brand.accent,
76+
borderWidth: 2,
77+
borderColor: colors.brand.accent,
78+
},
79+
ghost: {
80+
backgroundColor: 'transparent',
81+
textColor: colors.brand.accent,
82+
},
83+
destructive: {
84+
backgroundColor: colors.semantic.error,
85+
textColor: '#FFFFFF',
86+
shadowStyle: shadows.small,
87+
},
88+
};
89+
90+
const currentSize = sizeConfig[size];
91+
const currentVariant = variantConfig[variant];
92+
93+
// Base button style
94+
const buttonStyle = {
95+
borderRadius: borderRadius.md,
96+
alignItems: 'center',
97+
justifyContent: 'center',
98+
flexDirection: 'row',
99+
minHeight: currentSize.minHeight,
100+
paddingVertical: currentSize.paddingVertical,
101+
paddingHorizontal: currentSize.paddingHorizontal,
102+
width: fullWidth ? '100%' : 'auto',
103+
opacity: disabled ? 0.6 : 1,
104+
...currentVariant.shadowStyle,
105+
...currentVariant,
106+
...style,
107+
};
108+
109+
// Text style
110+
const textStyleConfig = {
111+
fontSize: currentSize.fontSize,
112+
fontWeight: currentSize.fontWeight,
113+
color: currentVariant.textColor,
114+
fontFamily: 'Inter',
115+
...textStyle,
116+
};
117+
118+
// Loading spinner color
119+
const spinnerColor = currentVariant.textColor;
120+
121+
const ButtonContent = () => (
122+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
123+
{loading && (
124+
<ActivityIndicator
125+
size="small"
126+
color={spinnerColor}
127+
style={{ marginRight: spacing.sm }}
128+
/>
129+
)}
130+
{icon && !loading && (
131+
<View style={{ marginRight: spacing.sm }}>
132+
{icon}
133+
</View>
134+
)}
135+
<Text style={textStyleConfig}>
136+
{title}
137+
</Text>
138+
</View>
139+
);
140+
141+
// Render with gradient if specified
142+
if (currentVariant.useGradient && !disabled) {
143+
return (
144+
<TouchableOpacity
145+
onPress={handlePress}
146+
disabled={disabled || loading}
147+
activeOpacity={0.8}
148+
style={[buttonStyle, { backgroundColor: 'transparent' }]}
149+
{...props}
150+
>
151+
<LinearGradient
152+
colors={currentVariant.gradientColors}
153+
style={{
154+
...buttonStyle,
155+
shadowColor: 'transparent', // Remove shadow from gradient container
156+
elevation: 0,
157+
}}
158+
start={{ x: 0, y: 0 }}
159+
end={{ x: 1, y: 0 }}
160+
>
161+
<ButtonContent />
162+
</LinearGradient>
163+
</TouchableOpacity>
164+
);
165+
}
166+
167+
// Regular button without gradient
168+
return (
169+
<TouchableOpacity
170+
onPress={handlePress}
171+
disabled={disabled || loading}
172+
activeOpacity={0.8}
173+
style={buttonStyle}
174+
{...props}
175+
>
176+
<ButtonContent />
177+
</TouchableOpacity>
178+
);
179+
};
180+
181+
export default Button;

0 commit comments

Comments
 (0)