feat: enhance CasinoButton, Chip, and TableBackground components; add size prop for responsive styling and update TableScreen controls visibility logic

This commit is contained in:
2025-12-21 01:29:28 +01:00
parent 46835c6071
commit 038d4f4caa
4 changed files with 116 additions and 70 deletions

View File

@@ -8,18 +8,19 @@ const gradients = {
green: ['#39c377', '#1f7a44'] green: ['#39c377', '#1f7a44']
}; };
export default function CasinoButton({ label, onPress, variant = 'gold', disabled }) { export default function CasinoButton({ label, onPress, variant = 'gold', disabled, size = 'default' }) {
const textColor = variant === 'gold' ? '#2b1d0b' : '#f7f2e6'; const textColor = variant === 'gold' ? '#2b1d0b' : '#f7f2e6';
const isSmall = size === 'small';
return ( return (
<Pressable onPress={onPress} disabled={disabled} style={styles.wrapper}> <Pressable onPress={onPress} disabled={disabled} style={styles.wrapper}>
<LinearGradient <LinearGradient
colors={gradients[variant] || gradients.gold} colors={gradients[variant] || gradients.gold}
start={{ x: 0, y: 0 }} start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }} end={{ x: 1, y: 1 }}
style={[styles.button, disabled && styles.disabled]} style={[styles.button, isSmall && styles.buttonSmall, disabled && styles.disabled]}
> >
<View style={styles.inner}> <View style={styles.inner}>
<Text style={[styles.text, { color: textColor }]}>{label}</Text> <Text style={[styles.text, isSmall && styles.textSmall, { color: textColor }]}>{label}</Text>
</View> </View>
</LinearGradient> </LinearGradient>
</Pressable> </Pressable>
@@ -40,6 +41,10 @@ const styles = StyleSheet.create({
borderWidth: 1, borderWidth: 1,
borderColor: 'rgba(255,255,255,0.2)' borderColor: 'rgba(255,255,255,0.2)'
}, },
buttonSmall: {
paddingVertical: 8,
paddingHorizontal: 16
},
inner: { inner: {
alignItems: 'center' alignItems: 'center'
}, },
@@ -49,6 +54,10 @@ const styles = StyleSheet.create({
letterSpacing: 1, letterSpacing: 1,
textTransform: 'uppercase' textTransform: 'uppercase'
}, },
textSmall: {
fontSize: 13,
letterSpacing: 0.8
},
disabled: { disabled: {
opacity: 0.5 opacity: 0.5
} }

View File

@@ -7,11 +7,12 @@ const chipColors = {
green: colors.chipGreen green: colors.chipGreen
}; };
export default function Chip({ label, color = 'blue' }) { export default function Chip({ label, color = 'blue', size = 'default' }) {
const isSmall = size === 'small';
return ( return (
<View style={[styles.chip, { backgroundColor: chipColors[color] || colors.chipBlue }]}> <View style={[styles.chip, isSmall && styles.chipSmall, { backgroundColor: chipColors[color] || colors.chipBlue }]}>
<View style={styles.inner}> <View style={styles.inner}>
<Text style={styles.text}>{label}</Text> <Text style={[styles.text, isSmall && styles.textSmall]}>{label}</Text>
</View> </View>
</View> </View>
); );
@@ -27,6 +28,11 @@ const styles = StyleSheet.create({
borderWidth: 3, borderWidth: 3,
borderColor: '#f2f1e8' borderColor: '#f2f1e8'
}, },
chipSmall: {
width: 40,
height: 40,
borderRadius: 20
},
inner: { inner: {
width: 28, width: 28,
height: 28, height: 28,
@@ -41,5 +47,8 @@ const styles = StyleSheet.create({
fontWeight: '700', fontWeight: '700',
fontSize: 12, fontSize: 12,
fontFamily: fonts.mono fontFamily: fonts.mono
},
textSmall: {
fontSize: 11
} }
}); });

View File

@@ -2,11 +2,11 @@ import { StyleSheet, View } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import { colors } from '../theme'; import { colors } from '../theme';
export default function TableBackground({ children, style }) { export default function TableBackground({ children, style, showRing = true }) {
return ( return (
<View style={[styles.wrapper, style]}> <View style={[styles.wrapper, style]}>
<LinearGradient <LinearGradient
colors={[colors.tableFeltDark, '#0a3a2a']} colors={[colors.tableFeltDark, colors.tableFelt]}
start={{ x: 0, y: 0 }} start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }} end={{ x: 1, y: 1 }}
style={styles.edge} style={styles.edge}
@@ -17,7 +17,7 @@ export default function TableBackground({ children, style }) {
end={{ x: 1, y: 1 }} end={{ x: 1, y: 1 }}
style={styles.felt} style={styles.felt}
> >
<View style={styles.innerRing} /> {showRing ? <View style={styles.innerRing} /> : null}
{children} {children}
</LinearGradient> </LinearGradient>
</LinearGradient> </LinearGradient>
@@ -33,32 +33,23 @@ const styles = StyleSheet.create({
}, },
edge: { edge: {
flex: 1, flex: 1,
borderTopLeftRadius: 12, borderRadius: 18,
borderTopRightRadius: 12, padding: 6
borderBottomLeftRadius: 280,
borderBottomRightRadius: 280,
padding: 4
}, },
felt: { felt: {
flex: 1, flex: 1,
borderTopLeftRadius: 8, borderRadius: 16,
borderTopRightRadius: 8, padding: 14,
borderBottomLeftRadius: 260,
borderBottomRightRadius: 260,
padding: 12,
overflow: 'hidden' overflow: 'hidden'
}, },
innerRing: { innerRing: {
position: 'absolute', position: 'absolute',
top: 16, top: 12,
bottom: 16, bottom: 12,
left: 16, left: 12,
right: 16, right: 12,
borderWidth: 2, borderWidth: 1,
borderColor: 'rgba(255,255,255,0.15)', borderColor: 'rgba(255,255,255,0.15)',
borderTopLeftRadius: 6, borderRadius: 14
borderTopRightRadius: 6,
borderBottomLeftRadius: 230,
borderBottomRightRadius: 230
} }
}); });

