From: Jeremy Davis Date: Tue, 28 Nov 2023 17:41:12 +0000 (+0100) Subject: SONAR-21067 Project General Settings adopt the new UI X-Git-Tag: 10.4.0.87286~374 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=42dfd5eb6df45e8bc3f7ddeae0f40bd03addbe9a;p=sonarqube.git SONAR-21067 Project General Settings adopt the new UI --- diff --git a/server/sonar-web/design-system/src/components/Switch.tsx b/server/sonar-web/design-system/src/components/Switch.tsx index 7ff7b0a22d5..2ec38d55a9b 100644 --- a/server/sonar-web/design-system/src/components/Switch.tsx +++ b/server/sonar-web/design-system/src/components/Switch.tsx @@ -57,6 +57,7 @@ export function Switch(props: Readonly) { name={name} onClick={handleClick} role="switch" + type="button" > {value && } diff --git a/server/sonar-web/design-system/src/components/input/InputField.tsx b/server/sonar-web/design-system/src/components/input/InputField.tsx index c5d97ead8ce..e4cb40bbbd2 100644 --- a/server/sonar-web/design-system/src/components/input/InputField.tsx +++ b/server/sonar-web/design-system/src/components/input/InputField.tsx @@ -129,18 +129,10 @@ const baseStyle = (props: ThemedProps) => css` `; const StyledInput = styled.input` - input[type='text']& { - ${getInputVariant} - ${baseStyle} - ${tw`sw-h-control`} - } - - input[type='number']& { - ${getInputVariant} - ${baseStyle} - ${tw`sw-h-control`} - } - input[type='password']& { + input[type='text']&, + input[type='number']&, + input[type='password']&, + input[type='email']& { ${getInputVariant} ${baseStyle} ${tw`sw-h-control`} 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 54ff544fdaf..94c7ee61069 100644 --- a/server/sonar-web/design-system/src/components/subnavigation/SubnavigationItem.tsx +++ b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationItem.tsx @@ -19,7 +19,7 @@ */ import styled from '@emotion/styled'; import classNames from 'classnames'; -import { ReactNode, useCallback } from 'react'; +import { ReactNode, SyntheticEvent, useCallback } from 'react'; import tw, { theme as twTheme } from 'twin.macro'; import { themeBorder, themeColor, themeContrast } from '../../helpers/theme'; @@ -27,20 +27,25 @@ interface Props { active?: boolean; children: ReactNode; className?: string; - innerRef?: (node: HTMLDivElement) => void; + innerRef?: (node: HTMLAnchorElement) => void; onClick: (value?: string) => void; value?: string; } -export function SubnavigationItem(props: Props) { +export function SubnavigationItem(props: Readonly) { const { active, className, children, innerRef, onClick, value } = props; - const handleClick = useCallback(() => { - onClick(value); - }, [onClick, value]); + const handleClick = useCallback( + (e: SyntheticEvent) => { + e.preventDefault(); + onClick(value); + }, + [onClick, value], + ); return ( @@ -49,7 +54,7 @@ export function SubnavigationItem(props: Props) { ); } -const StyledSubnavigationItem = styled.div` +const StyledSubnavigationItem = styled.a` ${tw`sw-flex sw-items-center sw-justify-between`} ${tw`sw-box-border`} ${tw`sw-body-sm`} @@ -60,6 +65,7 @@ const StyledSubnavigationItem = styled.div` padding-left: calc(${twTheme('spacing.4')} - 3px); color: ${themeContrast('subnavigation')}; background-color: ${themeColor('subnavigation')}; + border-bottom: none; border-left: ${themeBorder('active', 'transparent')}; transition: 0.2 ease; transition-property: border-left, background-color, color; 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 9a6fb80de82..05087d96fe6 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 @@ -17,10 +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 classNames from 'classnames'; +import { SubnavigationGroup, SubnavigationItem } from 'design-system'; import { sortBy } from 'lodash'; import * as React from 'react'; -import { NavLink } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import withAvailableFeatures, { WithAvailableFeaturesProps, } from '../../../app/components/available-features/withAvailableFeatures'; @@ -38,9 +38,22 @@ export interface CategoriesListProps extends WithAvailableFeaturesProps { selectedCategory: string; } -function CategoriesList(props: CategoriesListProps) { +function CategoriesList(props: Readonly) { const { categories, component, defaultCategory, selectedCategory } = props; + const navigate = useNavigate(); + + const openCategory = React.useCallback( + (category: string | undefined) => { + const url = component + ? getProjectSettingsUrl(component.key, category) + : getGlobalSettingsUrl(category); + + navigate(url); + }, + [component, navigate], + ); + const categoriesWithName = categories .filter((key) => !CATEGORY_OVERRIDES[key.toLowerCase()]) .map((key) => ({ @@ -61,31 +74,20 @@ function CategoriesList(props: CategoriesListProps) { const sortedCategories = sortBy(categoriesWithName, (category) => category.name.toLowerCase()); return ( -
    + {sortedCategories.map((c) => { const category = c.key !== defaultCategory ? c.key.toLowerCase() : undefined; return ( -
  • - - classNames({ - active: c.key.toLowerCase() === selectedCategory.toLowerCase(), - }) - } - title={c.name} - to={ - component - ? getProjectSettingsUrl(component.key, category) - : getGlobalSettingsUrl(category) - } - > - {c.name} - -
  • + openCategory(category)} + key={c.key} + > + {c.name} + ); })} -
+ ); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx b/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx index fce5db8a2e3..bd1747a4611 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx @@ -17,8 +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 styled from '@emotion/styled'; +import { LightLabel } from 'design-system/lib'; import * as React from 'react'; -import DocLink from '../../../components/common/DocLink'; +import DocumentationLink from '../../../components/common/DocumentationLink'; import { translate } from '../../../helpers/l10n'; import { AdditionalCategoryComponentProps } from './AdditionalCategories'; import CategoryDefinitionsList from './CategoryDefinitionsList'; @@ -28,37 +30,37 @@ export function AnalysisScope(props: AdditionalCategoryComponentProps) { return ( <> -

- {translate('settings.analysis_scope.wildcards.introduction')} - - {translate('learn_more')} - -

+ +

+ {translate('settings.analysis_scope.wildcards.introduction')} +

- - - - - - - - - - - - - - - -
*{translate('settings.analysis_scope.wildcards.zero_more_char')}
**{translate('settings.analysis_scope.wildcards.zero_more_dir')}
?{translate('settings.analysis_scope.wildcards.single_char')}
+ * + {translate('settings.analysis_scope.wildcards.zero_more_char')} -
- -
+ ** + {translate('settings.analysis_scope.wildcards.zero_more_dir')} + + ? + {translate('settings.analysis_scope.wildcards.single_char')} + +
+ + {translate('learn_more')} + +
+
+ + ); } + +const StyledGrid = styled.div` + display: grid; + grid-template-columns: 1.5rem auto; +`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx b/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx index a72e37a629c..7745de1d70a 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx @@ -17,9 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ButtonPrimary, ButtonSecondary, DangerButtonPrimary, Modal, Note } from 'design-system'; import * as React from 'react'; -import { Button, ResetButtonLink, SubmitButton } from '../../../components/controls/buttons'; -import Modal from '../../../components/controls/Modal'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { Setting } from '../../../types/settings'; import { getDefaultValue, getPropertyName, isEmptyValue } from '../utils'; @@ -38,6 +37,8 @@ type Props = { type State = { reseting: boolean }; +const MODAL_FORM_ID = 'SETTINGS.RESET_CONFIRM.FORM'; + export default class DefinitionActions extends React.PureComponent { state: State = { reseting: false }; @@ -49,7 +50,8 @@ export default class DefinitionActions extends React.PureComponent this.setState({ reseting: true }); }; - handleSubmit = () => { + handleSubmit = (e: React.SyntheticEvent) => { + e.preventDefault(); this.props.onReset(); this.handleClose(); }; @@ -57,78 +59,68 @@ export default class DefinitionActions extends React.PureComponent renderModal() { const header = translate('settings.reset_confirm.title'); return ( - -
-

{header}

-
-
-
+

{translate('settings.reset_confirm.description')}

-
-
- {translate('reset_verb')} - {translate('cancel')} -
-
-
+ + } + primaryButton={ + + {translate('reset_verb')} + + } + secondaryButtonLabel={translate('cancel')} + /> ); } render() { - const { setting, changedValue, isDefault, isEditing, hasValueChanged } = this.props; + const { setting, changedValue, isDefault, isEditing, hasValueChanged, hasError } = this.props; const hasBeenChangedToEmptyValue = changedValue !== undefined && isEmptyValue(setting.definition, changedValue); const showReset = hasBeenChangedToEmptyValue || (!isDefault && setting.hasValue); const showCancel = hasValueChanged || isEditing; return ( - <> - {isDefault && !hasValueChanged && ( -
- {translate('settings._default')} -
+
+ {hasValueChanged && ( + + {translate('save')} + )} -
- {hasValueChanged && ( - - )} - {showReset && ( - - )} + {showReset && ( + + {translate('reset_verb')} + + )} - {showCancel && ( - - {translate('cancel')} - - )} + {showCancel && ( + + {translate('cancel')} + + )} - {showReset && ( - - {translate('default')} - {': '} - {getDefaultValue(setting)} - - )} + {showReset && ( + + {translate('default')} + {': '} + {getDefaultValue(setting)} + + )} - {this.state.reseting && this.renderModal()} -
- + {this.state.reseting && this.renderModal()} +
); } } diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionDescription.tsx b/server/sonar-web/src/main/js/apps/settings/components/DefinitionDescription.tsx index 6f866b2a9e3..e85bf25586b 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/DefinitionDescription.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionDescription.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Note, SubHeading } from 'design-system'; import * as React from 'react'; import Tooltip from '../../../components/controls/Tooltip'; import { translateWithParameters } from '../../../helpers/l10n'; @@ -28,28 +29,26 @@ interface Props { definition: ExtendedSettingDefinition; } -export default function DefinitionDescription({ definition }: Props) { +export default function DefinitionDescription({ definition }: Readonly) { const propertyName = getPropertyName(definition); const description = getPropertyDescription(definition); return ( -
-

- {propertyName} -

+
+ {propertyName} {description && (
)} -
+ {translateWithParameters('settings.key_x', definition.key)} -
+
); diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/DefinitionRenderer.tsx index 47249b579be..9d0dab638f1 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/DefinitionRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionRenderer.tsx @@ -17,10 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import classNames from 'classnames'; +import { FlagMessage, Note, Spinner, TextError } from 'design-system'; import * as React from 'react'; -import AlertErrorIcon from '../../../components/icons/AlertErrorIcon'; -import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { ExtendedSettingDefinition, SettingValue } from '../../../types/settings'; import { combineDefinitionAndSettingValue, getSettingValue, isDefaultOrInherited } from '../utils'; @@ -45,7 +43,7 @@ export interface DefinitionRendererProps { const formNoop = (e: React.FormEvent) => e.preventDefault(); -export default function DefinitionRenderer(props: DefinitionRendererProps) { +export default function DefinitionRenderer(props: Readonly) { const { changedValue, loading, validationMessage, settingValue, success, definition, isEditing } = props; @@ -57,39 +55,10 @@ export default function DefinitionRenderer(props: DefinitionRendererProps) { const settingDefinitionAndValue = combineDefinitionAndSettingValue(definition, settingValue); return ( -
+
-
-
- {loading && ( - - - {translate('settings.state.saving')} - - )} - - {!loading && validationMessage && ( - - - - {translateWithParameters('settings.state.validation_failed', validationMessage)} - - - )} - - {!loading && !hasError && success && ( - - - {translate('settings.state.saved')} - - )} -
+
+ +
+ {loading && ( +
+ + {translate('settings.state.saving')} +
+ )} + + {!loading && validationMessage && ( +
+ +
+ )} + + {!loading && !hasError && success && ( + {translate('settings.state.saved')} + )} +
+ ) { const { component, settings } = props; return ( -
    +
      {settings.map((setting) => ( -
    • -
    • + ))}
    ); } + +const StyledListItem = styled.li` + & + & { + border-top: ${themeBorder('default')}; + } +`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx index 47dd8d91b1a..ab0e8cf45ab 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx @@ -17,14 +17,20 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { + BasicSeparator, + ButtonPrimary, + FlagMessage, + FormField, + InputField, + InputTextArea, + Spinner, + SubHeading, +} from 'design-system'; import * as React from 'react'; import { sendTestEmail } from '../../../api/settings'; import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; -import { SubmitButton } from '../../../components/controls/buttons'; -import { Alert } from '../../../components/ui/Alert'; -import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker'; import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation'; -import Spinner from '../../../components/ui/Spinner'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { parseError } from '../../../helpers/request'; import { LoggedInUser } from '../../../types/users'; @@ -97,80 +103,65 @@ export class EmailForm extends React.PureComponent { render() { const { error, loading, message, recipient, subject, success } = this.state; return ( -
    -
    -

    - {translate('email_configuration.test.title')} -

    -
    - - - {success && ( -
    - - {translateWithParameters('email_configuration.test.email_was_sent_to_x', success)} - -
    - )} - - {error !== undefined && ( -
    - {error} + <> + +
    +
    + {translate('email_configuration.test.title')} +
    +
    - )} - - - -
    - - -
    -
    - - -
    -
    - -