From c49df4a6f7ee55283c891139d79418a482538769 Mon Sep 17 00:00:00 2001 From: Kevin Silva Date: Wed, 25 Oct 2023 14:59:52 +0200 Subject: [PATCH] SONAR-20878 Create Switch component into Design System --- .../design-system/src/components/Switch.tsx | 119 ++++++++++++++++++ .../src/components/__tests__/Switch-test.tsx | 64 ++++++++++ .../design-system/src/components/index.ts | 1 + 3 files changed, 184 insertions(+) create mode 100644 server/sonar-web/design-system/src/components/Switch.tsx create mode 100644 server/sonar-web/design-system/src/components/__tests__/Switch-test.tsx diff --git a/server/sonar-web/design-system/src/components/Switch.tsx b/server/sonar-web/design-system/src/components/Switch.tsx new file mode 100644 index 00000000000..7ff7b0a22d5 --- /dev/null +++ b/server/sonar-web/design-system/src/components/Switch.tsx @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { themeBorder, themeColor, themeContrast, themeShadow } from '../helpers'; +import { CheckIcon } from './icons'; + +interface Props { + disabled?: boolean; + labels: { + off: string; + on: string; + }; + name?: string; + onChange?: (value: boolean) => void; + value: boolean | string; +} + +const getValue = (value: boolean | string) => { + return typeof value === 'string' ? value === 'true' : value; +}; + +export function Switch(props: Readonly) { + const { disabled, onChange, name, labels } = props; + const value = getValue(props.value); + + const handleClick = () => { + if (!disabled && onChange) { + const value = getValue(props.value); + onChange(!value); + } + }; + + return ( + + + {value && } + + + ); +} + +interface StyledProps { + active: boolean; + disabled?: boolean; +} + +const CheckIconContainer = styled.div` + ${tw`sw-rounded-pill`} + ${tw`sw-flex sw-items-center sw-justify-center`} + ${tw`sw-w-4 sw-h-4`} + color: ${({ disabled }) => + disabled ? themeContrast('switchButtonDisabled') : themeContrast('switchButton')}; + background: ${({ disabled }) => + disabled ? themeColor('switchButtonDisabled') : themeColor('switchButton')}; + border: none; + box-shadow: ${themeShadow('xs')}; + transform: ${({ active }) => (active ? 'translateX(1rem)' : 'translateX(0)')}; + cursor: inherit; + transition: transform 0.3s ease; +`; + +const StyledSwitch = styled.button` + ${tw`sw-flex sw-flex-row`} + ${tw`sw-rounded-pill`} + ${tw`sw-p-1/2`} + ${tw`sw-cursor-pointer`} + width: 2.25rem; + height: 1.25rem; + background: ${({ active }) => (active ? themeColor('switchActive') : themeColor('switch'))}; + border: none; + transition: 0.3s ease; + transition-property: background, outline; + + &:hover:not(:disabled), + &:active:not(:disabled), + &:focus:not(:disabled) { + background: ${({ active }) => + active ? themeColor('switchHoverActive') : themeColor('switchHover')}; + ${CheckIconContainer} { + color: ${themeContrast('switchHover')}; + } + } + + &:disabled { + background: ${themeColor('switchDisabled')}; + } + + &:focus:not(:disabled), + &:active:not(:disabled) { + outline: ${({ active }) => + active ? themeBorder('focus', 'switchActive') : themeBorder('focus', 'switch')}; + } +`; diff --git a/server/sonar-web/design-system/src/components/__tests__/Switch-test.tsx b/server/sonar-web/design-system/src/components/__tests__/Switch-test.tsx new file mode 100644 index 00000000000..ed43459a201 --- /dev/null +++ b/server/sonar-web/design-system/src/components/__tests__/Switch-test.tsx @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { Switch } from '../Switch'; + +const defaultProps = { + labels: { + off: 'Off', + on: 'On', + }, + value: false, +}; + +it('renders switch correctly if value is false and change to true on click', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + + render(); + const switchContainer = screen.getByRole('switch'); + expect(switchContainer).not.toBeChecked(); + + await user.click(switchContainer); + + expect(onChange).toHaveBeenCalledWith(true); +}); + +it('renders switch correctly if value is true and change to false on click', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + + render(); + const switchContainer = screen.getByRole('switch'); + expect(switchContainer).toBeChecked(); + + await user.click(switchContainer); + + expect(onChange).toHaveBeenCalledWith(false); +}); + +it('renders switch correctly if value is true and disabled', () => { + render(); + const switchContainer = screen.getByRole('switch'); + expect(switchContainer).toBeDisabled(); +}); diff --git a/server/sonar-web/design-system/src/components/index.ts b/server/sonar-web/design-system/src/components/index.ts index 9064185c0b4..4e103229287 100644 --- a/server/sonar-web/design-system/src/components/index.ts +++ b/server/sonar-web/design-system/src/components/index.ts @@ -70,6 +70,7 @@ export * from './SonarCodeColorizer'; export * from './SonarQubeLogo'; export { Spinner } from './Spinner'; export * from './SpotlightTour'; +export * from './Switch'; export * from './Table'; export * from './Tags'; export * from './Text'; -- 2.39.5