Skip to content
62 linesCodeBlameRaw
1import * as Haptics from "expo-haptics";
2import { ArrowUp } from "lucide-react-native";
3import { useState } from "react";
4import { Pressable, StyleSheet, TextInput, View } from "react-native";
5import { useSafeAreaInsets } from "react-native-safe-area-context";
6
7import { radius, space, useColors } from "@/theme";
8
9/** What was typed and not sent, per conversation, while the app runs. */
10const drafts = new Map<string, string>();
11
12/**
13 * The box at the bottom of a conversation. Enter makes a new line, as on
14 * a phone; the arrow sends. Typing tells the others (at most every 3 s).
15 */
16export function Composer({ draftKey, placeholder, onSend, onTyping }: { draftKey: string; placeholder: string; onSend: (body: string) => void; onTyping: () => void }) {
17 const colors = useColors();
18 const insets = useSafeAreaInsets();
19 const [text, setText] = useState(() => drafts.get(draftKey) ?? "");
20 const ready = text.trim().length > 0;
21
22 const send = () => {
23 const body = text.trim();
24 if (!body) return;
25 void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => undefined);
26 onSend(body);
27 setText("");
28 drafts.delete(draftKey);
29 };
30
31 return (
32 <View style={[styles.bar, { borderTopColor: colors.line, backgroundColor: colors.surface, paddingBottom: Math.max(insets.bottom, space.sm) }]}>
33 <TextInput
34 value={text}
35 onChangeText={(next) => {
36 setText(next);
37 drafts.set(draftKey, next);
38 if (next.length > 0) onTyping();
39 }}
40 placeholder={placeholder}
41 placeholderTextColor={colors.faint}
42 multiline
43 style={[styles.input, { color: colors.fg, backgroundColor: colors.bg, borderColor: colors.lineStrong }]}
44 />
45 <Pressable
46 accessibilityRole="button"
47 accessibilityLabel="Send"
48 onPress={send}
49 disabled={!ready}
50 style={[styles.send, { backgroundColor: ready ? colors.accent : colors.raised }]}
51 >
52 <ArrowUp size={20} color={ready ? colors.onAccent : colors.faint} strokeWidth={2.5} />
53 </Pressable>
54 </View>
55 );
56}
57
58const styles = StyleSheet.create({
59 bar: { flexDirection: "row", alignItems: "flex-end", gap: space.sm, paddingHorizontal: space.md, paddingTop: space.sm, borderTopWidth: StyleSheet.hairlineWidth },
60 input: { flex: 1, minHeight: 40, maxHeight: 160, borderRadius: radius.lg, borderWidth: 1, paddingHorizontal: space.md, paddingTop: 10, paddingBottom: 10, fontSize: 15 },
61 send: { width: 40, height: 40, borderRadius: 20, alignItems: "center", justifyContent: "center" },
62});