Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
1,548 changes: 1,162 additions & 386 deletions apps/example/ios/Podfile.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion apps/example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
},
"dependencies": {
"@react-native/new-app-screen": "0.84.1",
"@react-navigation/native": "^7.2.2",
"@react-navigation/native-stack": "^7.14.12",
"react": "19.2.3",
"react-native": "0.84.1",
"react-native-safe-area-context": "^5.5.2"
"react-native-safe-area-context": "^5.5.2",
"react-native-screens": "^4.24.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
Expand Down
184 changes: 48 additions & 136 deletions apps/example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,142 +1,54 @@
import { useState, useMemo } from 'react';
import {
StyleSheet,
ScrollView,
Alert,
Linking,
Platform,
View,
Text,
} from 'react-native';
import {
EnrichedMarkdownText,
type LinkPressEvent,
} from 'react-native-enriched-markdown';
import { SafeAreaView } from 'react-native-safe-area-context';
import { sampleMarkdown } from './sampleMarkdown';
import { customMarkdownStyle } from './markdownStyles';
import InputScreen from './InputScreen';
import StreamingMarkdownSimulator from './StreamingMarkdownSimulator';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';
import PlaygroundScreen from './screens/PlaygroundScreen';
import TextScreen from './screens/TextScreen';
import InputScreen from './screens/InputScreen';
import StreamingMarkdownSimulator from './screens/StreamingMarkdownSimulator';

type Screen = 'text' | 'input' | 'stream';
const Stack = createNativeStackNavigator();

export default function App() {
const [screen, setScreen] = useState<Screen>('input');
const markdownStyle = useMemo(() => customMarkdownStyle, []);

const contextMenuItems = useMemo(
() => [
{
text: 'Summarize with AI',
icon: Platform.OS === 'ios' ? 'sparkles' : undefined,
onPress: ({ text }: { text: string }) => {
Alert.alert('✦ Summarize with AI', `"${text}"`, [
{ text: 'Dismiss', style: 'cancel' },
]);
},
},
{
text: 'Translate',
icon: Platform.OS === 'ios' ? 'globe' : undefined,
onPress: ({ text }: { text: string }) => {
Alert.alert('Translate', `"${text}"`, [
{ text: 'Dismiss', style: 'cancel' },
]);
},
},
],
[]
);

const handleLinkPress = (event: LinkPressEvent) => {
const { url } = event;
Alert.alert('Link Pressed!', `You tapped on: ${url}`, [
{
text: 'Open in Browser',
onPress: () => {
Linking.openURL(url);
},
},
{
text: 'Cancel',
style: 'cancel',
},
]);
};

return (
<SafeAreaView style={styles.root}>
<View style={styles.tabs}>
<Text
style={[styles.tab, screen === 'input' && styles.tabActive]}
onPress={() => setScreen('input')}
>
Input
</Text>
<Text
style={[styles.tab, screen === 'text' && styles.tabActive]}
onPress={() => setScreen('text')}
>
Text
</Text>
<Text
style={[styles.tab, screen === 'stream' && styles.tabActive]}
onPress={() => setScreen('stream')}
>
Stream
</Text>
</View>

{screen === 'input' ? (
<InputScreen />
) : screen === 'stream' ? (
<StreamingMarkdownSimulator />
) : (
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.content}
>
<EnrichedMarkdownText
flavor="github"
markdown={sampleMarkdown}
onLinkPress={handleLinkPress}
markdownStyle={markdownStyle}
contextMenuItems={contextMenuItems}
selectionColor={Platform.OS === 'ios' ? '#5A52FA' : '#DCDDFE'}
selectionHandleColor="#5A52FA"
/>
</ScrollView>
)}
</SafeAreaView>
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: {
backgroundColor: '#007AFF',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Enriched Markdown Examples' }}
/>
<Stack.Screen
name="Playground"
component={PlaygroundScreen}
options={{ title: 'Playground' }}
/>
<Stack.Screen
name="Text"
component={TextScreen}
options={{ title: 'Text' }}
/>
<Stack.Screen
name="Input"
component={InputScreen}
options={{ title: 'Input' }}
/>
<Stack.Screen
name="Stream"
component={StreamingMarkdownSimulator}
options={{ title: 'Stream' }}
/>
</Stack.Navigator>
</NavigationContainer>
);
}

const styles = StyleSheet.create({
root: {
flex: 1,
},
tabs: {
flexDirection: 'row',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#D1D5DB',
},
tab: {
flex: 1,
textAlign: 'center',
paddingVertical: 12,
fontSize: 15,
fontWeight: '500',
color: '#6B7280',
},
tabActive: {
color: '#2563EB',
borderBottomWidth: 2,
borderBottomColor: '#2563EB',
},
scrollView: {
paddingHorizontal: 16,
},
content: {
paddingVertical: 16,
},
});
106 changes: 106 additions & 0 deletions apps/example/src/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import type { NavigationProp } from '@react-navigation/native';

type Props = {
navigation: NavigationProp<any>;
Comment thread
eszlamczyk marked this conversation as resolved.
Outdated
};

export default function HomeScreen({ navigation }: Props) {
return (
<View style={styles.container} testID="home-screen">
<Text style={styles.title}>Enriched Markdown Examples</Text>
<Text style={styles.subtitle}>
Explore different markdown rendering and input capabilities
</Text>

<TouchableOpacity
style={[styles.button, styles.playgroundButton]}
onPress={() => navigation.navigate('Playground')}
testID="home-block-playground"
>
<Text style={styles.buttonText}>Playground</Text>
<Text style={styles.buttonSubtext}>live editor with preview</Text>
</TouchableOpacity>
Comment thread
eszlamczyk marked this conversation as resolved.
Outdated

<TouchableOpacity
style={[styles.button, styles.textButton]}
onPress={() => navigation.navigate('Text')}
testID="home-block-text"
>
<Text style={styles.buttonText}>Text</Text>
<Text style={styles.buttonSubtext}>static markdown rendering</Text>
</TouchableOpacity>

<TouchableOpacity
style={[styles.button, styles.inputButton]}
onPress={() => navigation.navigate('Input')}
testID="home-block-input"
>
<Text style={styles.buttonText}>Input</Text>
<Text style={styles.buttonSubtext}>chat-style rich text input</Text>
</TouchableOpacity>

<TouchableOpacity
style={[styles.button, styles.streamButton]}
onPress={() => navigation.navigate('Stream')}
testID="home-block-stream"
>
<Text style={styles.buttonText}>Stream</Text>
<Text style={styles.buttonSubtext}>streaming markdown with tables</Text>
</TouchableOpacity>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 10,
textAlign: 'center',
},
subtitle: {
fontSize: 16,
color: '#666',
marginBottom: 40,
textAlign: 'center',
},
button: {
paddingHorizontal: 30,
paddingVertical: 15,
borderRadius: 10,
marginVertical: 10,
minWidth: 250,
},
playgroundButton: {
backgroundColor: '#007AFF',
},
textButton: {
backgroundColor: '#34C759',
},
inputButton: {
backgroundColor: '#FF9500',
},
streamButton: {
backgroundColor: '#AF52DE',
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: '600',
textAlign: 'center',
},
buttonSubtext: {
color: 'rgba(255,255,255,0.8)',
fontSize: 12,
textAlign: 'center',
marginTop: 2,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import {
Platform,
Alert,
} from 'react-native';
import type { HostInstance } from 'react-native';
import { useHeaderHeight } from '@react-navigation/elements';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import {
EnrichedMarkdownTextInput,
EnrichedMarkdownText,
type EnrichedMarkdownTextInputInstance,
type StyleState,
} from 'react-native-enriched-markdown';
import { LinkModal } from './LinkModal';
import { LinkModal } from '../LinkModal';

interface Message {
id: number;
Expand Down Expand Up @@ -77,6 +78,9 @@ export default function InputScreen() {
const [linkModalText, setLinkModalText] = useState('');
const [linkModalUrl, setLinkModalUrl] = useState('');
const hasSelectionRef = useRef(false);
const navHeaderHeight = useHeaderHeight();
const [chatHeaderHeight, setChatHeaderHeight] = useState(0);
const { bottom: bottomInset } = useSafeAreaInsets();

const sendMessage = useCallback(async () => {
const md = await inputRef.current?.getMarkdown();
Expand Down Expand Up @@ -168,20 +172,12 @@ export default function InputScreen() {
[]
);

const containerRef = useRef<HostInstance | null>(null);
const [keyboardOffset, setKeyboardOffset] = useState(0);

const measureContainer = useCallback(() => {
containerRef.current?.measureInWindow((_x, y) => setKeyboardOffset(y));
}, []);

return (
<View
style={styles.container}
ref={containerRef}
onLayout={measureContainer}
>
<View style={styles.header}>
<View style={styles.container} testID="input-screen">
<View
style={styles.header}
onLayout={(e) => setChatHeaderHeight(e.nativeEvent.layout.height)}
>
<View style={styles.avatar}>
<Text style={styles.avatarText}>JD</Text>
</View>
Expand All @@ -194,7 +190,7 @@ export default function InputScreen() {
<KeyboardAvoidingView
style={styles.flex}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={keyboardOffset}
keyboardVerticalOffset={navHeaderHeight + chatHeaderHeight}
>
<ScrollView
ref={scrollRef}
Expand Down Expand Up @@ -301,7 +297,7 @@ export default function InputScreen() {
</Text>
</TouchableOpacity>
</View>
<View style={styles.inputRow}>
<View style={[styles.inputRow, { paddingBottom: 12 + bottomInset }]}>
<EnrichedMarkdownTextInput
ref={inputRef}
placeholder="Message..."
Expand Down
Loading
Loading