'src/components/**/*.{ts,tsx,js}',
'src/helpers/**/*.{ts,tsx,js}',
'src/hooks/**/*.{ts,tsx,js}',
+ 'src/sonar-aligned/**/*.{ts,tsx,js}',
'!src/helpers/{keycodes,testUtils}.{ts,tsx}',
],
coverageReporters: ['lcovonly', 'text'],
import React from 'react';
import tw from 'twin.macro';
import { themeBorder, themeColor, themeContrast } from '../helpers';
+import { BareButton } from '../sonar-aligned/components/buttons';
import { ThemedProps } from '../types';
-import { BareButton } from './buttons';
import { OpenCloseIndicator } from './icons/OpenCloseIndicator';
interface AccordionProps {
import classNames from 'classnames';
import { uniqueId } from 'lodash';
import * as React from 'react';
-import { BareButton } from './buttons';
+import { BareButton } from '../sonar-aligned/components/buttons';
import { OpenCloseIndicator } from './icons/OpenCloseIndicator';
interface AccordionProps {
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
import tw from 'twin.macro';
import { themeColor, themeContrast } from '../helpers';
+import { ButtonSecondary } from '../sonar-aligned/components/buttons';
import { BubbleColorVal } from '../types/charts';
import { Note } from './Text';
import { Tooltip } from './Tooltip';
-import { ButtonSecondary } from './buttons';
const TICKS_COUNT = 5;
import { ReactNode } from 'react';
import tw from 'twin.macro';
import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
-import { BareButton } from './buttons';
+import { BareButton } from '../sonar-aligned/components/buttons';
import { OpenCloseIndicator } from './icons/OpenCloseIndicator';
interface Props {
import * as React from 'react';
import tw from 'twin.macro';
import { themeColor } from '../helpers';
+import { BareButton } from '../sonar-aligned/components/buttons';
import { Badge } from './Badge';
import { DestructiveIcon } from './InteractiveIcon';
import { Spinner } from './Spinner';
import { Tooltip as SCTooltip } from './Tooltip';
-import { BareButton } from './buttons';
import { OpenCloseIndicator } from './icons';
import { CloseIcon } from './icons/CloseIcon';
import tw from 'twin.macro';
import { themeBorder, themeColor, themeContrast } from '../helpers';
import { isDefined } from '../helpers/types';
-import { ButtonProps, ButtonSecondary } from './buttons';
+import { ButtonProps, ButtonSecondary } from '../sonar-aligned/components/buttons';
export type FacetItemProps = Omit<ButtonProps, 'name' | 'onClick'> & {
active?: boolean;
import styled from '@emotion/styled';
import tw from 'twin.macro';
import { themeColor } from '../helpers/theme';
-import { ButtonProps, DangerButtonSecondary } from './buttons';
+import { ButtonProps, DangerButtonSecondary } from '../sonar-aligned/components/buttons';
import { ChevronRightIcon } from './icons/ChevronRightIcon';
const StyledFailedQGConditionLink = styled(DangerButtonSecondary)`
import { ReactNode } from 'react';
import tw from 'twin.macro';
import { themeBorder, themeColor } from '../helpers/theme';
-import { BareButton } from './buttons';
+import { BareButton } from '../sonar-aligned/components/buttons';
interface Props {
className?: string;
import tw from 'twin.macro';
import { GLOBAL_POPUP_Z_INDEX, PopupZLevel, themeColor } from '../helpers';
import { findAnchor } from '../helpers/dom';
-import { ButtonLink, ButtonPrimary, WrapperButton } from './buttons';
+import { ButtonPrimary } from '../sonar-aligned/components/buttons';
+import { ButtonLink, WrapperButton } from './buttons';
import { CloseIcon } from './icons';
import { PopupWrapper } from './popups';
import tw from 'twin.macro';
import { OPACITY_20_PERCENT, themeBorder, themeColor } from '../helpers';
import { getTabId, getTabPanelId } from '../helpers/tabs';
+import { BareButton } from '../sonar-aligned/components/buttons';
import { Badge } from './Badge';
-import { BareButton } from './buttons';
type TabValueType = string | number | boolean;
import React, { ReactNode } from 'react';
import tw from 'twin.macro';
import { themeColor } from '../helpers';
+import { BareButton } from '../sonar-aligned/components/buttons';
import { Note } from './Text';
-import { BareButton } from './buttons';
import { OpenCloseIndicator } from './icons';
interface Props {
import tw from 'twin.macro';
import { getTabId, getTabPanelId } from '../helpers/tabs';
import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
+import { ButtonSecondary } from '../sonar-aligned/components/buttons';
import { Badge } from './Badge';
-import { ButtonSecondary } from './buttons';
type ToggleButtonValueType = string | number | boolean;
*/
import { screen } from '@testing-library/react';
import { renderWithRouter } from '../../helpers/testUtils';
+import { ButtonSecondary } from '../../sonar-aligned/components/buttons';
import { ActionsDropdown, Dropdown } from '../Dropdown';
-import { ButtonSecondary } from '../buttons';
describe('Dropdown', () => {
it('renders', async () => {
import styled from '@emotion/styled';
import tw from 'twin.macro';
import { themeBorder, themeColor, themeContrast } from '../../helpers';
-
-export const BareButton = styled.button`
- all: unset;
- cursor: pointer;
-
- &:focus-visible {
- background-color: ${themeColor('dropdownMenuHover')};
- }
-`;
+import { BareButton } from '../../sonar-aligned/components/buttons';
interface CodeViewerExpanderProps {
direction: 'UP' | 'DOWN';
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { css } from '@emotion/react';
-import styled from '@emotion/styled';
-import React from 'react';
-import tw from 'twin.macro';
-import { themeBorder, themeColor, themeContrast } from '../../helpers/theme';
-import { ThemedProps } from '../../types/theme';
-import { BaseLink, LinkProps } from '../Link';
-
-type AllowedButtonAttributes = Pick<
- React.ButtonHTMLAttributes<HTMLButtonElement>,
- 'aria-label' | 'autoFocus' | 'id' | 'name' | 'role' | 'style' | 'title' | 'type' | 'form'
->;
-
-export interface ButtonProps extends AllowedButtonAttributes {
- children?: React.ReactNode;
- className?: string;
- disabled?: boolean;
- download?: string;
- icon?: React.ReactNode;
- innerRef?: React.Ref<HTMLButtonElement>;
- isExternal?: LinkProps['isExternal'];
- onClick?: (event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => unknown;
-
- preventDefault?: boolean;
- reloadDocument?: LinkProps['reloadDocument'];
- stopPropagation?: boolean;
- target?: LinkProps['target'];
- to?: LinkProps['to'];
-}
-
-export class Button extends React.PureComponent<ButtonProps> {
- handleClick = (event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
- const { disabled, onClick, stopPropagation = false, type } = this.props;
- const { preventDefault = type !== 'submit' } = this.props;
-
- if (preventDefault || disabled) {
- event.preventDefault();
- }
-
- if (stopPropagation) {
- event.stopPropagation();
- }
-
- if (onClick && !disabled) {
- onClick(event);
- }
- };
-
- render() {
- const {
- children,
- disabled,
- icon,
- innerRef,
- onClick,
- preventDefault,
- stopPropagation,
- to,
- type = 'button',
- ...htmlProps
- } = this.props;
-
- const props = {
- ...htmlProps,
- 'aria-disabled': disabled,
- disabled,
- type,
- };
-
- if (to) {
- return (
- <BaseButtonLink {...props} onClick={onClick} to={to}>
- {icon}
- {children}
- </BaseButtonLink>
- );
- }
-
- return (
- <BaseButton {...props} onClick={this.handleClick} ref={innerRef}>
- {icon}
- {children}
- </BaseButton>
- );
- }
-}
-
-export const buttonStyle = (props: ThemedProps) => css`
- box-sizing: border-box;
- text-decoration: none;
- outline: none;
- border: var(--border);
- color: var(--color);
- background-color: var(--background);
- transition:
- background-color 0.2s ease,
- outline 0.2s ease;
-
- ${tw`sw-inline-flex sw-items-center`}
- ${tw`sw-h-control`}
- ${tw`sw-body-sm-highlight`}
- ${tw`sw-py-2 sw-px-4`}
- ${tw`sw-rounded-2`}
- ${tw`sw-cursor-pointer`}
-
- &:hover, &:active {
- color: var(--color);
- background-color: var(--backgroundHover);
- }
-
- &:focus,
- &:active {
- color: var(--color);
- outline: ${themeBorder('focus', 'var(--focus)')(props)};
- }
-
- &:disabled,
- &:disabled:hover {
- color: ${themeContrast('buttonDisabled')(props)};
- background-color: ${themeColor('buttonDisabled')(props)};
- border: ${themeBorder('default', 'buttonDisabledBorder')(props)};
-
- ${tw`sw-cursor-not-allowed`}
- }
-
- & > svg {
- ${tw`sw-mr-1`}
- }
-`;
-
-const BaseButtonLink = styled(BaseLink)`
- ${buttonStyle}
-
- /*
- Workaround to apply disable style to button-link
- as link does not have disabled attribute, using props instead
- */
-
- ${({ disabled, theme }) =>
- disabled
- ? `&, &:hover, &:focus, &:active {
- color: ${themeContrast('buttonDisabled')({ theme })};
- background-color: ${themeColor('buttonDisabled')({ theme })};
- border: ${themeBorder('default', 'buttonDisabledBorder')({ theme })};
- cursor: not-allowed;
- }`
- : undefined};
-`;
-
-const BaseButton = styled.button`
- ${buttonStyle}
-
- /*
- Workaround for tooltips issue with onMouseLeave in disabled buttons:
- https://github.com/facebook/react/issues/4251
- */
- & [disabled] {
- ${tw`sw-pointer-events-none`};
- }
-`;
import styled from '@emotion/styled';
import tw from 'twin.macro';
import { themeBorder, themeColor } from '../../helpers';
-import { BareButton } from './BareButtons';
+import { BareButton } from '../../sonar-aligned/components/buttons';
export const ButtonLink = styled(BareButton)`
color: ${themeColor('linkDefault')};
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { css } from '@emotion/react';
-import styled from '@emotion/styled';
-import { OPACITY_20_PERCENT, themeBorder, themeColor, themeContrast } from '../../helpers';
-import { ThemedProps } from '../../types';
-import { Button, ButtonProps } from './Button';
-
-export const PrimaryStyle = (props: ThemedProps) => css`
- --background: ${themeColor('button')(props)};
- --backgroundHover: ${themeColor('buttonHover')(props)};
- --color: ${themeContrast('primary')(props)};
- --focus: ${themeColor('button', OPACITY_20_PERCENT)(props)};
- --border: ${themeBorder('default', 'transparent')(props)};
-`;
-
-export const ButtonPrimary: React.FC<React.PropsWithChildren<ButtonProps>> = styled(Button)`
- ${PrimaryStyle}
-`;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import styled from '@emotion/styled';
-import { OPACITY_20_PERCENT, themeBorder, themeColor, themeContrast } from '../../helpers';
-import { Button, ButtonProps } from './Button';
-
-export const ButtonSecondary: React.FC<React.PropsWithChildren<ButtonProps>> = styled(Button)`
- --background: ${themeColor('buttonSecondary')};
- --backgroundHover: ${themeColor('buttonSecondaryHover')};
- --color: ${themeContrast('buttonSecondary')};
- --focus: ${themeColor('buttonSecondaryBorder', OPACITY_20_PERCENT)};
- --border: ${themeBorder('default', 'buttonSecondaryBorder')};
-`;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import styled from '@emotion/styled';
-import { OPACITY_20_PERCENT, themeBorder, themeColor, themeContrast } from '../../helpers';
-import { Button, ButtonProps } from './Button';
-
-export const DangerButtonPrimary: React.FC<React.PropsWithChildren<ButtonProps>> = styled(Button)`
- --background: ${themeColor('dangerButton')};
- --backgroundHover: ${themeColor('dangerButtonHover')};
- --color: ${themeContrast('dangerButton')};
- --focus: ${themeColor('dangerButtonFocus', OPACITY_20_PERCENT)};
- --border: ${themeBorder('default', 'transparent')};
-`;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import styled from '@emotion/styled';
-import { OPACITY_20_PERCENT, themeBorder, themeColor, themeContrast } from '../../helpers';
-import { Button, ButtonProps } from './Button';
-
-export const DangerButtonSecondary: React.FC<React.PropsWithChildren<ButtonProps>> = styled(Button)`
- --background: ${themeColor('dangerButtonSecondary')};
- --backgroundHover: ${themeColor('dangerButtonSecondaryHover')};
- --color: ${themeContrast('dangerButtonSecondary')};
- --focus: ${themeColor('dangerButtonSecondaryFocus', OPACITY_20_PERCENT)};
- --border: ${themeBorder('default', 'dangerButtonSecondaryBorder')};
-`;
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import styled from '@emotion/styled';
-import { buttonStyle } from './Button';
-import { PrimaryStyle } from './ButtonPrimary';
+import { buttonStyle, PrimaryStyle } from '../../sonar-aligned/components/buttons';
export const DownloadButton = styled.a`
${buttonStyle}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import styled from '@emotion/styled';
-import React from 'react';
-import { OPACITY_20_PERCENT } from '../../helpers/constants';
-import { themeBorder, themeColor, themeContrast } from '../../helpers/theme';
-import { Button, ButtonProps } from './Button';
-
-interface ThirdPartyProps extends Omit<ButtonProps, 'Icon'> {
- iconPath: string;
- name: string;
-}
-
-export function ThirdPartyButton({
- children,
- iconPath,
- name,
- ...buttonProps
-}: Readonly<ThirdPartyProps>) {
- const size = 16;
- return (
- <ThirdPartyButtonStyled {...buttonProps}>
- <img alt={name} className="sw-mr-2" height={size} src={iconPath} width={size} />
- {children}
- </ThirdPartyButtonStyled>
- );
-}
-
-const ThirdPartyButtonStyled: React.FC<React.PropsWithChildren<ButtonProps>> = styled(Button)`
- --background: ${themeColor('thirdPartyButton')};
- --backgroundHover: ${themeColor('thirdPartyButtonHover')};
- --color: ${themeContrast('thirdPartyButton')};
- --focus: ${themeColor('thirdPartyButtonBorder', OPACITY_20_PERCENT)};
- --border: ${themeBorder('default', 'thirdPartyButtonBorder')};
-`;
*/
import styled from '@emotion/styled';
import { OPACITY_20_PERCENT, themeColor } from '../../helpers';
-import { Button, ButtonProps } from './Button';
+import { Button, ButtonProps } from '../../sonar-aligned/components/buttons';
export const WrapperButton: React.FC<React.PropsWithChildren<ButtonProps>> = styled(Button)`
--background: none;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { render, screen } from '@testing-library/react';
-import { ButtonPrimary } from '../ButtonPrimary';
-
-it('renders ButtonPrimary correctly', () => {
- render(<ButtonPrimary>Hello</ButtonPrimary>);
- expect(screen.getByRole('button', { name: 'Hello' })).toBeInTheDocument();
-});
-
-it('renders ButtonPrimary correctly when to is defined', () => {
- render(
- <ButtonPrimary download="http://link.com" to="http://link.com">
- Hello
- </ButtonPrimary>,
- );
- expect(screen.queryByRole('button', { name: 'Hello' })).not.toBeInTheDocument();
- expect(screen.getByRole('link', { name: 'Hello' })).toBeInTheDocument();
-});
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
export * from './BareButtons';
-export * from './Button';
export * from './ButtonLink';
-export * from './ButtonPrimary';
-export * from './ButtonSecondary';
-export * from './DangerButtonPrimary';
-export * from './DangerButtonSecondary';
export * from './DownloadButton';
-export * from './ThirdPartyButton';
export * from './WrapperButton';
import Clipboard from 'clipboard';
import React from 'react';
import { INTERACTIVE_TOOLTIP_DELAY } from '../helpers/constants';
+import { ButtonSecondary } from '../sonar-aligned/components/buttons';
import { DiscreetInteractiveIcon, InteractiveIcon, InteractiveIconSize } from './InteractiveIcon';
import { Tooltip } from './Tooltip';
-import { ButtonSecondary } from './buttons';
import { CopyIcon } from './icons/CopyIcon';
import { IconProps } from './icons/Icon';
import { forwardRef, Ref } from 'react';
import tw from 'twin.macro';
import { themeBorder, themeColor, themeContrast, themeShadow } from '../../helpers/theme';
-import { BareButton } from '../buttons';
+import { BareButton } from '../../sonar-aligned/components/buttons';
interface Props {
className?: string;
*/
import classNames from 'classnames';
import { useCallback, useRef, useState } from 'react';
+import { ButtonSecondary } from '../../sonar-aligned/components/buttons/ButtonSecondary';
import { Note } from '../Text';
-import { ButtonSecondary } from '../buttons/ButtonSecondary';
interface Props {
chooseLabel: string;
import styled from '@emotion/styled';
import classNames from 'classnames';
import { themeBorder } from '../../helpers';
+import { ButtonProps } from '../../sonar-aligned/components/buttons';
import { Badge } from '../Badge';
import { LightLabel } from '../Text';
-import { ButtonProps, WrapperButton } from '../buttons';
+import { WrapperButton } from '../buttons';
import { ChevronDownIcon } from '../icons';
interface Props extends Pick<ButtonProps, 'onClick'> {
import tw from 'twin.macro';
import { themeColor } from '../../helpers';
import { REACT_DOM_CONTAINER } from '../../helpers/constants';
+import { ButtonSecondary } from '../../sonar-aligned/components/buttons';
import { Theme } from '../../types/theme';
-import { ButtonSecondary } from '../buttons';
import { ModalBody } from './ModalBody';
import { ModalFooter } from './ModalFooter';
import { ModalHeader } from './ModalHeader';
import { ReactNode, useCallback, useState } from 'react';
import tw from 'twin.macro';
import { themeColor, themeContrast } from '../../helpers/theme';
-import { BareButton } from '../buttons';
+import { BareButton } from '../../sonar-aligned/components/buttons';
import { OpenCloseIndicator } from '../icons/OpenCloseIndicator';
import { SubnavigationGroup } from './SubnavigationGroup';
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import styled from '@emotion/styled';
+import { themeColor } from '../../../helpers';
+
+export const BareButton = styled.button`
+ all: unset;
+ cursor: pointer;
+
+ &:focus-visible {
+ background-color: ${themeColor('dropdownMenuHover')};
+ }
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { css } from '@emotion/react';
+import styled from '@emotion/styled';
+import React from 'react';
+import tw from 'twin.macro';
+import { BaseLink, LinkProps } from '../../../components/Link';
+import { themeBorder, themeColor, themeContrast } from '../../../helpers/theme';
+import { ThemedProps } from '../../../types/theme';
+
+type AllowedButtonAttributes = Pick<
+ React.ButtonHTMLAttributes<HTMLButtonElement>,
+ 'aria-label' | 'autoFocus' | 'id' | 'name' | 'role' | 'style' | 'title' | 'type' | 'form'
+>;
+
+export interface ButtonProps extends AllowedButtonAttributes {
+ children?: React.ReactNode;
+ className?: string;
+ disabled?: boolean;
+ download?: string;
+ icon?: React.ReactNode;
+ innerRef?: React.Ref<HTMLButtonElement>;
+ isExternal?: LinkProps['isExternal'];
+
+ onClick?: (event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => unknown;
+ preventDefault?: boolean;
+ reloadDocument?: LinkProps['reloadDocument'];
+ showExternalIcon?: boolean;
+ stopPropagation?: boolean;
+ target?: LinkProps['target'];
+ to?: LinkProps['to'];
+}
+
+export class Button extends React.PureComponent<ButtonProps> {
+ handleClick = (event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
+ const { disabled, onClick, stopPropagation = false, type } = this.props;
+ const { preventDefault = type !== 'submit' } = this.props;
+
+ if (preventDefault || disabled) {
+ event.preventDefault();
+ }
+
+ if (stopPropagation) {
+ event.stopPropagation();
+ }
+
+ if (onClick && !disabled) {
+ onClick(event);
+ }
+ };
+
+ render() {
+ const {
+ children,
+ disabled,
+ icon,
+ innerRef,
+ onClick,
+ preventDefault,
+ stopPropagation,
+ to,
+ type = 'button',
+ ...htmlProps
+ } = this.props;
+
+ const props = {
+ ...htmlProps,
+ 'aria-disabled': disabled,
+ disabled,
+ type,
+ };
+
+ if (to) {
+ return (
+ <BaseButtonLink {...props} onClick={onClick} to={to}>
+ {icon}
+ {children}
+ </BaseButtonLink>
+ );
+ }
+
+ return (
+ <BaseButton {...props} onClick={this.handleClick} ref={innerRef}>
+ {icon}
+ {children}
+ </BaseButton>
+ );
+ }
+}
+
+export const buttonStyle = (props: ThemedProps) => css`
+ box-sizing: border-box;
+ text-decoration: none;
+ outline: none;
+ border: var(--border);
+ color: var(--color);
+ background-color: var(--background);
+ transition:
+ background-color 0.2s ease,
+ outline 0.2s ease;
+
+ ${tw`sw-inline-flex sw-items-center`}
+ ${tw`sw-h-control`}
+ ${tw`sw-body-sm-highlight`}
+ ${tw`sw-py-2 sw-px-4`}
+ ${tw`sw-rounded-2`}
+ ${tw`sw-cursor-pointer`}
+
+ &:hover, &:active {
+ color: var(--color);
+ background-color: var(--backgroundHover);
+ }
+
+ &:focus,
+ &:active {
+ color: var(--color);
+ outline: ${themeBorder('focus', 'var(--focus)')(props)};
+ }
+
+ &:disabled,
+ &:disabled:hover {
+ color: ${themeContrast('buttonDisabled')(props)};
+ background-color: ${themeColor('buttonDisabled')(props)};
+ border: ${themeBorder('default', 'buttonDisabledBorder')(props)};
+
+ ${tw`sw-cursor-not-allowed`}
+ }
+
+ & > svg {
+ ${tw`sw-mr-1`}
+ }
+`;
+
+const BaseButtonLink = styled(BaseLink)`
+ ${buttonStyle}
+
+ /*
+ Workaround to apply disable style to button-link
+ as link does not have disabled attribute, using props instead
+ */
+
+ ${({ disabled, theme }) =>
+ disabled
+ ? `&, &:hover, &:focus, &:active {
+ color: ${themeContrast('buttonDisabled')({ theme })};
+ background-color: ${themeColor('buttonDisabled')({ theme })};
+ border: ${themeBorder('default', 'buttonDisabledBorder')({ theme })};
+ cursor: not-allowed;
+ }`
+ : undefined};
+`;
+
+const BaseButton = styled.button`
+ ${buttonStyle}
+
+ /*
+ Workaround for tooltips issue with onMouseLeave in disabled buttons:
+ https://github.com/facebook/react/issues/4251
+ */
+ & [disabled] {
+ ${tw`sw-pointer-events-none`};
+ }
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { css } from '@emotion/react';
+import styled from '@emotion/styled';
+import { OPACITY_20_PERCENT, themeBorder, themeColor, themeContrast } from '../../../helpers';
+import { ThemedProps } from '../../../types';
+import { Button, ButtonProps } from './Button';
+
+export const PrimaryStyle = (props: ThemedProps) => css`
+ --background: ${themeColor('button')(props)};
+ --backgroundHover: ${themeColor('buttonHover')(props)};
+ --color: ${themeContrast('primary')(props)};
+ --focus: ${themeColor('button', OPACITY_20_PERCENT)(props)};
+ --border: ${themeBorder('default', 'transparent')(props)};
+`;
+
+export const ButtonPrimary: React.FC<React.PropsWithChildren<ButtonProps>> = styled(Button)`
+ ${PrimaryStyle}
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import styled from '@emotion/styled';
+import { OPACITY_20_PERCENT, themeBorder, themeColor, themeContrast } from '../../../helpers';
+import { Button, ButtonProps } from './Button';
+
+export const ButtonSecondary: React.FC<React.PropsWithChildren<ButtonProps>> = styled(Button)`
+ --background: ${themeColor('buttonSecondary')};
+ --backgroundHover: ${themeColor('buttonSecondaryHover')};
+ --color: ${themeContrast('buttonSecondary')};
+ --focus: ${themeColor('buttonSecondaryBorder', OPACITY_20_PERCENT)};
+ --border: ${themeBorder('default', 'buttonSecondaryBorder')};
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import styled from '@emotion/styled';
+import { OPACITY_20_PERCENT, themeBorder, themeColor, themeContrast } from '../../../helpers';
+import { Button, ButtonProps } from './Button';
+
+export const DangerButtonPrimary: React.FC<React.PropsWithChildren<ButtonProps>> = styled(Button)`
+ --background: ${themeColor('dangerButton')};
+ --backgroundHover: ${themeColor('dangerButtonHover')};
+ --color: ${themeContrast('dangerButton')};
+ --focus: ${themeColor('dangerButtonFocus', OPACITY_20_PERCENT)};
+ --border: ${themeBorder('default', 'transparent')};
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import styled from '@emotion/styled';
+import { OPACITY_20_PERCENT, themeBorder, themeColor, themeContrast } from '../../../helpers';
+import { Button, ButtonProps } from './Button';
+
+export const DangerButtonSecondary: React.FC<React.PropsWithChildren<ButtonProps>> = styled(Button)`
+ --background: ${themeColor('dangerButtonSecondary')};
+ --backgroundHover: ${themeColor('dangerButtonSecondaryHover')};
+ --color: ${themeContrast('dangerButtonSecondary')};
+ --focus: ${themeColor('dangerButtonSecondaryFocus', OPACITY_20_PERCENT)};
+ --border: ${themeBorder('default', 'dangerButtonSecondaryBorder')};
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import styled from '@emotion/styled';
+import React from 'react';
+import { OPACITY_20_PERCENT } from '../../../helpers/constants';
+import { themeBorder, themeColor, themeContrast } from '../../../helpers/theme';
+import { Button, ButtonProps } from './Button';
+
+interface ThirdPartyProps extends Omit<ButtonProps, 'Icon'> {
+ iconPath: string;
+ name: string;
+}
+
+export function ThirdPartyButton({
+ children,
+ iconPath,
+ name,
+ ...buttonProps
+}: Readonly<ThirdPartyProps>) {
+ const size = 16;
+ return (
+ <ThirdPartyButtonStyled {...buttonProps}>
+ <img alt={name} className="sw-mr-2" height={size} src={iconPath} width={size} />
+ {children}
+ </ThirdPartyButtonStyled>
+ );
+}
+
+const ThirdPartyButtonStyled: React.FC<React.PropsWithChildren<ButtonProps>> = styled(Button)`
+ --background: ${themeColor('thirdPartyButton')};
+ --backgroundHover: ${themeColor('thirdPartyButtonHover')};
+ --color: ${themeContrast('thirdPartyButton')};
+ --focus: ${themeColor('thirdPartyButtonBorder', OPACITY_20_PERCENT)};
+ --border: ${themeBorder('default', 'thirdPartyButtonBorder')};
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { render, screen } from '@testing-library/react';
+import { ButtonPrimary } from '../ButtonPrimary';
+
+it('renders ButtonPrimary correctly', () => {
+ render(<ButtonPrimary>Hello</ButtonPrimary>);
+ expect(screen.getByRole('button', { name: 'Hello' })).toBeInTheDocument();
+});
+
+it('renders ButtonPrimary correctly when to is defined', () => {
+ render(
+ <ButtonPrimary download="http://link.com" to="http://link.com">
+ Hello
+ </ButtonPrimary>,
+ );
+ expect(screen.queryByRole('button', { name: 'Hello' })).not.toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'Hello' })).toBeInTheDocument();
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+export * from './BareButton';
+export * from './Button';
+export * from './ButtonPrimary';
+export * from './ButtonSecondary';
+export * from './DangerButtonPrimary';
+export * from './DangerButtonSecondary';
+export * from './ThirdPartyButton';
export * from './Card';
export * from './MetricsRatingBadge';
+export * from './buttons';