Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
A
AppUms_Lecturer
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
tungnq
AppUms_Lecturer
Commits
3db2ac7d
Commit
3db2ac7d
authored
Aug 29, 2025
by
tungnq
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
TODO: Đã hoàn thiện xong tab view componet
parent
eaf55b41
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
50 additions
and
196 deletions
+50
-196
README.md
src/components/TabView/README.md
+0
-183
view.js
src/components/TabView/view.js
+22
-9
view.js
src/screens/list_work/view.js
+28
-4
No files found.
src/components/TabView/README.md
deleted
100644 → 0
View file @
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
src/components/TabView/view.js
View file @
3db2ac7d
...
@@ -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
>
);
);
};
};
...
@@ -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
:
1
4
,
// Cỡ chữ 14px
fontSize
:
1
2
,
// 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
:
{
...
...
src/screens/list_work/view.js
View file @
3db2ac7d
...
@@ -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
}
/
>
/
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment