From 012bf4a5f40dfa9034e534f81941ce3cdbd5ac13 Mon Sep 17 00:00:00 2001 From: stanislavh Date: Tue, 23 Jul 2024 13:19:34 +0200 Subject: [PATCH] SONAR-22543 Improve SR navigation --- .../subnavigation/SubnavigationItem.tsx | 30 ++- .../templates/FilterBarTemplate.tsx | 187 ++++++++++++++++++ .../__tests__/FilterBarTemplate-test.tsx | 43 ++++ 3 files changed, 253 insertions(+), 7 deletions(-) create mode 100644 server/sonar-web/src/main/js/components/templates/FilterBarTemplate.tsx create mode 100644 server/sonar-web/src/main/js/components/templates/__tests__/FilterBarTemplate-test.tsx diff --git a/server/sonar-web/design-system/src/components/subnavigation/SubnavigationItem.tsx b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationItem.tsx index f2c6208e513..0d03117246a 100644 --- a/server/sonar-web/design-system/src/components/subnavigation/SubnavigationItem.tsx +++ b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationItem.tsx @@ -17,11 +17,14 @@ * 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 classNames from 'classnames'; import { ReactNode, SyntheticEvent, useCallback } from 'react'; import tw, { theme as twTheme } from 'twin.macro'; import { themeBorder, themeColor, themeContrast } from '../../helpers/theme'; +import { ThemedProps } from '../../types'; +import NavLink, { NavLinkProps } from '../NavLink'; interface Props { active?: boolean; @@ -54,7 +57,11 @@ export function SubnavigationItem(props: Readonly) { ); } -const StyledSubnavigationItem = styled.a` +export function SubnavigationLinkItem({ children, ...props }: NavLinkProps) { + return {children}; +} + +const ItemBaseStyle = (props: ThemedProps) => css` ${tw`sw-flex sw-items-center sw-justify-between`} ${tw`sw-box-border`} ${tw`sw-body-sm`} @@ -63,21 +70,30 @@ const StyledSubnavigationItem = styled.a` ${tw`sw-cursor-pointer`} padding-left: calc(${twTheme('spacing.4')} - 3px); - color: ${themeContrast('subnavigation')}; - background-color: ${themeColor('subnavigation')}; + color: ${themeContrast('subnavigation')(props)}; + background-color: ${themeColor('subnavigation')(props)}; border-bottom: none; - border-left: ${themeBorder('active', 'transparent')}; + border-left: ${themeBorder('active', 'transparent')(props)}; transition: 0.2 ease; transition-property: border-left, background-color, color; &:hover, &:focus, &.active { - background-color: ${themeColor('subnavigationHover')}; + background-color: ${themeColor('subnavigationHover')(props)}; } &.active { - color: ${themeContrast('subnavigationHover')}; - border-left: ${themeBorder('active')}; + color: ${themeContrast('subnavigationHover')(props)}; + border-left: ${themeBorder('active')(props)}; } `; + +const StyledSubnavigationItem = styled.a` + ${ItemBaseStyle}; +`; + +const SubnavigationLinkItemStyled = styled(NavLink)` + ${ItemBaseStyle}; + ${tw`sw-no-underline`} +`; diff --git a/server/sonar-web/src/main/js/components/templates/FilterBarTemplate.tsx b/server/sonar-web/src/main/js/components/templates/FilterBarTemplate.tsx new file mode 100644 index 00000000000..a0ddf19618b --- /dev/null +++ b/server/sonar-web/src/main/js/components/templates/FilterBarTemplate.tsx @@ -0,0 +1,187 @@ +/* + * 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 classNames from 'classnames'; +import { + LAYOUT_FILTERBAR_HEADER, + LAYOUT_FOOTER_HEIGHT, + LAYOUT_GLOBAL_NAV_HEIGHT, + LAYOUT_PROJECT_NAV_HEIGHT, + themeBorder, + themeColor, +} from 'design-system'; +import React from 'react'; +import { translate } from '../../helpers/l10n'; + +import useFollowScroll from '../../hooks/useFollowScroll'; + +export type LayoutFilterBarSize = 'default' | 'large'; + +const HEADER_PADDING_BOTTOM = 24; +const HEADER_PADDING = 32 + HEADER_PADDING_BOTTOM; //32 padding top and 24 padding bottom + +interface Props { + className?: string; + content: React.ReactNode; + contentClassName?: string; + filterbar: React.ReactNode; + filterbarContentClassName?: string; + filterbarHeader?: React.ReactNode; + filterbarHeaderClassName?: string; + filterbarRef?: React.RefObject; + header?: React.ReactNode; + headerHeight?: number; + id?: string; + size?: LayoutFilterBarSize; + withBorderLeft?: boolean; +} + +export default function FilterBarTemplate(props: Readonly) { + const { + className, + content, + contentClassName, + header, + headerHeight = 0, + id, + filterbarRef, + filterbar, + filterbarHeader, + filterbarHeaderClassName, + filterbarContentClassName, + size = 'default', + withBorderLeft = false, + } = props; + + const headerHeightWithPadding = headerHeight ? headerHeight + HEADER_PADDING : 0; + const { top: topScroll, scrolledOnce } = useFollowScroll(); + const distanceFromBottom = topScroll + window.innerHeight - document.body.scrollHeight; + const footerVisibleHeight = + (scrolledOnce && + (distanceFromBottom > -LAYOUT_FOOTER_HEIGHT + ? LAYOUT_FOOTER_HEIGHT + distanceFromBottom + : 0)) || + 0; + + return ( + <> + {header && ( +
+ {header} +
+ )} +
+ + {filterbarHeader && ( + + {filterbarHeader} + + )} + + {filterbar} + + +
+ {content} +
+
+ + ); +} + +const Filterbar = styled.div` + position: sticky; + box-sizing: border-box; + overflow-x: hidden; + overflow-y: auto; + background-color: ${themeColor('filterbar')}; + border-right: ${themeBorder('default', 'filterbarBorder')}; + + &.border-left { + border-left: ${themeBorder('default', 'filterbarBorder')}; + } + + &.bordered { + border: ${themeBorder('default', 'filterbarBorder')}; + } +`; + +const FilterbarContent = styled.nav` + position: relative; + box-sizing: border-box; + width: 100%; +`; + +const FilterbarHeader = styled.div` + position: sticky; + box-sizing: border-box; + height: ${LAYOUT_FILTERBAR_HEADER}px; + background-color: inherit; + border-bottom: ${themeBorder('default')}; +`; + +const Main = styled.div` + flex-grow: 1; +`; diff --git a/server/sonar-web/src/main/js/components/templates/__tests__/FilterBarTemplate-test.tsx b/server/sonar-web/src/main/js/components/templates/__tests__/FilterBarTemplate-test.tsx new file mode 100644 index 00000000000..a6c9cea12a5 --- /dev/null +++ b/server/sonar-web/src/main/js/components/templates/__tests__/FilterBarTemplate-test.tsx @@ -0,0 +1,43 @@ +/* + * 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 React from 'react'; +import { renderComponent } from '../../../helpers/testReactTestingUtils'; +import { FCProps } from '../../../types/misc'; +import FilterBarTemplate from '../FilterBarTemplate'; + +it('should render with filter header', () => { + setupWithProps({ header: header, headerHeight: 16 }); + + expect(screen.getByTestId('filter-header')).toHaveTextContent('header'); +}); + +function setupWithProps(props: Partial> = {}) { + return renderComponent( + } + filterbar={
} + filterbarHeader={
} + size="large" + {...props} + />, + ); +} -- 2.39.5