Live video on the AT Protocol

redo the color picker thing

+160 -99
+160 -99
js/app/components/name-color-picker/name-color-picker.tsx
··· 1 - import { Button } from "@streamplace/components"; 1 + import { Button, zero } from "@streamplace/components"; 2 2 import { 3 3 createChatProfileRecord, 4 4 getChatProfileRecordFromPDS, 5 5 selectChatProfile, 6 6 selectUserProfile, 7 7 } from "features/bluesky/blueskySlice"; 8 - import { SwatchBook } from "lucide-react-native"; 8 + import { Palette, SwatchBook, X } from "lucide-react-native"; 9 9 import { useEffect, useState } from "react"; 10 10 import { 11 11 Keyboard, 12 12 Modal, 13 13 Platform, 14 + Pressable, 14 15 Text, 15 16 TouchableOpacity, 16 17 View, ··· 26 27 27 28 /** 28 29 * Parses an RGB color string and returns an object with red, green, and blue values 29 - * @param rgbString - RGB color string in the format "rgb(r,g,b)" or "rgba(r,g,b,a)" 30 - * @returns An object containing red, green, and blue values as numbers 31 30 */ 32 31 function parseRgbString(rgbString: string): PlaceStreamChatProfile.Color { 33 - // Check if the string is empty or not in the expected format 34 32 if ( 35 33 !rgbString || 36 34 (!rgbString.startsWith("rgb(") && !rgbString.startsWith("rgba(")) 37 35 ) { 38 36 throw new Error("Invalid color string (not rgb or rgba)"); 39 37 } 40 - // Extract the numbers from the string 38 + 41 39 const numbersString = rgbString.replace(/^rgba?\(|\)$/g, ""); 42 40 const parts = numbersString.split(","); 43 41 44 - // Make sure we have at least 3 parts for r, g, b 45 42 if (parts.length < 3) { 46 43 throw new Error("Invalid color string (not enough parts)"); 47 44 } ··· 55 52 56 53 export default function NameColorPicker({ 57 54 children, 58 - text, 55 + text: textProp, 59 56 buttonProps, 60 57 }: { 61 58 children?: React.ReactNode; 62 59 text?: (color: string) => React.ReactNode; 63 60 buttonProps?: any; 64 61 }) { 65 - const [open, setOpen] = useState(false); 62 + const [modalVisible, setModalVisible] = useState(false); 63 + const [tempColor, setTempColor] = useState("#bd6e86"); 66 64 const dispatch = useAppDispatch(); 67 65 const chatProfile = useAppSelector(selectChatProfile); 68 - const [color, setColor] = useState("#bd6e86"); 69 66 const profile = useAppSelector(selectUserProfile); 70 67 const isWeb = Platform.OS === "web"; 71 68 69 + const currentColor = chatProfile?.profile?.color 70 + ? `rgb(${chatProfile.profile.color.red}, ${chatProfile.profile.color.green}, ${chatProfile.profile.color.blue})` 71 + : "#bd6e86"; 72 + 72 73 useEffect(() => { 73 74 if (profile?.did && !chatProfile?.profile) { 74 75 dispatch(getChatProfileRecordFromPDS()); 75 76 } 76 - if (chatProfile?.profile && chatProfile?.profile.color) { 77 - const { red, green, blue } = chatProfile.profile.color; 78 - setColor(`rgb(${red}, ${green}, ${blue})`); 77 + setTempColor(currentColor); 78 + }, [profile?.did, chatProfile?.profile?.color, currentColor]); 79 + 80 + const handleOpenModal = () => { 81 + if (!isWeb) { 82 + Keyboard.dismiss(); 79 83 } 80 - }, [profile?.did, chatProfile?.profile?.color]); 84 + setTempColor(currentColor); 85 + setModalVisible(true); 86 + }; 87 + 88 + const handleCloseModal = () => { 89 + setModalVisible(false); 90 + setTempColor(currentColor); // Reset to current color on cancel 91 + }; 92 + 93 + const handleSaveColor = () => { 94 + setModalVisible(false); 95 + dispatch(createChatProfileRecord(parseRgbString(tempColor))); 96 + }; 81 97 82 98 return ( 83 - <View style={[{ alignItems: "center" }, { flexDirection: "row" }]}> 99 + <View style={[zero.layout.flex.alignCenter, zero.layout.flex.row]}> 84 100 <Button 85 101 variant="secondary" 86 - leftIcon={<SwatchBook color={color} />} 102 + leftIcon={<SwatchBook color={currentColor} />} 87 103 style={[buttonProps?.style]} 88 - onPress={() => { 89 - if (!isWeb) { 90 - Keyboard.dismiss(); 91 - } 92 - setOpen(true); 93 - }} 104 + onPress={handleOpenModal} 94 105 {...buttonProps} 95 106 > 96 - <Text style={[{ color, fontWeight: "600" }]}> 97 - {text ? text(color) : "Change Name Color"} 107 + <Text style={[{ color: currentColor, fontWeight: "600" }]}> 108 + {textProp ? textProp(currentColor) : "Change Name Color"} 98 109 </Text> 99 110 </Button> 100 111 101 112 <Modal 102 - visible={open} 113 + visible={modalVisible} 103 114 transparent={true} 104 - animationType="slide" 105 - onRequestClose={() => { 106 - setOpen(false); 107 - dispatch(getChatProfileRecordFromPDS()); 108 - }} 115 + animationType="fade" 116 + onRequestClose={handleCloseModal} 109 117 > 110 118 <View 111 119 style={[ 120 + zero.layout.flex[1], 121 + zero.layout.flex.center, 122 + zero.layout.flex.alignCenter, 123 + zero.layout.flex.justifyCenter, 112 124 { 113 - flex: 1, 114 - backgroundColor: "rgba(0, 0, 0, 0.5)", 115 - justifyContent: "center", 116 - alignItems: "center", 125 + backgroundColor: "rgba(0, 0, 0, 0.6)", 126 + position: "absolute", 127 + top: 0, 128 + left: 0, 129 + right: 0, 130 + bottom: 0, 131 + width: "100%", 132 + height: "100%", 117 133 }, 118 134 ]} 119 135 > 120 - <View 136 + <Pressable 121 137 style={[ 122 - { 123 - backgroundColor: "#1a1a1a", 124 - borderRadius: 16, 125 - padding: 16, 126 - paddingBottom: 300, 127 - gap: 20, 128 - maxWidth: 600, 129 - width: "90%", 130 - alignItems: "stretch", 131 - justifyContent: "center", 132 - }, 138 + zero.bg.gray[900], 139 + zero.r.xl, 140 + zero.p[6], 141 + { width: 420, maxWidth: "90%", maxHeight: "85%" }, 133 142 ]} 143 + onPress={(e) => e.stopPropagation()} 134 144 > 135 - <TouchableOpacity 145 + {/* Header */} 146 + <View 136 147 style={[ 137 - { 138 - position: "absolute", 139 - top: 0, 140 - right: 0, 141 - marginRight: -15, 142 - marginTop: 5, 143 - backgroundColor: "transparent", 144 - padding: 8, 145 - zIndex: 1, 146 - }, 148 + zero.layout.flex.row, 149 + zero.layout.flex.spaceBetween, 150 + zero.layout.flex.alignCenter, 151 + zero.mb[5], 147 152 ]} 148 - onPress={(e) => { 149 - e.stopPropagation(); 150 - setOpen(false); 151 - }} 152 153 > 153 - <Text style={[{ fontSize: 24, color: "#fff" }]}>×</Text> 154 - </TouchableOpacity> 154 + <View 155 + style={[ 156 + zero.layout.flex.row, 157 + zero.layout.flex.alignCenter, 158 + zero.gap.all[3], 159 + ]} 160 + > 161 + <Palette color={tempColor} size={20} /> 162 + <Text style={[{ color: tempColor, fontWeight: "bold" }]}> 163 + Choose Color 164 + </Text> 165 + </View> 166 + <TouchableOpacity 167 + style={[zero.p[1]]} 168 + onPress={handleCloseModal} 169 + hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} 170 + > 171 + <X color="#888" size={20} /> 172 + </TouchableOpacity> 173 + </View> 155 174 156 - <Text 157 - style={[ 158 - { 159 - fontSize: 24, 160 - fontWeight: "bold", 161 - textAlign: "center", 162 - color, 163 - }, 164 - ]} 165 - > 166 - @{profile?.handle} 167 - </Text> 175 + {/* User Preview */} 176 + {profile?.handle && ( 177 + <View 178 + style={[ 179 + zero.bg.gray[800], 180 + zero.r.md, 181 + zero.p[3], 182 + zero.mb[5], 183 + zero.layout.flex.alignCenter, 184 + ]} 185 + > 186 + <Text style={[{ color: tempColor, fontWeight: "600" }]}> 187 + @{profile.handle} 188 + </Text> 189 + <Text 190 + style={[ 191 + zero.text.gray[400], 192 + { textTransform: "uppercase", letterSpacing: 1 }, 193 + ]} 194 + > 195 + Preview 196 + </Text> 197 + </View> 198 + )} 168 199 169 - <ColorPicker value={color} onCompleteJS={(x) => setColor(x.rgb)}> 170 - <Preview /> 171 - <Panel1 /> 172 - <HueSlider /> 173 - <Swatches style={{ margin: 10 }} /> 174 - </ColorPicker> 200 + {/* Color Picker */} 201 + <View style={[zero.mb[5]]}> 202 + <ColorPicker 203 + value={tempColor} 204 + onCompleteJS={(result) => setTempColor(result.rgb)} 205 + > 206 + <View style={[zero.mb[3]]}> 207 + <Preview style={[zero.r.md]} /> 208 + </View> 209 + <View style={[zero.mb[3]]}> 210 + <Panel1 style={[zero.r.md]} /> 211 + </View> 212 + <View style={[zero.mb[3]]}> 213 + <HueSlider style={[zero.r.sm]} /> 214 + </View> 215 + <View style={[zero.mb[3]]}> 216 + <Swatches style={[zero.r.sm]} /> 217 + </View> 218 + </ColorPicker> 219 + </View> 175 220 176 - <TouchableOpacity 177 - style={[ 178 - { 179 - backgroundColor: "#bd6e86", 180 - borderRadius: 8, 181 - paddingHorizontal: 16, 182 - paddingVertical: 12, 183 - alignItems: "center", 184 - }, 185 - ]} 186 - onPress={() => { 187 - setOpen(false); 188 - dispatch(createChatProfileRecord(parseRgbString(color))); 189 - }} 190 - > 191 - <Text style={[{ color: "#fff", fontWeight: "600" }]}>Save</Text> 192 - </TouchableOpacity> 193 - </View> 221 + {/* Actions */} 222 + <View style={[zero.layout.flex.row, zero.gap.all[3]]}> 223 + <TouchableOpacity 224 + style={[ 225 + zero.layout.flex[1], 226 + zero.bg.gray[700], 227 + zero.r.md, 228 + zero.p[3], 229 + zero.layout.flex.center, 230 + ]} 231 + onPress={handleCloseModal} 232 + > 233 + <Text style={[zero.text.white, { fontWeight: "600" }]}> 234 + Cancel 235 + </Text> 236 + </TouchableOpacity> 237 + <TouchableOpacity 238 + style={[ 239 + zero.layout.flex[1], 240 + zero.r.md, 241 + zero.p[3], 242 + zero.layout.flex.center, 243 + { backgroundColor: tempColor }, 244 + ]} 245 + onPress={handleSaveColor} 246 + > 247 + <Text style={[zero.text.white, { fontWeight: "600" }]}> 248 + Save Color 249 + </Text> 250 + </TouchableOpacity> 251 + </View> 252 + </Pressable> 194 253 </View> 195 254 </Modal> 255 + 256 + {children} 196 257 </View> 197 258 ); 198 259 }