410 lines
13 KiB
Markdown
410 lines
13 KiB
Markdown
# 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:
|
|
|
|
```bash
|
|
npx expo install @expo/vector-icons
|
|
```
|
|
|
|
## Import
|
|
|
|
```tsx
|
|
import Modal from "@/components/ui/modal";
|
|
```
|
|
|
|
## Các tính năng chính
|
|
|
|
### 1. Basic Modal
|
|
|
|
```tsx
|
|
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)
|
|
|
|
```tsx
|
|
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
|
|
|
|
```tsx
|
|
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
|
|
|
|
```tsx
|
|
<Modal
|
|
open={open}
|
|
title="Modal Without Footer"
|
|
footer={null}
|
|
onCancel={() => setOpen(false)}
|
|
>
|
|
<Text>Modal content without footer buttons</Text>
|
|
</Modal>
|
|
```
|
|
|
|
### 5. Centered Modal
|
|
|
|
```tsx
|
|
<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
|
|
|
|
```tsx
|
|
<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 đủ:**
|
|
|
|
```tsx
|
|
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
|
|
|
|
```tsx
|
|
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<void> | - | 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 methods
|
|
|
|
- `info(config)`: Show info modal
|
|
- `success(config)`: Show success modal
|
|
- `error(config)`: Show error modal
|
|
- `warning(config)`: Show warning modal
|
|
- `confirm(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<void> | - | Callback when clicking OK |
|
|
| onCancel | (e?) => void | - | Callback when clicking Cancel |
|
|
|
|
### Modal Instance
|
|
|
|
Modal instance được trả về bởi `Modal.useModal()`:
|
|
|
|
```tsx
|
|
interface ModalInstance {
|
|
destroy: () => void;
|
|
update: (config: ConfirmModalProps) => void;
|
|
}
|
|
```
|
|
|
|
## Lưu ý
|
|
|
|
1. **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ụng `Modal.useModal()` hook thay thế.
|
|
|
|
2. **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à đặt `contextHolder` trong component tree.
|
|
|
|
3. **Animation**: Modal sử dụng React Native's built-in Modal với `animationType="fade"`.
|
|
|
|
4. **Icons**: Component sử dụng `@expo/vector-icons` (Ionicons). Đảm bảo đã cài đặt package này.
|
|
|
|
5. **Keyboard**: Prop `keyboard` trong 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
|