toggle-button-group.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import {
  2. Children,
  3. cloneElement,
  4. isValidElement,
  5. useState,
  6. useEffect,
  7. } from 'react'
  8. import {
  9. ToggleButtonGroup as BS3ToggleButtonGroup,
  10. ToggleButtonGroupProps as BS3ToggleButtonGroupProps,
  11. ToggleButtonProps as BS3ToggleButtonProps,
  12. } from 'react-bootstrap'
  13. function ToggleButtonGroup<T extends string | number>({
  14. children,
  15. value,
  16. defaultValue,
  17. onChange,
  18. ...props
  19. }: BS3ToggleButtonGroupProps) {
  20. const [selectedValue, setSelectedValue] = useState<T | T[] | null>(
  21. defaultValue || (props.type === 'checkbox' ? [] : null)
  22. )
  23. const isControlled = value !== undefined
  24. useEffect(() => {
  25. if (isControlled) {
  26. if (props.type === 'radio') {
  27. setSelectedValue(value)
  28. } else {
  29. if (Array.isArray(value)) {
  30. setSelectedValue(Array.from(value))
  31. } else {
  32. setSelectedValue([value])
  33. }
  34. }
  35. }
  36. }, [isControlled, value, props.type])
  37. const handleButtonClick = (buttonValue: T) => {
  38. if (props.type === 'radio') {
  39. if (!isControlled) {
  40. setSelectedValue(buttonValue)
  41. }
  42. onChange?.(buttonValue as any)
  43. } else if (props.type === 'checkbox') {
  44. const newValue = Array.isArray(selectedValue)
  45. ? selectedValue.includes(buttonValue)
  46. ? selectedValue.filter(val => val !== buttonValue) // Deselect
  47. : [...selectedValue, buttonValue] // Select
  48. : [buttonValue] // Initial selection if value is not array yet
  49. if (!isControlled) {
  50. setSelectedValue(newValue)
  51. }
  52. onChange?.(newValue)
  53. }
  54. }
  55. // Clone children and add custom onClick handlers
  56. const modifiedChildren = Children.map(children, child => {
  57. if (isValidElement(child)) {
  58. const childElement = child as React.ReactElement<
  59. BS3ToggleButtonProps & { active?: boolean }
  60. >
  61. const isActive =
  62. props.type === 'radio'
  63. ? selectedValue === childElement.props.value
  64. : Array.isArray(selectedValue) &&
  65. selectedValue.includes(childElement.props.value as T)
  66. return cloneElement(childElement, {
  67. onClick: () => {
  68. handleButtonClick(childElement.props.value as T)
  69. },
  70. active: isActive,
  71. })
  72. }
  73. return child
  74. })
  75. return (
  76. <BS3ToggleButtonGroup
  77. {...props}
  78. value={isControlled ? value : undefined}
  79. defaultValue={defaultValue}
  80. // Ignore the broken onChange handler
  81. onChange={() => {}}
  82. >
  83. {modifiedChildren}
  84. </BS3ToggleButtonGroup>
  85. )
  86. }
  87. export default ToggleButtonGroup