您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import React from 'react'
  2. import cn from '@/utils/classnames'
  3. import type { RemixiconComponentType } from '@remixicon/react'
  4. import Divider from '../divider'
  5. import type { VariantProps } from 'class-variance-authority'
  6. import { cva } from 'class-variance-authority'
  7. import './index.css'
  8. type SegmentedControlOption<T> = {
  9. value: T
  10. text?: string
  11. Icon?: RemixiconComponentType
  12. count?: number
  13. disabled?: boolean
  14. }
  15. type SegmentedControlProps<T extends string | number | symbol> = {
  16. options: SegmentedControlOption<T>[]
  17. value: T
  18. onChange: (value: T) => void
  19. className?: string
  20. activeClassName?: string
  21. }
  22. const SegmentedControlVariants = cva(
  23. 'segmented-control',
  24. {
  25. variants: {
  26. size: {
  27. regular: 'segmented-control-regular',
  28. small: 'segmented-control-small',
  29. large: 'segmented-control-large',
  30. },
  31. padding: {
  32. none: 'no-padding',
  33. with: 'padding',
  34. },
  35. },
  36. defaultVariants: {
  37. size: 'regular',
  38. padding: 'with',
  39. },
  40. },
  41. )
  42. const SegmentedControlItemVariants = cva(
  43. 'segmented-control-item disabled:segmented-control-item-disabled',
  44. {
  45. variants: {
  46. size: {
  47. regular: ['segmented-control-item-regular', 'system-sm-medium'],
  48. small: ['segmented-control-item-small', 'system-xs-medium'],
  49. large: ['segmented-control-item-large', 'system-md-semibold'],
  50. },
  51. activeState: {
  52. default: '',
  53. accent: 'accent',
  54. accentLight: 'accent-light',
  55. },
  56. },
  57. defaultVariants: {
  58. size: 'regular',
  59. activeState: 'default',
  60. },
  61. },
  62. )
  63. const ItemTextWrapperVariants = cva(
  64. 'item-text',
  65. {
  66. variants: {
  67. size: {
  68. regular: 'item-text-regular',
  69. small: 'item-text-small',
  70. large: 'item-text-large',
  71. },
  72. },
  73. defaultVariants: {
  74. size: 'regular',
  75. },
  76. },
  77. )
  78. export const SegmentedControl = <T extends string | number | symbol>({
  79. options,
  80. value,
  81. onChange,
  82. className,
  83. size,
  84. padding,
  85. activeState,
  86. activeClassName,
  87. }: SegmentedControlProps<T>
  88. & VariantProps<typeof SegmentedControlVariants>
  89. & VariantProps<typeof SegmentedControlItemVariants>
  90. & VariantProps<typeof ItemTextWrapperVariants>) => {
  91. const selectedOptionIndex = options.findIndex(option => option.value === value)
  92. return (
  93. <div className={cn(
  94. SegmentedControlVariants({ size, padding }),
  95. className,
  96. )}>
  97. {options.map((option, index) => {
  98. const { Icon, text, count, disabled } = option
  99. const isSelected = index === selectedOptionIndex
  100. const isNextSelected = index === selectedOptionIndex - 1
  101. const isLast = index === options.length - 1
  102. return (
  103. <button
  104. type='button'
  105. key={String(option.value)}
  106. className={cn(
  107. isSelected ? 'active' : 'default',
  108. SegmentedControlItemVariants({ size, activeState: isSelected ? activeState : 'default' }),
  109. isSelected && activeClassName,
  110. disabled && 'disabled',
  111. )}
  112. onClick={() => {
  113. if (!isSelected)
  114. onChange(option.value)
  115. }}
  116. disabled={disabled}
  117. >
  118. {Icon && <Icon className='size-4 shrink-0' />}
  119. {text && (
  120. <div className={cn('inline-flex items-center gap-x-1', ItemTextWrapperVariants({ size }))}>
  121. <span>{text}</span>
  122. {count && size === 'large' && (
  123. <div className='system-2xs-medium-uppercase inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-[5px] text-text-tertiary'>
  124. {count}
  125. </div>
  126. )}
  127. </div>
  128. )}
  129. {!isLast && !isSelected && !isNextSelected && (
  130. <div data-testid={`segmented-control-divider-${index}`} className='absolute right-[-1px] top-0 flex h-full items-center'>
  131. <Divider type='vertical' className='mx-0 h-3.5' />
  132. </div>
  133. )}
  134. </button>
  135. )
  136. })}
  137. </div>
  138. )
  139. }
  140. export default React.memo(SegmentedControl)