tab-bar.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import React from 'react'
  2. import { View, Text } from '@tarojs/components'
  3. import { cn } from '../utils/cn'
  4. export interface TabBarItem {
  5. key: string
  6. title: string
  7. icon?: string
  8. selectedIcon?: string
  9. iconClass?: string
  10. selectedIconClass?: string
  11. badge?: number | string
  12. dot?: boolean
  13. }
  14. export interface TabBarProps {
  15. items: TabBarItem[]
  16. activeKey?: string
  17. onChange?: (key: string) => void
  18. className?: string
  19. style?: React.CSSProperties
  20. fixed?: boolean
  21. safeArea?: boolean
  22. color?: string
  23. selectedColor?: string
  24. backgroundColor?: string
  25. }
  26. const TabBar = React.forwardRef<HTMLDivElement, TabBarProps>(({
  27. items,
  28. activeKey,
  29. onChange,
  30. className,
  31. style,
  32. fixed = true,
  33. safeArea = true,
  34. color = '#7f7f7f',
  35. selectedColor = '#1890ff',
  36. backgroundColor = '#ffffff',
  37. }, ref) => {
  38. const currentActiveKey = activeKey || items[0]?.key
  39. const handleTabChange = (key: string) => {
  40. if (key !== currentActiveKey) {
  41. onChange?.(key)
  42. }
  43. }
  44. return (
  45. <View
  46. ref={ref}
  47. className={cn(
  48. 'tab-bar',
  49. fixed && 'fixed bottom-0 left-0 right-0',
  50. safeArea && 'pb-safe',
  51. 'z-50',
  52. className
  53. )}
  54. style={{
  55. backgroundColor,
  56. ...style,
  57. }}
  58. >
  59. <View className="flex h-16 border-t border-gray-200">
  60. {items.map((item) => {
  61. const isActive = item.key === currentActiveKey
  62. return (
  63. <View
  64. key={item.key}
  65. className={cn(
  66. 'flex-1 flex flex-col items-center justify-center',
  67. 'px-2 py-1',
  68. 'cursor-pointer',
  69. 'transition-colors duration-200',
  70. 'hover:opacity-80'
  71. )}
  72. onClick={() => handleTabChange(item.key)}
  73. >
  74. <View className="relative">
  75. {(item.iconClass || item.icon) && (
  76. <View
  77. className={cn(
  78. 'mb-1',
  79. 'flex items-center justify-center',
  80. item.iconClass ? 'w-6 h-6' : 'text-2xl',
  81. isActive ? 'text-blue-500' : 'text-gray-500'
  82. )}
  83. style={{
  84. color: isActive ? selectedColor : color,
  85. }}
  86. >
  87. {item.iconClass ? (
  88. <View
  89. className={cn(
  90. isActive && item.selectedIconClass
  91. ? item.selectedIconClass
  92. : item.iconClass,
  93. 'w-full h-full'
  94. )}
  95. />
  96. ) : (
  97. isActive && item.selectedIcon ? item.selectedIcon : item.icon
  98. )}
  99. </View>
  100. )}
  101. {item.badge && (
  102. <View
  103. className={cn(
  104. 'absolute -top-1 -right-2',
  105. 'bg-red-500 text-white text-xs',
  106. 'rounded-full px-1.5 py-0.5',
  107. 'min-w-4 h-4 flex items-center justify-center'
  108. )}
  109. >
  110. {typeof item.badge === 'number' && item.badge > 99 ? '99+' : item.badge}
  111. </View>
  112. )}
  113. {item.dot && (
  114. <View
  115. className={cn(
  116. 'absolute -top-1 -right-1',
  117. 'w-2 h-2 bg-red-500 rounded-full'
  118. )}
  119. />
  120. )}
  121. </View>
  122. <Text
  123. className={cn(
  124. 'text-xs',
  125. 'leading-tight',
  126. isActive ? 'font-medium' : 'font-normal'
  127. )}
  128. style={{
  129. color: isActive ? selectedColor : color,
  130. }}
  131. numberOfLines={1}
  132. >
  133. {item.title}
  134. </Text>
  135. </View>
  136. )
  137. })}
  138. </View>
  139. </View>
  140. )
  141. })
  142. TabBar.displayName = 'TabBar'
  143. export { TabBar }