frontend: finally add sliders for mood entry
Pedro Lucas Porcellis porcellis@eletrotupi.com · 5 months ago
3dd5c9c336e53556c29b79567dbbc8913667907e Browse filesParents
0329ddcChanged files
2
Additions
+213
Deletions
-1
frontend/app/(tabs)/new/entry.tsx
1 1
import { useState, useEffect } from 'react';
2 2
import {
3 3
Link, router, useLocalSearchParams
4 4
} from 'expo-router';
5 5
19 19
import { Card } from '@/components/ui/Cards';
20 20
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");
32 40
}
33 41
45 53
Muito Bem
46 54
</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}>
53 92
<TextArea
54 93
label="Notas sobre o dia"
94 133
},
95 134
annotationsCard: {
96 135
padding: Spacing.cardGap
97 136
},
98 137
resumeCard: {
99 -
height: 361
138 +
padding: Spacing.cardGap,
100 139
}
101 140
});
/dev/null → frontend/components/ui/ScaleSlider.tsx
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 +
});