UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

18 lines 7.34 kB
{ "name": "social-components-postbutton", "type": "registry:component", "dependencies": [ "@radix-ui/react-icons", "bmap-api-types" ], "devDependencies": [], "registryDependencies": [], "files": [ { "path": "components/social/components/PostButton.tsx", "type": "registry:component", "content": "import { Cross1Icon, PlusIcon } from \"@radix-ui/react-icons\";\n// PostButton Component - Radix-based social post button\nimport type { PostTransaction } from \"bmap-api-types\";\nimport React, { useState } from \"react\";\nimport { useBitcoinAuth } from \"../../../hooks/useBitcoinAuth.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport { Button } from \"../../ui/button.js\";\nimport {\n\tDialog,\n\tDialogClose,\n\tDialogContent,\n\tDialogTitle,\n\tDialogTrigger,\n} from \"../../ui/dialog.js\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"../../ui/select.js\";\nimport { Textarea } from \"../../ui/textarea.js\";\nimport { useFetchPost } from \"../hooks/useFetchPost.js\";\nimport { useSocialPost } from \"../hooks/useSocialPost.js\";\nimport type { SocialButtonProps } from \"../types/social.js\";\n\nexport interface PostButtonProps extends SocialButtonProps {\n\tonPost?: (post: PostTransaction) => void;\n\tdefaultContent?: string;\n}\n\nexport interface PostButtonComponentProps extends PostButtonProps {\n\t// Additional component-specific props\n\ttriggerText?: string;\n\tplaceholder?: string;\n\tmaxLength?: number;\n\tshowContentTypeSelector?: boolean;\n\tenableMarkdown?: boolean;\n}\n\nexport function PostButton({\n\tonPost,\n\tdefaultContent = \"\",\n\tplaceholder = \"What's happening?\",\n\tmaxLength = 10000,\n\ttriggerText = \"New Post\",\n\tshowContentTypeSelector = true,\n\tenableMarkdown = true,\n\tclassName,\n\tvariant = \"default\",\n\tsize = \"default\",\n\tdisabled,\n\t...props\n}: PostButtonComponentProps) {\n\tconst [isOpen, setIsOpen] = useState(false);\n\tconst [content, setContent] = useState(defaultContent);\n\tconst [contentType, setContentType] = useState<\n\t\t\"text/plain\" | \"text/markdown\"\n\t>(\"text/plain\");\n\tconst [isPreview, setIsPreview] = useState(false);\n\tconst [pendingTxid, setPendingTxid] = useState<string | null>(null);\n\n\tconst { isAuthenticated } = useBitcoinAuth();\n\n\t// Fetch the full post after creation\n\tconst { post: fetchedPost } = useFetchPost({\n\t\ttxid: pendingTxid || \"\",\n\t\tenabled: !!pendingTxid,\n\t});\n\n\t// When we get the full post, call the callback\n\tReact.useEffect(() => {\n\t\tif (fetchedPost?.post && onPost) {\n\t\t\tonPost(fetchedPost.post);\n\t\t\tsetPendingTxid(null); // Clear pending state\n\t\t}\n\t}, [fetchedPost, onPost]);\n\n\tconst {\n\t\tmutate: createPost,\n\t\tisLoading,\n\t\terror,\n\t} = useSocialPost({\n\t\tonSuccess: (result) => {\n\t\t\tsetIsOpen(false);\n\t\t\tsetContent(\"\");\n\t\t\tif (result.txid) {\n\t\t\t\tsetPendingTxid(result.txid); // Trigger fetch of full post\n\t\t\t}\n\t\t},\n\t});\n\n\tconst handleSubmit = () => {\n\t\tif (!content.trim()) return;\n\n\t\tcreatePost({\n\t\t\tcontent: content.trim(),\n\t\t\tcontentType,\n\t\t});\n\t};\n\n\tconst remainingChars = maxLength - content.length;\n\tconst isOverLimit = remainingChars < 0;\n\tconst canSubmit = content.trim().length > 0 && !isOverLimit && !isLoading;\n\n\tif (!isAuthenticated) {\n\t\treturn (\n\t\t\t<Button\n\t\t\t\tvariant=\"outline\"\n\t\t\t\tsize={size}\n\t\t\t\tdisabled\n\t\t\t\tclassName={className}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\tSign in to post\n\t\t\t</Button>\n\t\t);\n\t}\n\n\treturn (\n\t\t<Dialog open={isOpen} onOpenChange={setIsOpen}>\n\t\t\t<DialogTrigger asChild>\n\t\t\t\t<Button\n\t\t\t\t\tvariant={variant}\n\t\t\t\t\tsize={size}\n\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\tclassName={className}\n\t\t\t\t\t{...props}\n\t\t\t\t>\n\t\t\t\t\t<PlusIcon className=\"w-4 h-4 mr-1\" />\n\t\t\t\t\t{triggerText}\n\t\t\t\t</Button>\n\t\t\t</DialogTrigger>\n\n\t\t\t<DialogContent className=\"max-w-[520px]\">\n\t\t\t\t<DialogTitle>Create Post</DialogTitle>\n\n\t\t\t\t<div className=\"flex flex-col gap-4 mt-4\">\n\t\t\t\t\t{/* Content Type Selector */}\n\t\t\t\t\t{showContentTypeSelector && enableMarkdown && (\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">Format:</p>\n\t\t\t\t\t\t\t<Select\n\t\t\t\t\t\t\t\tvalue={contentType}\n\t\t\t\t\t\t\t\tonValueChange={(value: \"text/plain\" | \"text/markdown\") =>\n\t\t\t\t\t\t\t\t\tsetContentType(value)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<SelectTrigger className=\"w-40\">\n\t\t\t\t\t\t\t\t\t<SelectValue />\n\t\t\t\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t\t\t<SelectItem value=\"text/plain\">Plain Text</SelectItem>\n\t\t\t\t\t\t\t\t\t<SelectItem value=\"text/markdown\">Markdown</SelectItem>\n\t\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t\t</Select>\n\n\t\t\t\t\t\t\t{contentType === \"text/markdown\" && (\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tvariant={isPreview ? \"default\" : \"ghost\"}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\tonClick={() => setIsPreview(!isPreview)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isPreview ? \"Edit\" : \"Preview\"}\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\n\t\t\t\t\t{/* Content Input */}\n\t\t\t\t\t{!isPreview ? (\n\t\t\t\t\t\t<Textarea\n\t\t\t\t\t\t\tplaceholder={placeholder}\n\t\t\t\t\t\t\tvalue={content}\n\t\t\t\t\t\t\tonChange={(e) => setContent(e.target.value)}\n\t\t\t\t\t\t\trows={6}\n\t\t\t\t\t\t\tclassName=\"min-h-[120px] text-base leading-relaxed resize-y\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<div className=\"min-h-[120px] p-3 border rounded-md bg-muted text-base leading-relaxed\">\n\t\t\t\t\t\t\t{/* Simple markdown preview - in real implementation, use a markdown renderer */}\n\t\t\t\t\t\t\t<p>{content || \"Nothing to preview...\"}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\n\t\t\t\t\t{/* Character Counter */}\n\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t<p\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\"text-sm\",\n\t\t\t\t\t\t\t\tisOverLimit\n\t\t\t\t\t\t\t\t\t? \"text-red-600\"\n\t\t\t\t\t\t\t\t\t: remainingChars < 50\n\t\t\t\t\t\t\t\t\t\t? \"text-orange-600\"\n\t\t\t\t\t\t\t\t\t\t: \"text-muted-foreground\",\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{remainingChars} characters remaining\n\t\t\t\t\t\t</p>\n\n\t\t\t\t\t\t{error && <p className=\"text-sm text-red-600\">{error.message}</p>}\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Action Buttons */}\n\t\t\t\t\t<div className=\"flex gap-2 justify-end\">\n\t\t\t\t\t\t<DialogClose asChild>\n\t\t\t\t\t\t\t<Button variant=\"secondary\">Cancel</Button>\n\t\t\t\t\t\t</DialogClose>\n\n\t\t\t\t\t\t<Button onClick={handleSubmit} disabled={!canSubmit || isLoading}>\n\t\t\t\t\t\t\t{isLoading ? \"Posting...\" : \"Post\"}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\n\t\t\t\t<DialogClose className=\"absolute top-4 right-4\">\n\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" className=\"h-8 w-8 p-0\">\n\t\t\t\t\t\t<Cross1Icon className=\"h-4 w-4\" />\n\t\t\t\t\t</Button>\n\t\t\t\t</DialogClose>\n\t\t\t</DialogContent>\n\t\t</Dialog>\n\t);\n}\n", "target": "<%- config.aliases.components %>/postbutton.tsx" } ] }