From: 7PH Date: Tue, 9 May 2023 10:49:55 +0000 (+0200) Subject: SONAR-19246 Implement sub navigation in the new component library X-Git-Tag: 10.1.0.73491~320 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=23cdf7853c8e1d8adea934361c7f36d2c635cff5;p=sonarqube.git SONAR-19246 Implement sub navigation in the new component library --- diff --git a/server/sonar-web/design-system/src/components/index.ts b/server/sonar-web/design-system/src/components/index.ts index 553212a1502..9164936c048 100644 --- a/server/sonar-web/design-system/src/components/index.ts +++ b/server/sonar-web/design-system/src/components/index.ts @@ -57,3 +57,4 @@ export * from './buttons'; export * from './icons'; export * from './layouts'; export * from './popups'; +export * from './subnavigation'; diff --git a/server/sonar-web/design-system/src/components/subnavigation/SubnavigationAccordion.tsx b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationAccordion.tsx new file mode 100644 index 00000000000..38c1d11a996 --- /dev/null +++ b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationAccordion.tsx @@ -0,0 +1,82 @@ +/* + * 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 { ReactNode, useCallback, useState } from 'react'; +import tw from 'twin.macro'; +import { themeColor, themeContrast } from '../../helpers/theme'; +import { BareButton } from '../buttons'; +import { OpenCloseIndicator } from '../icons/OpenCloseIndicator'; +import { SubnavigationGroup } from './SubnavigationGroup'; + +interface Props { + children: ReactNode; + className?: string; + header: ReactNode; + id: string; + initExpanded?: boolean; +} + +export function SubnavigationAccordion(props: Props) { + const { children, className, header, id, initExpanded } = props; + const [expanded, setExpanded] = useState(initExpanded); + const toggleExpanded = useCallback(() => { + setExpanded((expanded) => !expanded); + }, [setExpanded]); + + return ( + + + {header} + + + {expanded && children} + + ); +} + +const SubnavigationAccordionItem = styled(BareButton)` + ${tw`sw-flex sw-items-center sw-justify-between`} + ${tw`sw-box-border`} + ${tw`sw-body-sm-highlight`} + ${tw`sw-p-4`} + ${tw`sw-w-full`} + ${tw`sw-cursor-pointer`} + + color: ${themeContrast('subnavigation')}; + background-color: ${themeColor('subnavigation')}; + transition: 0.2 ease; + transition-property: border-left, background-color, color; + + &:hover, + &:focus { + background-color: ${themeColor('subnavigationHover')}; + } +`; +SubnavigationAccordionItem.displayName = 'SubnavigationAccordionItem'; diff --git a/server/sonar-web/design-system/src/components/subnavigation/SubnavigationGroup.tsx b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationGroup.tsx new file mode 100644 index 00000000000..c26670c2023 --- /dev/null +++ b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationGroup.tsx @@ -0,0 +1,59 @@ +/* + * 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 { Children, Fragment, HtmlHTMLAttributes, ReactNode } from 'react'; +import tw from 'twin.macro'; +import { themeBorder, themeColor } from '../../helpers/theme'; +import { isDefined } from '../../helpers/types'; + +interface Props extends HtmlHTMLAttributes { + children: ReactNode; + className?: string; +} + +export function SubnavigationGroup({ className, children, ...htmlProps }: Props) { + const childrenArray = Children.toArray(children).filter(isDefined); + return ( + + {childrenArray.map((child, index) => ( + + {child} + {index < childrenArray.length - 1 && } + + ))} + + ); +} + +const Group = styled.div` + ${tw`sw-relative`} + ${tw`sw-flex sw-flex-col`} + ${tw`sw-w-full`} + + background-color: ${themeColor('subnavigation')}; + border: ${themeBorder('default', 'subnavigationBorder')}; +`; + +const Separator = styled.div` + ${tw`sw-w-full`} + + height: 1px; + background-color: ${themeColor('subnavigationSeparator')}; +`; diff --git a/server/sonar-web/design-system/src/components/subnavigation/SubnavigationHeading.tsx b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationHeading.tsx new file mode 100644 index 00000000000..5ae5f9f95e2 --- /dev/null +++ b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationHeading.tsx @@ -0,0 +1,34 @@ +/* + * 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 { themeColor, themeContrast } from '../../helpers/theme'; + +export const SubnavigationHeading = styled.div` + ${tw`sw-flex sw-items-center sw-justify-between`} + ${tw`sw-box-border`} + ${tw`sw-body-sm`} + ${tw`sw-p-4`} + ${tw`sw-w-full`} + + color: ${themeContrast('subnavigation')}; + background-color: ${themeColor('subnavigation')}; +`; +SubnavigationHeading.displayName = 'SubnavigationHeading'; diff --git a/server/sonar-web/design-system/src/components/subnavigation/SubnavigationItem.tsx b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationItem.tsx new file mode 100644 index 00000000000..eade58670ab --- /dev/null +++ b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationItem.tsx @@ -0,0 +1,78 @@ +/* + * 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 classNames from 'classnames'; +import { ReactNode, useCallback } from 'react'; +import tw, { theme as twTheme } from 'twin.macro'; +import { themeBorder, themeColor, themeContrast } from '../../helpers/theme'; +import { BareButton } from '../buttons'; + +interface Props { + active?: boolean; + children: ReactNode; + className?: string; + innerRef?: (node: HTMLButtonElement) => void; + onClick: (value?: string) => void; + value?: string; +} + +export function SubnavigationItem(props: Props) { + const { active, className, children, innerRef, onClick, value } = props; + const handleClick = useCallback(() => { + onClick(value); + }, [onClick, value]); + return ( + + {children} + + ); +} + +const SubnavigationItemStyled = styled(BareButton)` + ${tw`sw-flex sw-items-center sw-justify-between`} + ${tw`sw-box-border`} + ${tw`sw-body-sm`} + ${tw`sw-py-4 sw-pr-4`} + ${tw`sw-w-full`} + ${tw`sw-cursor-pointer`} + + padding-left: calc(${twTheme('spacing.4')} - 3px); + color: ${themeContrast('subnavigation')}; + background-color: ${themeColor('subnavigation')}; + border-left: ${themeBorder('active', 'transparent')}; + transition: 0.2 ease; + transition-property: border-left, background-color, color; + + &:hover, + &:focus, + &.active { + background-color: ${themeColor('subnavigationHover')}; + } + + &.active { + color: ${themeContrast('subnavigationHover')}; + border-left: ${themeBorder('active')}; + } +`; diff --git a/server/sonar-web/design-system/src/components/subnavigation/__tests__/SubnavigationAccordion-test.tsx b/server/sonar-web/design-system/src/components/subnavigation/__tests__/SubnavigationAccordion-test.tsx new file mode 100644 index 00000000000..4491543456d --- /dev/null +++ b/server/sonar-web/design-system/src/components/subnavigation/__tests__/SubnavigationAccordion-test.tsx @@ -0,0 +1,57 @@ +/* + * 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 { FCProps } from '../../../types/misc'; +import { SubnavigationAccordion } from '../SubnavigationAccordion'; + +it('should have correct style and html structure', () => { + setupWithProps(); + + expect(screen.getByRole('button', { expanded: false })).toBeVisible(); + expect(screen.queryByText('Foo')).not.toBeInTheDocument(); +}); + +it('should display expanded', () => { + setupWithProps({ initExpanded: true }); + + expect(screen.getByRole('button', { expanded: true })).toBeVisible(); + expect(screen.getByText('Foo')).toBeVisible(); +}); + +it('should toggle expand', async () => { + const user = userEvent.setup(); + setupWithProps(); + + expect(screen.queryByText('Foo')).not.toBeInTheDocument(); + await user.click(screen.getByRole('button')); + expect(screen.getByText('Foo')).toBeVisible(); + await user.click(screen.getByRole('button')); + expect(screen.queryByText('Foo')).not.toBeInTheDocument(); +}); + +function setupWithProps(props: Partial> = {}) { + return render( + + Foo + + ); +} diff --git a/server/sonar-web/design-system/src/components/subnavigation/__tests__/SubnavigationItem-test.tsx b/server/sonar-web/design-system/src/components/subnavigation/__tests__/SubnavigationItem-test.tsx new file mode 100644 index 00000000000..3646eee2b70 --- /dev/null +++ b/server/sonar-web/design-system/src/components/subnavigation/__tests__/SubnavigationItem-test.tsx @@ -0,0 +1,51 @@ +/* + * 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 { render } from '../../../helpers/testUtils'; +import { FCProps } from '../../../types/misc'; +import { SubnavigationItem } from '../SubnavigationItem'; + +it('should render correctly', () => { + setupWithProps(); + + expect(screen.getByRole('button', { current: false })).toBeVisible(); +}); + +it('should display selected', () => { + setupWithProps({ active: true }); + + expect(screen.getByRole('button', { current: true })).toBeVisible(); +}); + +it('should call onClick with value when clicked', async () => { + const onClick = jest.fn(); + const { user } = setupWithProps({ onClick }); + + await user.click(screen.getByRole('button')); + expect(onClick).toHaveBeenCalledWith('foo'); +}); + +function setupWithProps(props: Partial> = {}) { + return render( + + Foo + + ); +} diff --git a/server/sonar-web/design-system/src/components/subnavigation/index.ts b/server/sonar-web/design-system/src/components/subnavigation/index.ts new file mode 100644 index 00000000000..89410cd2e49 --- /dev/null +++ b/server/sonar-web/design-system/src/components/subnavigation/index.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ +export * from './SubnavigationAccordion'; +export * from './SubnavigationGroup'; +export * from './SubnavigationHeading'; +export * from './SubnavigationItem';