({
emoji,
icon,
isClicked,
onClick,
className,
suppressFlyUp,
}: {
emoji?: string;
icon?: string;
isClicked: boolean;
onClick: () => void;
className?: string;
suppressFlyUp?: boolean;
})
| 5 | import { useLayoutEffect, useRef, useState } from "react"; |
| 6 | |
| 7 | export const EmojiButton = ({ |
| 8 | emoji, |
| 9 | icon, |
| 10 | isClicked, |
| 11 | onClick, |
| 12 | className, |
| 13 | suppressFlyUp, |
| 14 | }: { |
| 15 | emoji?: string; |
| 16 | icon?: string; |
| 17 | isClicked: boolean; |
| 18 | onClick: () => void; |
| 19 | className?: string; |
| 20 | suppressFlyUp?: boolean; |
| 21 | }) => { |
| 22 | const [showFloating, setShowFloating] = useState(false); |
| 23 | const prevClickedRef = useRef(isClicked); |
| 24 | |
| 25 | useLayoutEffect(() => { |
| 26 | if (isClicked && !prevClickedRef.current && !suppressFlyUp) { |
| 27 | setShowFloating(true); |
| 28 | setTimeout(() => setShowFloating(false), 600); |
| 29 | } |
| 30 | prevClickedRef.current = isClicked; |
| 31 | }, [isClicked, suppressFlyUp]); |
| 32 | |
| 33 | const content = icon ? <i className={makeIconClass(icon, false)} /> : emoji; |
| 34 | |
| 35 | return ( |
| 36 | <div className="relative inline-block"> |
| 37 | <button |
| 38 | onClick={onClick} |
| 39 | className={cn( |
| 40 | "px-2 py-1 rounded border cursor-pointer transition-colors", |
| 41 | isClicked |
| 42 | ? "bg-accent/20 border-accent text-accent" |
| 43 | : "bg-transparent border-border/50 text-foreground/70 hover:border-border", |
| 44 | className |
| 45 | )} |
| 46 | > |
| 47 | {content} |
| 48 | </button> |
| 49 | {showFloating && ( |
| 50 | <span |
| 51 | className="absolute pointer-events-none animate-[float-up_0.6s_ease-out_forwards]" |
| 52 | style={{ |
| 53 | left: "50%", |
| 54 | bottom: "100%", |
| 55 | }} |
| 56 | > |
| 57 | {content} |
| 58 | </span> |
| 59 | )} |
| 60 | </div> |
| 61 | ); |
| 62 | }; |
nothing calls this directly
no test coverage detected