]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20500 Migrate Rule Details to the new UI
authorJeremy Davis <jeremy.davis@sonarsource.com>
Mon, 25 Sep 2023 08:12:18 +0000 (10:12 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 5 Oct 2023 20:02:47 +0000 (20:02 +0000)
16 files changed:
server/sonar-web/design-system/package.json
server/sonar-web/design-system/src/components/CodeSyntaxHighlighter.tsx
server/sonar-web/design-system/src/components/icons/InheritanceIcon.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/icons/index.ts
server/sonar-web/src/main/js/apps/coding-rules/components/ActivationButton.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RemoveExtendedDescriptionModal.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsParameters.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx
server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx
server/sonar-web/src/main/js/components/rules/RuleTabViewer.tsx
server/sonar-web/yarn.lock
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index c9fd9bdaab8f797115f283e4fbf085a32dea3f1d..3b34f44e7c2810625f02bcd2e1b1e05539a46a71 100644 (file)
@@ -39,9 +39,6 @@
     "eslint-plugin-import": "2.28.1",
     "eslint-plugin-local-rules": "2.0.0",
     "eslint-plugin-typescript-sort-keys": "2.3.0",
-    "highlight.js": "11.8.0",
-    "highlightjs-apex": "1.2.0",
-    "highlightjs-sap-abap": "0.3.0",
     "history": "5.3.0",
     "jest": "29.6.4",
     "postcss": "8.4.29",
       "config": "../tailwind.config.js",
       "preset": "emotion"
     }
+  },
+  "dependencies": {
+    "highlight.js": "11.8.0",
+    "highlightjs-apex": "1.2.0",
+    "highlightjs-cobol": "0.3.3",
+    "highlightjs-sap-abap": "0.3.0"
   }
 }
index f6fda7072a1fa6564b425dc164bf6a8e38c67f6f..bf2ca3d5ced082e1013166fffad74024688ccea9 100644 (file)
@@ -22,12 +22,14 @@ import styled from '@emotion/styled';
 import classNames from 'classnames';
 import hljs, { HighlightResult } from 'highlight.js';
 import apex from 'highlightjs-apex';
+import cobol from 'highlightjs-cobol';
 import abap from 'highlightjs-sap-abap';
 import tw from 'twin.macro';
 import { themeColor, themeContrast } from '../helpers/theme';
 
 hljs.registerLanguage('abap', abap);
 hljs.registerLanguage('apex', apex);
+hljs.registerLanguage('cobol', cobol);
 
 hljs.registerAliases('azureresourcemanager', { languageName: 'json' });
 hljs.registerAliases('flex', { languageName: 'actionscript' });
