Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

index.tsx 4.2KB

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