import { PropsWithChildren } from 'react';
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 { getTabId, getTabPanelId } from '../sonar-aligned/helpers/tabs';
import { Badge } from './Badge';
type TabValueType = string | number | boolean;
+++ /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 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';
-
-type ToggleButtonValueType = string | number | boolean;
-
-export interface ToggleButtonsOption<T extends ToggleButtonValueType> {
- counter?: number;
- disabled?: boolean;
- label: string | React.ReactNode;
- value: T;
-}
-
-export interface ButtonToggleProps<T extends ToggleButtonValueType> {
- disabled?: boolean;
- label?: string;
- onChange: (value: T) => void;
- options: ReadonlyArray<ToggleButtonsOption<T>>;
- role?: 'radiogroup' | 'tablist';
- value?: T;
-}
-
-export function ToggleButton<T extends ToggleButtonValueType>(props: ButtonToggleProps<T>) {
- const { disabled = false, label, options, value, role = 'radiogroup' } = props;
- const isRadioGroup = role === 'radiogroup';
-
- return (
- <Wrapper aria-label={label} role={role}>
- {options.map((option) => (
- <OptionButton
- aria-checked={isRadioGroup ? option.value === value : undefined}
- aria-controls={isRadioGroup ? undefined : getTabPanelId(String(option.value))}
- aria-current={option.value === value}
- data-value={option.value}
- disabled={disabled || option.disabled}
- id={getTabId(String(option.value))}
- key={option.value.toString()}
- onClick={() => {
- if (option.value !== value) {
- props.onChange(option.value);
- }
- }}
- role={isRadioGroup ? 'radio' : 'tab'}
- selected={option.value === value}
- >
- {option.label}
- {option.counter ? (
- <Badge className="sw-ml-1" variant="counter">
- {option.counter}
- </Badge>
- ) : null}
- </OptionButton>
- ))}
- </Wrapper>
- );
-}
-
-const Wrapper = styled.div`
- border: ${themeBorder('default', 'toggleBorder')};
-
- ${tw`sw-inline-flex`}
- ${tw`sw-h-control`}
- ${tw`sw-box-border`}
- ${tw`sw-font-semibold`}
- ${tw`sw-rounded-2`}
-`;
-
-const OptionButton = styled(ButtonSecondary)<{ selected: boolean }>`
- background: ${(props) => (props.selected ? themeColor('toggleHover') : themeColor('toggle'))};
- color: ${(props) => (props.selected ? themeContrast('toggleHover') : themeContrast('toggle'))};
- border: none;
- height: auto;
- ${tw`sw-rounded-0`};
- ${tw`sw-truncate`};
-
- &:first-of-type {
- ${tw`sw-rounded-l-2`};
- }
-
- &:last-of-type {
- ${tw`sw-rounded-r-2`};
- }
-
- &:not(:last-of-type) {
- border-right: ${themeBorder('default', 'toggleBorder')};
- }
-
- &:hover {
- background: ${themeColor('toggleHover')};
- color: ${themeContrast('toggleHover')};
- }
-
- &:focus,
- &:active {
- outline: ${themeBorder('focus', 'toggleFocus')};
- z-index: 1;
- }
-`;
+++ /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 { screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { getTabPanelId } from '../../helpers';
-import { render } from '../../helpers/testUtils';
-import { FCProps } from '../../types/misc';
-import { ToggleButton, ToggleButtonsOption } from '../ToggleButton';
-
-it('should render all options', async () => {
- const user = userEvent.setup();
- const onChange = jest.fn();
- const options: Array<ToggleButtonsOption<number>> = [
- { value: 1, label: 'first' },
- { value: 2, label: 'disabled', disabled: true },
- { value: 3, label: 'has counter', counter: 7 },
- ];
- renderToggleButtons({ onChange, options, value: 1 });
-
- expect(screen.getAllByRole('radio')).toHaveLength(3);
-
- await user.click(screen.getByText('first'));
-
- expect(onChange).not.toHaveBeenCalled();
-
- await user.click(screen.getByText('has counter'));
-
- expect(onChange).toHaveBeenCalledWith(3);
-});
-
-it('should work in tablist mode', () => {
- const onChange = jest.fn();
- const options: Array<ToggleButtonsOption<number>> = [
- { value: 1, label: 'first' },
- { value: 2, label: 'second' },
- { value: 3, label: 'third' },
- ];
- renderToggleButtons({ onChange, options, value: 1, role: 'tablist' });
-
- expect(screen.getAllByRole('tab')).toHaveLength(3);
- expect(screen.getByRole('tab', { name: 'second' })).toHaveAttribute(
- 'aria-controls',
- getTabPanelId(2),
- );
-});
-
-function renderToggleButtons(props: Partial<FCProps<typeof ToggleButton>> = {}) {
- return render(<ToggleButton onChange={jest.fn()} options={[]} {...props} />);
-}
export * from './Text';
export * from './TextAccordion';
export * from './Title';
-export * from './ToggleButton';
export { Tooltip } from './Tooltip';
export { TopBar } from './TopBar';
export * from './TreeMap';
+++ /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 { getTabId, getTabPanelId } from '../tabs';
-
-it('should correctly generate IDs', () => {
- expect(getTabId('ID')).toBe('tab-ID');
- expect(getTabPanelId('ID')).toBe('tabpanel-ID');
-});
export * from './constants';
export * from './keyboard';
export * from './positioning';
-export * from './tabs';
export * from './theme';
+++ /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 function getTabPanelId(key: string | number) {
- return `tabpanel-${key}`;
-}
-
-export function getTabId(key: string | number) {
- return `tab-${key}`;
-}
--- /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 tw from 'twin.macro';
+import { Badge } from '../../components/Badge';
+import { themeBorder, themeColor, themeContrast } from '../../helpers/theme';
+import { getTabId, getTabPanelId } from '../helpers/tabs';
+import { ButtonSecondary } from './buttons';
+
+type ToggleButtonValueType = string | number | boolean;
+
+export interface ToggleButtonsOption<T extends ToggleButtonValueType> {
+ counter?: number;
+ disabled?: boolean;
+ label: string | React.ReactNode;
+ value: T;
+}
+
+export interface ButtonToggleProps<T extends ToggleButtonValueType> {
+ disabled?: boolean;
+ label?: string;
+ onChange: (value: T) => void;
+ options: ReadonlyArray<ToggleButtonsOption<T>>;
+ role?: 'radiogroup' | 'tablist';
+ value?: T;
+}
+
+export function ToggleButton<T extends ToggleButtonValueType>(props: ButtonToggleProps<T>) {
+ const { disabled = false, label, options, value, role = 'radiogroup' } = props;
+ const isRadioGroup = role === 'radiogroup';
+
+ return (
+ <Wrapper aria-label={label} role={role}>
+ {options.map((option) => (
+ <OptionButton
+ aria-checked={isRadioGroup ? option.value === value : undefined}
+ aria-controls={isRadioGroup ? undefined : getTabPanelId(String(option.value))}
+ aria-current={option.value === value}
+ data-value={option.value}
+ disabled={disabled || option.disabled}
+ id={getTabId(String(option.value))}
+ key={option.value.toString()}
+ onClick={() => {
+ if (option.value !== value) {
+ props.onChange(option.value);
+ }
+ }}
+ role={isRadioGroup ? 'radio' : 'tab'}
+ selected={option.value === value}
+ >
+ {option.label}
+ {option.counter ? (
+ <Badge className="sw-ml-1" variant="counter">
+ {option.counter}
+ </Badge>
+ ) : null}
+ </OptionButton>
+ ))}
+ </Wrapper>
+ );
+}
+
+const Wrapper = styled.div`
+ border: ${themeBorder('default', 'toggleBorder')};
+
+ ${tw`sw-inline-flex`}
+ ${tw`sw-h-control`}
+ ${tw`sw-box-border`}
+ ${tw`sw-font-semibold`}
+ ${tw`sw-rounded-2`}
+`;
+
+const OptionButton = styled(ButtonSecondary)<{ selected: boolean }>`
+ background: ${(props) => (props.selected ? themeColor('toggleHover') : themeColor('toggle'))};
+ color: ${(props) => (props.selected ? themeContrast('toggleHover') : themeContrast('toggle'))};
+ border: none;
+ height: auto;
+ ${tw`sw-rounded-0`};
+ ${tw`sw-truncate`};
+
+ &:first-of-type {
+ ${tw`sw-rounded-l-2`};
+ }
+
+ &:last-of-type {
+ ${tw`sw-rounded-r-2`};
+ }
+
+ &:not(:last-of-type) {
+ border-right: ${themeBorder('default', 'toggleBorder')};
+ }
+
+ &:hover {
+ background: ${themeColor('toggleHover')};
+ color: ${themeContrast('toggleHover')};
+ }
+
+ &:focus,
+ &:active {
+ outline: ${themeBorder('focus', 'toggleFocus')};
+ z-index: 1;
+ }
+`;
--- /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 { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { render } from '../../../helpers/testUtils';
+import { FCProps } from '../../../types/misc';
+import { getTabPanelId } from '../../helpers';
+import { ToggleButton, ToggleButtonsOption } from '../ToggleButton';
+
+it('should render all options', async () => {
+ const user = userEvent.setup();
+ const onChange = jest.fn();
+ const options: Array<ToggleButtonsOption<number>> = [
+ { value: 1, label: 'first' },
+ { value: 2, label: 'disabled', disabled: true },
+ { value: 3, label: 'has counter', counter: 7 },
+ ];
+ renderToggleButtons({ onChange, options, value: 1 });
+
+ expect(screen.getAllByRole('radio')).toHaveLength(3);
+
+ await user.click(screen.getByText('first'));
+
+ expect(onChange).not.toHaveBeenCalled();
+
+ await user.click(screen.getByText('has counter'));
+
+ expect(onChange).toHaveBeenCalledWith(3);
+});
+
+it('should work in tablist mode', () => {
+ const onChange = jest.fn();
+ const options: Array<ToggleButtonsOption<number>> = [
+ { value: 1, label: 'first' },
+ { value: 2, label: 'second' },
+ { value: 3, label: 'third' },
+ ];
+ renderToggleButtons({ onChange, options, value: 1, role: 'tablist' });
+
+ expect(screen.getAllByRole('tab')).toHaveLength(3);
+ expect(screen.getByRole('tab', { name: 'second' })).toHaveAttribute(
+ 'aria-controls',
+ getTabPanelId(2),
+ );
+});
+
+function renderToggleButtons(props: Partial<FCProps<typeof ToggleButton>> = {}) {
+ return render(<ToggleButton onChange={jest.fn()} options={[]} {...props} />);
+}
export { DismissableFlagMessage, FlagMessage } from './FlagMessage';
export * from './MetricsRatingBadge';
export * from './Table';
+export * from './ToggleButton';
export * from './buttons';
export * from './typography';
--- /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 { getTabId, getTabPanelId } from '../tabs';
+
+it('should correctly generate IDs', () => {
+ expect(getTabId('ID')).toBe('tab-ID');
+ expect(getTabPanelId('ID')).toBe('tabpanel-ID');
+});
--- /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 './tabs';
--- /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 function getTabPanelId(key: string | number) {
+ return `tabpanel-${key}`;
+}
+
+export function getTabId(key: string | number) {
+ return `tab-${key}`;
+}
*/
export * from './components';
+export * from './helpers';
export * from './types';