aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWouter Admiraal <wouter.admiraal@sonarsource.com>2020-12-22 13:56:38 +0100
committersonartech <sonartech@sonarsource.com>2021-01-04 20:14:29 +0000
commit2b93ed0c8f1f64655274cb9063d4e9231f23204a (patch)
treeba5eddf44a5dc6a33c3ee4f03a2c13d1016f4973
parent8af47608143b0282b4cf7fa4cc77d4e6ce7d3dde (diff)
downloadsonarqube-2b93ed0c8f1f64655274cb9063d4e9231f23204a.tar.gz
sonarqube-2b93ed0c8f1f64655274cb9063d4e9231f23204a.zip
SONAR-13140 Improve side-menu tab scrolling on settings page
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx95
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx64
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx23
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/PageHeader-test.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap72
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap194
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap53
-rw-r--r--server/sonar-web/src/main/js/apps/settings/side-tabs.css85
-rw-r--r--server/sonar-web/src/main/js/apps/settings/styles.css75
11 files changed, 472 insertions, 257 deletions
diff --git a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
index 375b4e1dd73..0116ba05a8d 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
@@ -27,11 +27,6 @@ import { getCategoryName } from '../utils';
import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
import CATEGORY_OVERRIDES from './CategoryOverrides';
-interface Category {
- key: string;
- name: string;
-}
-
export interface CategoriesListProps {
branchesEnabled?: boolean;
categories: string[];
@@ -40,55 +35,51 @@ export interface CategoriesListProps {
selectedCategory: string;
}
-export class CategoriesList extends React.PureComponent<CategoriesListProps> {
- renderLink(category: Category) {
- const { component, defaultCategory, selectedCategory } = this.props;
- const pathname = this.props.component ? '/project/settings' : '/settings';
- const query = {
- category: category.key !== defaultCategory ? category.key.toLowerCase() : undefined,
- id: component && component.key
- };
- return (
- <IndexLink
- className={classNames({
- active: category.key.toLowerCase() === selectedCategory.toLowerCase()
- })}
- title={category.name}
- to={{ pathname, query }}>
- {category.name}
- </IndexLink>
- );
- }
+export function CategoriesList(props: CategoriesListProps) {
+ const { branchesEnabled, categories, component, defaultCategory, selectedCategory } = props;
+ const pathname = component ? '/project/settings' : '/settings';
- render() {
- const { branchesEnabled } = this.props;
-
- const categoriesWithName = this.props.categories
- .filter(key => !CATEGORY_OVERRIDES[key.toLowerCase()])
- .map(key => ({
- key,
- name: getCategoryName(key)
- }))
- .concat(
- ADDITIONAL_CATEGORIES.filter(c => c.displayTab)
- .filter(c =>
- this.props.component
- ? // Project settings
- c.availableForProject
- : // Global settings
- c.availableGlobally
- )
- .filter(c => branchesEnabled || !c.requiresBranchesEnabled)
- );
- const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase());
- return (
- <ul className="side-tabs-menu">
- {sortedCategories.map(category => (
- <li key={category.key}>{this.renderLink(category)}</li>
- ))}
- </ul>
+ const categoriesWithName = categories
+ .filter(key => !CATEGORY_OVERRIDES[key.toLowerCase()])
+ .map(key => ({
+ key,
+ name: getCategoryName(key)
+ }))
+ .concat(
+ ADDITIONAL_CATEGORIES.filter(c => c.displayTab)
+ .filter(c =>
+ component
+ ? // Project settings
+ c.availableForProject
+ : // Global settings
+ c.availableGlobally
+ )
+ .filter(c => branchesEnabled || !c.requiresBranchesEnabled)
);
- }
+ const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase());
+
+ return (
+ <ul className="side-tabs-menu">
+ {sortedCategories.map(category => (
+ <li key={category.key}>
+ <IndexLink
+ className={classNames({
+ active: category.key.toLowerCase() === selectedCategory.toLowerCase()
+ })}
+ title={category.name}
+ to={{
+ pathname,
+ query: {
+ category: category.key !== defaultCategory ? category.key.toLowerCase() : undefined,
+ id: component && component.key
+ }
+ }}>
+ {category.name}
+ </IndexLink>
+ </li>
+ ))}
+ </ul>
+ );
}
const mapStateToProps = (state: Store) => ({
diff --git a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx b/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx
index 4659329d9d5..7377d4f2164 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx
@@ -23,9 +23,15 @@ import { Helmet } from 'react-helmet-async';
import { connect } from 'react-redux';
import { WithRouterProps } from 'react-router';
import { translate } from 'sonar-ui-common/helpers/l10n';
+import {
+ addSideBarClass,
+ addWhitePageClass,
+ removeSideBarClass,
+ removeWhitePageClass
+} from 'sonar-ui-common/helpers/pages';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import { getSettingsAppDefaultCategory, Store } from '../../../store/rootReducer';
-import '../side-tabs.css';
import { fetchSettings } from '../store/actions';
import '../styles.css';
import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
@@ -50,6 +56,8 @@ export class App extends React.PureComponent<Props & WithRouterProps, State> {
componentDidMount() {
this.mounted = true;
+ addSideBarClass();
+ addWhitePageClass();
this.fetchSettings();
}
@@ -61,6 +69,8 @@ export class App extends React.PureComponent<Props & WithRouterProps, State> {
componentWillUnmount() {
this.mounted = false;
+ removeSideBarClass();
+ removeWhitePageClass();
}
fetchSettings = () => {
@@ -91,32 +101,42 @@ export class App extends React.PureComponent<Props & WithRouterProps, State> {
(!isProjectSettings && foundAdditionalCategory.availableGlobally));
return (
- <div className="page page-limited" id="settings-page">
+ <div id="settings-page">
<Suggestions suggestions="settings" />
<Helmet defer={false} title={translate('settings.page')} />
-
<PageHeader component={this.props.component} />
- <div className="side-tabs-layout settings-layout">
- <div className="side-tabs-side">
- <AllCategoriesList
- component={this.props.component}
- defaultCategory={this.props.defaultCategory}
- selectedCategory={selectedCategory}
- />
- </div>
- <div className="side-tabs-main">
- {foundAdditionalCategory && shouldRenderAdditionalCategory ? (
- foundAdditionalCategory.renderComponent({
- component: this.props.component,
- selectedCategory: originalCategory
- })
- ) : (
- <CategoryDefinitionsList
- category={selectedCategory}
- component={this.props.component}
- />
+ <div className="layout-page">
+ <ScreenPositionHelper className="layout-page-side-outer">
+ {({ top }) => (
+ <div className="layout-page-side" style={{ top }}>
+ <div className="layout-page-side-inner">
+ <AllCategoriesList
+ component={this.props.component}
+ defaultCategory={this.props.defaultCategory}
+ selectedCategory={selectedCategory}
+ />
+ </div>
+ </div>
)}
+ </ScreenPositionHelper>
+
+ <div className="layout-page-main">
+ <div className="layout-page-main-inner">
+ <div className="big-padded">
+ {foundAdditionalCategory && shouldRenderAdditionalCategory ? (
+ foundAdditionalCategory.renderComponent({
+ component: this.props.component,
+ selectedCategory: originalCategory
+ })
+ ) : (
+ <CategoryDefinitionsList
+ category={selectedCategory}
+ component={this.props.component}
+ />
+ )}
+ </div>
+ </div>
</div>
</div>
</div>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx
index 61a7b24e83a..8ea6c4906b9 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx
@@ -21,11 +21,11 @@ import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
import InstanceMessage from '../../../components/common/InstanceMessage';
-interface Props {
+export interface PageHeaderProps {
component?: T.Component;
}
-export default function PageHeader({ component }: Props) {
+export default function PageHeader({ component }: PageHeaderProps) {
const title = component ? translate('project_settings.page') : translate('settings.page');
const description = component ? (
@@ -35,9 +35,13 @@ export default function PageHeader({ component }: Props) {
);
return (
- <header className="page-header">
- <h1 className="page-title">{title}</h1>
- <div className="page-description">{description}</div>
+ <header className="top-bar-outer">
+ <div className="top-bar">
+ <div className="top-bar-inner bordered-bottom big-padded-top padded-bottom">
+ <h1 className="page-title">{title}</h1>
+ <div className="page-description spacer-top">{description}</div>
+ </div>
+ </div>
</header>
);
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx
index e4b62b7165f..4d7abe86501 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx
@@ -61,25 +61,18 @@ jest.mock('../AdditionalCategories', () => ({
] as AdditionalCategory[]
}));
-it('should render correctly in global mode', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should render correctly in project mode', () => {
- expect(shallowRender({ component: mockComponent() })).toMatchSnapshot();
-});
-
-it('should render correctly when branches are disabled', () => {
- expect(shallowRender({ branchesEnabled: false })).toMatchSnapshot();
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('global mode');
+ expect(shallowRender({ selectedCategory: 'CAT_2' })).toMatchSnapshot('selected category');
+ expect(shallowRender({ component: mockComponent() })).toMatchSnapshot('project mode');
+ expect(shallowRender({ branchesEnabled: false })).toMatchSnapshot('branches disabled');
});
function shallowRender(props?: Partial<CategoriesListProps>) {
- const categories = ['general'];
-
- return shallow(
+ return shallow<CategoriesListProps>(
<CategoriesList
branchesEnabled={true}
- categories={categories}
+ categories={['general']}
defaultCategory="general"
selectedCategory=""
{...props}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx
index eb9f80b6741..cb4dc239eaa 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx
@@ -19,7 +19,14 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import {
+ addSideBarClass,
+ addWhitePageClass,
+ removeSideBarClass,
+ removeWhitePageClass
+} from 'sonar-ui-common/helpers/pages';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper';
import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
import {
ALM_INTEGRATION,
@@ -30,11 +37,27 @@ import {
} from '../AdditionalCategoryKeys';
import { App } from '../AppContainer';
+jest.mock('sonar-ui-common/helpers/pages', () => ({
+ addSideBarClass: jest.fn(),
+ addWhitePageClass: jest.fn(),
+ removeSideBarClass: jest.fn(),
+ removeWhitePageClass: jest.fn()
+}));
+
it('should render default view correctly', async () => {
const wrapper = shallowRender();
+ expect(addSideBarClass).toBeCalled();
+ expect(addWhitePageClass).toBeCalled();
+
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
+ expect(wrapper.find(ScreenPositionHelper).dive()).toMatchSnapshot();
+
+ wrapper.unmount();
+
+ expect(removeSideBarClass).toBeCalled();
+ expect(removeWhitePageClass).toBeCalled();
});
it('should render newCodePeriod correctly', async () => {
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/PageHeader-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/PageHeader-test.tsx
new file mode 100644
index 00000000000..25b8cb4d09e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/PageHeader-test.tsx
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockComponent } from '../../../../helpers/testMocks';
+import PageHeader, { PageHeaderProps } from '../PageHeader';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ component: mockComponent() })).toMatchSnapshot('for project');
+});
+
+function shallowRender(props: Partial<PageHeaderProps> = {}) {
+ return shallow<PageHeaderProps>(<PageHeader {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap
index 5a1a854e1cf..161cb051782 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap
@@ -1,6 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly in global mode 1`] = `
+exports[`should render correctly: branches disabled 1`] = `
+<ul
+ className="side-tabs-menu"
+>
+ <li
+ key="CAT_2"
+ >
+ <IndexLink
+ className=""
+ title="CAT_2_NAME"
+ to={
+ Object {
+ "pathname": "/settings",
+ "query": Object {
+ "category": "cat_2",
+ "id": undefined,
+ },
+ }
+ }
+ >
+ CAT_2_NAME
+ </IndexLink>
+ </li>
+ <li
+ key="general"
+ >
+ <IndexLink
+ className=""
+ title="general"
+ to={
+ Object {
+ "pathname": "/settings",
+ "query": Object {
+ "category": undefined,
+ "id": undefined,
+ },
+ }
+ }
+ >
+ general
+ </IndexLink>
+ </li>
+</ul>
+`;
+
+exports[`should render correctly: global mode 1`] = `
<ul
className="side-tabs-menu"
>
@@ -64,7 +109,7 @@ exports[`should render correctly in global mode 1`] = `
</ul>
`;
-exports[`should render correctly in project mode 1`] = `
+exports[`should render correctly: project mode 1`] = `
<ul
className="side-tabs-menu"
>
@@ -128,15 +173,34 @@ exports[`should render correctly in project mode 1`] = `
</ul>
`;
-exports[`should render correctly when branches are disabled 1`] = `
+exports[`should render correctly: selected category 1`] = `
<ul
className="side-tabs-menu"
>
<li
- key="CAT_2"
+ key="CAT_1"
>
<IndexLink
className=""
+ title="CAT_1_NAME"
+ to={
+ Object {
+ "pathname": "/settings",
+ "query": Object {
+ "category": "cat_1",
+ "id": undefined,
+ },
+ }
+ }
+ >
+ CAT_1_NAME
+ </IndexLink>
+ </li>
+ <li
+ key="CAT_2"
+ >
+ <IndexLink
+ className="active"
title="CAT_2_NAME"
to={
Object {
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap
index 96e4786b9d9..fcc9f2ce7d7 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap
@@ -2,7 +2,6 @@
exports[`should render ALM integration correctly 1`] = `
<div
- className="page page-limited"
id="settings-page"
>
<Suggestions
@@ -15,22 +14,27 @@ exports[`should render ALM integration correctly 1`] = `
/>
<PageHeader />
<div
- className="side-tabs-layout settings-layout"
+ className="layout-page"
>
- <div
- className="side-tabs-side"
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
>
- <Connect(CategoriesList)
- defaultCategory="general"
- selectedCategory="almintegration"
- />
- </div>
+ <Component />
+ </ScreenPositionHelper>
<div
- className="side-tabs-main"
+ className="layout-page-main"
>
- <withRouter(Connect(withAppState(AlmIntegration)))
- selectedCategory="almintegration"
- />
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <withRouter(Connect(withAppState(AlmIntegration)))
+ selectedCategory="almintegration"
+ />
+ </div>
+ </div>
</div>
</div>
</div>
@@ -38,7 +42,6 @@ exports[`should render ALM integration correctly 1`] = `
exports[`should render analysis scope correctly 1`] = `
<div
- className="page page-limited"
id="settings-page"
>
<Suggestions
@@ -51,22 +54,27 @@ exports[`should render analysis scope correctly 1`] = `
/>
<PageHeader />
<div
- className="side-tabs-layout settings-layout"
+ className="layout-page"
>
- <div
- className="side-tabs-side"
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
>
- <Connect(CategoriesList)
- defaultCategory="general"
- selectedCategory="exclusions"
- />
- </div>
+ <Component />
+ </ScreenPositionHelper>
<div
- className="side-tabs-main"
+ className="layout-page-main"
>
- <AnalysisScope
- selectedCategory="exclusions"
- />
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <AnalysisScope
+ selectedCategory="exclusions"
+ />
+ </div>
+ </div>
</div>
</div>
</div>
@@ -74,7 +82,6 @@ exports[`should render analysis scope correctly 1`] = `
exports[`should render default view correctly 1`] = `
<div
- className="page page-limited"
id="settings-page"
>
<Suggestions
@@ -87,21 +94,50 @@ exports[`should render default view correctly 1`] = `
/>
<PageHeader />
<div
- className="side-tabs-layout settings-layout"
+ className="layout-page"
>
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
<div
- className="side-tabs-side"
+ className="layout-page-main"
>
- <Connect(CategoriesList)
- defaultCategory="general"
- selectedCategory="general"
- />
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <Connect(SubCategoryDefinitionsList)
+ category="general"
+ />
+ </div>
+ </div>
</div>
+ </div>
+</div>
+`;
+
+exports[`should render default view correctly 2`] = `
+<div
+ className="layout-page-side-outer"
+>
+ <div
+ className="layout-page-side"
+ style={
+ Object {
+ "top": 0,
+ }
+ }
+ >
<div
- className="side-tabs-main"
+ className="layout-page-side-inner"
>
- <Connect(SubCategoryDefinitionsList)
- category="general"
+ <Connect(CategoriesList)
+ defaultCategory="general"
+ selectedCategory="general"
/>
</div>
</div>
@@ -110,7 +146,6 @@ exports[`should render default view correctly 1`] = `
exports[`should render languages correctly 1`] = `
<div
- className="page page-limited"
id="settings-page"
>
<Suggestions
@@ -123,22 +158,27 @@ exports[`should render languages correctly 1`] = `
/>
<PageHeader />
<div
- className="side-tabs-layout settings-layout"
+ className="layout-page"
>
- <div
- className="side-tabs-side"
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
>
- <Connect(CategoriesList)
- defaultCategory="general"
- selectedCategory="languages"
- />
- </div>
+ <Component />
+ </ScreenPositionHelper>
<div
- className="side-tabs-main"
+ className="layout-page-main"
>
- <withRouter(Connect(Languages))
- selectedCategory="languages"
- />
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <withRouter(Connect(Languages))
+ selectedCategory="languages"
+ />
+ </div>
+ </div>
</div>
</div>
</div>
@@ -146,7 +186,6 @@ exports[`should render languages correctly 1`] = `
exports[`should render newCodePeriod correctly 1`] = `
<div
- className="page page-limited"
id="settings-page"
>
<Suggestions
@@ -159,20 +198,25 @@ exports[`should render newCodePeriod correctly 1`] = `
/>
<PageHeader />
<div
- className="side-tabs-layout settings-layout"
+ className="layout-page"
>
- <div
- className="side-tabs-side"
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
>
- <Connect(CategoriesList)
- defaultCategory="general"
- selectedCategory="new_code_period"
- />
- </div>
+ <Component />
+ </ScreenPositionHelper>
<div
- className="side-tabs-main"
+ className="layout-page-main"
>
- <NewCodePeriod />
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <NewCodePeriod />
+ </div>
+ </div>
</div>
</div>
</div>
@@ -180,7 +224,6 @@ exports[`should render newCodePeriod correctly 1`] = `
exports[`should render pull request decoration binding correctly 1`] = `
<div
- className="page page-limited"
id="settings-page"
>
<Suggestions
@@ -193,22 +236,27 @@ exports[`should render pull request decoration binding correctly 1`] = `
/>
<PageHeader />
<div
- className="side-tabs-layout settings-layout"
+ className="layout-page"
>
- <div
- className="side-tabs-side"
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
>
- <Connect(CategoriesList)
- defaultCategory="general"
- selectedCategory="pull_request_decoration_binding"
- />
- </div>
+ <Component />
+ </ScreenPositionHelper>
<div
- className="side-tabs-main"
+ className="layout-page-main"
>
- <Connect(SubCategoryDefinitionsList)
- category="pull_request_decoration_binding"
- />
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <Connect(SubCategoryDefinitionsList)
+ category="pull_request_decoration_binding"
+ />
+ </div>
+ </div>
</div>
</div>
</div>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
new file mode 100644
index 00000000000..93f8b63b548
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
@@ -0,0 +1,53 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: default 1`] = `
+<header
+ className="top-bar-outer"
+>
+ <div
+ className="top-bar"
+ >
+ <div
+ className="top-bar-inner bordered-bottom big-padded-top padded-bottom"
+ >
+ <h1
+ className="page-title"
+ >
+ settings.page
+ </h1>
+ <div
+ className="page-description spacer-top"
+ >
+ <InstanceMessage
+ message="settings.page.description"
+ />
+ </div>
+ </div>
+ </div>
+</header>
+`;
+
+exports[`should render correctly: for project 1`] = `
+<header
+ className="top-bar-outer"
+>
+ <div
+ className="top-bar"
+ >
+ <div
+ className="top-bar-inner bordered-bottom big-padded-top padded-bottom"
+ >
+ <h1
+ className="page-title"
+ >
+ project_settings.page
+ </h1>
+ <div
+ className="page-description spacer-top"
+ >
+ project_settings.page.description
+ </div>
+ </div>
+ </div>
+</header>
+`;
diff --git a/server/sonar-web/src/main/js/apps/settings/side-tabs.css b/server/sonar-web/src/main/js/apps/settings/side-tabs.css
deleted file mode 100644
index 5fb044b8967..00000000000
--- a/server/sonar-web/src/main/js/apps/settings/side-tabs.css
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2020 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.
- */
-.side-tabs-layout {
- display: flex;
- justify-content: space-between;
- align-items: stretch;
-}
-
-.modal .side-tabs-layout {
- padding-left: 10px;
- background-color: var(--barBackgroundColor);
-}
-
-.side-tabs-main {
- position: relative;
- z-index: var(--normalZIndex);
- flex-grow: 1;
- padding: 15px 20px;
- border: 1px solid var(--barBorderColor);
- box-sizing: border-box;
- background-color: #fff;
- overflow: auto;
- height: 100vh;
-}
-
-.modal .side-tabs-main {
- border-top: none;
- border-bottom: none;
- border-right: none;
-}
-
-.side-tabs-side {
- position: relative;
- z-index: var(--aboveNormalZIndex);
- width: 160px;
- flex-shrink: 0;
- padding: 10px 0;
- box-sizing: border-box;
- transform: translateX(1px);
-}
-
-.side-tabs-menu > li {
- margin-bottom: 4px;
-}
-
-.side-tabs-menu > li > a {
- display: block;
- padding: 10px 10px;
- line-height: 1.5;
- border-top-left-radius: 3px;
- border-bottom-left-radius: 3px;
- border: 1px solid var(--barBorderColor);
- border-right: none;
- overflow: hidden;
- text-overflow: ellipsis;
- transition: color 0.3s ease, background-color 0.3s ease;
-}
-
-.side-tabs-menu > li > a:hover,
-.side-tabs-menu > li > a:focus,
-.side-tabs-menu > li > a.active {
- background-color: #fff;
-}
-
-.side-tabs-menu > li > a.active {
- color: var(--baseFontColor);
- cursor: default;
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/styles.css b/server/sonar-web/src/main/js/apps/settings/styles.css
index fbcb0bb05d5..87f7b5b89dc 100644
--- a/server/sonar-web/src/main/js/apps/settings/styles.css
+++ b/server/sonar-web/src/main/js/apps/settings/styles.css
@@ -17,8 +17,47 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-.settings-layout {
- margin-bottom: 60px;
+#settings-page .layout-page-side,
+#settings-page .layout-page-side-outer {
+ width: calc(50vw - 480px);
+ border-right: none;
+}
+
+#settings-page .layout-page-side-inner {
+ width: 160px;
+ margin-left: calc(50vw - 639px); /* 640px -1px for overlapping the border */
+}
+
+#settings-page .layout-page-main {
+ padding: 0;
+}
+
+#settings-page .layout-page-main-inner {
+ max-width: 1110px;
+}
+
+#settings-page .top-bar-outer {
+ height: 80px;
+}
+
+#settings-page .top-bar {
+ background-color: #f3f3f3;
+ position: fixed;
+ z-index: 55; /* todo */
+ left: 0;
+ right: 0;
+}
+
+#settings-page .top-bar-inner {
+ max-width: 1280px;
+ margin: 0 auto;
+ height: 80px;
+ box-sizing: border-box;
+}
+
+#settings-page .page-title,
+#settings-page .page-description {
+ float: none;
}
.settings-definitions-list > li + li {
@@ -105,3 +144,35 @@
max-width: 400px;
min-width: 200px;
}
+
+.side-tabs-menu {
+ margin-top: calc(2 * var(--gridSize));
+}
+
+.side-tabs-menu > li {
+ margin-bottom: 4px;
+}
+
+.side-tabs-menu > li > a {
+ display: block;
+ padding: 10px 10px;
+ line-height: 1.5;
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ border: 1px solid var(--barBorderColor);
+ border-right: none;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ transition: color 0.3s ease, background-color 0.3s ease;
+}
+
+.side-tabs-menu > li > a:hover,
+.side-tabs-menu > li > a:focus,
+.side-tabs-menu > li > a.active {
+ background-color: #fff;
+}
+
+.side-tabs-menu > li > a.active {
+ color: var(--baseFontColor);
+ cursor: default;
+}