13 KiB
Modal Component
Modal component tương tự như Modal của Ant Design, được tạo cho React Native/Expo.
Cài đặt
Component này sử dụng @expo/vector-icons cho các icon. Đảm bảo bạn đã cài đặt:
npx expo install @expo/vector-icons
Import
import Modal from "@/components/ui/modal";
Các tính năng chính
1. Basic Modal
import React, { useState } from "react";
import { View, Text, Button } from "react-native";
import Modal from "@/components/ui/modal";
export default function BasicExample() {
const [open, setOpen] = useState(false);
return (
<View>
<Button title="Open Modal" onPress={() => setOpen(true)} />
<Modal
open={open}
title="Basic Modal"
onOk={() => {
console.log("OK clicked");
setOpen(false);
}}
onCancel={() => setOpen(false)}
>
<Text>Some contents...</Text>
<Text>Some contents...</Text>
<Text>Some contents...</Text>
</Modal>
</View>
);
}
2. Async Close (với confirmLoading)
import React, { useState } from "react";
import { View, Text, Button } from "react-native";
import Modal from "@/components/ui/modal";
export default function AsyncExample() {
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const handleOk = async () => {
setLoading(true);
// Giả lập API call
await new Promise((resolve) => setTimeout(resolve, 2000));
setLoading(false);
setOpen(false);
};
return (
<View>
<Button title="Open Modal" onPress={() => setOpen(true)} />
<Modal
open={open}
title="Async Modal"
confirmLoading={loading}
onOk={handleOk}
onCancel={() => setOpen(false)}
>
<Text>Modal will be closed after 2 seconds when you click OK.</Text>
</Modal>
</View>
);
}
3. Customized Footer
import React, { useState } from "react";
import { View, Text, Button, TouchableOpacity, StyleSheet } from "react-native";
import Modal from "@/components/ui/modal";
export default function CustomFooterExample() {
const [open, setOpen] = useState(false);
return (
<View>
<Button title="Open Modal" onPress={() => setOpen(true)} />
<Modal
open={open}
title="Custom Footer Modal"
onCancel={() => setOpen(false)}
footer={
<View style={styles.customFooter}>
<TouchableOpacity
style={styles.customButton}
onPress={() => setOpen(false)}
>
<Text style={styles.buttonText}>Return</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.customButton, styles.submitButton]}
onPress={() => {
console.log("Submit");
setOpen(false);
}}
>
<Text style={[styles.buttonText, styles.submitText]}>Submit</Text>
</TouchableOpacity>
</View>
}
>
<Text>Custom footer buttons</Text>
</Modal>
</View>
);
}
const styles = StyleSheet.create({
customFooter: {
flexDirection: "row",
gap: 8,
},
customButton: {
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 6,
borderWidth: 1,
borderColor: "#d9d9d9",
},
submitButton: {
backgroundColor: "#1890ff",
borderColor: "#1890ff",
},
buttonText: {
fontSize: 14,
color: "#000",
},
submitText: {
color: "#fff",
},
});
4. No Footer
<Modal
open={open}
title="Modal Without Footer"
footer={null}
onCancel={() => setOpen(false)}
>
<Text>Modal content without footer buttons</Text>
</Modal>
5. Centered Modal
<Modal
open={open}
title="Centered Modal"
centered
onOk={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
<Text>This modal is centered on the screen</Text>
</Modal>
6. Custom Width
<Modal
open={open}
title="Custom Width Modal"
width={700}
onOk={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
<Text>This modal has custom width</Text>
</Modal>
7. Confirm Modal với useModal Hook
Đây là cách khuyến nghị sử dụng trong React Native để có context đầy đủ:
import React from "react";
import { View, Button } from "react-native";
import Modal from "@/components/ui/modal";
export default function HookExample() {
const [modal, contextHolder] = Modal.useModal();
const showConfirm = () => {
modal.confirm({
title: "Do you want to delete these items?",
content:
"When clicked the OK button, this dialog will be closed after 1 second",
onOk: async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log("OK");
},
onCancel: () => {
console.log("Cancel");
},
});
};
const showInfo = () => {
modal.info({
title: "This is a notification message",
content: "Some additional information...",
});
};
const showSuccess = () => {
modal.success({
title: "Success",
content: "Operation completed successfully!",
});
};
const showError = () => {
modal.error({
title: "Error",
content: "Something went wrong!",
});
};
const showWarning = () => {
modal.warning({
title: "Warning",
content: "This is a warning message!",
});
};
return (
<View style={{ padding: 20, gap: 10 }}>
{/* contextHolder phải được đặt trong component */}
{contextHolder}
<Button title="Confirm" onPress={showConfirm} />
<Button title="Info" onPress={showInfo} />
<Button title="Success" onPress={showSuccess} />
<Button title="Error" onPress={showError} />
<Button title="Warning" onPress={showWarning} />
</View>
);
}
8. Update Modal Instance
import React from "react";
import { View, Button } from "react-native";
import Modal from "@/components/ui/modal";
export default function UpdateExample() {
const [modal, contextHolder] = Modal.useModal();
const showModal = () => {
const instance = modal.success({
title: "Loading...",
content: "Please wait...",
});
// Update after 2 seconds
setTimeout(() => {
instance.update({
title: "Success!",
content: "Operation completed successfully!",
});
}, 2000);
// Close after 4 seconds
setTimeout(() => {
instance.destroy();
}, 4000);
};
return (
<View>
{contextHolder}
<Button title="Show Updating Modal" onPress={showModal} />
</View>
);
}
API
Modal Props
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | false | Whether the modal dialog is visible or not |
| title | ReactNode | - | The modal dialog's title |
| closable | boolean | true | Whether a close (x) button is visible on top right or not |
| closeIcon | ReactNode | - | Custom close icon |
| maskClosable | boolean | true | Whether to close the modal dialog when the mask is clicked |
| centered | boolean | false | Centered Modal |
| width | number | string | 520 | Width of the modal dialog |
| confirmLoading | boolean | false | Whether to apply loading visual effect for OK button |
| okText | string | 'OK' | Text of the OK button |
| cancelText | string | 'Cancel' | Text of the Cancel button |
| okType | 'primary' | 'default' | 'primary' | Button type of the OK button |
| footer | ReactNode | null | - | Footer content, set as footer={null} to hide default buttons |
| mask | boolean | true | Whether show mask or not |
| zIndex | number | 1000 | The z-index of the Modal |
| onOk | (e?) => void | Promise | - | Callback when clicking OK button |
| onCancel | (e?) => void | - | Callback when clicking cancel button or close icon |
| afterOpenChange | (open: boolean) => void | - | Callback when animation ends |
| afterClose | () => void | - | Callback when modal is closed completely |
| destroyOnClose | boolean | false | Whether to unmount child components on close |
| keyboard | boolean | true | Whether support press back button to close (Android) |
Modal.useModal()
Khi bạn cần sử dụng Context, bạn có thể dùng Modal.useModal() để tạo contextHolder và chèn vào children. Modal được tạo bởi hooks sẽ có tất cả context nơi contextHolder được đặt.
Returns: [modalMethods, contextHolder]
-
modalMethods: Object chứa các methodsinfo(config): Show info modalsuccess(config): Show success modalerror(config): Show error modalwarning(config): Show warning modalconfirm(config): Show confirm modal
-
contextHolder: React element cần được render trong component tree
Modal Methods Config
| Prop | Type | Default | Description |
|---|---|---|---|
| type | 'info' | 'success' | 'error' | 'warning' | 'confirm' | 'confirm' | Type of the modal |
| title | ReactNode | - | Title |
| content | ReactNode | - | Content |
| icon | ReactNode | - | Custom icon |
| okText | string | 'OK' | Text of the OK button |
| cancelText | string | 'Cancel' | Text of the Cancel button |
| onOk | (e?) => void | Promise | - | Callback when clicking OK |
| onCancel | (e?) => void | - | Callback when clicking Cancel |
Modal Instance
Modal instance được trả về bởi Modal.useModal():
interface ModalInstance {
destroy: () => void;
update: (config: ConfirmModalProps) => void;
}
Lưu ý
-
React Native Limitations: Các static methods như
Modal.info(),Modal.confirm()gọi trực tiếp (không qua hook) không được hỗ trợ đầy đủ trong React Native do không thể render imperatively. Hãy sử dụngModal.useModal()hook thay thế. -
Context Support: Khi cần sử dụng Context (như Redux, Theme Context), bắt buộc phải dùng
Modal.useModal()hook và đặtcontextHoldertrong component tree. -
Animation: Modal sử dụng React Native's built-in Modal với
animationType="fade". -
Icons: Component sử dụng
@expo/vector-icons(Ionicons). Đảm bảo đã cài đặt package này. -
Keyboard: Prop
keyboardtrong React Native chỉ hoạt động với nút back của Android (không có ESC key như web).
So sánh với Ant Design Modal
| Feature | Ant Design (Web) | This Component (RN) |
|---|---|---|
| Basic Modal | ✅ | ✅ |
| Centered | ✅ | ✅ |
| Custom Footer | ✅ | ✅ |
| Confirm Dialog | ✅ | ✅ (via useModal) |
| Info/Success/Error/Warning | ✅ | ✅ (via useModal) |
| Async close | ✅ | ✅ |
| Custom width | ✅ | ✅ |
| Mask closable | ✅ | ✅ |
| Keyboard close | ✅ (ESC) | ✅ (Back button on Android) |
| Static methods without hook | ✅ | ⚠️ (Limited support) |
| useModal hook | ✅ | ✅ (Recommended) |
| Draggable | ✅ | ❌ (Not applicable) |
| destroyAll() | ✅ | ❌ |
License
MIT