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.

Button.tsx 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 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 { css } from '@emotion/react';
  21. import styled from '@emotion/styled';
  22. import React from 'react';
  23. import tw from 'twin.macro';
  24. import { themeBorder, themeColor, themeContrast } from '../../helpers/theme';
  25. import { ThemedProps } from '../../types/theme';
  26. import { BaseLink, LinkProps } from '../Link';
  27. type AllowedButtonAttributes = Pick<
  28. React.ButtonHTMLAttributes<HTMLButtonElement>,
  29. 'aria-label' | 'autoFocus' | 'id' | 'name' | 'role' | 'style' | 'title' | 'type' | 'form'
  30. >;
  31. export interface ButtonProps extends AllowedButtonAttributes {
  32. children?: React.ReactNode;
  33. className?: string;
  34. disabled?: boolean;
  35. download?: string;
  36. icon?: React.ReactNode;
  37. innerRef?: React.Ref<HTMLButtonElement>;
  38. isExternal?: LinkProps['isExternal'];
  39. onClick?: (event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => unknown;
  40. preventDefault?: boolean;
  41. reloadDocument?: LinkProps['reloadDocument'];
  42. stopPropagation?: boolean;
  43. target?: LinkProps['target'];
  44. to?: LinkProps['to'];
  45. }
  46. export class Button extends React.PureComponent<ButtonProps> {
  47. handleClick = (event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
  48. const { disabled, onClick, stopPropagation = false, type } = this.props;
  49. const { preventDefault = type !== 'submit' } = this.props;
  50. if (preventDefault || disabled) {
  51. event.preventDefault();
  52. }
  53. if (stopPropagation) {
  54. event.stopPropagation();
  55. }
  56. if (onClick && !disabled) {
  57. onClick(event);
  58. }
  59. };
  60. render() {
  61. const {
  62. children,
  63. disabled,
  64. icon,
  65. innerRef,
  66. onClick,
  67. preventDefault,
  68. stopPropagation,
  69. to,
  70. type = 'button',
  71. ...htmlProps
  72. } = this.props;
  73. const props = {
  74. ...htmlProps,
  75. 'aria-disabled': disabled,
  76. disabled,
  77. type,
  78. };
  79. if (to) {
  80. return (
  81. <BaseButtonLink {...props} onClick={onClick} to={to}>
  82. {icon}
  83. {children}
  84. </BaseButtonLink>
  85. );
  86. }
  87. return (
  88. <BaseButton {...props} onClick={this.handleClick} ref={innerRef}>
  89. {icon}
  90. {children}
  91. </BaseButton>
  92. );
  93. }
  94. }
  95. export const buttonStyle = (props: ThemedProps) => css`
  96. box-sizing: border-box;
  97. text-decoration: none;
  98. outline: none;
  99. border: var(--border);
  100. color: var(--color);
  101. background-color: var(--background);
  102. transition:
  103. background-color 0.2s ease,
  104. outline 0.2s ease;
  105. ${tw`sw-inline-flex sw-items-center`}
  106. ${tw`sw-h-control`}
  107. ${tw`sw-body-sm-highlight`}
  108. ${tw`sw-py-2 sw-px-4`}
  109. ${tw`sw-rounded-2`}
  110. ${tw`sw-cursor-pointer`}
  111. &:hover, &:active {
  112. color: var(--color);
  113. background-color: var(--backgroundHover);
  114. }
  115. &:focus,
  116. &:active {
  117. color: var(--color);
  118. outline: ${themeBorder('focus', 'var(--focus)')(props)};
  119. }
  120. &:disabled,
  121. &:disabled:hover {
  122. color: ${themeContrast('buttonDisabled')(props)};
  123. background-color: ${themeColor('buttonDisabled')(props)};
  124. border: ${themeBorder('default', 'buttonDisabledBorder')(props)};
  125. ${tw`sw-cursor-not-allowed`}
  126. }
  127. & > svg {
  128. ${tw`sw-mr-1`}
  129. }
  130. `;
  131. const BaseButtonLink = styled(BaseLink)`
  132. ${buttonStyle}
  133. `;
  134. const BaseButton = styled.button`
  135. ${buttonStyle}
  136. /*
  137. Workaround for tooltips issue with onMouseLeave in disabled buttons:
  138. https://github.com/facebook/react/issues/4251
  139. */
  140. & [disabled] {
  141. ${tw`sw-pointer-events-none`};
  142. }
  143. `;