| | |
| | | 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> |
| | | ) |
| | | } |