Skip to content

Commit 809a242

Browse files
authored
example app further improvements (#140)
* feat: add readme * fix: fix lint * fix: revert prop typo fix * chore: code review changes * feat: make links editable and insertable and improve their UI * feat: add setValue modal and html display * chore: code review changes * fix: improve html section and mention popup
1 parent 36ebfcd commit 809a242

File tree

6 files changed

+260
-45
lines changed

6 files changed

+260
-45
lines changed

example/src/App.tsx

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
StyleSheet,
44
Text,
55
type NativeSyntheticEvent,
6-
TextInput,
76
ScrollView,
87
} from 'react-native';
98
import {
@@ -21,10 +20,12 @@ import { useRef, useState } from 'react';
2120
import { Button } from './components/Button';
2221
import { Toolbar } from './components/Toolbar';
2322
import { LinkModal } from './components/LinkModal';
23+
import { ValueModal } from './components/ValueModal';
2424
import { launchImageLibrary } from 'react-native-image-picker';
2525
import { type MentionItem, MentionPopup } from './components/MentionPopup';
2626
import { useUserMention } from './useUserMention';
2727
import { useChannelMention } from './useChannelMention';
28+
import { HtmlSection } from './components/HtmlSection';
2829

2930
type StylesState = OnChangeStateEvent;
3031

@@ -71,10 +72,11 @@ export default function App() {
7172
const [isChannelPopupOpen, setIsChannelPopupOpen] = useState(false);
7273
const [isUserPopupOpen, setIsUserPopupOpen] = useState(false);
7374
const [isLinkModalOpen, setIsLinkModalOpen] = useState(false);
75+
const [isValueModalOpen, setIsValueModalOpen] = useState(false);
76+
const [currentHtml, setCurrentHtml] = useState('');
7477

7578
const [selection, setSelection] = useState<Selection>();
7679
const [stylesState, setStylesState] = useState<StylesState>(DEFAULT_STYLE);
77-
const [defaultValue, setDefaultValue] = useState('');
7880
const [currentLink, setCurrentLink] =
7981
useState<CurrentLinkState>(DEFAULT_LINK_STATE);
8082

@@ -83,12 +85,21 @@ export default function App() {
8385
const userMention = useUserMention();
8486
const channelMention = useChannelMention();
8587

88+
const insideCurrentLink =
89+
stylesState.isLink &&
90+
currentLink.url.length > 0 &&
91+
(currentLink.start || currentLink.end) &&
92+
selection &&
93+
selection.start >= currentLink.start &&
94+
selection.end <= currentLink.end;
95+
8696
const handleChangeText = (e: NativeSyntheticEvent<OnChangeTextEvent>) => {
8797
console.log('Text changed:', e?.nativeEvent.value);
8898
};
8999

90100
const handleChangeHtml = (e: NativeSyntheticEvent<OnChangeHtmlEvent>) => {
91101
console.log('HTML changed:', e?.nativeEvent.value);
102+
setCurrentHtml(e?.nativeEvent.value);
92103
};
93104

94105
const handleChangeState = (e: NativeSyntheticEvent<OnChangeStateEvent>) => {
@@ -103,10 +114,6 @@ export default function App() {
103114
ref.current?.blur();
104115
};
105116

106-
const handleSetValue = () => {
107-
ref.current?.setValue('<html><b>Hello</b> <i>world</i></html>');
108-
};
109-
110117
const openLinkModal = () => {
111118
setIsLinkModalOpen(true);
112119
};
@@ -133,6 +140,14 @@ export default function App() {
133140
channelMention.onMentionChange('');
134141
};
135142

143+
const openValueModal = () => {
144+
setIsValueModalOpen(true);
145+
};
146+
147+
const closeValueModal = () => {
148+
setIsValueModalOpen(false);
149+
};
150+
136151
const handleStartMention = (indicator: string) => {
137152
indicator === '@' ? openUserMentionPopup() : openChannelMentionPopup();
138153
};
@@ -151,12 +166,27 @@ export default function App() {
151166
};
152167

153168
const submitLink = (text: string, url: string) => {
154-
if (!selection) return;
169+
if (!selection || url.length === 0) {
170+
closeLinkModal();
171+
return;
172+
}
173+
174+
const newText = text.length > 0 ? text : url;
175+
176+
if (insideCurrentLink) {
177+
ref.current?.setLink(currentLink.start, currentLink.end, newText, url);
178+
} else {
179+
ref.current?.setLink(selection.start, selection.end, newText, url);
180+
}
155181

156-
ref.current?.setLink(selection.start, selection.end, text, url);
157182
closeLinkModal();
158183
};
159184

185+
const submitSetValue = (value: string) => {
186+
ref.current?.setValue(value);
187+
closeValueModal();
188+
};
189+
160190
const selectImage = async () => {
161191
const response = await launchImageLibrary({
162192
mediaType: 'photo',
@@ -219,7 +249,7 @@ export default function App() {
219249
style={styles.container}
220250
contentContainerStyle={styles.content}
221251
>
222-
<Text style={styles.label}>react-native-enriched</Text>
252+
<Text style={styles.label}>Enriched Text Input</Text>
223253
<View style={styles.editor}>
224254
<EnrichedTextInput
225255
autoFocus
@@ -232,7 +262,6 @@ export default function App() {
232262
selectionColor="deepskyblue"
233263
cursorColor="dodgerblue"
234264
autoCapitalize="sentences"
235-
defaultValue={defaultValue}
236265
onChangeText={handleChangeText}
237266
onChangeHtml={handleChangeHtml}
238267
onChangeState={handleChangeState}
@@ -255,22 +284,32 @@ export default function App() {
255284
onSelectImage={selectImage}
256285
/>
257286
</View>
258-
<TextInput
259-
placeholder="Default value"
260-
style={styles.defaultInput}
261-
onChangeText={setDefaultValue}
287+
<View style={styles.buttonStack}>
288+
<Button title="Focus" onPress={handleFocus} style={styles.button} />
289+
<Button title="Blur" onPress={handleBlur} style={styles.button} />
290+
</View>
291+
<Button
292+
title="Set input's value"
293+
onPress={openValueModal}
294+
style={styles.valueButton}
262295
/>
263-
<Button title="Focus" onPress={handleFocus} />
264-
<Button title="Blur" onPress={handleBlur} />
265-
<Button title="Set value" onPress={handleSetValue} />
296+
<HtmlSection currentHtml={currentHtml} />
266297
{DEBUG_SCROLLABLE && <View style={styles.scrollPlaceholder} />}
267298
</ScrollView>
268299
<LinkModal
269-
defaults={currentLink}
270300
isOpen={isLinkModalOpen}
301+
editedText={
302+
insideCurrentLink ? currentLink.text : (selection?.text ?? '')
303+
}
304+
editedUrl={insideCurrentLink ? currentLink.url : ''}
271305
onSubmit={submitLink}
272306
onClose={closeLinkModal}
273307
/>
308+
<ValueModal
309+
isOpen={isValueModalOpen}
310+
onSubmit={submitSetValue}
311+
onClose={closeValueModal}
312+
/>
274313
<MentionPopup
275314
variant="user"
276315
data={userMention.data}
@@ -368,23 +407,28 @@ const styles = StyleSheet.create({
368407
textAlign: 'center',
369408
color: 'rgb(0, 26, 114)',
370409
},
410+
buttonStack: {
411+
flexDirection: 'row',
412+
alignItems: 'center',
413+
justifyContent: 'space-between',
414+
width: '100%',
415+
},
416+
button: {
417+
width: '45%',
418+
},
419+
valueButton: {
420+
width: '100%',
421+
},
371422
editorInput: {
372423
marginTop: 24,
373424
width: '100%',
374-
maxHeight: 240,
425+
maxHeight: 180,
375426
backgroundColor: 'gainsboro',
376427
fontSize: 18,
377428
fontFamily: 'Nunito-Regular',
378429
paddingVertical: 12,
379430
paddingHorizontal: 14,
380431
},
381-
defaultInput: {
382-
marginTop: 24,
383-
width: '100%',
384-
height: 40,
385-
borderBottomWidth: 1,
386-
borderBottomColor: 'grey',
387-
},
388432
scrollPlaceholder: {
389433
marginTop: 24,
390434
width: '100%',

example/src/components/Button.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
11
import { Pressable, StyleSheet, Text } from 'react-native';
22
import type { FC } from 'react';
3+
import type { StyleProp, ViewStyle } from 'react-native';
34

45
interface ButtonProps {
56
title: string;
67
onPress: () => void;
8+
disabled?: boolean;
9+
style?: StyleProp<ViewStyle>;
710
}
811

9-
export const Button: FC<ButtonProps> = ({ title, onPress }) => {
12+
export const Button: FC<ButtonProps> = ({
13+
title,
14+
onPress,
15+
disabled = false,
16+
style = {},
17+
}) => {
1018
return (
1119
<Pressable
1220
onPress={onPress}
13-
style={({ pressed }) =>
14-
pressed ? [styles.button, { opacity: 0.9 }] : styles.button
15-
}
21+
style={({ pressed }) => [
22+
styles.button,
23+
pressed && styles.pressed,
24+
disabled && styles.disabled,
25+
style,
26+
]}
27+
disabled={disabled}
1628
>
1729
<Text style={styles.buttonLabel}>{title}</Text>
1830
</Pressable>
@@ -25,7 +37,6 @@ const styles = StyleSheet.create({
2537
justifyContent: 'center',
2638
alignItems: 'center',
2739
marginTop: 24,
28-
width: '100%',
2940
backgroundColor: 'rgb(0, 26, 114)',
3041
borderRadius: 8,
3142
},
@@ -34,4 +45,10 @@ const styles = StyleSheet.create({
3445
fontWeight: 'bold',
3546
fontSize: 16,
3647
},
48+
disabled: {
49+
backgroundColor: 'darkgray',
50+
},
51+
pressed: {
52+
opacity: 0.9,
53+
},
3754
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useState } from 'react';
2+
import { Button } from './Button';
3+
import { StyleSheet, TextInput } from 'react-native';
4+
5+
interface HtmlSectionProps {
6+
currentHtml: string;
7+
}
8+
9+
export const HtmlSection = ({ currentHtml }: HtmlSectionProps) => {
10+
const [showHtml, setShowHtml] = useState(false);
11+
12+
return (
13+
<>
14+
<Button
15+
title={showHtml ? 'Hide HTML' : 'Show HTML'}
16+
onPress={() => setShowHtml((current) => !current)}
17+
style={styles.button}
18+
/>
19+
{showHtml && (
20+
<TextInput
21+
multiline
22+
editable={false}
23+
style={styles.htmlText}
24+
value={currentHtml}
25+
/>
26+
)}
27+
</>
28+
);
29+
};
30+
31+
const styles = StyleSheet.create({
32+
htmlText: {
33+
marginTop: 24,
34+
width: '100%',
35+
maxHeight: 190,
36+
borderWidth: 1,
37+
borderRadius: 15,
38+
backgroundColor: 'gainsboro',
39+
padding: 10,
40+
alignSelf: 'flex-start',
41+
color: 'rgb(0, 0, 0, 0.8)',
42+
fontWeight: '500',
43+
fontSize: 16,
44+
},
45+
button: {
46+
width: '100%',
47+
},
48+
});

0 commit comments

Comments
 (0)