Commit 3db2ac7d by tungnq

TODO: Đã hoàn thiện xong tab view componet

parent eaf55b41
# TabView Component
A reusable TabView component that supports both filter and navigation modes.
## Features
- **Auto-generate tabs** from data array
- **Two modes**: Filter mode and Navigate mode
- **Scrollable tabs** when there are many tabs
- **Icon support** for each tab
- **Active state styling** with underline indicator
- **Disabled state** support
- **Auto-scroll** to active tab when scrollable
- **Edge case handling** (empty data, duplicate keys, etc.)
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `data` | Array | `[]` | Array of tab objects |
| `mode` | String | `'filter'` | `'filter'` or `'navigate'` |
| `defaultActiveKey` | String | `null` | Initial active tab key |
| `scrollable` | Boolean | `false` | Enable horizontal scrolling |
| `onTabChange` | Function | `null` | Called when tab changes |
| `onFilterChange` | Function | `null` | Called in filter mode |
| `onNavigate` | Function | `null` | Called in navigate mode |
| `style` | Object | `{}` | Container style |
| `tabStyle` | Object | `{}` | Individual tab style |
| `activeTabStyle` | Object | `{}` | Active tab style |
| `textStyle` | Object | `{}` | Tab text style |
| `activeTextStyle` | Object | `{}` | Active tab text style |
## Data Object Structure
```javascript
{
key: 'unique_key', // Required: unique identifier
label: 'Tab Label', // Required: display text
icon: 'icon_name', // Optional: MaterialIcons name
disabled: false, // Optional: disable tab
// ... any other custom properties
}
```
## Usage Examples
### 1. Filter Mode (Default)
```javascript
import React, { useState } from 'react';
import { View } from 'react-native';
import TabView from '../components/TabView';
const FilterScreen = () => {
const [filteredData, setFilteredData] = useState([]);
const [allData] = useState([
// Your data array
]);
const tabData = [
{ key: 'all', label: 'Tất cả', icon: 'list' },
{ key: 'public', label: 'Công khai', icon: 'public' },
{ key: 'private', label: 'Hạn chế', icon: 'lock' },
{ key: 'draft', label: 'Nháp', icon: 'edit' },
];
const handleFilterChange = (item) => {
if (item.key === 'all') {
setFilteredData(allData);
} else {
const filtered = allData.filter(data => data.status === item.key);
setFilteredData(filtered);
}
};
return (
<View>
<TabView
data={tabData}
mode="filter"
defaultActiveKey="all"
scrollable={true}
onFilterChange={handleFilterChange}
/>
{/* Render your filtered list here */}
</View>
);
};
```
### 2. Navigate Mode
```javascript
import React from 'react';
import { View } from 'react-native';
import TabView from '../components/TabView';
const NavigationScreen = ({ navigation }) => {
const tabData = [
{ key: 'home', label: 'Trang chủ', icon: 'home' },
{ key: 'profile', label: 'Hồ sơ', icon: 'person' },
{ key: 'settings', label: 'Cài đặt', icon: 'settings' },
{ key: 'help', label: 'Trợ giúp', icon: 'help' },
];
const handleNavigate = (item) => {
switch (item.key) {
case 'home':
navigation.navigate('HomeScreen');
break;
case 'profile':
navigation.navigate('ProfileScreen');
break;
case 'settings':
navigation.navigate('SettingsScreen');
break;
case 'help':
navigation.navigate('HelpScreen');
break;
}
};
return (
<View>
<TabView
data={tabData}
mode="navigate"
defaultActiveKey="home"
onNavigate={handleNavigate}
/>
</View>
);
};
```
### 3. Custom Styling
```javascript
<TabView
data={tabData}
style={{ backgroundColor: '#f8f9fa' }}
tabStyle={{
paddingHorizontal: 20,
paddingVertical: 15
}}
activeTabStyle={{
backgroundColor: '#e3f2fd'
}}
textStyle={{
fontSize: 16,
color: '#333'
}}
activeTextStyle={{
color: '#1976d2',
fontWeight: 'bold'
}}
/>
```
### 4. With Disabled Tabs
```javascript
const tabData = [
{ key: 'tab1', label: 'Available', icon: 'check' },
{ key: 'tab2', label: 'Disabled', icon: 'block', disabled: true },
{ key: 'tab3', label: 'Coming Soon', icon: 'schedule', disabled: true },
];
```
## Edge Cases Handled
- **Empty data**: Shows "Không có tab" placeholder
- **Invalid defaultActiveKey**: Falls back to first tab
- **Duplicate keys**: Shows warning in console
- **Many tabs**: Use `scrollable={true}` for horizontal scrolling
## Styling Notes
- Active tab has blue color (`#007AFF`) and underline
- Inactive tabs have gray color (`#666`)
- Disabled tabs have reduced opacity (0.5)
- Tab height is 40px with 16px horizontal padding
- Icons are 16px size with 6px right margin
...@@ -8,6 +8,7 @@ import { ...@@ -8,6 +8,7 @@ import {
Dimensions, Dimensions,
} from 'react-native'; } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons'; import Icon from 'react-native-vector-icons/MaterialIcons';
import R from '../../assets/R';
const { width: screenWidth } = Dimensions.get('window'); const { width: screenWidth } = Dimensions.get('window');
...@@ -24,6 +25,10 @@ const { width: screenWidth } = Dimensions.get('window'); ...@@ -24,6 +25,10 @@ const { width: screenWidth } = Dimensions.get('window');
* - onFilterChange: Callback cho chế độ filter * - onFilterChange: Callback cho chế độ filter
* - onNavigate: Callback cho chế độ navigate * - onNavigate: Callback cho chế độ navigate
* - style, tabStyle, activeTabStyle, textStyle, activeTextStyle: Tùy chỉnh giao diện * - style, tabStyle, activeTabStyle, textStyle, activeTextStyle: Tùy chỉnh giao diện
* - activeColor: Màu khi tab được chọn (mặc định #007AFF)
* - inactiveColor: Màu khi tab không được chọn (mặc định #666)
* - activeIndicatorColor: Màu thanh gạch dưới (mặc định dùng activeColor)
* - showActiveIndicator: Hiển thị thanh gạch dưới cho tab active (mặc định true)
*/ */
const TabViewComponent = ({ const TabViewComponent = ({
data = [], // Mảng dữ liệu tab data = [], // Mảng dữ liệu tab
...@@ -38,6 +43,10 @@ const TabViewComponent = ({ ...@@ -38,6 +43,10 @@ const TabViewComponent = ({
activeTabStyle = {}, // Style tab đang active activeTabStyle = {}, // Style tab đang active
textStyle = {}, // Style text tab textStyle = {}, // Style text tab
activeTextStyle = {}, // Style text tab active activeTextStyle = {}, // Style text tab active
activeColor = '#007AFF', // Màu khi tab được chọn
inactiveColor = '#666', // Màu khi tab không được chọn
activeIndicatorColor = null, // Màu thanh gạch dưới (mặc định dùng activeColor)
showActiveIndicator = true, // Hiển thị thanh gạch dưới cho tab active
}) => { }) => {
// State lưu key của tab đang được chọn // State lưu key của tab đang được chọn
const [activeKey, setActiveKey] = useState(null); const [activeKey, setActiveKey] = useState(null);
...@@ -152,7 +161,7 @@ const TabViewComponent = ({ ...@@ -152,7 +161,7 @@ const TabViewComponent = ({
<Icon <Icon
name={item.icon} name={item.icon}
size={16} size={16}
color={isActive ? '#007AFF' : '#666'} // Màu xanh khi active, xám khi không color={isActive ? activeColor : inactiveColor} // Dùng màu từ props
style={styles.tabIcon} style={styles.tabIcon}
/> />
)} )}
...@@ -160,6 +169,7 @@ const TabViewComponent = ({ ...@@ -160,6 +169,7 @@ const TabViewComponent = ({
<Text <Text
style={[ style={[
styles.tabText, // Style text cơ bản styles.tabText, // Style text cơ bản
{ color: isActive ? activeColor : inactiveColor}, // Dùng màu từ props
textStyle, // Style text tùy chỉnh textStyle, // Style text tùy chỉnh
isActive && styles.activeTabText, // Style text khi active isActive && styles.activeTabText, // Style text khi active
isActive && activeTextStyle, // Style text active tùy chỉnh isActive && activeTextStyle, // Style text active tùy chỉnh
...@@ -172,7 +182,14 @@ const TabViewComponent = ({ ...@@ -172,7 +182,14 @@ const TabViewComponent = ({
</Text> </Text>
</View> </View>
{/* Thanh gạch dưới khi tab active */} {/* Thanh gạch dưới khi tab active */}
{isActive && <View style={styles.activeIndicator} />} {isActive && showActiveIndicator && (
<View
style={[
styles.activeIndicator,
{ backgroundColor: activeIndicatorColor || activeColor } // Dùng màu từ props
]}
/>
)}
</TouchableOpacity> </TouchableOpacity>
); );
}; };
...@@ -205,7 +222,7 @@ const TabViewComponent = ({ ...@@ -205,7 +222,7 @@ const TabViewComponent = ({
<View style={[styles.container, style]}> <View style={[styles.container, style]}>
<TabContainer {...containerProps}> <TabContainer {...containerProps}>
{data.map((item, index) => renderTab(item, index))} {data.map((item, index) => renderTab(item, index))}
</TabContainer> </TabContainer>
</View> </View>
); );
}; };
...@@ -215,7 +232,6 @@ const styles = StyleSheet.create({ ...@@ -215,7 +232,6 @@ const styles = StyleSheet.create({
// Container chính của TabView // Container chính của TabView
container: { container: {
backgroundColor: '#fff', // Nền trắng backgroundColor: '#fff', // Nền trắng
borderBottomWidth: 1, // Viền dưới mỏng
borderBottomColor: '#E5E5E5', // Màu viền xám nhạt borderBottomColor: '#E5E5E5', // Màu viền xám nhạt
}, },
// Container cho các tab khi không scrollable // Container cho các tab khi không scrollable
...@@ -229,8 +245,6 @@ const styles = StyleSheet.create({ ...@@ -229,8 +245,6 @@ const styles = StyleSheet.create({
}, },
// Style cơ bản cho mỗi tab // Style cơ bản cho mỗi tab
tab: { tab: {
paddingHorizontal: 16, // Padding ngang 16px
paddingVertical: 12, // Padding dọc 12px
minHeight: 40, // Chiều cao tối thiểu 40px minHeight: 40, // Chiều cao tối thiểu 40px
justifyContent: 'center', // Căn giữa theo chiều dọc justifyContent: 'center', // Căn giữa theo chiều dọc
alignItems: 'center', // Căn giữa theo chiều ngang alignItems: 'center', // Căn giữa theo chiều ngang
...@@ -256,13 +270,12 @@ const styles = StyleSheet.create({ ...@@ -256,13 +270,12 @@ const styles = StyleSheet.create({
}, },
// Style text cơ bản của tab // Style text cơ bản của tab
tabText: { tabText: {
fontSize: 14, // Cỡ chữ 14px fontSize: 12, // Cỡ chữ 14px
color: '#666', // Màu xám color: '#666', // Màu xám
fontWeight: '400', // Độ đậm bình thường fontWeight: '400', // Độ đậm bình thường
}, },
// Style text khi tab active // Style text khi tab active (màu sẽ được override bởi props)
activeTabText: { activeTabText: {
color: '#007AFF', // Màu xanh iOS
fontWeight: '600', // Đậm hơn fontWeight: '600', // Đậm hơn
}, },
// Style text khi tab disabled // Style text khi tab disabled
...@@ -276,7 +289,7 @@ const styles = StyleSheet.create({ ...@@ -276,7 +289,7 @@ const styles = StyleSheet.create({
left: 0, // Từ trái left: 0, // Từ trái
right: 0, // Đến phải right: 0, // Đến phải
height: 2, // Cao 2px height: 2, // Cao 2px
backgroundColor: '#007AFF', // Màu xanh iOS // backgroundColor sẽ được set từ props
}, },
// Container khi không có data // Container khi không có data
emptyContainer: { emptyContainer: {
......
...@@ -21,14 +21,38 @@ const ListWorkView = (props) => { ...@@ -21,14 +21,38 @@ const ListWorkView = (props) => {
<View style = {styles.body}> <View style = {styles.body}>
<TabViewComponent <TabViewComponent
data={[ data={[
{key: 'all', label: 'Tất cả', icon: 'list'}, {key: 'all', label: 'Tất cả'},
{key: 'public', label: 'Công khai', icon: 'public'}, {key: 'public', label: 'Công khai'},
{key: 'private', label: 'Hạn chế', icon: 'lock'}, {key: 'private', label: 'Hạn chế'},
{key: 'draft', label: 'Nháp', icon: 'edit'}, {key: 'draft', label: 'Nháp'},
]} ]}
tabStyle={{
backgroundColor: R.colors.gray,
marginHorizontal: 5,
borderRadius: 10,
width : 100,
height : 35,
justifyContent : 'center',
alignItems : 'center',
}}
style={{
// marginHorizontal: 5,
marginVertical: 5,
}}
mode="filter" mode="filter"
defaultActiveKey="all" defaultActiveKey="all"
scrollable={true} scrollable={true}
activeTabStyle={{
backgroundColor: R.colors.blue,
}}
textStyle={{
color: R.colors.white,
fontWeight : '400',
fontFamily : R.fonts.fontRegular,
fontSize : R.fontsize.fontSizeContent,
}}
showActiveIndicator={false}
onFilterChange={handleFilterChange} onFilterChange={handleFilterChange}
/> />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment