eletrotupi / tcc/ frontend/components/ui/ConfirmModal.tsx6bdc28a3adcc45cf37288df8777dfa536ce0f07a
4.0 KB | 171 lines Raw
1
import React, { useEffect, useState } from 'react';
2
import { Animated, Modal, Pressable, StyleSheet, View } from 'react-native';
3

4
import { Button } from '@/components/ui/Button';
5
import { Text } from '@/components/ui/Text';
6
import {
7
  BorderRadius,
8
  Colors,
9
  Spacing,
10
  Shadows,
11
  Typography,
12
} from '@/constants/theme';
13

14
interface ConfirmModalProps {
15
  visible: boolean;
16
  title: string;
17
  message?: string;
18
  detail?: string;
19
  confirmLabel?: string;
20
  cancelLabel?: string;
21
  destructive?: boolean;
22
  loading?: boolean;
23
  onConfirm: () => void;
24
  onCancel: () => void;
25
  testID?: string;
26
}
27

28
export const ConfirmModal: React.FC<ConfirmModalProps> = ({
29
  visible,
30
  title,
31
  message,
32
  detail,
33
  confirmLabel = 'Confirmar',
34
  cancelLabel = 'Cancelar',
35
  destructive = false,
36
  loading = false,
37
  onConfirm,
38
  onCancel,
39
  testID = 'confirm-modal',
40
}) => {
41
  const [fade] = useState(() => new Animated.Value(0));
42

43
  useEffect(() => {
44
    if (visible) {
45
      fade.setValue(0);
46
      Animated.timing(fade, {
47
        toValue: 1,
48
        duration: 180,
49
        useNativeDriver: true,
50
      }).start();
51
    }
52
  }, [visible, fade]);
53

54
  const dismiss = () => {
55
    if (!loading) {
56
      onCancel();
57
    }
58
  };
59

60
  return (
61
    <Modal
62
      visible={visible}
63
      transparent
64
      animationType="none"
65
      statusBarTranslucent
66
      onRequestClose={dismiss}
67
    >
68
      <Animated.View style={[styles.overlay, { opacity: fade }]}>
69
        <Pressable
70
          style={StyleSheet.absoluteFill}
71
          onPress={dismiss}
72
          testID={`${testID}-overlay`}
73
        />
74

75
        <View style={styles.cardContainer} pointerEvents="box-none">
76
          <Pressable
77
            style={styles.card}
78
            accessibilityViewIsModal
79
            testID={testID}
80
          >
81
            <Text style={styles.title}>{title}</Text>
82

83
            {message ? <Text style={styles.message}>{message}</Text> : null}
84

85
            {detail ? (
86
              <View style={styles.detail}>
87
                <Text style={styles.detailText}>{detail}</Text>
88
              </View>
89
            ) : null}
90

91
            <View style={styles.footer}>
92
              <Button
93
                title={cancelLabel}
94
                variant="ghost"
95
                style={styles.footerButton}
96
                disabled={loading}
97
                onPress={onCancel}
98
                testID={`${testID}-cancel`}
99
              />
100
              <Button
101
                title={confirmLabel}
102
                variant={destructive ? 'danger' : 'primary'}
103
                style={styles.footerButton}
104
                loading={loading}
105
                onPress={onConfirm}
106
                testID={`${testID}-confirm`}
107
              />
108
            </View>
109
          </Pressable>
110
        </View>
111
      </Animated.View>
112
    </Modal>
113
  );
114
};
115

116
const OVERLAY_BACKGROUND = 'rgba(15, 23, 42, 0.55)';
117

118
const styles = StyleSheet.create({
119
  overlay: {
120
    flex: 1,
121
    backgroundColor: OVERLAY_BACKGROUND,
122
  },
123
  cardContainer: {
124
    flex: 1,
125
    width: '100%',
126
    alignItems: 'center',
127
    justifyContent: 'center',
128
    padding: Spacing.containerPadding * 1.5,
129
  },
130
  card: {
131
    width: '100%',
132
    maxWidth: 400,
133
    backgroundColor: Colors.light.surface,
134
    borderRadius: BorderRadius.xl,
135
    borderWidth: 1,
136
    borderColor: Colors.light.cardBorder,
137
    padding: Spacing.containerPadding + 4,
138
    ...Shadows.md,
139
  },
140
  title: {
141
    ...Typography.headlineMd,
142
    color: Colors.light.text,
143
    textAlign: 'center',
144
  },
145
  message: {
146
    ...Typography.bodyMd,
147
    color: Colors.light.textSecondary,
148
    textAlign: 'center',
149
    marginTop: Spacing.inlineGapSm,
150
  },
151
  detail: {
152
    marginTop: Spacing.cardGap,
153
    backgroundColor: Colors.light.accentBlue,
154
    borderRadius: BorderRadius.md,
155
    paddingVertical: Spacing.inlineGapSm,
156
    paddingHorizontal: Spacing.cardGap,
157
  },
158
  detailText: {
159
    ...Typography.bodyLg,
160
    color: Colors.light.text,
161
    textAlign: 'center',
162
  },
163
  footer: {
164
    flexDirection: 'row',
165
    gap: Spacing.cardGap,
166
    marginTop: Spacing.containerPadding,
167
  },
168
  footerButton: {
169
    flex: 1,
170
  },
171
});