index.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import type { FC, ReactNode } from 'react'
  2. import { useEffect, useState } from 'react'
  3. import cn from '@/utils/classnames'
  4. import Badge, { BadgeState } from '@/app/components/base/badge/index'
  5. import { useInstalledPluginList } from '@/service/use-plugins'
  6. type Option = {
  7. value: string
  8. text: ReactNode
  9. }
  10. type TabSliderProps = {
  11. className?: string
  12. value: string
  13. onChange: (v: string) => void
  14. options: Option[]
  15. }
  16. const TabSlider: FC<TabSliderProps> = ({
  17. className,
  18. value,
  19. onChange,
  20. options,
  21. }) => {
  22. const [activeIndex, setActiveIndex] = useState(options.findIndex(option => option.value === value))
  23. const [sliderStyle, setSliderStyle] = useState({})
  24. const { data: pluginList } = useInstalledPluginList()
  25. const updateSliderStyle = (index: number) => {
  26. const tabElement = document.getElementById(`tab-${index}`)
  27. if (tabElement) {
  28. const { offsetLeft, offsetWidth } = tabElement
  29. setSliderStyle({
  30. transform: `translateX(${offsetLeft}px)`,
  31. width: `${offsetWidth}px`,
  32. })
  33. }
  34. }
  35. useEffect(() => {
  36. const newIndex = options.findIndex(option => option.value === value)
  37. setActiveIndex(newIndex)
  38. updateSliderStyle(newIndex)
  39. }, [value, options, pluginList])
  40. return (
  41. <div className={cn(className, 'relative inline-flex items-center justify-center rounded-[10px] bg-components-segmented-control-bg-normal p-0.5')}>
  42. <div
  43. className="shadows-shadow-xs absolute bottom-0.5 left-0 right-0 top-0.5 rounded-[10px] bg-components-panel-bg transition-transform duration-300 ease-in-out"
  44. style={sliderStyle}
  45. />
  46. {options.map((option, index) => (
  47. <div
  48. id={`tab-${index}`}
  49. key={option.value}
  50. className={cn(
  51. 'relative z-10 flex cursor-pointer items-center justify-center gap-1 rounded-[10px] px-2.5 py-1.5 transition-colors duration-300 ease-in-out',
  52. 'system-md-semibold',
  53. index === activeIndex
  54. ? 'text-text-primary'
  55. : 'text-text-tertiary',
  56. )}
  57. onClick={() => {
  58. if (index !== activeIndex) {
  59. onChange(option.value)
  60. updateSliderStyle(index)
  61. }
  62. }}
  63. >
  64. {option.text}
  65. {/* if no plugin installed, the badge won't show */}
  66. {option.value === 'plugins'
  67. && (pluginList?.plugins.length ?? 0) > 0
  68. && <Badge
  69. size='s'
  70. uppercase={true}
  71. state={BadgeState.Default}
  72. >
  73. {pluginList?.plugins.length}
  74. </Badge>
  75. }
  76. </div>
  77. ))}
  78. </div>
  79. )
  80. }
  81. export default TabSlider