diff --git a/src/components/chat/EmojiPickerDialog.tsx b/src/components/chat/EmojiPickerDialog.tsx index 70348bc..8303899 100644 --- a/src/components/chat/EmojiPickerDialog.tsx +++ b/src/components/chat/EmojiPickerDialog.tsx @@ -7,6 +7,7 @@ import type { EmojiTag } from "@/lib/emoji-helpers"; import { useEmojiSearch } from "@/hooks/useEmojiSearch"; import { useEmojiFrequency } from "@/hooks/useEmojiFrequency"; import { CustomEmoji } from "../nostr/CustomEmoji"; +import { UnicodeEmoji } from "../nostr/UnicodeEmoji"; interface EmojiPickerDialogProps { open: boolean; @@ -173,7 +174,12 @@ export function EmojiPickerDialog({ title={`:${result.shortcode}:`} > {result.source === "unicode" ? ( - {result.url} + ) : ( {reaction.customEmoji ? ( - {`:${reaction.customEmoji.shortcode}:`} ) : ( - - {reaction.emoji} - + )} {item.source === "unicode" ? ( - // Unicode emoji - render as text - {item.url} + // Unicode emoji - render with UnicodeEmoji component + ) : ( - // Custom emoji - render as image - {`:${item.shortcode}:`} { - // Replace with fallback on error - e.currentTarget.style.display = "none"; - }} + // Custom emoji - render using CustomEmoji component + )} diff --git a/src/components/nostr/CustomEmoji.tsx b/src/components/nostr/CustomEmoji.tsx index 6125466..106b949 100644 --- a/src/components/nostr/CustomEmoji.tsx +++ b/src/components/nostr/CustomEmoji.tsx @@ -8,12 +8,15 @@ export interface CustomEmojiProps { /** The image URL */ url: string; /** Size variant */ - size?: "sm" | "md" | "lg"; + size?: "xs" | "sm" | "md" | "lg"; /** Additional class names */ className?: string; + /** Whether to show tooltip on hover (default: true) */ + showTooltip?: boolean; } const sizeClasses = { + xs: "size-3.5", sm: "size-4", md: "size-6", lg: "size-12", @@ -28,6 +31,7 @@ export function CustomEmoji({ url, size = "md", className, + showTooltip = true, }: CustomEmojiProps) { const [error, setError] = useState(false); @@ -46,22 +50,28 @@ export function CustomEmoji({ ); } + const img = ( + {`:${shortcode}:`} setError(true)} + /> + ); + + if (!showTooltip) { + return img; + } + return ( - - {`:${shortcode}:`} setError(true)} - /> - + {img} :{shortcode}: ); diff --git a/src/components/nostr/RichText/Emoji.tsx b/src/components/nostr/RichText/Emoji.tsx index 7c5308e..8ffceee 100644 --- a/src/components/nostr/RichText/Emoji.tsx +++ b/src/components/nostr/RichText/Emoji.tsx @@ -1,3 +1,5 @@ +import { CustomEmoji } from "../CustomEmoji"; + interface EmojiNodeProps { node: { url: string; @@ -7,11 +9,11 @@ interface EmojiNodeProps { export function Emoji({ node }: EmojiNodeProps) { return ( - {`:${node.code}:`} ); } diff --git a/src/components/nostr/UnicodeEmoji.tsx b/src/components/nostr/UnicodeEmoji.tsx new file mode 100644 index 0000000..96e93ac --- /dev/null +++ b/src/components/nostr/UnicodeEmoji.tsx @@ -0,0 +1,63 @@ +import { cn } from "@/lib/utils"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; + +export interface UnicodeEmojiProps { + /** The emoji character */ + emoji: string; + /** The shortcode for tooltip (without colons) */ + shortcode?: string; + /** Size variant - matches CustomEmoji sizes */ + size?: "xs" | "sm" | "md" | "lg"; + /** Additional class names */ + className?: string; + /** Whether to show tooltip on hover (default: true, requires shortcode) */ + showTooltip?: boolean; +} + +/** + * Text size classes that visually match CustomEmoji image sizes + * - xs: size-3.5 (14px) → text-sm (14px) + * - sm: size-4 (16px) → text-base (16px) + * - md: size-6 (24px) → text-2xl (24px) + * - lg: size-12 (48px) → text-5xl (48px) + */ +const sizeClasses = { + xs: "text-sm", + sm: "text-base", + md: "text-2xl", + lg: "text-5xl", +}; + +/** + * Renders a unicode emoji with consistent sizing + * Size variants match CustomEmoji for visual consistency + */ +export function UnicodeEmoji({ + emoji, + shortcode, + size = "md", + className, + showTooltip = true, +}: UnicodeEmojiProps) { + const emojiSpan = ( + + {emoji} + + ); + + // Only show tooltip if shortcode is provided and showTooltip is true + if (!showTooltip || !shortcode) { + return emojiSpan; + } + + return ( + + {emojiSpan} + :{shortcode}: + + ); +} diff --git a/src/components/nostr/compact/ReactionCompactPreview.tsx b/src/components/nostr/compact/ReactionCompactPreview.tsx index fc0faca..8d5f788 100644 --- a/src/components/nostr/compact/ReactionCompactPreview.tsx +++ b/src/components/nostr/compact/ReactionCompactPreview.tsx @@ -5,6 +5,8 @@ import { useNostrEvent } from "@/hooks/useNostrEvent"; import { UserName } from "../UserName"; import { RichText } from "../RichText"; import { EMOJI_SHORTCODE_REGEX } from "@/lib/emoji-helpers"; +import { CustomEmoji } from "../CustomEmoji"; +import { UnicodeEmoji } from "../UnicodeEmoji"; /** * Compact preview for Kind 7 (Reaction) @@ -100,18 +102,18 @@ export function ReactionCompactPreview({ event }: { event: NostrEvent }) { case "😊": return ; default: - return {content}; + return ; } }; return ( {parsedReaction.type === "custom" ? ( - {`:${parsedReaction.shortcode}:`} ) : ( diff --git a/src/components/nostr/kinds/ReactionRenderer.tsx b/src/components/nostr/kinds/ReactionRenderer.tsx index 10a4c3f..b78a1d6 100644 --- a/src/components/nostr/kinds/ReactionRenderer.tsx +++ b/src/components/nostr/kinds/ReactionRenderer.tsx @@ -7,6 +7,8 @@ import { KindRenderer } from "./index"; import { EventCardSkeleton } from "@/components/ui/skeleton"; import { parseReplaceableAddress } from "applesauce-core/helpers/pointers"; import { EMOJI_SHORTCODE_REGEX } from "@/lib/emoji-helpers"; +import { CustomEmoji } from "../CustomEmoji"; +import { UnicodeEmoji } from "../UnicodeEmoji"; /** * Renderer for Kind 7 - Reactions @@ -100,7 +102,7 @@ export function Kind7Renderer({ event }: BaseEventProps) { case "😊": return ; default: - return {content}; + return ; } }; @@ -110,11 +112,10 @@ export function Kind7Renderer({ event }: BaseEventProps) { {/* Reaction indicator */}
{parsedReaction.type === "custom" ? ( - {`:${parsedReaction.shortcode}:`} ) : ( getReactionIcon(parsedReaction.emoji)