tangled
alpha
login
or
join now
stream.place
/
streamplace
77
fork
atom
Live video on the AT Protocol
77
fork
atom
overview
issues
1
pulls
pipelines
add skin tone
Natalie B.
2 weeks ago
402e8ca2
48cdb8ee
+154
-63
1 changed file
expand all
collapse all
unified
split
js
app
components
emoji-picker
emoji-picker.web.tsx
+154
-63
js/app/components/emoji-picker/emoji-picker.web.tsx
···
1
1
-
import { Text, useTheme } from "@streamplace/components";
2
2
-
import { EmojiPicker as FrimousseEmojiPicker } from "frimousse";
3
3
-
import { useEffect, useMemo, useRef } from "react";
1
1
+
import { Text, useAQState, useTheme } from "@streamplace/components";
2
2
+
import {
3
3
+
EmojiPicker as FrimousseEmojiPicker,
4
4
+
SkinTone,
5
5
+
useSkinTone,
6
6
+
} from "frimousse";
7
7
+
import { ChevronUp } from "lucide-react-native";
8
8
+
import React, { useEffect, useMemo, useRef, useState } from "react";
4
9
import { View } from "react-native";
5
10
import { useEmojiData } from "utils/emoji";
6
11
···
21
26
alt?: string;
22
27
}
23
28
29
29
+
interface SkinTonePickerOpen {
30
30
+
open: boolean;
31
31
+
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
32
32
+
}
33
33
+
34
34
+
export function SkinToneTray({ open, setOpen }: SkinTonePickerOpen) {
35
35
+
const [aqSkinTone, setAQSkinTone] = useAQState(
36
36
+
"emoji-picker-tone",
37
37
+
"none" as SkinTone,
38
38
+
);
39
39
+
const [skinTone, setSkinTone, skinToneVariations] = useSkinTone("👋");
40
40
+
41
41
+
useEffect(() => {
42
42
+
setSkinTone(aqSkinTone);
43
43
+
}, [aqSkinTone, setSkinTone]);
44
44
+
45
45
+
const handleSelect = (tone: SkinTone) => {
46
46
+
setAQSkinTone(tone);
47
47
+
setOpen(false);
48
48
+
};
49
49
+
50
50
+
return (
51
51
+
<div
52
52
+
style={{
53
53
+
overflow: "hidden",
54
54
+
maxHeight: open ? 48 : 0,
55
55
+
opacity: open ? 1 : 0,
56
56
+
transition: "max-height 0.2s ease, opacity 0.15s ease",
57
57
+
display: "flex",
58
58
+
gap: 4,
59
59
+
padding: open ? "6px 8px" : "0 8px",
60
60
+
borderTop: open ? "1px solid rgba(255,255,255,0.07)" : "none",
61
61
+
}}
62
62
+
>
63
63
+
{skinToneVariations.map((variation) => (
64
64
+
<button
65
65
+
key={variation.skinTone}
66
66
+
onClick={() => handleSelect(variation.skinTone)}
67
67
+
style={{
68
68
+
width: 30,
69
69
+
height: 30,
70
70
+
borderRadius: 6,
71
71
+
border:
72
72
+
skinTone === variation.skinTone
73
73
+
? "1px solid rgba(255,255,255,0.35)"
74
74
+
: "1px solid rgba(255,255,255,0.08)",
75
75
+
background:
76
76
+
skinTone === variation.skinTone
77
77
+
? "rgba(255,255,255,0.15)"
78
78
+
: "transparent",
79
79
+
cursor: "pointer",
80
80
+
fontSize: 18,
81
81
+
display: "flex",
82
82
+
alignItems: "center",
83
83
+
justifyContent: "center",
84
84
+
}}
85
85
+
>
86
86
+
{variation.emoji}
87
87
+
</button>
88
88
+
))}
89
89
+
</div>
90
90
+
);
91
91
+
}
92
92
+
93
93
+
export function SkinToneTrigger({ open, setOpen }: SkinTonePickerOpen) {
94
94
+
const [aqSkinTone] = useAQState("emoji-picker-tone", "none" as SkinTone);
95
95
+
const [, , skinToneVariations] = useSkinTone("👋");
96
96
+
const current =
97
97
+
skinToneVariations.find((v) => v.skinTone === aqSkinTone) ??
98
98
+
skinToneVariations[0];
99
99
+
100
100
+
return (
101
101
+
<button
102
102
+
onClick={() => setOpen((o) => !o)}
103
103
+
title="Skin tone"
104
104
+
style={{
105
105
+
display: "flex",
106
106
+
alignItems: "center",
107
107
+
gap: 4,
108
108
+
padding: "2px 4px",
109
109
+
border: "1px solid rgba(255,255,255,0.08)",
110
110
+
borderRadius: 6,
111
111
+
background: open ? "rgba(255,255,255,0.1)" : "transparent",
112
112
+
cursor: "pointer",
113
113
+
fontSize: 18,
114
114
+
flexShrink: 0,
115
115
+
}}
116
116
+
>
117
117
+
<span>{current?.emoji}</span>
118
118
+
<span
119
119
+
style={{
120
120
+
fontSize: 9,
121
121
+
transform: open ? "rotate(180deg)" : "rotate(0deg)",
122
122
+
display: "inline-block",
123
123
+
transition: "transform 0.2s ease",
124
124
+
color: "grey",
125
125
+
}}
126
126
+
>
127
127
+
<ChevronUp />
128
128
+
</span>
129
129
+
</button>
130
130
+
);
131
131
+
}
132
132
+
24
133
export function EmojiPicker({
25
134
isOpen,
26
135
onClose,
···
29
138
}: EmojiPickerProps) {
30
139
const { theme } = useTheme();
31
140
const emojiData = useEmojiData();
141
141
+
const [skinToneOpen, setSkinToneOpen] = useState(false);
32
142
33
143
const nativeToId = useMemo(() => {
34
144
if (!emojiData) return null;
···
47
157
if (e.key === "Escape") onClose();
48
158
};
49
159
const handlePointerDown = (e: PointerEvent) => {
50
50
-
console.log("got click");
51
160
if (containerRef.current && !containerRef.current.contains(e.target)) {
52
161
onClose();
53
162
}
···
61
170
}, [isOpen, onClose]);
62
171
63
172
if (!isOpen) return null;
64
64
-
65
65
-
console.log(
66
66
-
"[EmojiPicker.web] rendering, onSelect:",
67
67
-
!!onSelect,
68
68
-
"customEmoji:",
69
69
-
customEmoji.length,
70
70
-
);
71
71
-
72
173
const handleStandardSelect = (arg: any) => {
73
73
-
console.log(
74
74
-
"[EmojiPicker.web] handleStandardSelect raw arg:",
75
75
-
arg,
76
76
-
"onSelect:",
77
77
-
!!onSelect,
78
78
-
);
79
174
onSelect?.({ type: "standard", native: arg.emoji ?? arg });
80
175
};
81
176
···
293
388
<FrimousseEmojiPicker.ActiveEmoji>
294
389
{({ emoji }) => {
295
390
return (
296
296
-
<div
297
297
-
style={{
298
298
-
padding: "6px 20px",
299
299
-
borderTop: `1px solid ${theme.colors.border}`,
300
300
-
}}
301
301
-
>
302
302
-
{emoji ? (
303
303
-
<div
304
304
-
style={{
305
305
-
display: "flex",
306
306
-
flexDirection: "row",
307
307
-
alignItems: "center",
308
308
-
gap: 8,
309
309
-
height: 46,
310
310
-
}}
311
311
-
>
312
312
-
<Text size="4xl">{emoji.emoji}</Text>
313
313
-
<div
314
314
-
style={{
315
315
-
display: "flex",
316
316
-
flexDirection: "column",
317
317
-
gap: -2,
318
318
-
}}
319
319
-
>
320
320
-
<Text size="sm">{emoji.label}</Text>
321
321
-
{nativeToId?.get(emoji.emoji) && (
322
322
-
<Text color="muted" size="xs">
323
323
-
:{nativeToId.get(emoji.emoji)}:
324
324
-
</Text>
325
325
-
)}
326
326
-
</div>
327
327
-
</div>
328
328
-
) : (
329
329
-
<div
330
330
-
style={{
331
331
-
display: "flex",
332
332
-
flexDirection: "row",
333
333
-
alignItems: "center",
334
334
-
gap: 8,
335
335
-
height: 46,
336
336
-
}}
337
337
-
>
338
338
-
<Text color="muted" size="sm">
391
391
+
<div>
392
392
+
<SkinToneTray open={skinToneOpen} setOpen={setSkinToneOpen} />
393
393
+
<div
394
394
+
style={{
395
395
+
padding: "6px 10px 6px 20px",
396
396
+
borderTop: `1px solid ${theme.colors.border}`,
397
397
+
display: "flex",
398
398
+
flexDirection: "row",
399
399
+
alignItems: "center",
400
400
+
gap: 8,
401
401
+
height: 46,
402
402
+
}}
403
403
+
>
404
404
+
{emoji ? (
405
405
+
<>
406
406
+
<Text size="4xl">{emoji.emoji}</Text>
407
407
+
<div
408
408
+
style={{
409
409
+
display: "flex",
410
410
+
flexDirection: "column",
411
411
+
gap: -2,
412
412
+
flex: 1,
413
413
+
minWidth: 0,
414
414
+
}}
415
415
+
>
416
416
+
<Text size="sm">{emoji.label}</Text>
417
417
+
{nativeToId?.get(emoji.emoji) && (
418
418
+
<Text color="muted" size="xs">
419
419
+
:{nativeToId.get(emoji.emoji)}:
420
420
+
</Text>
421
421
+
)}
422
422
+
</div>
423
423
+
</>
424
424
+
) : (
425
425
+
<Text color="muted" size="sm" style={{ flex: 1 }}>
339
426
Select an emoji...
340
427
</Text>
341
341
-
</div>
342
342
-
)}
428
428
+
)}
429
429
+
<SkinToneTrigger
430
430
+
open={skinToneOpen}
431
431
+
setOpen={setSkinToneOpen}
432
432
+
/>
433
433
+
</div>
343
434
</div>
344
435
);
345
436
}}