View File

@@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Animated, Pressable, StyleSheet, Text, useWindowDimensions, View } from 'react-native'; import { Animated, Pressable, StyleSheet, Text, useWindowDimensions, View } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import { BlurView } from 'expo-blur'; import { BlurView } from 'expo-blur';
@@ -18,7 +18,9 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
const [message, setMessage] = useState(''); const [message, setMessage] = useState('');
const [betAmount, setBetAmount] = useState(10); const [betAmount, setBetAmount] = useState(10);
const [turnSeconds, setTurnSeconds] = useState(null); const [turnSeconds, setTurnSeconds] = useState(null);
const [controlsVisible, setControlsVisible] = useState(true);
const wsRef = useRef(null); const wsRef = useRef(null);
const hideTimerRef = useRef(null);
const pulse = useRef(new Animated.Value(0)).current; const pulse = useRef(new Animated.Value(0)).current;
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const { width, height } = useWindowDimensions(); const { width, height } = useWindowDimensions();
@@ -69,6 +71,32 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
const isMyTurn = table?.phase === 'playing' && table?.currentSeatIndex === mySeat?.index; const isMyTurn = table?.phase === 'playing' && table?.currentSeatIndex === mySeat?.index;
const bettingLocked = ['playing', 'dealer', 'payout'].includes(table?.phase); const bettingLocked = ['playing', 'dealer', 'payout'].includes(table?.phase);
const scheduleHide = useCallback(() => {
if (hideTimerRef.current) {
clearTimeout(hideTimerRef.current);
}
hideTimerRef.current = setTimeout(() => {
setControlsVisible(false);
}, 5000);
}, []);
const showControls = useCallback(() => {
setControlsVisible(true);
scheduleHide();
}, [scheduleHide]);
useEffect(() => {
if (isMyTurn) {
setControlsVisible(true);
}
scheduleHide();
return () => {
if (hideTimerRef.current) {
clearTimeout(hideTimerRef.current);
}
};
}, [isMyTurn, scheduleHide]);
useEffect(() => { useEffect(() => {
if (!table?.turnEndsAt || !isMyTurn) { if (!table?.turnEndsAt || !isMyTurn) {
setTurnSeconds(null); setTurnSeconds(null);
@@ -114,6 +142,7 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
const send = (payload) => { const send = (payload) => {
if (wsRef.current?.readyState === 1) { if (wsRef.current?.readyState === 1) {
showControls();
wsRef.current.send(JSON.stringify(payload)); wsRef.current.send(JSON.stringify(payload));
} }
}; };
@@ -122,6 +151,7 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
if (!table) { if (!table) {
return; return;
} }
showControls();
setBetAmount((prev) => { setBetAmount((prev) => {
const next = prev + delta; const next = prev + delta;
return Math.max(table.minBet, Math.min(table.maxBet, next)); return Math.max(table.minBet, Math.min(table.maxBet, next));
@@ -173,7 +203,7 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
</View> </View>
<View style={styles.tableWrap}> <View style={styles.tableWrap}>
<TableBackground style={styles.tableSurface}> <TableBackground style={styles.tableSurface} showRing={false}>
<View style={styles.tableInner}> <View style={styles.tableInner}>
<TableMarkings /> <TableMarkings />
<View style={styles.tableContent}> <View style={styles.tableContent}>
@@ -189,35 +219,39 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
</View> </View>
</View> </View>
<View style={styles.controlsOverlay}> {controlsVisible ? (
<BlurView intensity={28} tint="dark" style={styles.blurPanel}> <View style={styles.controlsOverlay}>
<View style={styles.betRow}> <BlurView intensity={28} tint="dark" style={styles.blurPanel}>
<Text style={styles.sectionLabel}>Tét</Text> <View style={styles.betRow}>
<View style={styles.betControls}> <Text style={styles.sectionLabel}>Tét</Text>
<Pressable onPress={() => adjustBet(-10)} style={[styles.betAdjust, bettingLocked && styles.betAdjustDisabled]} disabled={bettingLocked}> <View style={styles.betControls}>
<Text style={styles.betAdjustText}>-</Text> <Pressable onPress={() => adjustBet(-10)} style={[styles.betAdjust, bettingLocked && styles.betAdjustDisabled]} disabled={bettingLocked}>
</Pressable> <Text style={styles.betAdjustText}>-</Text>
<Chip label={`${betAmount}`} color="red" /> </Pressable>
<Pressable onPress={() => adjustBet(10)} style={[styles.betAdjust, bettingLocked && styles.betAdjustDisabled]} disabled={bettingLocked}> <Chip label={`${betAmount}`} color="red" size="small" />
<Text style={styles.betAdjustText}>+</Text> <Pressable onPress={() => adjustBet(10)} style={[styles.betAdjust, bettingLocked && styles.betAdjustDisabled]} disabled={bettingLocked}>
</Pressable> <Text style={styles.betAdjustText}>+</Text>
</Pressable>
</View>
<CasinoButton label="Tét" onPress={() => send({ type: 'bet', amount: betAmount })} variant="gold" size="small" disabled={bettingLocked} />
<CasinoButton label="Kész" onPress={() => send({ type: 'ready' })} variant="green" size="small" disabled={bettingLocked} />
</View> </View>
<CasinoButton label="Tét" onPress={() => send({ type: 'bet', amount: betAmount })} variant="gold" disabled={bettingLocked} /> </BlurView>
<CasinoButton label="Kész" onPress={() => send({ type: 'ready' })} variant="green" disabled={bettingLocked} />
</View>
</BlurView>
<BlurView intensity={28} tint="dark" style={styles.blurPanel}> <BlurView intensity={28} tint="dark" style={styles.blurPanel}>
{isMyTurn && turnSeconds !== null ? ( {isMyTurn && turnSeconds !== null ? (
<Text style={styles.timer}>Idő: {turnSeconds} mp</Text> <Text style={styles.timer}>Idő: {turnSeconds} mp</Text>
) : null} ) : null}
<Animated.View style={[styles.actionRow, isMyTurn && pulseStyle]}> <Animated.View style={[styles.actionRow, isMyTurn && pulseStyle]}>
<CasinoButton label="Hit" onPress={() => send({ type: 'action', action: 'hit' })} variant="gold" disabled={!isMyTurn} /> <CasinoButton label="Hit" onPress={() => send({ type: 'action', action: 'hit' })} variant="gold" size="small" disabled={!isMyTurn} />
<CasinoButton label="Stand" onPress={() => send({ type: 'action', action: 'stand' })} variant="gold" disabled={!isMyTurn} /> <CasinoButton label="Stand" onPress={() => send({ type: 'action', action: 'stand' })} variant="gold" size="small" disabled={!isMyTurn} />
<CasinoButton label="Double" onPress={() => send({ type: 'action', action: 'double' })} variant="gold" disabled={!isMyTurn} /> <CasinoButton label="Double" onPress={() => send({ type: 'action', action: 'double' })} variant="gold" size="small" disabled={!isMyTurn} />
</Animated.View> </Animated.View>
</BlurView> </BlurView>
</View> </View>
) : (
<Pressable style={styles.controlsReveal} onPress={showControls} />
)}
{message ? <Text style={styles.message}>{message}</Text> : null} {message ? <Text style={styles.message}>{message}</Text> : null}
</View> </View>
@@ -273,7 +307,7 @@ const styles = StyleSheet.create({
tableContent: { tableContent: {
flex: 1, flex: 1,
justifyContent: 'space-between', justifyContent: 'space-between',
paddingBottom: 120 paddingBottom: 140
}, },
seatLayer: { seatLayer: {
flex: 1, flex: 1,
@@ -281,20 +315,23 @@ const styles = StyleSheet.create({
}, },
seatSpot: { seatSpot: {
position: 'absolute', position: 'absolute',
width: 92, width: 90,
transform: [{ translateX: -46 }] transform: [{ translateX: -45 }]
}, },
controlsOverlay: { controlsOverlay: {
position: 'absolute', position: 'absolute',
left: 18, left: 20,
right: 18, right: 20,
bottom: 12, bottom: 10,
gap: 10 gap: 10
}, },
controlsReveal: {
...StyleSheet.absoluteFillObject
},
blurPanel: { blurPanel: {
borderRadius: 18, borderRadius: 18,
paddingVertical: 10, paddingVertical: 8,
paddingHorizontal: 12, paddingHorizontal: 10,
backgroundColor: 'rgba(10, 18, 14, 0.45)', backgroundColor: 'rgba(10, 18, 14, 0.45)',
borderWidth: 1, borderWidth: 1,
borderColor: 'rgba(255,255,255,0.12)', borderColor: 'rgba(255,255,255,0.12)',
@@ -312,16 +349,16 @@ const styles = StyleSheet.create({
gap: 6 gap: 6
}, },
betAdjust: { betAdjust: {
width: 32, width: 28,
height: 32, height: 28,
borderRadius: 16, borderRadius: 14,
backgroundColor: 'rgba(255,255,255,0.12)', backgroundColor: 'rgba(255,255,255,0.12)',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
}, },
betAdjustText: { betAdjustText: {
color: colors.text, color: colors.text,
fontSize: 18, fontSize: 16,
fontWeight: '700' fontWeight: '700'
}, },
betAdjustDisabled: { betAdjustDisabled: {
@@ -346,7 +383,7 @@ const styles = StyleSheet.create({
timer: { timer: {
color: colors.goldBright, color: colors.goldBright,
textAlign: 'center', textAlign: 'center',
marginBottom: 6, marginBottom: 4,
fontFamily: fonts.mono fontFamily: fonts.mono
}, },
rotateWrap: { rotateWrap: {