wwf
3 天以前 a430284aa21e3ae1f0d5654e55b2ad2852519cc2
app/components/base/tab-slider/index.tsx
@@ -1,85 +1,64 @@
import type { FC, ReactNode } from 'react'
import { useEffect, useState } from 'react'
import type { FC } from 'react'
import cn from '@/utils/classnames'
import Badge, { BadgeState } from '@/app/components/base/badge/index'
import { useInstalledPluginList } from '@/service/use-plugins'
type Option = {
  value: string
  text: ReactNode
  text: string
}
type TabSliderProps = {
  className?: string
  itemWidth?: number
  value: string
  onChange: (v: string) => void
  options: Option[]
}
const TabSlider: FC<TabSliderProps> = ({
  className,
  itemWidth = 118,
  value,
  onChange,
  options,
}) => {
  const [activeIndex, setActiveIndex] = useState(options.findIndex(option => option.value === value))
  const [sliderStyle, setSliderStyle] = useState({})
  const { data: pluginList } = useInstalledPluginList()
  const updateSliderStyle = (index: number) => {
    const tabElement = document.getElementById(`tab-${index}`)
    if (tabElement) {
      const { offsetLeft, offsetWidth } = tabElement
      setSliderStyle({
        transform: `translateX(${offsetLeft}px)`,
        width: `${offsetWidth}px`,
      })
    }
  }
  useEffect(() => {
    const newIndex = options.findIndex(option => option.value === value)
    setActiveIndex(newIndex)
    updateSliderStyle(newIndex)
  }, [value, options, pluginList])
  const currentIndex = options.findIndex(option => option.value === value)
  const current = options[currentIndex]
  return (
    <div className={cn(className, 'relative inline-flex items-center justify-center rounded-[10px] bg-components-segmented-control-bg-normal p-0.5')}>
      <div
        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"
        style={sliderStyle}
      />
      {options.map((option, index) => (
        <div
          id={`tab-${index}`}
          key={option.value}
          className={cn(
            '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',
            'system-md-semibold',
            index === activeIndex
              ? 'text-text-primary'
              : 'text-text-tertiary',
          )}
          onClick={() => {
            if (index !== activeIndex) {
              onChange(option.value)
              updateSliderStyle(index)
            }
          }}
        >
          {option.text}
          {/* if no plugin installed, the badge won't show */}
          {option.value === 'plugins'
            && (pluginList?.plugins.length ?? 0) > 0
            && <Badge
              size='s'
              uppercase={true}
              state={BadgeState.Default}
            >
              {pluginList?.plugins.length}
            </Badge>
          }
        </div>
      ))}
    <div className={cn(className, 'relative flex p-0.5 rounded-lg bg-gray-200')}>
      {
        options.map((option, index) => (
          <div
            key={option.value}
            className={`
              flex justify-center items-center h-7 text-[13px]
              font-semibold text-gray-600 rounded-[7px] cursor-pointer
              hover:bg-gray-50
              ${index !== options.length - 1 && 'mr-[1px]'}
            `}
            style={{
              width: itemWidth,
            }}
            onClick={() => onChange(option.value)}
          >
            {option.text}
          </div>
        ))
      }
      {
        current && (
          <div
            className={`
              absolute flex justify-center items-center h-7 bg-white text-[13px] font-semibold text-primary-600
              border-[0.5px] border-gray-200 rounded-[7px] shadow-xs transition-transform
            `}
            style={{
              width: itemWidth,
              transform: `translateX(${currentIndex * itemWidth + 1}px)`,
            }}
          >
            {current.text}
          </div>
        )
      }
    </div>
  )
}