{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "code-block", "type": "registry:ui", "title": "Code Block", "description": "Code container with copy button and language badge.", "registryDependencies": [ "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/utils.json", "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/theme.json" ], "dependencies": [ "lucide-react-native" ], "files": [ { "path": "registry/shared/ui/code-block.tsx", "type": "registry:ui", "target": "components/ui/code-block.tsx", "content": "import { useEffect, useRef, useState } from 'react';\nimport {\n Pressable,\n ScrollView,\n Text,\n View,\n type ViewProps,\n} from 'react-native';\nimport { Check, Copy } from 'lucide-react-native';\nimport { cn, useIconColor } from '@/lib/utils';\n\n// Optional peer: expo-clipboard gives native copy support.\n// Expo: npx expo install expo-clipboard\ninterface ExpoClipboardModule {\n setStringAsync?: (s: string) => Promise;\n}\nlet expoClipboard: ExpoClipboardModule | null = null;\ntry {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n expoClipboard = require('expo-clipboard');\n} catch {\n // expo-clipboard not installed — fall back to navigator.clipboard / RN core\n}\n\ninterface WebNavigatorClipboard {\n clipboard?: { writeText: (s: string) => Promise };\n}\n\nfunction copyToClipboard(text: string): void {\n if (expoClipboard?.setStringAsync) {\n void expoClipboard.setStringAsync(text);\n return;\n }\n const nav: WebNavigatorClipboard | undefined =\n typeof globalThis === 'object' && 'navigator' in globalThis\n ? (globalThis.navigator as WebNavigatorClipboard) // RN web shim; shape checked by property access below\n : undefined;\n if (nav?.clipboard) {\n void nav.clipboard.writeText(text).catch(() => {});\n return;\n }\n try {\n // Deprecated RN core Clipboard — last resort.\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('react-native').Clipboard?.setString(text);\n } catch {\n // No clipboard implementation available.\n }\n}\n\nexport interface CodeBlockProps extends ViewProps {\n /** Source text to render. */\n code: string;\n /** Cosmetic language badge shown in the header. */\n language?: string;\n /** Header title (e.g. file name). */\n title?: string;\n /** Show the copy button. */\n showCopyButton?: boolean;\n /** Cap the scrollable code area. */\n maxHeight?: number;\n /** Called after a copy attempt. */\n onCopy?: () => void;\n className?: string;\n /** Extra classes for the code text. */\n codeClassName?: string;\n}\n\n/**\n * Monospace code container with a copy button and optional title bar.\n * `language` is purely cosmetic — no syntax highlighting.\n */\nexport function CodeBlock({\n code,\n language,\n title,\n showCopyButton = true,\n maxHeight,\n onCopy,\n className,\n codeClassName,\n ...props\n}: CodeBlockProps) {\n const [copied, setCopied] = useState(false);\n const timer = useRef(undefined);\n const iconColor = useIconColor('muted');\n\n useEffect(() => () => clearTimeout(timer.current), []);\n\n const handleCopy = () => {\n if (!code) return;\n copyToClipboard(code);\n setCopied(true);\n onCopy?.();\n clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), 2000);\n };\n\n const copyButton = showCopyButton ? (\n \n {copied ? (\n \n ) : (\n \n )}\n {copied ? (\n Copied!\n ) : null}\n \n ) : null;\n\n const hasHeader = !!title || !!language;\n\n return (\n \n {hasHeader ? (\n \n {title ? (\n \n {title}\n \n ) : (\n \n )}\n {language ? (\n \n {language}\n \n ) : null}\n {copyButton}\n \n ) : null}\n \n \n \n {code}\n \n \n \n {!hasHeader && copyButton ? (\n {copyButton}\n ) : null}\n \n );\n}\n" } ] }