/ components / CustomColorPicker.tsx
CustomColorPicker.tsx
  1  import React, { useState, useEffect } from 'react';
  2  import {
  3    View,
  4    Text,
  5    TouchableOpacity,
  6    StyleSheet,
  7    Modal,
  8    FlatList,
  9    Platform,
 10    SafeAreaView,
 11  } from 'react-native';
 12  import { Ionicons } from '@expo/vector-icons';
 13  
 14  // Expanded color palette to ensure even rows
 15  const COLORS = [
 16    {
 17      category: 'Greens',
 18      items: [
 19        { color: '#2E8B57', name: 'Sea Green' },
 20        { color: '#3CB371', name: 'Medium Sea Green' },
 21        { color: '#00A36C', name: 'Jade' },
 22        { color: '#2AAA8A', name: 'Green Slate' },
 23        { color: '#4CBB17', name: 'Kelly Green' },
 24        { color: '#008000', name: 'Green' },
 25        { color: '#006400', name: 'Dark Green' },
 26        { color: '#32CD32', name: 'Lime Green' },
 27      ],
 28    },
 29    {
 30      category: 'Blues',
 31      items: [
 32        { color: '#4682B4', name: 'Steel Blue' },
 33        { color: '#6495ED', name: 'Cornflower Blue' },
 34        { color: '#1E90FF', name: 'Dodger Blue' },
 35        { color: '#00BFFF', name: 'Deep Sky Blue' },
 36        { color: '#0047AB', name: 'Cobalt Blue' },
 37        { color: '#000080', name: 'Navy' },
 38        { color: '#0000CD', name: 'Medium Blue' },
 39        { color: '#5D8AA8', name: 'Air Force Blue' },
 40      ],
 41    },
 42    {
 43      category: 'Purples',
 44      items: [
 45        { color: '#9370DB', name: 'Medium Purple' },
 46        { color: '#8A2BE2', name: 'Blue Violet' },
 47        { color: '#9932CC', name: 'Dark Orchid' },
 48        { color: '#BA55D3', name: 'Medium Orchid' },
 49        { color: '#6A0DAD', name: 'Purple' },
 50        { color: '#800080', name: 'Deep Purple' },
 51        { color: '#483D8B', name: 'Dark Slate Blue' },
 52        { color: '#DA70D6', name: 'Orchid' },
 53      ],
 54    },
 55    {
 56      category: 'Reds',
 57      items: [
 58        { color: '#FF6B6B', name: 'Light Red' },
 59        { color: '#FF4500', name: 'Orange Red' },
 60        { color: '#FF1493', name: 'Deep Pink' },
 61        { color: '#C71585', name: 'Medium Violet Red' },
 62        { color: '#DC143C', name: 'Crimson' },
 63        { color: '#B22222', name: 'Firebrick' },
 64        { color: '#CD5C5C', name: 'Indian Red' },
 65        { color: '#8B0000', name: 'Dark Red' },
 66      ],
 67    },
 68    {
 69      category: 'Yellows & Oranges',
 70      items: [
 71        { color: '#FFD700', name: 'Gold' },
 72        { color: '#FFA500', name: 'Orange' },
 73        { color: '#FF8C00', name: 'Dark Orange' },
 74        { color: '#FF7F50', name: 'Coral' },
 75        { color: '#F4C430', name: 'Saffron' },
 76        { color: '#DAA520', name: 'Goldenrod' },
 77        { color: '#FFFF00', name: 'Yellow' },
 78        { color: '#BDB76B', name: 'Dark Khaki' },
 79      ],
 80    },
 81  ];
 82  
 83  interface CustomColorPickerProps {
 84    visible: boolean;
 85    onClose: () => void;
 86    onColorSelected: (color: string) => void;
 87    initialColor: string;
 88    colors: any;
 89  }
 90  
 91  const CustomColorPicker: React.FC<CustomColorPickerProps> = ({
 92    visible,
 93    onClose,
 94    onColorSelected,
 95    initialColor,
 96    colors,
 97  }) => {
 98    const [selectedColor, setSelectedColor] = useState(initialColor);
 99  
100    // Fixed number of columns regardless of screen size
101  
102    useEffect(() => {
103      if (visible) setSelectedColor(initialColor);
104    }, [visible, initialColor]);
105  
106    const getContrastText = (color: string) => {
107      const hex = color.replace('#', '');
108      const r = parseInt(hex.substr(0, 2), 16);
109      const g = parseInt(hex.substr(2, 2), 16);
110      const b = parseInt(hex.substr(4, 2), 16);
111      return (r * 299 + g * 587 + b * 114) / 1000 >= 128 ? '#000' : '#fff';
112    };
113  
114    const selectedColorName =
115      COLORS.flatMap((cat) => cat.items).find(
116        (item) => item.color === selectedColor
117      )?.name || 'Custom';
118  
119    if (!visible) return null;
120  
121    return (
122      <Modal
123        visible={visible}
124        transparent={true}
125        animationType="fade"
126        onRequestClose={onClose}
127        statusBarTranslucent
128      >
129        <SafeAreaView style={styles.container}>
130          <View style={[styles.modal, { backgroundColor: colors.card }]}>
131            <View style={[styles.header, { borderBottomColor: colors.border }]}>
132              <Text style={[styles.title, { color: colors.text }]}>
133                Choose Color
134              </Text>
135              <TouchableOpacity
136                style={[styles.closeBtn, { backgroundColor: colors.background }]}
137                onPress={onClose}
138              >
139                <Ionicons name="close" size={20} color={colors.text} />
140              </TouchableOpacity>
141            </View>
142  
143            <FlatList
144              data={COLORS}
145              keyExtractor={(item) => item.category}
146              renderItem={({ item: category }) => (
147                <View style={styles.categorySection}>
148                  <Text style={[styles.categoryTitle, { color: colors.text }]}>
149                    {category.category}
150                  </Text>
151                  <View style={styles.colorGrid}>
152                    {category.items.map((colorItem) => (
153                      <TouchableOpacity
154                        key={colorItem.color}
155                        style={styles.colorItem}
156                        onPress={() => setSelectedColor(colorItem.color)}
157                        activeOpacity={0.7}
158                      >
159                        <View
160                          style={[
161                            styles.colorSwatch,
162                            { backgroundColor: colorItem.color },
163                            selectedColor === colorItem.color && [
164                              styles.selectedSwatch,
165                              { borderColor: colors.primary },
166                            ],
167                          ]}
168                        >
169                          {selectedColor === colorItem.color && (
170                            <Ionicons name="checkmark" size={22} color="#fff" />
171                          )}
172                        </View>
173                        <Text
174                          style={[
175                            styles.colorName,
176                            { color: colors.secondaryText },
177                          ]}
178                          numberOfLines={1}
179                        >
180                          {colorItem.name}
181                        </Text>
182                      </TouchableOpacity>
183                    ))}
184                  </View>
185                </View>
186              )}
187              style={styles.list}
188              showsVerticalScrollIndicator={true}
189            />
190  
191            <View
192              style={[
193                styles.preview,
194                {
195                  borderTopColor: colors.border,
196                  backgroundColor: colors.background + '40',
197                },
198              ]}
199            >
200              <View
201                style={[
202                  styles.previewSwatch,
203                  {
204                    backgroundColor: selectedColor,
205                    borderColor: colors.border,
206                  },
207                ]}
208              />
209              <View style={styles.previewInfo}>
210                <Text style={[styles.previewName, { color: colors.text }]}>
211                  {selectedColorName}
212                </Text>
213                <Text
214                  style={[styles.previewHex, { color: colors.secondaryText }]}
215                >
216                  {selectedColor.toUpperCase()}
217                </Text>
218              </View>
219            </View>
220  
221            <View style={[styles.actions, { borderTopColor: colors.border }]}>
222              <TouchableOpacity
223                style={[
224                  styles.button,
225                  styles.cancelButton,
226                  { backgroundColor: colors.background },
227                ]}
228                onPress={onClose}
229              >
230                <Text style={[styles.buttonText, { color: colors.text }]}>
231                  Cancel
232                </Text>
233              </TouchableOpacity>
234              <TouchableOpacity
235                style={[styles.button, { backgroundColor: selectedColor }]}
236                onPress={() => {
237                  onColorSelected(selectedColor);
238                  onClose();
239                }}
240              >
241                <Text
242                  style={[
243                    styles.buttonText,
244                    { color: getContrastText(selectedColor) },
245                  ]}
246                >
247                  Apply
248                </Text>
249              </TouchableOpacity>
250            </View>
251          </View>
252        </SafeAreaView>
253      </Modal>
254    );
255  };
256  
257  const styles = StyleSheet.create({
258    container: {
259      flex: 1,
260      backgroundColor: 'rgba(0,0,0,0.5)',
261      justifyContent: 'center',
262      alignItems: 'center',
263      padding: 16,
264    },
265    modal: {
266      width: '94%',
267      maxWidth: 500,
268      maxHeight: '80%',
269      borderRadius: 16,
270      overflow: 'hidden',
271      shadowColor: '#000',
272      shadowOffset: { width: 0, height: 4 },
273      shadowOpacity: 0.2,
274      shadowRadius: 8,
275      elevation: 8,
276    },
277    header: {
278      flexDirection: 'row',
279      justifyContent: 'space-between',
280      alignItems: 'center',
281      padding: 16,
282      borderBottomWidth: 1,
283    },
284    title: {
285      fontSize: 18,
286      fontWeight: '600',
287    },
288    closeBtn: {
289      width: 32,
290      height: 32,
291      borderRadius: 16,
292      alignItems: 'center',
293      justifyContent: 'center',
294    },
295    list: {
296      flexGrow: 0,
297      maxHeight: 400,
298    },
299    categorySection: {
300      marginBottom: 16,
301      paddingHorizontal: 16,
302    },
303    categoryTitle: {
304      fontSize: 16,
305      fontWeight: '600',
306      marginVertical: 8,
307    },
308    colorGrid: {
309      flexDirection: 'row',
310      flexWrap: 'wrap',
311    },
312    colorItem: {
313      width: '25%', // Fixed width for 4 columns
314      alignItems: 'center',
315      marginBottom: 12,
316      paddingHorizontal: 4,
317    },
318    colorSwatch: {
319      width: 60,
320      height: 60,
321      borderRadius: Platform.OS === 'ios' ? 12 : 30,
322      alignItems: 'center',
323      justifyContent: 'center',
324      borderWidth: 2,
325      borderColor: 'transparent',
326      marginBottom: 4,
327    },
328    selectedSwatch: {
329      transform: [{ scale: 1.05 }],
330    },
331    colorName: {
332      fontSize: 11,
333      textAlign: 'center',
334      width: '100%',
335    },
336    preview: {
337      padding: 16,
338      flexDirection: 'row',
339      alignItems: 'center',
340      borderTopWidth: 1,
341    },
342    previewSwatch: {
343      width: 48,
344      height: 48,
345      borderRadius: 24,
346      marginRight: 16,
347      borderWidth: 1.5,
348    },
349    previewInfo: {
350      flex: 1,
351    },
352    previewName: {
353      fontSize: 16,
354      fontWeight: '600',
355    },
356    previewHex: {
357      fontSize: 14,
358      marginTop: 2,
359    },
360    actions: {
361      flexDirection: 'row',
362      padding: 16,
363      borderTopWidth: 1,
364    },
365    button: {
366      flex: 1,
367      height: 44,
368      borderRadius: 10,
369      alignItems: 'center',
370      justifyContent: 'center',
371    },
372    cancelButton: {
373      marginRight: 8,
374    },
375    buttonText: {
376      fontSize: 16,
377      fontWeight: '600',
378    },
379  });
380  
381  export default CustomColorPicker;