diff options
author | Ismail Cherri <ismail.cherri@sonarsource.com> | 2024-02-19 10:02:47 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-02-19 20:02:26 +0000 |
commit | 68767ffafbb364ec59bc94e329ec3719ab1df085 (patch) | |
tree | 651a6d83c6ab584d5036eef36a7c2fa08bc23055 /server/sonar-web | |
parent | b50db984f07fdfce6a5c8c053d8a86394681463a (diff) | |
download | sonarqube-68767ffafbb364ec59bc94e329ec3719ab1df085.tar.gz sonarqube-68767ffafbb364ec59bc94e329ec3719ab1df085.zip |
SONAR-21235 Add shadow to main app bar and separator to project list sticky header (#10625)
Diffstat (limited to 'server/sonar-web')
4 files changed, 64 insertions, 13 deletions
diff --git a/server/sonar-web/design-system/src/components/MainAppBar.tsx b/server/sonar-web/design-system/src/components/MainAppBar.tsx index 492c5482a26..ab1c28403ec 100644 --- a/server/sonar-web/design-system/src/components/MainAppBar.tsx +++ b/server/sonar-web/design-system/src/components/MainAppBar.tsx @@ -17,7 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { throttle } from 'lodash'; +import React from 'react'; import tw from 'twin.macro'; import { LAYOUT_GLOBAL_NAV_HEIGHT, @@ -25,11 +28,12 @@ import { LAYOUT_LOGO_MAX_HEIGHT, LAYOUT_LOGO_MAX_WIDTH, LAYOUT_VIEWPORT_MIN_WIDTH, + THROTTLE_SCROLL_DELAY, } from '../helpers/constants'; -import { themeBorder, themeColor, themeContrast } from '../helpers/theme'; +import { themeBorder, themeColor, themeContrast, themeShadow } from '../helpers/theme'; import { BaseLink } from './Link'; -const MainAppBarDiv = styled.div` +const MainAppBarHeader = styled.header` ${tw`sw-flex`}; ${tw`sw-items-center`}; ${tw`sw-px-6`}; @@ -68,14 +72,28 @@ export function MainAppBar({ children, Logo, }: React.PropsWithChildren<{ Logo: React.ElementType }>) { + const theme = useTheme(); + const [boxShadow, setBoxShadow] = React.useState('none'); + + React.useEffect(() => { + const handleScroll = throttle(() => { + setBoxShadow(document.documentElement?.scrollTop > 0 ? themeShadow('md')({ theme }) : 'none'); + }, THROTTLE_SCROLL_DELAY); + + document.addEventListener('scroll', handleScroll); + return () => { + document.removeEventListener('scroll', handleScroll); + }; + }, [theme]); + return ( - <MainAppBarDiv> + <MainAppBarHeader style={{ boxShadow }}> <MainAppBarNavLogoDiv> <MainAppBarNavLogoLink to="/"> <Logo /> </MainAppBarNavLogoLink> </MainAppBarNavLogoDiv> <MainAppBarNavRightDiv>{children}</MainAppBarNavRightDiv> - </MainAppBarDiv> + </MainAppBarHeader> ); } diff --git a/server/sonar-web/design-system/src/components/__tests__/MainAppBar-test.tsx b/server/sonar-web/design-system/src/components/__tests__/MainAppBar-test.tsx index e25f59ed23f..0972b51eac5 100644 --- a/server/sonar-web/design-system/src/components/__tests__/MainAppBar-test.tsx +++ b/server/sonar-web/design-system/src/components/__tests__/MainAppBar-test.tsx @@ -19,7 +19,7 @@ */ /* eslint-disable import/no-extraneous-dependencies */ -import { screen } from '@testing-library/react'; +import { fireEvent, screen } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { LAYOUT_LOGO_MAX_HEIGHT, LAYOUT_LOGO_MAX_WIDTH } from '../../helpers/constants'; import { render } from '../../helpers/testUtils'; @@ -45,6 +45,21 @@ it('should render the logo', () => { expect(element.container.querySelector('svg')).toHaveStyle({ height: '40px', width: '132px' }); }); +it('should add shadow when scrolled', () => { + setupWithProps(); + + expect(screen.getByRole('banner')).toHaveStyle({ + 'box-shadow': 'none', + }); + + document.documentElement.scrollTop = 100; + fireEvent.scroll(document, { target: { scrollTop: 100 } }); + + expect(screen.getByRole('banner')).toHaveStyle({ + 'box-shadow': '0px 4px 8px -2px rgba(29,33,47,0.1),0px 2px 15px -2px rgba(29,33,47,0.06)', + }); +}); + function setupWithProps( props: FCProps<typeof MainAppBar> = { Logo: () => <img alt="logo" src="http://example.com/logo.png" />, diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx index e57a7822858..de49740d9ad 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx @@ -264,7 +264,7 @@ export class AllProjects extends React.PureComponent<Props, State> { ); renderHeader = () => ( - <div className="sw-w-full" style={{ height: '120px' }}> + <PageHeaderWrapper className="sw-w-full"> <PageHeader currentUser={this.props.currentUser} onPerspectiveChange={this.handlePerspectiveChange} @@ -275,7 +275,7 @@ export class AllProjects extends React.PureComponent<Props, State> { total={this.state.total} view={this.getView()} /> - </div> + </PageHeaderWrapper> ); renderMain = () => { @@ -382,3 +382,8 @@ const SideBarStyle = styled.div` border-right: ${themeBorder('default', 'filterbarBorder')}; background-color: ${themeColor('backgroundSecondary')}; `; + +const PageHeaderWrapper = styled.div` + height: 7.5rem; + border-bottom: ${themeBorder('default', 'filterbarBorder')}; +`; diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx index c2e647a2e33..35ba75d85cf 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import classNames from 'classnames'; import { Spinner } from 'design-system'; import * as React from 'react'; import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'; @@ -80,7 +81,12 @@ export default class ProjectsList extends React.PureComponent<Props> { const project = projects[index]; return ( - <div key={key} role="row" style={{ ...style, height: PROJECT_CARD_HEIGHT }}> + <div + className={classNames({ 'sw-mt-4': index === 0 })} + key={key} + role="row" + style={{ ...style, height: PROJECT_CARD_HEIGHT }} + > <div className="sw-h-full" role="gridcell"> <ProjectCard currentUser={this.props.currentUser} @@ -105,11 +111,18 @@ export default class ProjectsList extends React.PureComponent<Props> { height={height} overscanRowCount={2} rowCount={this.props.projects.length + 1} - rowHeight={({ index }) => - index === this.props.projects.length - ? PROJECT_LIST_FOOTER_HEIGHT - : PROJECT_CARD_HEIGHT + PROJECT_CARD_MARGIN - } + rowHeight={({ index }) => { + if (index === 0) { + // first card, double top and bottom margin + return PROJECT_CARD_HEIGHT + PROJECT_CARD_MARGIN * 2; + } + if (index === this.props.projects.length) { + // Footer card, no margin + return PROJECT_LIST_FOOTER_HEIGHT; + } + // all other cards, only bottom margin + return PROJECT_CARD_HEIGHT + PROJECT_CARD_MARGIN; + }} rowRenderer={this.renderRow} style={{ outline: 'none' }} tabIndex={-1} |