frontend/components: add emoji selectors

Pedro Lucas Porcellis porcellis@eletrotupi.com 1 month ago 6f7c341bccb04f7d79cf57fb8c20a5dbe7756f3f
Parents: ddfbd4e
1 file(s) changed
  • frontend/components/ui/EmojiSelectors.tsx +124 -0
frontend/components/ui/EmojiSelectors.tsx
@@ -0,0 +1,124 @@
1 + import React from 'react';
2 + import {
3 + View,
4 + Pressable,
5 + StyleSheet,
6 + Image,
7 + Text,
8 + ImageSourcePropType,
9 + ViewStyle,
10 + } from 'react-native';
11 + import { Spacing, Typography, BorderRadius } from '@/constants/theme';
12 + import { useThemeColor } from '@/hooks/use-theme-color';
13 +
14 + type IconSource = string | ImageSourcePropType; // either string or a svg
15 +
16 + // Specialized variant with labels below
17 + interface MoodSelectorProps {
18 + items: Array<{
19 + id: string;
20 + icon: IconSource;
21 + label: string;
22 + }>;
23 + value?: string;
24 + onSelect: (id: string) => void;
25 + disabled?: boolean;
26 + style?: ViewStyle;
27 + }
28 +
29 + export const MoodSelector: React.FC<MoodSelectorProps> = ({
30 + items,
31 + value,
32 + onSelect,
33 + disabled = false,
34 + style,
35 + }) => {
36 + const accentBlueColor = useThemeColor({}, 'accentBlue');
37 + const tintColor = useThemeColor({}, 'tint');
38 + const surfaceColor = useThemeColor({}, 'surface');
39 + const dividerColor = useThemeColor({}, 'divider');
40 + const textSecondaryColor = useThemeColor({}, 'textSecondary');
41 +
42 + const styles = StyleSheet.create({
43 + container: {
44 + flexDirection: 'row',
45 + gap: Spacing.inlineGapSm,
46 + justifyContent: 'space-around',
47 + alignItems: 'flex-start',
48 + flexWrap: 'wrap',
49 + },
50 + itemWrapper: {
51 + alignItems: 'center',
52 + gap: 8,
53 + flex: 1,
54 + minWidth: '8%',
55 + },
56 + itemButton: {
57 + width: 50,
58 + height: 50,
59 + borderRadius: BorderRadius.lg,
60 + justifyContent: 'center',
61 + alignItems: 'center',
62 + backgroundColor: surfaceColor,
63 + borderWidth: 1,
64 + borderColor: dividerColor,
65 + },
66 + itemButtonActive: {
67 + backgroundColor: accentBlueColor,
68 + borderColor: 'transparent',
69 + },
70 + icon: {
71 + fontSize: 48,
72 + lineHeight: 48,
73 + },
74 + emojiText: {
75 + fontSize: 36,
76 + lineHeight: 40,
77 + },
78 + label: {
79 + fontSize: Typography.labelSm.fontSize,
80 + fontWeight: '500',
81 + color: textSecondaryColor,
82 + textAlign: 'center',
83 + },
84 + });
85 +
86 + const isEmoji = (icon: IconSource): icon is string => typeof icon === 'string';
87 +
88 + return (
89 + <View style={[styles.container, style]}>
90 + {items.map((item) => {
91 + const isSelected = value === item.id;
92 +
93 + return (
94 + <View key={item.id} style={styles.itemWrapper}>
95 + <Pressable
96 + onPress={() => !disabled && onSelect(item.id)}
97 + disabled={disabled}
98 + style={({ pressed }) => [
99 + styles.itemButton,
100 + isSelected && styles.itemButtonActive,
101 + pressed && !disabled && { opacity: 0.7 },
102 + disabled && { opacity: 0.5 },
103 + ]}
104 + >
105 + {isEmoji(item.icon) ? (
106 + <Text style={styles.emojiText}>{item.icon}</Text>
107 + ) : (
108 + <Image
109 + source={item.icon}
110 + style={{
111 + width: 48,
112 + height: 48,
113 + }}
114 + resizeMode="contain"
115 + />
116 + )}
117 + </Pressable>
118 + <Text style={styles.label}>{item.label}</Text>
119 + </View>
120 + );
121 + })}
122 + </View>
123 + );
124 + };