Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,85 @@ const CustomComponent = () => {
- [x] List (ordered, unordered)
- [x] Horizontal Rule
- [x] Table
- [x] React Components (via `useMarkdownWithComponents`)
- [ ] HTML

Ref: [CommonMark](https://commonmark.org/help/)

> HTML will be treated as plain text. Please refer [issue#290](https://github.com/gmsgowtham/react-native-marked/issues/290) for a potential solution

## Advanced

### Embedding React Components in Markdown

You can embed React components directly in your markdown using JSX-style syntax. This is useful for adding interactive elements like buttons, custom info boxes, or any other React component.

```tsx
import React, { Fragment } from "react";
import { Pressable, ScrollView, Text, View } from "react-native";
import {
ReactComponentRegistryProvider,
useMarkdownWithComponents,
type ReactComponentRegistry,
} from "react-native-marked";

// Define your components
const components: ReactComponentRegistry = {
Button: ({ props }) => (
<Pressable onPress={() => console.log("Pressed!")}>
<Text>{String(props.label ?? "Click me")}</Text>
</Pressable>
),
InfoBox: ({ props, children }) => (
<View style={{ backgroundColor: "#E3F2FD", padding: 16 }}>
{props.title && <Text style={{ fontWeight: "bold" }}>{String(props.title)}</Text>}
<Text>{children}</Text>
</View>
),
};

const markdown = `
# Hello World

Click the button below:

<Button label="Get Started" />

<InfoBox title="Note">
This is an info box with **markdown** content.
</InfoBox>
`;

function MarkdownContent() {
const elements = useMarkdownWithComponents(markdown);
return (
<ScrollView>
{elements.map((element, index) => (
<Fragment key={index}>{element}</Fragment>
))}
</ScrollView>
);
}

export default function App() {
return (
<ReactComponentRegistryProvider components={components}>
<MarkdownContent />
</ReactComponentRegistryProvider>
);
}
```

#### Component Syntax

- **Self-closing:** `<ComponentName prop="value" />`
- **With children:** `<ComponentName>content</ComponentName>`
- **Props:** Supports string (`"value"`), number (`{42}`), and boolean (`{true}`) props

#### Component Registry

Components must be registered via `ReactComponentRegistryProvider`. Unregistered components are automatically removed from the output.

### Using custom components

> Custom components can be used to override elements, i.e. Code Highlighting, Fast Image integration
Expand Down
52 changes: 38 additions & 14 deletions examples/react-native-marked-sample/App.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import React, { type ReactNode } from "react";
import React, { Fragment, type ReactNode } from "react";
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
type TextStyle,
useColorScheme,
View,
} from "react-native";
import Markdown, {
import {
MarkedHooks,
MarkedTokenizer,
Renderer,
type RendererInterface,
type Tokens,
useMarkdown,
} from "react-native-marked";
import { MD_STRING } from "./const";
import ReactComponentsExample from "./ReactComponentsExample";

class CustomTokenizer extends MarkedTokenizer {
codespan(this: MarkedTokenizer, src: string): Tokens.Codespan | undefined {
Expand All @@ -26,7 +30,6 @@ class CustomTokenizer extends MarkedTokenizer {
text: match[1].trim(),
};
}

return super.codespan(src);
}
}
Expand All @@ -47,39 +50,60 @@ const renderer = new CustomRenderer();

class CustomHooks extends MarkedHooks {
emStrongMask(src: string): string {
// mask part of the content that should not be interpreted as Markdown em/strong delimiters.
return src;
}
}

const hooks = new CustomHooks();

function StandardMarkdownSection() {
const elements = useMarkdown(MD_STRING, {
renderer,
tokenizer,
hooks,
});

return (
<>
{elements.map((element, index) => (
<Fragment key={`md_${index}`}>{element as ReactNode}</Fragment>
))}
</>
);
}

export default function App() {
const theme = useColorScheme();
const isLightTheme = theme === "light";

return (
<>
<StatusBar
barStyle={isLightTheme ? "dark-content" : "light-content"}
backgroundColor={isLightTheme ? "#fff" : "#000"}
/>
<SafeAreaView>
<Markdown
value={MD_STRING}
flatListProps={{
contentContainerStyle: styles.container,
}}
renderer={renderer}
tokenizer={tokenizer}
hooks={hooks}
/>
<SafeAreaView style={styles.safeArea}>
<ScrollView contentContainerStyle={styles.container}>
<StandardMarkdownSection />
<View style={styles.divider} />
<ReactComponentsExample />
</ScrollView>
</SafeAreaView>
</>
);
}

const styles = StyleSheet.create({
safeArea: {
flex: 1,
},
container: {
paddingHorizontal: 16,
paddingBottom: 32,
},
divider: {
borderTopColor: "#ccc",
borderTopWidth: 1,
marginVertical: 24,
},
});
90 changes: 90 additions & 0 deletions examples/react-native-marked-sample/ReactComponentsExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, { Fragment, type ReactNode } from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
import {
type ReactComponentRegistry,
ReactComponentRegistryProvider,
useMarkdownWithComponents,
} from "react-native-marked";
import { MARKDOWN_WITH_COMPONENTS } from "./const";

const components: ReactComponentRegistry = {
Button: ({ props }) => (
<Pressable
style={styles.button}
onPress={() => console.log(`Button pressed: ${props.label}`)}
>
<Text style={styles.buttonText}>{String(props.label ?? "Click me")}</Text>
</Pressable>
),
InfoBox: ({ props, children }) => (
<View style={styles.infoBox}>
{props.title && (
<Text style={styles.infoTitle}>{String(props.title)}</Text>
)}
<Text style={styles.infoContent}>{children as ReactNode}</Text>
</View>
),
Highlight: ({ children }) => (
<View style={styles.highlight}>
<Text>{children as ReactNode}</Text>
</View>
),
};

function MarkdownContent() {
const elements = useMarkdownWithComponents(MARKDOWN_WITH_COMPONENTS);

return (
<>
{elements.map((element, index) => (
<Fragment key={`el_${index}`}>{element as ReactNode}</Fragment>
))}
</>
);
}

export default function ReactComponentsExample() {
return (
<ReactComponentRegistryProvider components={components}>
<MarkdownContent />
</ReactComponentRegistryProvider>
);
}

const styles = StyleSheet.create({
button: {
backgroundColor: "#007AFF",
borderRadius: 8,
marginVertical: 8,
paddingHorizontal: 16,
paddingVertical: 12,
},
buttonText: {
color: "#FFFFFF",
fontSize: 16,
fontWeight: "600",
textAlign: "center",
},
infoBox: {
backgroundColor: "#E3F2FD",
borderRadius: 8,
marginVertical: 8,
padding: 16,
},
infoTitle: {
fontSize: 16,
fontWeight: "600",
marginBottom: 4,
},
infoContent: {
fontSize: 14,
},
highlight: {
backgroundColor: "#FFF3E0",
borderLeftColor: "#FF9800",
borderLeftWidth: 4,
marginVertical: 8,
paddingHorizontal: 12,
paddingVertical: 8,
},
});
34 changes: 33 additions & 1 deletion examples/react-native-marked-sample/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,36 @@ With a reference later in the document defining the URL location:
$ latex code $\n\n\` other code\`
`;

export { MD_STRING };
const MARKDOWN_WITH_COMPONENTS = `
# Markdown with React Components

This example shows how to embed **React components** inside markdown.

<Button label="Get Started" />

You can create info boxes with custom content:

<InfoBox title="Did you know?">
Components can contain text and will be rendered inline with the rest of the content.
</InfoBox>

## More Examples

Here's a highlight box:

<Highlight>
This is highlighted content that stands out from the rest.
</Highlight>

And another button:

<Button label="Learn More" />

Regular markdown continues after components.

- List item 1
- List item 2
- List item 3
`;

export { MD_STRING, MARKDOWN_WITH_COMPONENTS };
Loading