@@ -67,9 +69,12 @@ export function CodeSyntaxHighlighter(props: Props) {
     let highlightedCode: HighlightResult;
 
     try {
+      const actualLanguage =
+        language !== undefined && hljs.getLanguage(language) ? language : 'plaintext';
+
       highlightedCode = hljs.highlight(unescapedCode, {
         ignoreIllegals: true,
-        language: language ?? 'plaintext',
+        language: actualLanguage,
       });
     } catch {
       highlightedCode = hljs.highlight(unescapedCode, {
@@ -82,7 +87,7 @@ export function CodeSyntaxHighlighter(props: Props) {
       codeBlock,
       // Use a function to avoid triggering special replacement patterns
       // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_the_replacement
-      () => `<${tag}${attributes}>${highlightedCode.value}</${tag}>`
+      () => `<${tag}${attributes}>${highlightedCode.value}</${tag}>`,
     );
   });
 
diff --git a/server/sonar-web/design-system/src/components/icons/InheritanceIcon.tsx b/server/sonar-web/design-system/src/components/icons/InheritanceIcon.tsx
new file mode 100644 (file)
index 0000000..3c32a40
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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 { useTheme } from '@emotion/react';
+import { themeColor } from '../../helpers/theme';
+import { CustomIcon, IconProps } from './Icon';
+
+export function InheritanceIcon({ fill = 'currentColor', ...iconProps }: Readonly<IconProps>) {
+  const theme = useTheme();
+  const fillColor = themeColor(fill)({ theme });
+  return (
+    <CustomIcon {...iconProps}>
+      <mask fill="white" id="path-1-inside-1_3266_8058">
+        <rect height="6" rx="0.5" width="6" x="1" y="1" />
+      </mask>
+      <rect
+        height="6"
+        mask="url(#path-1-inside-1_3266_8058)"
+        rx="0.5"
+        stroke={fillColor}
+        strokeWidth="3"
+        width="6"
+        x="1"
+        y="1"
+      />
+      <mask fill="white" id="path-2-inside-2_3266_8058">
+        <rect height="6" rx="0.5" width="6" x="9" y="9" />
+      </mask>
+      <rect
+        height="6"
+        mask="url(#path-2-inside-2_3266_8058)"
+        rx="0.5"
+        stroke={fillColor}
+        strokeWidth="3"
+        width="6"
+        x="9"
+        y="9"
+      />
+      <path d="M4 7V11C4 11.5523 4.44772 12 5 12H9" stroke={fillColor} strokeWidth="1.5" />
+    </CustomIcon>
+  );
+}
index 9cbd5c2611899d087769b1f86f93f744b5b6e247..5bb8abda23c27e53f80b42318c487f737ba32add 100644 (file)
@@ -43,6 +43,7 @@ export { HelperHintIcon } from './HelperHintIcon';
 export { HomeFillIcon } from './HomeFillIcon';
 export { HomeIcon } from './HomeIcon';
 export * from './Icon';
+export { InheritanceIcon } from './InheritanceIcon';
 export { IssueLocationIcon } from './IssueLocationIcon';
 export { LinkIcon } from './LinkIcon';
 export { LockIcon } from './LockIcon';
index 6315b33f5b1c04a493f169a6f336ccb6c79147f6..24aa404d8218a42d08eb690d97b4f2939b2cf368 100644 (file)
@@ -17,9 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { ButtonSecondary } from 'design-system';
 import * as React from 'react';
 import { Profile as BaseProfile } from '../../../api/quality-profiles';
-import { Button } from '../../../components/controls/buttons';
 import { Rule, RuleActivation, RuleDetails } from '../../../types/types';
 import ActivationFormModal from './ActivationFormModal';
 
@@ -40,14 +40,14 @@ export default function ActivationButton(props: Props) {
 
   return (
     <>
-      <Button
+      <ButtonSecondary
         aria-label={ariaLabel}
         className={className}
         id="coding-rules-quality-profile-activate"
         onClick={() => setModalOpen(true)}
       >
         {buttonText}
-      </Button>
+      </ButtonSecondary>
 
       {modalOpen && (
         <ActivationFormModal
index 1246fd30a58a2aae76d72bfaa9198a3ad56a3db9..5c32d8addfaaa73db91167b44dde1f041fab563e 100644 (file)
@@ -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 { DangerButtonPrimary, Modal } from 'design-system';
 import * as React from 'react';
-import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
-import SimpleModal from '../../../components/controls/SimpleModal';
 import { translate } from '../../../helpers/l10n';
 
 interface Props {
@@ -28,28 +27,26 @@ interface Props {
 }
 
 export default function RemoveExtendedDescriptionModal({ onCancel, onSubmit }: Props) {
+  const [submitting, setSubmitting] = React.useState(false);
   const header = translate('coding_rules.remove_extended_description');
-  return (
-    <SimpleModal header={header} onClose={onCancel} onSubmit={onSubmit}>
-      {({ onCloseClick, onFormSubmit, submitting }) => (
-        <form onSubmit={onFormSubmit}>
-          <header className="modal-head">
-            <h2>{header}</h2>
-          </header>
 
-          <div className="modal-body">
-            {translate('coding_rules.remove_extended_description.confirm')}
-          </div>
+  const handleClick = React.useCallback(() => {
+    setSubmitting(true);
+    onSubmit();
+  }, [onSubmit]);
 
-          <footer className="modal-foot">
-            {submitting && <i className="spinner spacer-right" />}
-            <SubmitButton className="button-red" disabled={submitting}>
-              {translate('remove')}
-            </SubmitButton>
-            <ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink>
-          </footer>
-        </form>
-      )}
-    </SimpleModal>
+  return (
+    <Modal
+      headerTitle={header}
+      body={translate('coding_rules.remove_extended_description.confirm')}
+      onClose={onCancel}
+      primaryButton={
+        <DangerButtonPrimary disabled={submitting} onClick={handleClick}>
+          {translate('remove')}
+        </DangerButtonPrimary>
+      }
+      loading={submitting}
+      secondaryButtonLabel={translate('cancel')}
+    />
   );
 }
index eef92663a2dda552a0008ab7827dc55f4f295a9f..feafa53b4daa13d552979911022de0782f6c9c4d 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { SubHeadingHighlight } from 'design-system/lib';
 import * as React from 'react';
 import { Profile } from '../../../api/quality-profiles';
 import { deleteRule, getRuleDetails, updateRule } from '../../../api/rules';
 import ConfirmButton from '../../../components/controls/ConfirmButton';
 import HelpTooltip from '../../../components/controls/HelpTooltip';
 import { Button } from '../../../components/controls/buttons';
+import DateFormatter from '../../../components/intl/DateFormatter';
 import Spinner from '../../../components/ui/Spinner';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { Dict, RuleActivation, RuleDetails as TypeRuleDetails } from '../../../types/types';
@@ -252,6 +254,13 @@ export default class RuleDetails extends React.PureComponent<Props, State> {
           {!ruleDetails.isTemplate && ruleDetails.type !== 'SECURITY_HOTSPOT' && (
             <RuleDetailsIssues ruleDetails={ruleDetails} />
           )}
+
+          <div className="sw-mb-8" data-meta="available-since">
+            <SubHeadingHighlight as="h3">
+              {translate('coding_rules.available_since')}
+            </SubHeadingHighlight>
+            <DateFormatter date={ruleDetails.createdAt} />
+          </div>
         </Spinner>
       </div>
     );
index df822427393c794cdeed4788d9f0902983894f8e..d104dd1514b13c0f351bac287e3b489830278713 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import {
+  ButtonSecondary,
+  ContentCell,
+  DangerButtonSecondary,
+  HeadingDark,
+  Link,
+  Spinner,
+  Table,
+  TableRow,
+  UnorderedList,
+} from 'design-system';
 import { sortBy } from 'lodash';
 import * as React from 'react';
 import { deleteRule, searchRules } from '../../../api/rules';
-import Link from '../../../components/common/Link';
 import ConfirmButton from '../../../components/controls/ConfirmButton';
-import { Button } from '../../../components/controls/buttons';
-import SeverityHelper from '../../../components/shared/SeverityHelper';
-import Spinner from '../../../components/ui/Spinner';
+import IssueSeverityIcon from '../../../components/icon-mappers/IssueSeverityIcon';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { getRuleUrl } from '../../../helpers/urls';
+import { IssueSeverity } from '../../../types/issues';
 import { Rule, RuleDetails } from '../../../types/types';
 import CustomRuleButton from './CustomRuleButton';
 
@@ -40,6 +49,9 @@ interface State {
   rules?: Rule[];
 }
 
+const COLUMN_COUNT = 3;
+const COLUMN_COUNT_WITH_EDIT_PERMISSIONS = 4;
+
 export default class RuleDetailsCustomRules extends React.PureComponent<Props, State> {
   mounted = false;
   state: State = { loading: false };
@@ -97,32 +109,36 @@ export default class RuleDetailsCustomRules extends React.PureComponent<Props, S
   };
 
   renderRule = (rule: Rule) => (
-    <tr data-rule={rule.key} key={rule.key}>
-      <td className="coding-rules-detail-list-name">
+    <TableRow data-rule={rule.key} key={rule.key}>
+      <ContentCell>
         <Link to={getRuleUrl(rule.key)}>{rule.name}</Link>
-      </td>
-
-      <td className="coding-rules-detail-list-severity">
-        <SeverityHelper className="display-flex-center" severity={rule.severity} />
-      </td>
-
-      <td className="coding-rules-detail-list-parameters">
-        {rule.params &&
-          rule.params
-            .filter((param) => param.defaultValue)
+      </ContentCell>
+
+      <ContentCell>
+        <IssueSeverityIcon
+          className="sw-mr-1"
+          severity={rule.severity as IssueSeverity}
+          aria-hidden
+        />
+        {translate('severity', rule.severity)}
+      </ContentCell>
+
+      <ContentCell>
+        <UnorderedList className="sw-mt-0">
+          {rule.params
+            ?.filter((param) => param.defaultValue)
             .map((param) => (
-              <div className="coding-rules-detail-list-parameter" key={param.key}>
-                <span className="key">{param.key}</span>
-                <span className="sep">:&nbsp;</span>
-                <span className="value" title={param.defaultValue}>
-                  {param.defaultValue}
-                </span>
-              </div>
+              <li key={param.key}>
+                <span className="sw-font-semibold">{param.key}</span>
+                <span>:&nbsp;</span>
+                <span title={param.defaultValue}>{param.defaultValue}</span>
+              </li>
             ))}
-      </td>
+        </UnorderedList>
+      </ContentCell>
 
       {this.props.canChange && (
-        <td className="coding-rules-detail-list-actions">
+        <ContentCell>
           <ConfirmButton
             confirmButtonText={translate('delete')}
             confirmData={rule.key}
@@ -132,43 +148,49 @@ export default class RuleDetailsCustomRules extends React.PureComponent<Props, S
             onConfirm={this.handleRuleDelete}
           >
             {({ onClick }) => (
-              <Button
-                className="button-red js-delete-custom-rule"
+              <DangerButtonSecondary
+                className="js-delete-custom-rule"
                 aria-label={translateWithParameters('coding_rules.delete_rule_x', rule.name)}
                 onClick={onClick}
               >
                 {translate('delete')}
-              </Button>
+              </DangerButtonSecondary>
             )}
           </ConfirmButton>
-        </td>
+        </ContentCell>
       )}
-    </tr>
+    </TableRow>
   );
 
   render() {
     const { loading, rules = [] } = this.state;
 
     return (
-      <div className="js-rule-custom-rules coding-rule-section">
-        <div className="coding-rules-detail-custom-rules-section">
-          <h2 className="coding-rules-detail-title">{translate('coding_rules.custom_rules')}</h2>
+      <div className="js-rule-custom-rules">
+        <div>
+          <HeadingDark as="h2">{translate('coding_rules.custom_rules')}</HeadingDark>
 
           {this.props.canChange && (
             <CustomRuleButton onDone={this.handleRuleCreate} templateRule={this.props.ruleDetails}>
               {({ onClick }) => (
-                <Button className="js-create-custom-rule spacer-left" onClick={onClick}>
+                <ButtonSecondary className="js-create-custom-rule sw-mt-6" onClick={onClick}>
                   {translate('coding_rules.create')}
-                </Button>
+                </ButtonSecondary>
               )}
             </CustomRuleButton>
           )}
 
-          <Spinner className="spacer-left" loading={loading}>
+          <Spinner className="sw-my-6" loading={loading}>
             {rules.length > 0 && (
-              <table className="coding-rules-detail-list" id="coding-rules-detail-custom-rules">
-                <tbody>{sortBy(rules, (rule) => rule.name).map(this.renderRule)}</tbody>
-              </table>
+              <Table
+                className="sw-my-6"
+                id="coding-rules-detail-custom-rules"
+                columnCount={
+                  this.props.canChange ? COLUMN_COUNT_WITH_EDIT_PERMISSIONS : COLUMN_COUNT
+                }
+              >
+                {sortBy(rules, (rule) => rule.name).map(this.renderRule)}
+              </Table>
             )}
           </Spinner>
         </div>
index 62e860d6fab03da83ddb9368d7a0b134cc177fed..57a2fa0360234b97a8cc4322212d61de3f2046b5 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { CodeSyntaxHighlighter } from 'design-system';
+import {
+  ButtonPrimary,
+  ButtonSecondary,
+  CodeSyntaxHighlighter,
+  DangerButtonSecondary,
+  InputTextArea,
+  Spinner,
+} from 'design-system';
 import * as React from 'react';
 import { updateRule } from '../../../api/rules';
 import FormattingTips from '../../../components/common/FormattingTips';
-import { Button, ResetButtonLink } from '../../../components/controls/buttons';
 import RuleTabViewer from '../../../components/rules/RuleTabViewer';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { sanitizeString, sanitizeUserInput } from '../../../helpers/sanitize';
@@ -67,7 +73,8 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S
     this.setState({ descriptionForm: false });
   };
 
-  handleSaveClick = () => {
+  handleSaveClick = (event: React.SyntheticEvent<HTMLFormElement>) => {
+    event.preventDefault();
     this.updateDescription(this.state.description);
   };
 
@@ -116,88 +123,82 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S
     <div id="coding-rules-detail-description-extra">
       {this.props.ruleDetails.htmlNote !== undefined && (
         <CodeSyntaxHighlighter
-          className="rule-desc markdown sw-mb-2"
+          className="markdown sw-my-6"
           htmlAsString={sanitizeUserInput(this.props.ruleDetails.htmlNote)}
           language={this.props.ruleDetails.lang}
         />
       )}
 
-      {this.props.canWrite && (
-        <Button
-          id="coding-rules-detail-extend-description"
-          onClick={this.handleExtendDescriptionClick}
-        >
-          {translate('coding_rules.extend_description')}
-        </Button>
-      )}
+      <div className="sw-my-6">
+        {this.props.canWrite && (
+          <ButtonSecondary onClick={this.handleExtendDescriptionClick}>
+            {translate('coding_rules.extend_description')}
+          </ButtonSecondary>
+        )}
+      </div>
     </div>
   );
 
   renderForm = () => (
-    <div className="coding-rules-detail-extend-description-form">
-      <table className="width-100">
-        <tbody>
-          <tr>
-            <td colSpan={2}>
-              <textarea
-                autoFocus
-                aria-label={translate('coding_rules.extend_description')}
-                className="width-100 little-spacer-bottom"
-                id="coding-rules-detail-extend-description-text"
-                onChange={this.handleDescriptionChange}
-                rows={4}
-                value={this.state.description}
-              />
-            </td>
-          </tr>
-
-          <tr>
-            <td>
-              <Button
+    <form
+      aria-label={translate('coding_rules.detail.extend_description.form')}
+      className="sw-my-6"
+      onSubmit={this.handleSaveClick}
+    >
+      <InputTextArea
+        aria-label={translate('coding_rules.extend_description')}
+        className="sw-mb-2 sw-resize-y"
+        id="coding-rules-detail-extend-description-text"
+        size="full"
+        onChange={this.handleDescriptionChange}
+        rows={4}
+        value={this.state.description}
+      />
+
+      <div className="sw-flex sw-items-center sw-justify-between">
+        <div className="sw-flex sw-items-center">
+          <ButtonPrimary
+            id="coding-rules-detail-extend-description-submit"
+            disabled={this.state.submitting}
+            type="submit"
+          >
+            {translate('save')}
+          </ButtonPrimary>
+
+          {this.props.ruleDetails.mdNote !== undefined && (
+            <>
+              <DangerButtonSecondary
+                className="sw-ml-2"
                 disabled={this.state.submitting}
-                id="coding-rules-detail-extend-description-submit"
-                onClick={this.handleSaveClick}
+                id="coding-rules-detail-extend-description-remove"
+                onClick={this.handleRemoveDescriptionClick}
               >
-                {translate('save')}
-              </Button>
-
-              {this.props.ruleDetails.mdNote !== undefined && (
-                <>
-                  <Button
-                    className="button-red spacer-left"
-                    disabled={this.state.submitting}
-                    id="coding-rules-detail-extend-description-remove"
-                    onClick={this.handleRemoveDescriptionClick}
-                  >
-                    {translate('remove')}
-                  </Button>
-                  {this.state.removeDescriptionModal && (
-                    <RemoveExtendedDescriptionModal
-                      onCancel={this.handleCancelRemoving}
-                      onSubmit={this.handleConfirmRemoving}
-                    />
-                  )}
-                </>
+                {translate('remove')}
+              </DangerButtonSecondary>
+              {this.state.removeDescriptionModal && (
+                <RemoveExtendedDescriptionModal
+                  onCancel={this.handleCancelRemoving}
+                  onSubmit={this.handleConfirmRemoving}
+                />
               )}
-
-              <ResetButtonLink
-                className="spacer-left"
-                disabled={this.state.submitting}
-                id="coding-rules-detail-extend-description-cancel"
-                onClick={this.handleCancelClick}
-              >
-                {translate('cancel')}
-              </ResetButtonLink>
-              {this.state.submitting && <i className="spinner spacer-left" />}
-            </td>
-
-            <td className="text-right">
-              <FormattingTips />
-            </td>
-          </tr>
-        </tbody>
-      </table>
-    </div>
+            </>
+          )}
+
+          <ButtonSecondary
+            className="sw-ml-2"
+            disabled={this.state.submitting}
+            id="coding-rules-detail-extend-description-cancel"
+            onClick={this.handleCancelClick}
+          >
+            {translate('cancel')}
+          </ButtonSecondary>
+
+          <Spinner className="sw-ml-2" loading={this.state.submitting} />
+        </div>
+
+        <FormattingTips />
+      </div>
+    </form>
   );
 
   render() {
@@ -252,7 +253,7 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S
         )}
 
         {!ruleDetails.templateKey && (
-          <div className="coding-rules-detail-description coding-rules-detail-description-extra">
+          <div className="sw-mt-6">
             {!this.state.descriptionForm && this.renderExtendedDescription()}
             {this.state.descriptionForm && this.props.canWrite && this.renderForm()}
           </div>
index 874ed9da3c197a0fa89d634fb7435bed10b93787..057c9242af3ffe7ca0a643410f38dac78320ee16 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { ContentCell, Link, Spinner, SubHeadingHighlight, Table, TableRow } from 'design-system';
 import * as React from 'react';
 import { getFacet } from '../../../api/issues';
 import withAvailableFeatures, {
   WithAvailableFeaturesProps,
 } from '../../../app/components/available-features/withAvailableFeatures';
-import Link from '../../../components/common/Link';
 import Tooltip from '../../../components/controls/Tooltip';
-import Spinner from '../../../components/ui/Spinner';
 import { translate } from '../../../helpers/l10n';
 import { formatMeasure } from '../../../helpers/measures';
 import { getIssuesUrl } from '../../../helpers/urls';
@@ -137,12 +136,12 @@ export class RuleDetailsIssues extends React.PureComponent<Props, State> {
 
     const path = getIssuesUrl({ resolved: 'false', rules: key, projects: project.key });
     return (
-      <tr key={project.key}>
-        <td className="coding-rules-detail-list-name">{project.name}</td>
-        <td className="coding-rules-detail-list-parameters">
+      <TableRow key={project.key}>
+        <ContentCell>{project.name}</ContentCell>
+        <ContentCell>
           <Link to={path}>{formatMeasure(project.count, MetricType.Integer)}</Link>
-        </td>
-      </tr>
+        </ContentCell>
+      </TableRow>
     );
   };
 
@@ -150,26 +149,29 @@ export class RuleDetailsIssues extends React.PureComponent<Props, State> {
     const { loading, projects = [] } = this.state;
 
     return (
-      <div className="js-rule-issues coding-rule-section">
+      <div className="sw-mb-8">
         <Spinner loading={loading}>
-          <h2 className="coding-rules-detail-title">
+          <SubHeadingHighlight as="h2">
             {translate('coding_rules.issues')}
             {this.renderTotal()}
-          </h2>
+          </SubHeadingHighlight>
 
           {projects.length > 0 ? (
-            <table className="coding-rules-detail-list coding-rules-most-violated-projects">
-              <tbody>
-                <tr>
-                  <td className="coding-rules-detail-list-name" colSpan={2}>
+            <Table
+              className="sw-mt-6"
+              columnCount={2}
+              header={
+                <TableRow>
+                  <ContentCell colSpan={2}>
                     {translate('coding_rules.most_violating_projects')}
-                  </td>
-                </tr>
-                {projects.map(this.renderProject)}
-              </tbody>
-            </table>
+                  </ContentCell>
+                </TableRow>
+              }
+            >
+              {projects.map(this.renderProject)}
+            </Table>
           ) : (
-            <div className="big-padded-bottom">
+            <div className="sw-mb-6">
               {translate('coding_rules.no_issue_detected_for_projects')}
             </div>
           )}
index ba62978ab67352e1e3bd538b7dd4ca20fb298e97..88dff1ff20fb8ac44fec962ee400f0d663a806d7 100644 (file)
@@ -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 { CellComponent, Note, SubHeadingHighlight, Table, TableRow } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../helpers/l10n';
 import { sanitizeString } from '../../../helpers/sanitize';
@@ -29,33 +30,33 @@ interface Props {
 export default function RuleDetailsParameters({ params }: Props) {
   return (
     <div className="js-rule-parameters">
-      <h3 className="coding-rules-detail-title">{translate('coding_rules.parameters')}</h3>
-      <table className="coding-rules-detail-parameters">
-        <tbody>
-          {params.map((param) => (
-            <tr className="coding-rules-detail-parameter" key={param.key}>
-              <td className="coding-rules-detail-parameter-name">{param.key}</td>
-              <td className="coding-rules-detail-parameter-description">
+      <SubHeadingHighlight as="h3">{translate('coding_rules.parameters')}</SubHeadingHighlight>
+      <Table className="sw-my-4" columnCount={2} columnWidths={[0, 'auto']}>
+        {params.map((param) => (
+          <TableRow key={param.key}>
+            <CellComponent className="sw-align-top sw-font-semibold">{param.key}</CellComponent>
+            <CellComponent>
+              <div className="sw-flex sw-flex-col sw-gap-2">
                 {param.htmlDesc !== undefined && (
-                  <p
+                  <div
                     // eslint-disable-next-line react/no-danger
                     dangerouslySetInnerHTML={{ __html: sanitizeString(param.htmlDesc) }}
                   />
                 )}
                 {param.defaultValue !== undefined && (
-                  <div className="note spacer-top">
+                  <Note as="div">
                     {translate('coding_rules.parameters.default_value')}
                     <br />
                     <span className="coding-rules-detail-parameter-value">
                       {param.defaultValue}
                     </span>
-                  </div>
+                  </Note>
                 )}
-              </td>
-            </tr>
-          ))}
-        </tbody>
-      </table>
+              </div>
+            </CellComponent>
+          </TableRow>
+        ))}
+      </Table>
     </div>
   );
 }
index c5f2e31b5ac448982bb9fd301ad7a0b6eddef0f6..d99f838eec7d82dbe85133c5b2effd463e18b28a 100644 (file)
  * 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 {
+  ActionCell,
+  CellComponent,
+  ContentCell,
+  DangerButtonSecondary,
+  DiscreetLink,
+  InheritanceIcon,
+  Link,
+  Note,
+  SubHeadingHighlight,
+  Table,
+  TableRowInteractive,
+} from 'design-system';
 import { filter } from 'lodash';
 import * as React from 'react';
-import { activateRule, deactivateRule, Profile } from '../../../api/quality-profiles';
-import InstanceMessage from '../../../components/common/InstanceMessage';
-import Link from '../../../components/common/Link';
-import { Button } from '../../../components/controls/buttons';
+import { FormattedMessage } from 'react-intl';
+import { Profile, activateRule, deactivateRule } from '../../../api/quality-profiles';
 import ConfirmButton from '../../../components/controls/ConfirmButton';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { getQualityProfileUrl } from '../../../helpers/urls';
 import { Dict, RuleActivation, RuleDetails } from '../../../types/types';
 import BuiltInQualityProfileBadge from '../../quality-profiles/components/BuiltInQualityProfileBadge';
 import ActivationButton from './ActivationButton';
-import RuleInheritanceIcon from './RuleInheritanceIcon';
 
 interface Props {
   activations: RuleActivation[] | undefined;
@@ -40,6 +51,11 @@ interface Props {
   ruleDetails: RuleDetails;
 }
 
+const COLUMN_COUNT_WITH_PARAMS = 3;
+const COLUMN_COUNT_WITHOUT_PARAMS = 2;
+
+const PROFILES_HEADING_ID = 'rule-details-profiles-heading';
+
 export default class RuleDetailsProfiles extends React.PureComponent<Props> {
   handleActivate = () => this.props.onActivate();
 
@@ -68,16 +84,16 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props> {
     }
     const profilePath = getQualityProfileUrl(profile.parentName, profile.language);
     return (
-      <div className="coding-rules-detail-quality-profile-inheritance">
-        {(activation.inherit === 'OVERRIDES' || activation.inherit === 'INHERITED') && (
-          <>
-            <RuleInheritanceIcon className="text-middle" inheritance={activation.inherit} />
-            <Link className="little-spacer-left text-middle" to={profilePath}>
-              {profile.parentName}
-            </Link>
-          </>
-        )}
-      </div>
+      (activation.inherit === 'OVERRIDES' || activation.inherit === 'INHERITED') && (
+        <Note as="div" className="sw-flex sw-items-center sw-mt-2">
+          <InheritanceIcon
+            fill={activation.inherit === 'OVERRIDES' ? 'destructiveIconFocus' : 'currentColor'}
+          />
+          <DiscreetLink className="sw-ml-1" to={profilePath}>
+            {profile.parentName}
+          </DiscreetLink>
+        </Note>
+      )
     );
   };
 
@@ -86,25 +102,28 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props> {
     const originalValue = originalParam?.value;
 
     return (
-      <div className="coding-rules-detail-quality-profile-parameter" key={param.key}>
+      <StyledParameter className="sw-my-4" key={param.key}>
         <span className="key">{param.key}</span>
-        <span className="sep">: </span>
+        <span className="sep sw-mr-1">: </span>
         <span className="value" title={param.value}>
           {param.value}
         </span>
         {parentActivation && param.value !== originalValue && (
-          <div className="coding-rules-detail-quality-profile-inheritance">
-            {translate('coding_rules.original')} <span className="value">{originalValue}</span>
+          <div className="sw-flex sw-ml-4">
+            {translate('coding_rules.original')}
+            <span className="value sw-ml-1" title={originalValue}>
+              {originalValue}
+            </span>
           </div>
         )}
-      </div>
+      </StyledParameter>
     );
   };
 
   renderParameters = (activation: RuleActivation, parentActivation?: RuleActivation) => (
-    <td className="coding-rules-detail-quality-profile-parameters">
+    <CellComponent>
       {activation.params.map((param) => this.renderParameter(param, parentActivation))}
-    </td>
+    </CellComponent>
   );
 
   renderActions = (activation: RuleActivation, profile: Profile) => {
@@ -112,7 +131,7 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props> {
     const { ruleDetails } = this.props;
     const hasParent = activation.inherit !== 'NONE' && profile.parentKey;
     return (
-      <td className="coding-rules-detail-quality-profile-actions">
+      <ActionCell>
         {canEdit && (
           <>
             {!ruleDetails.isTemplate && !!ruleDetails.params?.length && (
@@ -120,7 +139,6 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props> {
                 activation={activation}
                 ariaLabel={translateWithParameters('coding_rules.change_details_x', profile.name)}
                 buttonText={translate('change_verb')}
-                className="coding-rules-detail-quality-profile-change"
                 modalHeader={translate('coding_rules.change_details')}
                 onDone={this.handleActivate}
                 profiles={[profile]}
@@ -140,12 +158,9 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props> {
                 onConfirm={this.handleRevert}
               >
                 {({ onClick }) => (
-                  <Button
-                    className="coding-rules-detail-quality-profile-revert button-red spacer-left"
-                    onClick={onClick}
-                  >
+                  <DangerButtonSecondary className="sw-ml-2" onClick={onClick}>
                     {translate('coding_rules.revert_to_parent_definition')}
-                  </Button>
+                  </DangerButtonSecondary>
                 )}
               </ConfirmButton>
             )}
@@ -159,8 +174,8 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props> {
                 onConfirm={this.handleDeactivate}
               >
                 {({ onClick }) => (
-                  <Button
-                    className="coding-rules-detail-quality-profile-deactivate button-red spacer-left"
+                  <DangerButtonSecondary
+                    className="sw-ml-2"
                     aria-label={translateWithParameters(
                       'coding_rules.deactivate_in_quality_profile_x',
                       profile.name,
@@ -168,13 +183,13 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props> {
                     onClick={onClick}
                   >
                     {translate('coding_rules.deactivate')}
-                  </Button>
+                  </DangerButtonSecondary>
                 )}
               </ConfirmButton>
             )}
           </>
         )}
-      </td>
+      </ActionCell>
     );
   };
 
@@ -188,16 +203,20 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props> {
     const parentActivation = activations.find((x) => x.qProfile === profile.parentKey);
 
     return (
-      <tr key={profile.key}>
-        <td className="coding-rules-detail-quality-profile-name">
-          <Link to={getQualityProfileUrl(profile.name, profile.language)}>{profile.name}</Link>
-          {profile.isBuiltIn && <BuiltInQualityProfileBadge className="spacer-left" />}
-          {this.renderInheritedProfile(activation, profile)}
-        </td>
+      <TableRowInteractive key={profile.key}>
+        <ContentCell className="coding-rules-detail-quality-profile-name">
+          <div className="sw-flex sw-flex-col">
+            <div>
+              <Link to={getQualityProfileUrl(profile.name, profile.language)}>{profile.name}</Link>
+              {profile.isBuiltIn && <BuiltInQualityProfileBadge className="sw-ml-2" />}
+            </div>
+            {this.renderInheritedProfile(activation, profile)}
+          </div>
+        </ContentCell>
 
         {!ruleDetails.templateKey && this.renderParameters(activation, parentActivation)}
         {this.renderActions(activation, profile)}
-      </tr>
+      </TableRowInteractive>
     );
   };
 
@@ -208,36 +227,52 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props> {
     );
 
     return (
-      <div className="js-rule-profiles coding-rule-section">
-        <div className="coding-rules-detail-quality-profiles-section">
-          <h2 className="coding-rules-detail-title">
-            <InstanceMessage message={translate('coding_rules.quality_profiles')} />
-          </h2>
-
-          {canActivate && (
-            <ActivationButton
-              buttonText={translate('coding_rules.activate')}
-              className="coding-rules-quality-profile-activate"
-              modalHeader={translate('coding_rules.activate_in_quality_profile')}
-              onDone={this.handleActivate}
-              profiles={filter(
-                this.props.referencedProfiles,
-                (profile) => !activations.find((activation) => activation.qProfile === profile.key),
-              )}
-              rule={ruleDetails}
-            />
-          )}
-
-          {activations.length > 0 && (
-            <table
-              className="coding-rules-detail-quality-profiles width-100"
-              id="coding-rules-detail-quality-profiles"
-            >
-              <tbody>{activations.map(this.renderActivation)}</tbody>
-            </table>
-          )}
-        </div>
+      <div className="js-rule-profiles sw-mb-8">
+        <SubHeadingHighlight as="h2" id={PROFILES_HEADING_ID}>
+          <FormattedMessage id="coding_rules.quality_profiles" />
+        </SubHeadingHighlight>
+
+        {canActivate && (
+          <ActivationButton
+            buttonText={translate('coding_rules.activate')}
+            className="sw-mt-6"
+            modalHeader={translate('coding_rules.activate_in_quality_profile')}
+            onDone={this.handleActivate}
+            profiles={filter(
+              this.props.referencedProfiles,
+              (profile) => !activations.find((activation) => activation.qProfile === profile.key),
+            )}
+            rule={ruleDetails}
+          />
+        )}
+
+        {activations.length > 0 && (
+          <Table
+            aria-labelledby={PROFILES_HEADING_ID}
+            className="sw-my-6"
+            columnCount={
+              ruleDetails.templateKey ? COLUMN_COUNT_WITHOUT_PARAMS : COLUMN_COUNT_WITH_PARAMS
+            }
+            id="coding-rules-detail-quality-profiles"
+          >
+            {activations.map(this.renderActivation)}
+          </Table>
+        )}
       </div>
     );
   }
 }
+
+const StyledParameter = styled.div`
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+
+  .value {
+    display: inline-block;
+    max-width: 300px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+`;
index 7af8515d6b03c9ee7379e1cef9d83d06a30e36e4..2a7b9afd7e99813c03890bb70e3dd97cb4e8a203 100644 (file)
@@ -124,7 +124,7 @@ const selectors = {
     name: 'coding_rules.description_section.title.assess_the_problem',
   }),
   moreInfoTab: byRole('tab', {
-    name: 'coding_rules.description_section.title.more_info',
+    name: /coding_rules.description_section.title.more_info/,
   }),
   howToFixTab: byRole('tab', {
     name: 'coding_rules.description_section.title.how_to_fix',
index a6f04251be1961348dce8d4d3b61cf7b6a98ce37..f8db72459ddb588e41582f8df9277443d4ed2bdc 100644 (file)
@@ -29,21 +29,19 @@ import { RuleDescriptionSections } from '../../apps/coding-rules/rule';
 import { translate } from '../../helpers/l10n';
 import { Issue, RuleDetails } from '../../types/types';
 import { NoticeType } from '../../types/users';
-import ScreenPositionHelper from '../common/ScreenPositionHelper';
-import BoxedTabs, { getTabId, getTabPanelId } from '../controls/BoxedTabs';
+import { getTabId, getTabPanelId } from '../controls/BoxedTabs';
 import withLocation from '../hoc/withLocation';
 import MoreInfoRuleDescription from './MoreInfoRuleDescription';
 import RuleDescription from './RuleDescription';
 
+import { ToggleButton } from 'design-system/lib';
 import './style.css';
 
 interface RuleTabViewerProps extends CurrentUserContextInterface {
   ruleDetails: RuleDetails;
   extendedDescription?: string;
   ruleDescriptionContextKey?: string;
-  codeTabContent?: React.ReactNode;
   activityTabContent?: React.ReactNode;
-  scrollInTab?: boolean;
   location: Location;
   selectedFlowIndex?: number;
   selectedLocationIndex?: number;
@@ -58,9 +56,10 @@ interface State {
 }
 
 export interface Tab {
-  key: TabKeys;
-  label: React.ReactNode;
+  value: TabKeys;
+  label: string;
   content: React.ReactNode;
+  counter?: number;
 }
 
 export enum TabKeys {
@@ -102,7 +101,7 @@ export class RuleTabViewer extends React.PureComponent<RuleTabViewerProps, State
 
     if (query.has('why')) {
       this.setState({
-        selectedTab: tabs.find((tab) => tab.key === TabKeys.WhyIsThisAnIssue) ?? tabs[0],
+        selectedTab: tabs.find((tab) => tab.value === TabKeys.WhyIsThisAnIssue) ?? tabs[0],
       });
     }
   }
@@ -138,12 +137,12 @@ export class RuleTabViewer extends React.PureComponent<RuleTabViewerProps, State
       );
     }
 
-    if (selectedTab?.key === TabKeys.MoreInfo) {
+    if (selectedTab?.value === TabKeys.MoreInfo) {
       this.checkIfEducationPrinciplesAreVisible();
     }
 
     if (
-      prevState.selectedTab?.key === TabKeys.MoreInfo &&
+      prevState.selectedTab?.value === TabKeys.MoreInfo &&
       prevState.displayEducationalPrinciplesNotification &&
       prevState.educationalPrinciplesNotificationHasBeenDismissed
     ) {
@@ -178,9 +177,7 @@ export class RuleTabViewer extends React.PureComponent<RuleTabViewerProps, State
 
   computeTabs = (displayEducationalPrinciplesNotification: boolean) => {
     const {
-      codeTabContent,
       ruleDetails: { descriptionSections, educationPrinciples, lang: ruleLanguage, type: ruleType },
-      ruleDescriptionContextKey,
       extendedDescription,
       activityTabContent,
     } = this.props;
@@ -211,8 +208,6 @@ export class RuleTabViewer extends React.PureComponent<RuleTabViewerProps, State
         content: (descriptionSectionsByKey[RuleDescriptionSections.DEFAULT] ||
           descriptionSectionsByKey[RuleDescriptionSections.ROOT_CAUSE]) && (
           <RuleDescription
-            className="padded"
-            defaultContextKey={ruleDescriptionContextKey}
             language={ruleLanguage}
             sections={
               descriptionSectionsByKey[RuleDescriptionSections.DEFAULT] ||
@@ -220,7 +215,7 @@ export class RuleTabViewer extends React.PureComponent<RuleTabViewerProps, State
             }
           />
         ),
-        key: TabKeys.WhyIsThisAnIssue,
+        value: TabKeys.WhyIsThisAnIssue,
         label:
           ruleType === 'SECURITY_HOTSPOT'
             ? translate('coding_rules.description_section.title.root_cause.SECURITY_HOTSPOT')
@@ -229,29 +224,26 @@ export class RuleTabViewer extends React.PureComponent<RuleTabViewerProps, State
       {
         content: descriptionSectionsByKey[RuleDescriptionSections.ASSESS_THE_PROBLEM] && (
           <RuleDescription
-            className="padded"
             language={ruleLanguage}
             sections={descriptionSectionsByKey[RuleDescriptionSections.ASSESS_THE_PROBLEM]}
           />
         ),
-        key: TabKeys.AssessTheIssue,
+        value: TabKeys.AssessTheIssue,
         label: translate('coding_rules.description_section.title', TabKeys.AssessTheIssue),
       },
       {
         content: descriptionSectionsByKey[RuleDescriptionSections.HOW_TO_FIX] && (
           <RuleDescription
-            className="padded"
-            defaultContextKey={ruleDescriptionContextKey}
             language={ruleLanguage}
             sections={descriptionSectionsByKey[RuleDescriptionSections.HOW_TO_FIX]}
           />
         ),
-        key: TabKeys.HowToFixIt,
+        value: TabKeys.HowToFixIt,
         label: translate('coding_rules.description_section.title', TabKeys.HowToFixIt),
       },
       {
         content: activityTabContent,
-        key: TabKeys.Activity,
+        value: TabKeys.Activity,
         label: translate('coding_rules.description_section.title', TabKeys.Activity),
       },
       {
@@ -265,24 +257,12 @@ export class RuleTabViewer extends React.PureComponent<RuleTabViewerProps, State
             sections={descriptionSectionsByKey[RuleDescriptionSections.RESOURCES]}
           />
         ),
-        key: TabKeys.MoreInfo,
-        label: (
-          <>
-            {translate('coding_rules.description_section.title', TabKeys.MoreInfo)}
-            {displayEducationalPrinciplesNotification && <div className="notice-dot" />}
-          </>
-        ),
+        value: TabKeys.MoreInfo,
+        label: translate('coding_rules.description_section.title', TabKeys.MoreInfo),
+        counter: displayEducationalPrinciplesNotification ? 1 : undefined,
       },
     ];
 
-    if (codeTabContent !== undefined) {
-      tabs.unshift({
-        content: codeTabContent,
-        key: TabKeys.Code,
-        label: translate('issue.tabs', TabKeys.Code),
-      });
-    }
-
     return tabs.filter((tab) => tab.content);
   };
 
@@ -327,12 +307,11 @@ export class RuleTabViewer extends React.PureComponent<RuleTabViewerProps, State
 
   handleSelectTabs = (currentTabKey: TabKeys) => {
     this.setState(({ tabs }) => ({
-      selectedTab: tabs.find((tab) => tab.key === currentTabKey) || tabs[0],
+      selectedTab: tabs.find((tab) => tab.value === currentTabKey) ?? tabs[0],
     }));
   };
 
   render() {
-    const { scrollInTab } = this.props;
     const { tabs, selectedTab } = this.state;
 
     if (!tabs || tabs.length === 0 || !selectedTab) {
@@ -341,42 +320,35 @@ export class RuleTabViewer extends React.PureComponent<RuleTabViewerProps, State
 
     return (
       <>
-        <div>
-          <BoxedTabs
-            className="big-spacer-top"
-            onSelect={this.handleSelectTabs}
-            selected={selectedTab.key}
-            tabs={tabs}
+        <div className="sw-mt-4">
+          <ToggleButton
+            role="tablist"
+            onChange={this.handleSelectTabs}
+            options={tabs}
+            value={selectedTab.value}
           />
         </div>
-        <ScreenPositionHelper>
-          {({ top }) => (
-            <div
-              aria-labelledby={getTabId(selectedTab.key)}
-              className="bordered display-flex-column"
-              id={getTabPanelId(selectedTab.key)}
-              role="tabpanel"
-              style={{
-                // We substract the footer height with padding (80) and the main layout padding (20)
-                maxHeight: scrollInTab ? `calc(100vh - ${top + 100}px)` : 'initial',
-              }}
-            >
-              {
-                // Preserve tabs state by always rendering all of them. Only hide them when not selected
-                tabs.map((tab) => (
-                  <div
-                    className={classNames('overflow-y-auto spacer', {
-                      hidden: tab.key !== selectedTab.key,
-                    })}
-                    key={tab.key}
-                  >
-                    {tab.content}
-                  </div>
-                ))
-              }
-            </div>
-          )}
-        </ScreenPositionHelper>
+
+        <div
+          aria-labelledby={getTabId(selectedTab.value)}
+          className="sw-flex sw-flex-col sw-py-6"
+          id={getTabPanelId(selectedTab.value)}
+          role="tabpanel"
+        >
+          {
+            // Preserve tabs state by always rendering all of them. Only hide them when not selected
+            tabs.map((tab) => (
+              <div
+                className={classNames({
+                  'sw-hidden': tab.value !== selectedTab.value,
+                })}
+                key={tab.value}
+              >
+                {tab.content}
+              </div>
+            ))
+          }
+        </div>
       </>
     );
   }
index 595b17865fbc2d05f4362a1a1d2db46fad1e2b89..34e9cb0041c206320c06d1a75362febadb6d2b41 100644 (file)
@@ -6935,6 +6935,7 @@ __metadata:
     eslint-plugin-typescript-sort-keys: 2.3.0
     highlight.js: 11.8.0
     highlightjs-apex: 1.2.0
+    highlightjs-cobol: 0.3.3
     highlightjs-sap-abap: 0.3.0
     history: 5.3.0
     jest: 29.6.4
@@ -8873,6 +8874,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"highlightjs-cobol@npm:0.3.3":
+  version: 0.3.3
+  resolution: "highlightjs-cobol@npm:0.3.3"
+  dependencies:
+    minimist: ">=1.2.6"
+    mkdirp: ^1.0.4
+  checksum: f59a694703f883ead2fbdf262f36eab583c8bbf64649e59d5de1e13a43e43979ad9030eab69e1cb08f7558ce60ca5cfd1fe42c7a38e34fd8b0baebf06df9118d
+  languageName: node
+  linkType: hard
+
 "highlightjs-sap-abap@npm:0.3.0":
   version: 0.3.0
   resolution: "highlightjs-sap-abap@npm:0.3.0"
@@ -10953,6 +10964,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"minimist@npm:>=1.2.6":
+  version: 1.2.8
+  resolution: "minimist@npm:1.2.8"
+  checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0
+  languageName: node
+  linkType: hard
+
 "minimist@npm:^1.2.0, minimist@npm:^1.2.5":
   version: 1.2.5
   resolution: "minimist@npm:1.2.5"
index b47918383fd8c7e020b8e4730470effcfa80c978..8a865753020c66ede92ac13eba56c3bf50d704b6 100644 (file)
@@ -2265,7 +2265,7 @@ coding_rules.custom_rules=Custom Rules
 coding_rules.deactivate_in_quality_profile=Deactivate In Quality Profile
 coding_rules.deactivate_in_quality_profile_x=Deactivate In Quality Profile {0}
 coding_rules.delete_rule=Delete Rule
-condig_rules.delete_rule_x=Delete Rule {0}
+coding_rules.delete_rule_x=Delete Rule {0}
 coding_rules.delete.custom.confirm=Are you sure you want to delete custom rule "{0}"?
 coding_rules.extend_description=Extend Description
 coding_rules.deactivate_in=Deactivate In
@@ -2404,6 +2404,8 @@ coding_rules.more_info.resources.title=Resources
 coding_rules.more_info.notification_message=We've added new information about Clean Code principles below to help you improve your code quality and security. Take a moment to read through them.
 coding_rules.more_info.scroll_message=Scroll down to Code Quality principles
 
+coding_rules.detail.extend_description.form=Extend this rule's description
+
 rule.impact.severity.tooltip=Issues found for this rule will have a {severity} impact on the {quality} of your software.
 
 rule.clean_code_attribute_category.CONSISTENT=Consistency