hooks/useInputPrompt.tsx

hook·mobile·4.3 KB · 157 lignes· Voir l'itinéraire
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.

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,
  },
});