| 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 | };
|