// MarkdownView - Main component for rendering markdown content
import React, { useMemo, useCallback } from 'react'
import { View, Text } from 'react-native'
import type { ViewStyle } from 'react-native'
import { parseMarkdown } from './parser'
import { useMarkdownTheme } from './context/ThemeContext'
import { getRenderer } from './renderers'
import type { MarkdownNode, ParserOptions } from './types/ast'
import type { MarkdownTheme } from './types/theme'
import type {
OnLinkPress,
OnImagePress,
OnCheckboxToggle,
CustomRenderers,
RenderContext,
} from './types/renderers'
/**
* Props for the MarkdownView component
*/
export interface MarkdownViewProps {
/** Markdown content to render */
content: string
/** Parser options */
parserOptions?: ParserOptions
/** Theme override (uses context theme if not provided) */
theme?: MarkdownTheme
/** Container style */
style?: ViewStyle
/** Link press handler */
onLinkPress?: OnLinkPress
/** Image press handler */
onImagePress?: OnImagePress
/** Checkbox toggle handler for task lists */
onCheckboxToggle?: OnCheckboxToggle
/** Show error message on parse failure */
showErrors?: boolean
/** Pre-parsed AST (skips parsing if provided) */
ast?: MarkdownNode[]
/** Custom renderers to override defaults */
customRenderers?: CustomRenderers
}
/**
* MarkdownView - Renders markdown content as React Native components
*/
export function MarkdownView({
content,
parserOptions,
theme: themeProp,
style,
onLinkPress,
onImagePress,
onCheckboxToggle,
showErrors = false,
ast: astProp,
customRenderers,
}: MarkdownViewProps): React.JSX.Element {
// Get theme from context or props
const contextTheme = useMarkdownTheme()
const theme = themeProp ?? contextTheme
// Parse markdown content
const parseResult = useMemo(() => {
if (astProp) {
return { success: true, nodes: astProp }
}
return parseMarkdown(content, parserOptions)
}, [content, parserOptions, astProp])
// Create memoized renderer getter
const getEffectiveRenderer = useMemo(() => {
return (nodeType: string) =>
customRenderers?.[nodeType] ?? getRenderer(nodeType)
}, [customRenderers])
// Recursive node renderer
const renderNode = useCallback(
(node: MarkdownNode, key: string | number): React.ReactNode => {
const renderer = getEffectiveRenderer(node.type)
const ctx: RenderContext = {
theme,
onLinkPress,
onImagePress,
onCheckboxToggle,
renderNode,
}
return renderer(node, key, ctx)
},
[theme, onLinkPress, onImagePress, onCheckboxToggle, getEffectiveRenderer]
)
// Handle parse error
if (!parseResult.success) {
if (showErrors) {
return (
Error parsing markdown:{' '}
{parseResult.error?.message ?? 'Unknown error'}
)
}
return
}
// Render AST
return (
{parseResult.nodes.map((node, index) => {
// For document nodes, render children directly
if (node.type === 'document' && node.children) {
return node.children.map((child, childIndex) =>
renderNode(child, `doc-${index}-${childIndex}`)
)
}
return renderNode(node, `node-${index}`)
})}
)
}