import { useMemo, type ReactNode } from "react"; import { Text, View, type StyleProp, type TextStyle, type ViewStyle } from "react-native"; import Markdown, { type ASTNode } from "react-native-markdown-display"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { createMarkdownStyles } from "@/styles/markdown-styles"; import { getMarkdownListMarker } from "@/utils/markdown-list"; type MarkdownRuleStyles = Record; function MarkdownInlineText({ textKey, inheritedStyle, ruleStyle, children, }: { textKey: string; inheritedStyle: StyleProp; ruleStyle: StyleProp; children: ReactNode; }) { const style = useMemo(() => [inheritedStyle, ruleStyle], [inheritedStyle, ruleStyle]); return ( {children} ); } function MarkdownListItemContent({ contentStyle, children, }: { contentStyle: StyleProp; children: ReactNode; }) { const style = useMemo(() => [contentStyle, LIST_ITEM_CONTENT_INNER], [contentStyle]); return {children}; } function MarkdownParagraph({ textKey, paragraphStyle, isLastChild, children, }: { textKey: string; paragraphStyle: StyleProp; isLastChild: boolean; children: ReactNode; }) { const style = useMemo>( () => [paragraphStyle, isLastChild ? PARAGRAPH_LAST_CHILD : null], [paragraphStyle, isLastChild], ); return ( {children} ); } function createPlanMarkdownRules() { return { text: ( node: ASTNode, _children: ReactNode[], _parent: ASTNode[], styles: MarkdownRuleStyles, inheritedStyles: TextStyle = {}, ) => ( {node.content} ), textgroup: ( node: ASTNode, children: ReactNode[], _parent: ASTNode[], styles: MarkdownRuleStyles, inheritedStyles: TextStyle = {}, ) => ( {children} ), code_block: ( node: ASTNode, _children: ReactNode[], _parent: ASTNode[], styles: MarkdownRuleStyles, inheritedStyles: TextStyle = {}, ) => ( {node.content} ), fence: ( node: ASTNode, _children: ReactNode[], _parent: ASTNode[], styles: MarkdownRuleStyles, inheritedStyles: TextStyle = {}, ) => ( {node.content} ), code_inline: ( node: ASTNode, _children: ReactNode[], _parent: ASTNode[], styles: MarkdownRuleStyles, inheritedStyles: TextStyle = {}, ) => ( {node.content} ), bullet_list: ( node: ASTNode, children: ReactNode[], _parent: ASTNode[], styles: MarkdownRuleStyles, ) => ( {children} ), ordered_list: ( node: ASTNode, children: ReactNode[], _parent: ASTNode[], styles: MarkdownRuleStyles, ) => ( {children} ), list_item: ( node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownRuleStyles, ) => { const { isOrdered, marker } = getMarkdownListMarker(node, parent); const iconStyle = isOrdered ? styles.ordered_list_icon : styles.bullet_list_icon; const contentStyle = isOrdered ? styles.ordered_list_content : styles.bullet_list_content; return ( {marker} {children} ); }, paragraph: ( node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownRuleStyles, ) => { const isLastChild = parent[0]?.children?.at(-1)?.key === node.key; return ( {children} ); }, }; } export function PlanCard({ title = "Plan", description, text, footer, disableOuterSpacing = false, testID, }: { title?: string; description?: string; text: string; footer?: ReactNode; disableOuterSpacing?: boolean; testID?: string; }) { const { theme } = useUnistyles(); const markdownStyles = createMarkdownStyles(theme); const markdownRules = createPlanMarkdownRules(); const containerStyle = useMemo( () => [ styles.container, disableOuterSpacing && styles.containerCompact, { backgroundColor: theme.colors.surface1, borderColor: theme.colors.border, }, ], [disableOuterSpacing, theme.colors.surface1, theme.colors.border], ); const titleStyle = useMemo( () => [styles.title, { color: theme.colors.foreground }], [theme.colors.foreground], ); const descriptionStyle = useMemo( () => [styles.description, { color: theme.colors.foregroundMuted }], [theme.colors.foregroundMuted], ); return ( {title} {description ? {description} : null} {text} {footer ? {footer} : null} ); } const styles = StyleSheet.create((theme) => ({ container: { marginVertical: theme.spacing[3], padding: theme.spacing[3], borderRadius: theme.spacing[2], borderWidth: 1, gap: theme.spacing[2], }, containerCompact: { marginVertical: 0, }, title: { fontSize: theme.fontSize.base, lineHeight: 22, }, description: { fontSize: theme.fontSize.sm, lineHeight: 20, }, footer: { gap: theme.spacing[2], }, })); const LIST_ITEM_CONTENT_INNER = { flex: 1, flexShrink: 1, minWidth: 0 }; const PARAGRAPH_LAST_CHILD = { marginBottom: 0 };