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