bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
18 lines • 7.83 kB
JSON
{
"name": "social-components-likebutton",
"type": "registry:component",
"dependencies": [
"@radix-ui/react-icons",
"bmap-api-types"
],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/social/components/LikeButton.tsx",
"type": "registry:component",
"content": "import { HeartFilledIcon, HeartIcon } from \"@radix-ui/react-icons\";\n// LikeButton Component - Radix-based like/reaction button\nimport type { LikeInfo } from \"bmap-api-types\";\nimport { useState } from \"react\";\nimport { useBitcoinAuth } from \"../../../hooks/useBitcoinAuth.js\";\nimport { CONTAINER_WIDTHS } from \"../../../lib/layout-constants.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport { Button } from \"../../ui/button.js\";\nimport {\n\tHoverCard,\n\tHoverCardContent,\n\tHoverCardTrigger,\n} from \"../../ui/hover-card.js\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../../ui/popover.js\";\nimport { useFetchLikes } from \"../hooks/useFetchLikes.js\";\nimport { useToggleLike } from \"../hooks/useLikePost.js\";\nimport type { LikeButtonProps } from \"../types/social.js\";\n\n// Common emoji reactions\nconst REACTION_EMOJIS = [\n\t{ emoji: \"❤️\", label: \"Heart\" },\n\t{ emoji: \"😂\", label: \"Laughing\" },\n\t{ emoji: \"😢\", label: \"Crying\" },\n\t{ emoji: \"😡\", label: \"Angry\" },\n\t{ emoji: \"😮\", label: \"Surprised\" },\n\t{ emoji: \"👍\", label: \"Thumbs Up\" },\n\t{ emoji: \"👎\", label: \"Thumbs Down\" },\n\t{ emoji: \"🔥\", label: \"Fire\" },\n];\n\nexport interface LikeButtonComponentProps extends LikeButtonProps {\n\t// Auto-fetch like data or pass explicit data\n\tautoFetch?: boolean;\n\tlikeInfo?: LikeInfo; // Can override auto-fetched data\n\t// Current user's like state\n\tisLiked?: boolean;\n\tcurrentUserEmoji?: string;\n\tenableEmojiPicker?: boolean;\n\tshowTooltip?: boolean;\n}\n\nexport function LikeButton({\n\ttxid,\n\temoji = \"❤️\",\n\tautoFetch = true,\n\tlikeInfo: propLikeInfo,\n\tisLiked = false,\n\tcurrentUserEmoji,\n\tshowCount = true,\n\tenableEmojiPicker = true,\n\tshowTooltip = true,\n\tonLike,\n\tonUnlike: _onUnlike,\n\tclassName,\n\tvariant = \"ghost\",\n\tsize = \"default\",\n\tdisabled,\n\tloading,\n\t...props\n}: LikeButtonComponentProps) {\n\tconst [selectedEmoji, setSelectedEmoji] = useState(emoji);\n\tconst [showEmojiPicker, setShowEmojiPicker] = useState(false);\n\n\tconst { isAuthenticated } = useBitcoinAuth();\n\n\t// Auto-fetch like data if enabled\n\tconst { likeInfo: fetchedLikeInfo, isLoading: fetchingLikes } = useFetchLikes(\n\t\t{\n\t\t\ttxid,\n\t\t\tenabled: autoFetch && !!txid,\n\t\t},\n\t);\n\n\t// Use prop data or fetched data\n\tconst likeInfo = propLikeInfo || fetchedLikeInfo;\n\tconst { toggle, isLoading, error } = useToggleLike({\n\t\tonSuccess: (_result) => {\n\t\t\t// Since we know this is from the like mutation\n\t\t\tonLike?.(txid, selectedEmoji);\n\t\t},\n\t});\n\n\tconst handleQuickLike = () => {\n\t\tif (!isAuthenticated || disabled || isLoading) return;\n\t\ttoggle(txid, selectedEmoji, isLiked);\n\t};\n\n\tconst handleEmojiSelect = (newEmoji: string) => {\n\t\tif (!isAuthenticated || disabled || isLoading) return;\n\t\tsetSelectedEmoji(newEmoji);\n\t\tsetShowEmojiPicker(false);\n\n\t\t// If already liked with same emoji, unlike. Otherwise, like with new emoji\n\t\tconst alreadyLikedWithEmoji = isLiked && selectedEmoji === newEmoji;\n\t\ttoggle(txid, newEmoji, alreadyLikedWithEmoji);\n\t};\n\n\t// Derive data from LikeInfo if available\n\tconst totalLikes = likeInfo?.total || 0;\n\tconst displayCount = totalLikes > 0 ? totalLikes : undefined;\n\tconst buttonColor = isLiked ? \"red\" : \"gray\";\n\tconst isButtonLoading = loading || isLoading || fetchingLikes;\n\n\t// Get reactions for emoji picker\n\tconst reactionCounts = likeInfo?.reactions || {};\n\n\tconst LikeButtonContent = (\n\t\t<Button\n\t\t\tvariant={variant}\n\t\t\tsize={size}\n\t\t\tdisabled={disabled || !isAuthenticated || isButtonLoading}\n\t\t\tclassName={cn(isLiked && \"text-red-500 hover:text-red-600\", className)}\n\t\t\tonClick={handleQuickLike}\n\t\t\t{...props}\n\t\t>\n\t\t\t<div className=\"flex items-center gap-1\">\n\t\t\t\t{isLiked ? (\n\t\t\t\t\t<HeartFilledIcon className=\"h-4 w-4\" />\n\t\t\t\t) : (\n\t\t\t\t\t<HeartIcon className=\"h-4 w-4\" />\n\t\t\t\t)}\n\n\t\t\t\t{showCount && displayCount && (\n\t\t\t\t\t<span className=\"text-sm font-medium\">{displayCount}</span>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</Button>\n\t);\n\n\t// Show simple button if emoji picker is disabled or user is not authenticated\n\tif (!enableEmojiPicker || !isAuthenticated) {\n\t\tif (showTooltip && !isAuthenticated) {\n\t\t\treturn (\n\t\t\t\t<HoverCard>\n\t\t\t\t\t<HoverCardTrigger asChild>{LikeButtonContent}</HoverCardTrigger>\n\t\t\t\t\t<HoverCardContent className=\"w-auto p-2\">\n\t\t\t\t\t\t<p className=\"text-sm\">Sign in to like posts</p>\n\t\t\t\t\t</HoverCardContent>\n\t\t\t\t</HoverCard>\n\t\t\t);\n\t\t}\n\n\t\treturn LikeButtonContent;\n\t}\n\n\treturn (\n\t\t<div className=\"flex items-center gap-1\">\n\t\t\t{/* Quick Like Button */}\n\t\t\t{LikeButtonContent}\n\n\t\t\t{/* Emoji Picker */}\n\t\t\t<Popover open={showEmojiPicker} onOpenChange={setShowEmojiPicker}>\n\t\t\t\t<PopoverTrigger asChild>\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\tdisabled={disabled || !isAuthenticated || isButtonLoading}\n\t\t\t\t\t>\n\t\t\t\t\t\t<span className=\"text-xs\">😊</span>\n\t\t\t\t\t</Button>\n\t\t\t\t</PopoverTrigger>\n\n\t\t\t\t<PopoverContent className=\"w-auto p-3\">\n\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t<p className=\"text-sm font-medium\">React with:</p>\n\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"flex flex-wrap gap-1\"\n\t\t\t\t\t\t\tstyle={{ maxWidth: CONTAINER_WIDTHS.POPOVER_SMALL }}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{REACTION_EMOJIS.map(\n\t\t\t\t\t\t\t\t({ emoji: reactionEmoji, label: _label }) => {\n\t\t\t\t\t\t\t\t\tconst reactionCount =\n\t\t\t\t\t\t\t\t\t\treactionCounts[reactionEmoji]?.length || 0;\n\t\t\t\t\t\t\t\t\tconst isSelected = selectedEmoji === reactionEmoji && isLiked;\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\tkey={reactionEmoji}\n\t\t\t\t\t\t\t\t\t\t\tvariant={isSelected ? \"default\" : \"ghost\"}\n\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => handleEmojiSelect(reactionEmoji)}\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"min-w-[32px] relative\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col items-center gap-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">{reactionEmoji}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t{reactionCount > 0 && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{reactionCount}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{error && (\n\t\t\t\t\t\t\t<p className=\"text-xs text-destructive\">{error.message}</p>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</PopoverContent>\n\t\t\t</Popover>\n\t\t</div>\n\t);\n}\n\n// Simple like button without emoji picker\nexport function SimpleLikeButton(\n\tprops: Omit<LikeButtonComponentProps, \"enableEmojiPicker\">,\n) {\n\treturn <LikeButton {...props} enableEmojiPicker={false} />;\n}\n\n// Heart-only like button (classic social media style)\nexport function HeartButton(\n\tprops: Omit<LikeButtonComponentProps, \"emoji\" | \"enableEmojiPicker\">,\n) {\n\treturn <LikeButton {...props} emoji=\"❤️\" enableEmojiPicker={false} />;\n}\n",
"target": "<%- config.aliases.components %>/likebutton.tsx"
}
]
}