hooks/useInputPrompt.tsx
Annotation non disponible
Lance npm run annotate (nécessite ANTHROPIC_API_KEY dans .env.local) pour générer une annotation française par Claude Haiku 4.5.
Concepts détectés — comprends la théorie
UI React Native
10 occurrencesComposants UI React Native (View, Text, etc.). Différents de l'HTML web — on rend du natif.
Voir l'article général
Hooks React
6 occurrencesCe fichier utilise des hooks React. Les hooks sont la façon moderne de gérer l'état et les effets dans React. Voir l'architecture mobile pour le pattern complet.
Voir l'article général
1 export
useInputPrompt
Code source· tsx
import React, { useCallback, useState } from 'react';
import {
KeyboardAvoidingView,
Modal,
Platform,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import { FONTS, RADIUS, SPACING } from '@/lib/constants';
import { useTheme } from '@/hooks/useTheme';
type PromptOptions = {
title: string;
message?: string;
defaultValue?: string;
placeholder?: string;
confirmLabel?: string;
cancelLabel?: string;
keyboardType?: 'default' | 'numeric' | 'email-address' | 'phone-pad';
autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
};
type ActiveState =
| (PromptOptions & { resolve: (value: string | null) => void })
| null;
// Drop-in Android-safe remplacement de Alert.prompt (iOS-only natif RN).
// Pattern : `const { prompt, node } = useInputPrompt()` + render `{node}` + `await prompt({...})`.
export function useInputPrompt() {
const [active, setActive] = useState<ActiveState>(null);
const [value, setValue] = useState('');
const { colors } = useTheme();
const prompt = useCallback(
(opts: PromptOptions) =>
new Promise<string | null>((resolve) => {
setValue(opts.defaultValue ?? '');
setActive({ ...opts, resolve });
}),
[],
);
const close = (out: string | null) => {
const r = active?.resolve;
setActive(null);
r?.(out);
};
const node = active ? (
<Modal
visible
transparent
animationType="fade"
statusBarTranslucent
onRequestClose={() => close(null)}
>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.overlay}
>
<TouchableOpacity
activeOpacity={1}
style={styles.backdropTouch}
onPress={() => close(null)}
/>
<View style={[styles.dialog, { backgroundColor: colors.bgCard }]}>
<Text style={[styles.title, { color: colors.text }]}>{active.title}</Text>
{active.message ? (
<Text style={[styles.message, { color: colors.textMuted }]}>
{active.message}
</Text>
) : null}
<TextInput
value={value}
onChangeText={setValue}
placeholder={active.placeholder}
placeholderTextColor={colors.textMuted}
keyboardType={active.keyboardType ?? 'default'}
autoCapitalize={active.autoCapitalize ?? 'sentences'}
style={[
styles.input,
{ color: colors.text, borderColor: colors.border, backgroundColor: colors.bg },
]}
autoFocus
returnKeyType="done"
onSubmitEditing={() => close(value)}
/>
<View style={styles.buttons}>
<TouchableOpacity onPress={() => close(null)} style={styles.btn}>
<Text style={[styles.btnText, { color: colors.textMuted }]}>
{active.cancelLabel ?? 'Annuler'}
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => close(value)} style={styles.btn}>
<Text
style={[
styles.btnText,
{ color: colors.primary, fontWeight: FONTS.bold },
]}
>
{active.confirmLabel ?? 'OK'}
</Text>
</TouchableOpacity>
</View>
</View>
</KeyboardAvoidingView>
</Modal>
) : null;
return { prompt, node };
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.5)',
justifyContent: 'center',
paddingHorizontal: SPACING.lg,
},
backdropTouch: { ...StyleSheet.absoluteFillObject },
dialog: {
borderRadius: RADIUS.md,
padding: SPACING.lg,
gap: SPACING.base,
},
title: {
fontSize: FONTS.md,
fontWeight: FONTS.semibold,
},
message: {
fontSize: FONTS.sm,
},
input: {
borderWidth: 1,
borderRadius: RADIUS.sm,
paddingHorizontal: SPACING.base,
paddingVertical: SPACING.sm,
fontSize: FONTS.base,
},
buttons: {
flexDirection: 'row',
justifyContent: 'flex-end',
gap: SPACING.base,
marginTop: SPACING.sm,
},
btn: {
paddingVertical: SPACING.sm,
paddingHorizontal: SPACING.base,
},
btnText: {
fontSize: FONTS.base,
},
});
import React, { useCallback, useState } from 'react';
import {
KeyboardAvoidingView,
Modal,
Platform,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import { FONTS, RADIUS, SPACING } from '@/lib/constants';
import { useTheme } from '@/hooks/useTheme';
type PromptOptions = {
title: string;
message?: string;
defaultValue?: string;
placeholder?: string;
confirmLabel?: string;
cancelLabel?: string;
keyboardType?: 'default' | 'numeric' | 'email-address' | 'phone-pad';
autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
};
type ActiveState =
| (PromptOptions & { resolve: (value: string | null) => void })
| null;
// Drop-in Android-safe remplacement de Alert.prompt (iOS-only natif RN).
// Pattern : `const { prompt, node } = useInputPrompt()` + render `{node}` + `await prompt({...})`.
export function useInputPrompt() {
const [active, setActive] = useState<ActiveState>(null);
const [value, setValue] = useState('');
const { colors } = useTheme();
const prompt = useCallback(
(opts: PromptOptions) =>
new Promise<string | null>((resolve) => {
setValue(opts.defaultValue ?? '');
setActive({ ...opts, resolve });
}),
[],
);
const close = (out: string | null) => {
const r = active?.resolve;
setActive(null);
r?.(out);
};
const node = active ? (
<Modal
visible
transparent
animationType="fade"
statusBarTranslucent
onRequestClose={() => close(null)}
>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.overlay}
>
<TouchableOpacity
activeOpacity={1}
style={styles.backdropTouch}
onPress={() => close(null)}
/>
<View style={[styles.dialog, { backgroundColor: colors.bgCard }]}>
<Text style={[styles.title, { color: colors.text }]}>{active.title}</Text>
{active.message ? (
<Text style={[styles.message, { color: colors.textMuted }]}>
{active.message}
</Text>
) : null}
<TextInput
value={value}
onChangeText={setValue}
placeholder={active.placeholder}
placeholderTextColor={colors.textMuted}
keyboardType={active.keyboardType ?? 'default'}
autoCapitalize={active.autoCapitalize ?? 'sentences'}
style={[
styles.input,
{ color: colors.text, borderColor: colors.border, backgroundColor: colors.bg },
]}
autoFocus
returnKeyType="done"
onSubmitEditing={() => close(value)}
/>
<View style={styles.buttons}>
<TouchableOpacity onPress={() => close(null)} style={styles.btn}>
<Text style={[styles.btnText, { color: colors.textMuted }]}>
{active.cancelLabel ?? 'Annuler'}
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => close(value)} style={styles.btn}>
<Text
style={[
styles.btnText,
{ color: colors.primary, fontWeight: FONTS.bold },
]}
>
{active.confirmLabel ?? 'OK'}
</Text>
</TouchableOpacity>
</View>
</View>
</KeyboardAvoidingView>
</Modal>
) : null;
return { prompt, node };
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.5)',
justifyContent: 'center',
paddingHorizontal: SPACING.lg,
},
backdropTouch: { ...StyleSheet.absoluteFillObject },
dialog: {
borderRadius: RADIUS.md,
padding: SPACING.lg,
gap: SPACING.base,
},
title: {
fontSize: FONTS.md,
fontWeight: FONTS.semibold,
},
message: {
fontSize: FONTS.sm,
},
input: {
borderWidth: 1,
borderRadius: RADIUS.sm,
paddingHorizontal: SPACING.base,
paddingVertical: SPACING.sm,
fontSize: FONTS.base,
},
buttons: {
flexDirection: 'row',
justifyContent: 'flex-end',
gap: SPACING.base,
marginTop: SPACING.sm,
},
btn: {
paddingVertical: SPACING.sm,
paddingHorizontal: SPACING.base,
},
btnText: {
fontSize: FONTS.base,
},
});