You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ToggleButton.tsx 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import styled from '@emotion/styled';
  21. import tw from 'twin.macro';
  22. import { Badge } from '../../components/Badge';
  23. import { themeBorder, themeColor, themeContrast } from '../../helpers/theme';
  24. import { getTabId, getTabPanelId } from '../helpers/tabs';
  25. import { ButtonSecondary } from './buttons';
  26. type ToggleButtonValueType = string | number | boolean;
  27. export interface ToggleButtonsOption<T extends ToggleButtonValueType> {
  28. counter?: number;
  29. disabled?: boolean;
  30. label: string | React.ReactNode;
  31. value: T;
  32. }
  33. export interface ButtonToggleProps<T extends ToggleButtonValueType> {
  34. disabled?: boolean;
  35. label?: string;
  36. onChange: (value: T) => void;
  37. options: ReadonlyArray<ToggleButtonsOption<T>>;
  38. role?: 'radiogroup' | 'tablist';
  39. value?: T;
  40. }
  41. export function ToggleButton<T extends ToggleButtonValueType>(props: ButtonToggleProps<T>) {
  42. const { disabled = false, label, options, value, role = 'radiogroup' } = props;
  43. const isRadioGroup = role === 'radiogroup';
  44. return (
  45. <Wrapper aria-label={label} role={role}>
  46. {options.map((option) => (
  47. <OptionButton
  48. aria-checked={isRadioGroup ? option.value === value : undefined}
  49. aria-controls={isRadioGroup ? undefined : getTabPanelId(String(option.value))}
  50. aria-current={option.value === value}
  51. data-value={option.value}
  52. disabled={disabled || option.disabled}
  53. id={getTabId(String(option.value))}
  54. key={option.value.toString()}
  55. onClick={() => {
  56. if (option.value !== value) {
  57. props.onChange(option.value);
  58. }
  59. }}
  60. role={isRadioGroup ? 'radio' : 'tab'}
  61. selected={option.value === value}
  62. >
  63. {option.label}
  64. {option.counter ? (
  65. <Badge className="sw-ml-1" variant="counter">
  66. {option.counter}
  67. </Badge>
  68. ) : null}
  69. </OptionButton>
  70. ))}
  71. </Wrapper>
  72. );
  73. }
  74. const Wrapper = styled.div`
  75. border: ${themeBorder('default', 'toggleBorder')};
  76. ${tw`sw-inline-flex`}
  77. ${tw`sw-h-control`}
  78. ${tw`sw-box-border`}
  79. ${tw`sw-font-semibold`}
  80. ${tw`sw-rounded-2`}
  81. `;
  82. const OptionButton = styled(ButtonSecondary)<{ selected: boolean }>`
  83. background: ${(props) => (props.selected ? themeColor('toggleHover') : themeColor('toggle'))};
  84. color: ${(props) => (props.selected ? themeContrast('toggleHover') : themeContrast('toggle'))};
  85. border: none;
  86. height: auto;
  87. ${tw`sw-rounded-0`};
  88. ${tw`sw-truncate`};
  89. &:first-of-type {
  90. ${tw`sw-rounded-l-2`};
  91. }
  92. &:last-of-type {
  93. ${tw`sw-rounded-r-2`};
  94. }
  95. &:not(:last-of-type) {
  96. border-right: ${themeBorder('default', 'toggleBorder')};
  97. }
  98. &:hover {
  99. background: ${themeColor('toggleHover')};
  100. color: ${themeContrast('toggleHover')};
  101. }
  102. &:focus,
  103. &:active {
  104. outline: ${themeBorder('focus', 'toggleFocus')};
  105. z-index: 1;
  106. }
  107. `;