frontend: finally add sliders for mood entry
Parents:
0329ddc2 file(s) changed
- frontend/app/(tabs)/new/entry.tsx +40 -1
- frontend/components/ui/ScaleSlider.tsx +173 -0
frontend/app/(tabs)/new/entry.tsx
@@ -21,11 +21,19 @@ import { Button } from '@/components/ui/Button';
21 21 import { Input, TextArea } from '@/components/ui/Input';
22 22 import { Section, SectionHeader } from '@/components/ui/Sections';
23 23 import { Spacing, Typography, Colors } from '@/constants/theme';
24 + import { ScaleSlider } from '@/components/ui/ScaleSlider';
25 + import { Ionicons } from '@expo/vector-icons';
26 + import { useThemeColor } from '@/hooks/use-theme-color';
24 27
25 28 export default function NewMoodEntry() {
26 29 const { user } = useAuth();
30 + const tintColor = useThemeColor({}, 'tint');
31 +
27 32 // TODO: transform this into a specific mood component
28 33 const { initialMood } = useLocalSearchParams();
34 + const [stress, setStress] = useState(0);
35 + const [anxiety, setAnxiety] = useState(0);
36 + const [energy, setEnergy] = useState(0);
29 37
30 38 const editComponents = () => {
31 39 console.log("Wants to open the components");
@@ -47,6 +55,37 @@ </ThemedText>
47 55 </Center>
48 56
49 57 <Card style={styles.resumeCard}>
58 + <Col gap={24}>
59 + <ScaleSlider
60 + variant="rich"
61 + label="Energia"
62 + value={energy}
63 + onValueChange={setEnergy}
64 + icon={<Ionicons name="flower-outline" size={20} color={tintColor} />}
65 + minLabel="Baixa"
66 + maxLabel="Alta"
67 + />
68 +
69 + <ScaleSlider
70 + variant="rich"
71 + label="Ansiedade"
72 + value={anxiety}
73 + onValueChange={setAnxiety}
74 + icon={<Ionicons name="sad-outline" size={20} color="#FB923C" />}
75 + minLabel="Calmo"
76 + maxLabel="Ansioso"
77 + />
78 +
79 + <ScaleSlider
80 + variant="rich"
81 + label="Estresse"
82 + value={stress}
83 + onValueChange={setStress}
84 + icon={<Ionicons name="flame" size={20} color="#f87171" />}
85 + minLabel="Relaxado"
86 + maxLabel="Estressado"
87 + />
88 + </Col>
50 89 </Card>
51 90
52 91 <Card style={styles.annotationsCard}>
@@ -96,6 +135,6 @@ annotationsCard: {
96 135 padding: Spacing.cardGap
97 136 },
98 137 resumeCard: {
99 - height: 361
138 + padding: Spacing.cardGap,
100 139 }
101 140 });
frontend/components/ui/ScaleSlider.tsx
@@ -0,0 +1,173 @@
1 + import React, { useState } from 'react';
2 + import {
3 + View,
4 + Text,
5 + StyleSheet,
6 + ViewStyle,
7 + } from 'react-native';
8 + import Slider from '@react-native-community/slider';
9 + import { useThemeColor } from '@/hooks/use-theme-color';
10 + import { Theme } from '@/constants/theme';
11 +
12 + type SliderVariant = 'rich' | 'compact';
13 +
14 + interface ScaleSliderProps {
15 + label: string;
16 + value: number;
17 + onValueChange: (value: number) => void;
18 + min?: number;
19 + max?: number;
20 + step?: number;
21 + minLabel?: string;
22 + maxLabel?: string;
23 + icon?: React.ReactNode; // rich variant only
24 + showMax?: boolean; // shows "x/10" format instead of just "x"
25 + variant?: SliderVariant;
26 + style?: ViewStyle;
27 + }
28 +
29 + export const ScaleSlider: React.FC<ScaleSliderProps> = ({
30 + label,
31 + value,
32 + onValueChange,
33 + min = 0,
34 + max = 10,
35 + step = 1,
36 + minLabel,
37 + maxLabel,
38 + icon,
39 + showMax,
40 + variant = 'compact',
41 + style,
42 + }) => {
43 + const textColor = useThemeColor({}, 'text');
44 + const secondaryColor = useThemeColor({}, 'textSecondary');
45 + const tintColor = useThemeColor({}, 'tint');
46 + const trackingColor = useThemeColor({}, 'sliderTracking');
47 + const sliderLabelsColor = useThemeColor({}, 'sliderLabels');
48 +
49 + const isRich = variant === 'rich';
50 +
51 + const valueDisplay = showMax || !isRich
52 + ? `${value}/${max}`
53 + : `${value}`;
54 +
55 + return (
56 + <View style={[styles.container, style]}>
57 +
58 + {/* Header row */}
59 + <View style={styles.header}>
60 + <View style={styles.labelRow}>
61 + {isRich && icon && (
62 + <View style={styles.iconWrapper}>{icon}</View>
63 + )}
64 + <Text style={[
65 + isRich ? styles.labelRich : styles.labelCompact,
66 + { color: textColor },
67 + ]}>
68 + {label}
69 + </Text>
70 + </View>
71 +
72 + <Text style={[
73 + isRich ? styles.valueRich : styles.valueCompact,
74 + { color: textColor },
75 + ]}>
76 + {valueDisplay}
77 + </Text>
78 + </View>
79 +
80 + <View style={{ marginRight: -10, marginLeft: -10 }}>
81 + {/* Slider */}
82 + <Slider
83 + style={styles.slider}
84 + minimumValue={min}
85 + maximumValue={max}
86 + step={step}
87 + value={value}
88 + onValueChange={onValueChange}
89 + minimumTrackTintColor={trackingColor}
90 + maximumTrackTintColor={trackingColor}
91 + thumbTintColor={tintColor}
92 + />
93 + </View>
94 +
95 + {/* Min / Max labels */}
96 + {(minLabel || maxLabel) && (
97 + <View style={styles.rangeLabels}>
98 + {minLabel && (
99 + <Text style={[styles.rangeLabel, { color: sliderLabelsColor }]}>
100 + {minLabel.toUpperCase()}
101 + </Text>
102 + )}
103 + {maxLabel && (
104 + <Text style={[styles.rangeLabel, { color: sliderLabelsColor }]}>
105 + {maxLabel.toUpperCase()}
106 + </Text>
107 + )}
108 + </View>
109 + )}
110 +
111 + </View>
112 + );
113 + };
114 +
115 + // ============================================================================
116 + // STYLES
117 + // ============================================================================
118 +
119 + const styles = StyleSheet.create({
120 + container: {
121 + width: '100%',
122 + },
123 + header: {
124 + flexDirection: 'row',
125 + justifyContent: 'space-between',
126 + alignItems: 'center',
127 + marginBottom: 4,
128 + },
129 + labelRow: {
130 + flexDirection: 'row',
131 + alignItems: 'center',
132 + gap: 8,
133 + },
134 + iconWrapper: {
135 + justifyContent: 'center',
136 + alignItems: 'center',
137 + },
138 +
139 + // Rich variant
140 + labelRich: {
141 + fontSize: 14,
142 + fontWeight: 500
143 + },
144 + valueRich: {
145 + fontSize: 18,
146 + lineHeight: 28,
147 + fontWeight: 600
148 + },
149 +
150 + // Compact variant
151 + labelCompact: {
152 + fontSize: Theme.typography.bodyLg.fontSize,
153 + },
154 + valueCompact: {
155 + fontSize: Theme.typography.bodyMd.fontSize,
156 + fontWeight: Theme.typography.bodyMd.fontWeight,
157 + color: secondaryColor,
158 + },
159 +
160 + slider: {
161 + width: '100%',
162 + height: 40,
163 + },
164 + rangeLabels: {
165 + flexDirection: 'row',
166 + justifyContent: 'space-between',
167 + marginTop: -4,
168 + },
169 + rangeLabel: {
170 + ...Theme.typography.labelXs,
171 + fontWeight: 500,
172 + },
173 + });