navbar.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import React from 'react'
  2. import { View, Text } from '@tarojs/components'
  3. import { cn } from '@/utils/cn'
  4. import Taro from '@tarojs/taro'
  5. import { isWeapp } from '@/utils/platform'
  6. export interface NavbarProps {
  7. title?: string
  8. leftText?: string
  9. leftIcon?: string
  10. rightText?: string
  11. rightIcon?: string
  12. backgroundColor?: string
  13. textColor?: string
  14. border?: boolean
  15. fixed?: boolean
  16. placeholder?: boolean
  17. onClickLeft?: () => void
  18. onClickRight?: () => void
  19. children?: React.ReactNode
  20. className?: string
  21. /** 是否在小程序环境下隐藏右侧按钮(默认false,会自动避让) */
  22. hideRightInWeapp?: boolean
  23. }
  24. const systemInfo = Taro.getSystemInfoSync()
  25. const menuButtonInfo = Taro.getMenuButtonBoundingClientRect()
  26. // 计算导航栏高度
  27. const NAVBAR_HEIGHT = 44
  28. const STATUS_BAR_HEIGHT = systemInfo.statusBarHeight || 0
  29. const TOTAL_HEIGHT = STATUS_BAR_HEIGHT + NAVBAR_HEIGHT
  30. export const Navbar: React.FC<NavbarProps> = ({
  31. title,
  32. leftText,
  33. leftIcon = 'i-heroicons-chevron-left-20-solid',
  34. rightText,
  35. rightIcon,
  36. backgroundColor = 'bg-white',
  37. textColor = 'text-gray-900',
  38. border = true,
  39. fixed = true,
  40. placeholder = true,
  41. onClickLeft,
  42. onClickRight,
  43. children,
  44. className,
  45. hideRightInWeapp,
  46. }) => {
  47. // 处理左侧点击
  48. const handleLeftClick = () => {
  49. if (onClickLeft) {
  50. onClickLeft()
  51. } else {
  52. // 默认返回上一页
  53. Taro.navigateBack()
  54. }
  55. }
  56. // 渲染左侧内容
  57. const renderLeft = () => {
  58. if (children) return null
  59. return (
  60. <View
  61. className="absolute left-3 top-0 bottom-0 flex items-center z-10"
  62. style={{ height: NAVBAR_HEIGHT }}
  63. onClick={handleLeftClick}
  64. >
  65. <View className="flex items-center">
  66. {leftIcon && (
  67. <View className={cn(leftIcon, 'w-5 h-5', textColor)} />
  68. )}
  69. {leftText && (
  70. <Text className={cn('ml-1 text-sm', textColor)}>{leftText}</Text>
  71. )}
  72. </View>
  73. </View>
  74. )
  75. }
  76. // 渲染右侧内容
  77. const renderRight = () => {
  78. if (!rightText && !rightIcon || (hideRightInWeapp && isWeapp())) return null
  79. if (isWeapp() && menuButtonInfo) {
  80. // 小程序环境下,调整右侧按钮位置
  81. return (
  82. <View
  83. className="absolute top-0 bottom-0 flex items-center z-10"
  84. style={{
  85. height: NAVBAR_HEIGHT,
  86. right: `${menuButtonInfo.right + 10}px`,
  87. }}
  88. onClick={onClickRight}
  89. >
  90. <View className="flex items-center">
  91. {rightText && (
  92. <Text className={cn('mr-1 text-sm', textColor)}>{rightText}</Text>
  93. )}
  94. {rightIcon && (
  95. <View className={cn(rightIcon, 'w-5 h-5', textColor)} />
  96. )}
  97. </View>
  98. </View>
  99. )
  100. }
  101. // H5或其他平台,保持原有样式
  102. return (
  103. <View
  104. className="absolute right-3 top-0 bottom-0 flex items-center z-10"
  105. style={{ height: NAVBAR_HEIGHT }}
  106. onClick={onClickRight}
  107. >
  108. <View className="flex items-center">
  109. {rightText && (
  110. <Text className={cn('mr-1 text-sm', textColor)}>{rightText}</Text>
  111. )}
  112. {rightIcon && (
  113. <View className={cn(rightIcon, 'w-5 h-5', textColor)} />
  114. )}
  115. </View>
  116. </View>
  117. )
  118. }
  119. // 渲染标题
  120. const renderTitle = () => {
  121. if (children) return children
  122. if (isWeapp() && menuButtonInfo) {
  123. // 小程序环境下,调整标题位置
  124. return (
  125. <View className="flex-1 flex items-center justify-center">
  126. <Text
  127. className={cn('text-base font-semibold truncate', textColor)}
  128. style={{
  129. maxWidth: `calc(100% - ${menuButtonInfo.right + 10}px - 60px - 60px)`
  130. }}
  131. >
  132. {title}
  133. </Text>
  134. </View>
  135. )
  136. }
  137. // H5或其他平台,保持原有样式
  138. return (
  139. <Text className={cn('text-base font-semibold', textColor)}>
  140. {title}
  141. </Text>
  142. )
  143. }
  144. // 导航栏样式
  145. const navbarStyle = {
  146. height: TOTAL_HEIGHT,
  147. paddingTop: STATUS_BAR_HEIGHT,
  148. }
  149. return (
  150. <>
  151. <View
  152. className={cn(
  153. 'relative w-full',
  154. backgroundColor,
  155. border && 'border-b border-gray-200',
  156. fixed && 'fixed top-0 left-0 right-0 z-50',
  157. className
  158. )}
  159. style={navbarStyle}
  160. >
  161. {/* 导航栏内容 */}
  162. <View
  163. className="relative flex items-center justify-center"
  164. style={{ height: NAVBAR_HEIGHT }}
  165. >
  166. {renderLeft()}
  167. {renderTitle()}
  168. {renderRight()}
  169. </View>
  170. </View>
  171. {/* 占位元素 */}
  172. {fixed && placeholder && (
  173. <View style={{ height: TOTAL_HEIGHT }} />
  174. )}
  175. </>
  176. )
  177. }
  178. // 预设样式
  179. export const NavbarPresets = {
  180. // 默认白色导航栏
  181. default: {
  182. backgroundColor: 'bg-white',
  183. textColor: 'text-gray-900',
  184. border: true,
  185. },
  186. // 深色导航栏
  187. dark: {
  188. backgroundColor: 'bg-gray-900',
  189. textColor: 'text-white',
  190. border: true,
  191. },
  192. // 透明导航栏
  193. transparent: {
  194. backgroundColor: 'bg-transparent',
  195. textColor: 'text-white',
  196. border: false,
  197. },
  198. // 主色调导航栏
  199. primary: {
  200. backgroundColor: 'bg-blue-500',
  201. textColor: 'text-white',
  202. border: false,
  203. },
  204. }
  205. // 快捷创建函数
  206. export const createNavbar = (preset: keyof typeof NavbarPresets) => {
  207. return NavbarPresets[preset]
  208. }
  209. export default Navbar