Live video on the AT Protocol

add skin tone

+154 -63
+154 -63
js/app/components/emoji-picker/emoji-picker.web.tsx
··· 1 - import { Text, useTheme } from "@streamplace/components"; 2 - import { EmojiPicker as FrimousseEmojiPicker } from "frimousse"; 3 - import { useEffect, useMemo, useRef } from "react"; 1 + import { Text, useAQState, useTheme } from "@streamplace/components"; 2 + import { 3 + EmojiPicker as FrimousseEmojiPicker, 4 + SkinTone, 5 + useSkinTone, 6 + } from "frimousse"; 7 + import { ChevronUp } from "lucide-react-native"; 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 + interface SkinTonePickerOpen { 30 + open: boolean; 31 + setOpen: React.Dispatch<React.SetStateAction<boolean>>; 32 + } 33 + 34 + export function SkinToneTray({ open, setOpen }: SkinTonePickerOpen) { 35 + const [aqSkinTone, setAQSkinTone] = useAQState( 36 + "emoji-picker-tone", 37 + "none" as SkinTone, 38 + ); 39 + const [skinTone, setSkinTone, skinToneVariations] = useSkinTone("👋"); 40 + 41 + useEffect(() => { 42 + setSkinTone(aqSkinTone); 43 + }, [aqSkinTone, setSkinTone]); 44 + 45 + const handleSelect = (tone: SkinTone) => { 46 + setAQSkinTone(tone); 47 + setOpen(false); 48 + }; 49 + 50 + return ( 51 + <div 52 + style={{ 53 + overflow: "hidden", 54 + maxHeight: open ? 48 : 0, 55 + opacity: open ? 1 : 0, 56 + transition: "max-height 0.2s ease, opacity 0.15s ease", 57 + display: "flex", 58 + gap: 4, 59 + padding: open ? "6px 8px" : "0 8px", 60 + borderTop: open ? "1px solid rgba(255,255,255,0.07)" : "none", 61 + }} 62 + > 63 + {skinToneVariations.map((variation) => ( 64 + <button 65 + key={variation.skinTone} 66 + onClick={() => handleSelect(variation.skinTone)} 67 + style={{ 68 + width: 30, 69 + height: 30, 70 + borderRadius: 6, 71 + border: 72 + skinTone === variation.skinTone 73 + ? "1px solid rgba(255,255,255,0.35)" 74 + : "1px solid rgba(255,255,255,0.08)", 75 + background: 76 + skinTone === variation.skinTone 77 + ? "rgba(255,255,255,0.15)" 78 + : "transparent", 79 + cursor: "pointer", 80 + fontSize: 18, 81 + display: "flex", 82 + alignItems: "center", 83 + justifyContent: "center", 84 + }} 85 + > 86 + {variation.emoji} 87 + </button> 88 + ))} 89 + </div> 90 + ); 91 + } 92 + 93 + export function SkinToneTrigger({ open, setOpen }: SkinTonePickerOpen) { 94 + const [aqSkinTone] = useAQState("emoji-picker-tone", "none" as SkinTone); 95 + const [, , skinToneVariations] = useSkinTone("👋"); 96 + const current = 97 + skinToneVariations.find((v) => v.skinTone === aqSkinTone) ?? 98 + skinToneVariations[0]; 99 + 100 + return ( 101 + <button 102 + onClick={() => setOpen((o) => !o)} 103 + title="Skin tone" 104 + style={{ 105 + display: "flex", 106 + alignItems: "center", 107 + gap: 4, 108 + padding: "2px 4px", 109 + border: "1px solid rgba(255,255,255,0.08)", 110 + borderRadius: 6, 111 + background: open ? "rgba(255,255,255,0.1)" : "transparent", 112 + cursor: "pointer", 113 + fontSize: 18, 114 + flexShrink: 0, 115 + }} 116 + > 117 + <span>{current?.emoji}</span> 118 + <span 119 + style={{ 120 + fontSize: 9, 121 + transform: open ? "rotate(180deg)" : "rotate(0deg)", 122 + display: "inline-block", 123 + transition: "transform 0.2s ease", 124 + color: "grey", 125 + }} 126 + > 127 + <ChevronUp /> 128 + </span> 129 + </button> 130 + ); 131 + } 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 + 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 - 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 - 65 - console.log( 66 - "[EmojiPicker.web] rendering, onSelect:", 67 - !!onSelect, 68 - "customEmoji:", 69 - customEmoji.length, 70 - ); 71 - 72 173 const handleStandardSelect = (arg: any) => { 73 - console.log( 74 - "[EmojiPicker.web] handleStandardSelect raw arg:", 75 - arg, 76 - "onSelect:", 77 - !!onSelect, 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 - <div 297 - style={{ 298 - padding: "6px 20px", 299 - borderTop: `1px solid ${theme.colors.border}`, 300 - }} 301 - > 302 - {emoji ? ( 303 - <div 304 - style={{ 305 - display: "flex", 306 - flexDirection: "row", 307 - alignItems: "center", 308 - gap: 8, 309 - height: 46, 310 - }} 311 - > 312 - <Text size="4xl">{emoji.emoji}</Text> 313 - <div 314 - style={{ 315 - display: "flex", 316 - flexDirection: "column", 317 - gap: -2, 318 - }} 319 - > 320 - <Text size="sm">{emoji.label}</Text> 321 - {nativeToId?.get(emoji.emoji) && ( 322 - <Text color="muted" size="xs"> 323 - :{nativeToId.get(emoji.emoji)}: 324 - </Text> 325 - )} 326 - </div> 327 - </div> 328 - ) : ( 329 - <div 330 - style={{ 331 - display: "flex", 332 - flexDirection: "row", 333 - alignItems: "center", 334 - gap: 8, 335 - height: 46, 336 - }} 337 - > 338 - <Text color="muted" size="sm"> 391 + <div> 392 + <SkinToneTray open={skinToneOpen} setOpen={setSkinToneOpen} /> 393 + <div 394 + style={{ 395 + padding: "6px 10px 6px 20px", 396 + borderTop: `1px solid ${theme.colors.border}`, 397 + display: "flex", 398 + flexDirection: "row", 399 + alignItems: "center", 400 + gap: 8, 401 + height: 46, 402 + }} 403 + > 404 + {emoji ? ( 405 + <> 406 + <Text size="4xl">{emoji.emoji}</Text> 407 + <div 408 + style={{ 409 + display: "flex", 410 + flexDirection: "column", 411 + gap: -2, 412 + flex: 1, 413 + minWidth: 0, 414 + }} 415 + > 416 + <Text size="sm">{emoji.label}</Text> 417 + {nativeToId?.get(emoji.emoji) && ( 418 + <Text color="muted" size="xs"> 419 + :{nativeToId.get(emoji.emoji)}: 420 + </Text> 421 + )} 422 + </div> 423 + </> 424 + ) : ( 425 + <Text color="muted" size="sm" style={{ flex: 1 }}> 339 426 Select an emoji... 340 427 </Text> 341 - </div> 342 - )} 428 + )} 429 + <SkinToneTrigger 430 + open={skinToneOpen} 431 + setOpen={setSkinToneOpen} 432 + /> 433 + </div> 343 434 </div> 344 435 ); 345 436 }}