]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21566 Migrate keyboard shortcut modal to new UI
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>
Tue, 6 Feb 2024 09:26:10 +0000 (10:26 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 6 Feb 2024 16:38:17 +0000 (16:38 +0000)
server/sonar-web/design-system/src/components/KeyboardHintKeys.tsx
server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHint-test.tsx.snap
server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHintKeys-test.tsx.snap
server/sonar-web/design-system/src/helpers/index.ts
server/sonar-web/src/main/js/app/components/KeyboardShortcutsModal.tsx
server/sonar-web/src/main/js/app/components/__tests__/KeyboardShortcutsModal-test.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 84cf5e12a5ed75cec8cfaf7578ede82c7b4984cc..44ac3e6c4ba085fe9f5dd68b9a94541065ab52a4 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import styled from '@emotion/styled';
+import classNames from 'classnames';
 import tw from 'twin.macro';
 import { themeColor, themeContrast } from '../helpers';
 import { Key } from '../helpers/keyboard';
@@ -35,6 +36,8 @@ export const mappedKeys = {
   [Key.Click]: 'click',
 };
 
+const NON_KEY_SYMBOLS = ['+', ' '];
+
 export function KeyboardHintKeys({ command }: { command: string }) {
   const keys = command
     .trim()
@@ -42,11 +45,19 @@ export function KeyboardHintKeys({ command }: { command: string }) {
     .map((key, index) => {
       const uniqueKey = `${key}-${index}`;
 
-      if (!(Object.keys(mappedKeys).includes(key) || Object.values(mappedKeys).includes(key))) {
+      if (NON_KEY_SYMBOLS.includes(key)) {
         return <span key={uniqueKey}>{key}</span>;
       }
 
-      return <KeyBox key={uniqueKey}>{mappedKeys[key as keyof typeof mappedKeys] || key}</KeyBox>;
+      const isNonMappedKey = !(
+        Object.keys(mappedKeys).includes(key) || Object.values(mappedKeys).includes(key)
+      );
+
+      return (
+        <KeyBox className={classNames({ 'sw-px-1': isNonMappedKey })} key={uniqueKey}>
+          {Object.keys(mappedKeys).includes(key) ? mappedKeys[key as keyof typeof mappedKeys] : key}
+        </KeyBox>
+      );
     });
 
   return <div className="sw-flex sw-gap-1">{keys}</div>;
index 798c34a273f5bb4c3b389805c476096ec5965e87..a45b1d68bd80582bcd784ab22959d92ecffde977 100644 (file)
@@ -46,12 +46,12 @@ exports[`renders on mac 1`] = `
       class="sw-flex sw-gap-1"
     >
       <span
-        class="emotion-2 emotion-3"
+        class=" emotion-2 emotion-3"
       >
         ⌘
       </span>
       <span
-        class="emotion-2 emotion-3"
+        class=" emotion-2 emotion-3"
       >
         ⌥
       </span>
@@ -106,12 +106,12 @@ exports[`renders on windows 1`] = `
       class="sw-flex sw-gap-1"
     >
       <span
-        class="emotion-2 emotion-3"
+        class=" emotion-2 emotion-3"
       >
         Ctrl
       </span>
       <span
-        class="emotion-2 emotion-3"
+        class=" emotion-2 emotion-3"
       >
         Alt
       </span>
@@ -138,6 +138,26 @@ exports[`renders with command 1`] = `
   color: rgb(106,117,144);
 }
 
+.emotion-2 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-align-items: center;
+  -webkit-box-align: center;
+  -ms-flex-align: center;
+  align-items: center;
+  -webkit-box-pack: center;
+  -ms-flex-pack: center;
+  -webkit-justify-content: center;
+  justify-content: center;
+  padding-left: 0.125rem;
+  padding-right: 0.125rem;
+  border-radius: 0.125rem;
+  background-color: rgb(225,230,243);
+  color: rgb(62,67,87);
+}
+
 <div>
   <div
     class="emotion-0 emotion-1"
@@ -145,7 +165,9 @@ exports[`renders with command 1`] = `
     <div
       class="sw-flex sw-gap-1"
     >
-      <span>
+      <span
+        class="sw-px-1 emotion-2 emotion-3"
+      >
         command
       </span>
     </div>
@@ -204,7 +226,7 @@ exports[`renders with title 1`] = `
       class="sw-flex sw-gap-1"
     >
       <span
-        class="emotion-2 emotion-3"
+        class=" emotion-2 emotion-3"
       >
         click
       </span>
@@ -259,7 +281,7 @@ exports[`renders without title 1`] = `
       class="sw-flex sw-gap-1"
     >
       <span
-        class="emotion-2 emotion-3"
+        class=" emotion-2 emotion-3"
       >
         click
       </span>
index 337d1b9b385fef69385182d577b114ca38985348..d1ec21d786418ee63e5663a6d5265c8e08000dce 100644 (file)
@@ -26,7 +26,7 @@ exports[`should render Alt 1`] = `
     class="sw-flex sw-gap-1"
   >
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       Alt
     </span>
@@ -60,7 +60,7 @@ exports[`should render ArrowDown 1`] = `
     class="sw-flex sw-gap-1"
   >
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       <svg
         aria-hidden="true"
@@ -108,7 +108,7 @@ exports[`should render ArrowLeft 1`] = `
     class="sw-flex sw-gap-1"
   >
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       <svg
         aria-hidden="true"
@@ -156,7 +156,7 @@ exports[`should render ArrowRight 1`] = `
     class="sw-flex sw-gap-1"
   >
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       <svg
         aria-hidden="true"
@@ -204,7 +204,7 @@ exports[`should render ArrowUp 1`] = `
     class="sw-flex sw-gap-1"
   >
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       <svg
         aria-hidden="true"
@@ -252,7 +252,7 @@ exports[`should render Click 1`] = `
     class="sw-flex sw-gap-1"
   >
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       click
     </span>
@@ -286,7 +286,7 @@ exports[`should render Command 1`] = `
     class="sw-flex sw-gap-1"
   >
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       ⌘
     </span>
@@ -320,7 +320,7 @@ exports[`should render Control 1`] = `
     class="sw-flex sw-gap-1"
   >
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       Ctrl
     </span>
@@ -354,7 +354,7 @@ exports[`should render Option 1`] = `
     class="sw-flex sw-gap-1"
   >
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       ⌥
     </span>
@@ -388,7 +388,7 @@ exports[`should render a default text if no keys match 1`] = `
     class="sw-flex sw-gap-1"
   >
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       Ctrl
     </span>
@@ -396,7 +396,7 @@ exports[`should render a default text if no keys match 1`] = `
       +
     </span>
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       click
     </span>
@@ -429,11 +429,13 @@ exports[`should render multiple keys 1`] = `
   <div
     class="sw-flex sw-gap-1"
   >
-    <span>
+    <span
+      class="sw-px-1 emotion-0 emotion-1"
+    >
       Use
     </span>
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       Ctrl
     </span>
@@ -441,7 +443,7 @@ exports[`should render multiple keys 1`] = `
       +
     </span>
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       <svg
         aria-hidden="true"
@@ -460,7 +462,7 @@ exports[`should render multiple keys 1`] = `
       </svg>
     </span>
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       <svg
         aria-hidden="true"
@@ -508,7 +510,7 @@ exports[`should render multiple keys with non-key symbols 1`] = `
     class="sw-flex sw-gap-1"
   >
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       Ctrl
     </span>
@@ -516,7 +518,7 @@ exports[`should render multiple keys with non-key symbols 1`] = `
       +
     </span>
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       <svg
         aria-hidden="true"
@@ -535,7 +537,7 @@ exports[`should render multiple keys with non-key symbols 1`] = `
       </svg>
     </span>
     <span
-      class="emotion-0 emotion-1"
+      class=" emotion-0 emotion-1"
     >
       <svg
         aria-hidden="true"
index f73322efb00ba68dca796340f524edef682e86db..a648aef78c4dd76aed5a7b0875def02703420a9e 100644 (file)
@@ -19,6 +19,7 @@
  */
 export * from './colors';
 export * from './constants';
+export * from './keyboard';
 export * from './positioning';
 export * from './tabs';
 export * from './theme';
index 0fbb5ccf0fbae9f0161af0cbf39e73941d9ca682..17d352d4c32569eb61ee7a171fae40314ffdbfe5 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
+import {
+  ContentCell,
+  Key,
+  KeyboardHint,
+  Link,
+  Modal,
+  SubTitle,
+  Table,
+  TableRow,
+} from 'design-system';
 import * as React from 'react';
-import Link from '../../components/common/Link';
-import Modal from '../../components/controls/Modal';
-import { Button } from '../../components/controls/buttons';
 import { isInput } from '../../helpers/keyboardEventHelpers';
 import { KeyboardKeys } from '../../helpers/keycodes';
 import { translate } from '../../helpers/l10n';
 import { getKeyboardShortcutEnabled } from '../../helpers/preferences';
 
-type Shortcuts = Array<{
-  category: string;
-  shortcuts: Array<{
-    keys: string[];
-    action: string;
-  }>;
-}>;
-
-const CATEGORIES: { left: Shortcuts; right: Shortcuts } = {
-  left: [
-    {
-      category: 'global',
-      shortcuts: [
-        { keys: ['s'], action: 'search' },
-        { keys: ['?'], action: 'open_shortcuts' },
-      ],
-    },
-    {
-      category: 'issues_page',
-      shortcuts: [
-        { keys: ['↑', '↓'], action: 'navigate' },
-        { keys: ['→'], action: 'source_code' },
-        { keys: ['←'], action: 'back' },
-        { keys: ['alt', '+', '↑', '↓'], action: 'navigate_locations' },
-        { keys: ['alt', '+', '←', '→'], action: 'switch_flows' },
-        { keys: ['f'], action: 'transition' },
-        { keys: ['a'], action: 'assign' },
-        { keys: ['m'], action: 'assign_to_me' },
-        { keys: ['i'], action: 'severity' },
-        { keys: ['ctrl', '+', 'enter'], action: 'submit_comment' },
-        { keys: ['t'], action: 'tags' },
-      ],
-    },
-  ],
-  right: [
-    {
-      category: 'code_page',
-      shortcuts: [
-        { keys: ['↑', '↓'], action: 'select_files' },
-        { keys: ['→'], action: 'open_file' },
-        { keys: ['←'], action: 'back' },
-      ],
-    },
-    {
-      category: 'measures_page',
-      shortcuts: [
-        { keys: ['↑', '↓'], action: 'select_files' },
-        { keys: ['→'], action: 'open_file' },
-        { keys: ['←'], action: 'back' },
-      ],
-    },
-    {
-      category: 'rules_page',
-      shortcuts: [
-        { keys: ['↑', '↓'], action: 'navigate' },
-        { keys: ['→'], action: 'rule_details' },
-        { keys: ['←'], action: 'back' },
-      ],
-    },
-  ],
+type Section = {
+  rows: Array<{ command: string; description: string }>;
+  subTitle: string;
 };
 
-function renderShortcuts(list: Shortcuts) {
-  return (
-    <>
-      {list.map(({ category, shortcuts }) => (
-        <div key={category} className="spacer-bottom">
-          <h3 className="null-spacer-top">{translate('keyboard_shortcuts', category, 'title')}</h3>
-          <table>
-            <thead>
-              <tr>
-                <th>{translate('keyboard_shortcuts.shortcut')}</th>
-                <th>{translate('keyboard_shortcuts.action')}</th>
-              </tr>
-            </thead>
-            <tbody>
-              {shortcuts.map(({ action, keys }) => (
-                <tr key={action}>
-                  <td>
-                    {keys.map((k) =>
-                      k === '+' ? (
-                        <span key={k} className="little-spacer-right">
-                          {k}
-                        </span>
-                      ) : (
-                        <code key={k} className="little-spacer-right">
-                          {k}
-                        </code>
-                      ),
-                    )}
-                  </td>
-                  <td>{translate('keyboard_shortcuts', category, action)}</td>
-                </tr>
-              ))}
-            </tbody>
-          </table>
-        </div>
-      ))}
-    </>
-  );
+const FILE_ROWS = [
+  {
+    command: `${Key.ArrowUp} ${Key.ArrowDown}`,
+    description: 'keyboard_shortcuts_modal.code_page.select_files',
+  },
+  {
+    command: `${Key.ArrowRight}`,
+    description: 'keyboard_shortcuts_modal.code_page.open_file',
+  },
+  {
+    command: `${Key.ArrowLeft}`,
+    description: 'keyboard_shortcuts_modal.return_back_to_the_list',
+  },
+];
+
+export const SECTIONS: Array<Section> = [
+  {
+    rows: [
+      {
+        command: 's',
+        description: 'keyboard_shortcuts_modal.global.open_search_bar',
+      },
+      {
+        command: '?',
+        description: 'keyboard_shortcuts_modal.global.open_keyboard_shortcuts_modal',
+      },
+    ],
+    subTitle: 'keyboard_shortcuts_modal.global',
+  },
+
+  {
+    rows: [
+      {
+        command: `${Key.ArrowUp} ${Key.ArrowDown}`,
+        description: 'keyboard_shortcuts_modal.navigate_between_issues',
+      },
+      {
+        command: `${Key.ArrowRight}`,
+        description: 'keyboard_shortcuts_modal.open_issue',
+      },
+      {
+        command: `${Key.ArrowLeft}`,
+        description: 'keyboard_shortcuts_modal.return_back_to_the_list',
+      },
+      {
+        command: `${Key.Alt} + ${Key.ArrowUp} ${Key.ArrowDown}`,
+        description: 'keyboard_shortcuts_modal.issue_details_page.navigate_issue_locations',
+      },
+      {
+        command: `${Key.Alt} + ${Key.ArrowLeft} ${Key.ArrowRight}`,
+        description: 'keyboard_shortcuts_modal.issue_details_page.switch_flows',
+      },
+      {
+        command: 'f',
+        description: 'keyboard_shortcuts_modal.do_issue_transition',
+      },
+      {
+        command: 'a',
+        description: 'keyboard_shortcuts_modal.assign_issue',
+      },
+      {
+        command: 'm',
+        description: 'keyboard_shortcuts_modal.assign_issue_to_me',
+      },
+      {
+        command: 't',
+        description: 'keyboard_shortcuts_modal.change_tags_of_issue',
+      },
+      {
+        command: `${Key.Control} + ${Key.Enter}`,
+        description: 'keyboard_shortcuts_modal.issue_details_page.submit_comment',
+      },
+    ],
+    subTitle: 'keyboard_shortcuts_modal.issues_page',
+  },
+
+  {
+    rows: FILE_ROWS,
+    subTitle: 'keyboard_shortcuts_modal.code_page',
+  },
+
+  {
+    rows: FILE_ROWS,
+    subTitle: 'keyboard_shortcuts_modal.measures_page',
+  },
+
+  {
+    rows: [
+      {
+        command: `${Key.ArrowUp} ${Key.ArrowDown}`,
+        description: 'keyboard_shortcuts_modal.rules_page.navigate_between_rule',
+      },
+      {
+        command: `${Key.ArrowRight}`,
+        description: 'keyboard_shortcuts_modal.rules_page.open_rule',
+      },
+      {
+        command: `${Key.ArrowLeft}`,
+        description: 'keyboard_shortcuts_modal.return_back_to_the_list',
+      },
+    ],
+    subTitle: 'keyboard_shortcuts_modal.rules_page',
+  },
+];
+
+function renderSection() {
+  return SECTIONS.map((section) => (
+    <div key={section.subTitle} className="sw-mb-4">
+      <SubTitle>{translate(section.subTitle)}</SubTitle>
+      <Table columnCount={2} columnWidths={['30%', '70%']}>
+        {section.rows.map((row) => (
+          <TableRow key={row.command}>
+            <ContentCell className="sw-justify-center">
+              <KeyboardHint command={row.command} title="" />
+            </ContentCell>
+            <ContentCell>{translate(row.description)}</ContentCell>
+          </TableRow>
+        ))}
+      </Table>
+    </div>
+  ));
 }
 
 export default function KeyboardShortcutsModal() {
@@ -158,31 +191,29 @@ export default function KeyboardShortcutsModal() {
     return null;
   }
 
-  const title = translate('keyboard_shortcuts.title');
+  const title = translate('keyboard_shortcuts_modal.title');
+
+  const body = (
+    <>
+      <Link
+        to="/account"
+        onClick={() => {
+          setDisplay(false);
+          return true;
+        }}
+      >
+        {translate('keyboard_shortcuts_modal.disable_link')}
+      </Link>
+      <div className="sw-mt-4">{renderSection()}</div>
+    </>
+  );
 
   return (
-    <Modal contentLabel={title} onRequestClose={() => setDisplay(false)} size="medium">
-      <div className="modal-head display-flex-space-between">
-        <h2>{title}</h2>
-        <Link
-          to="/account"
-          onClick={() => {
-            setDisplay(false);
-            return true;
-          }}
-        >
-          {translate('keyboard_shortcuts.disable_link')}
-        </Link>
-      </div>
-
-      <div className="modal-body modal-container markdown display-flex-start shortcuts-modal">
-        <div className="flex-1">{renderShortcuts(CATEGORIES.left)}</div>
-        <div className="flex-1 huge-spacer-left">{renderShortcuts(CATEGORIES.right)}</div>
-      </div>
-
-      <div className="modal-foot">
-        <Button onClick={() => setDisplay(false)}>{translate('close')}</Button>
-      </div>
-    </Modal>
+    <Modal
+      headerTitle={title}
+      onClose={() => setDisplay(false)}
+      body={body}
+      secondaryButtonLabel={translate('close')}
+    />
   );
 }
index 78cd1cc3c7f857d631d3b5b8c4c9760caa6f4ee0..e78305808625fde854b75c490976f69996791da4 100644 (file)
@@ -69,7 +69,7 @@ function renderKeyboardShortcutsModal() {
 }
 
 const ui = {
-  modalTitle: byRole('heading', { name: 'keyboard_shortcuts.title' }),
+  modalTitle: byRole('heading', { name: 'keyboard_shortcuts_modal.title' }),
   closeButton: byRole('button', { name: 'close' }),
 
   textInput: byRole('textbox'),
index 84fb6571a6bf7d00e873503903210c52963ca915..3f1fccb31849aaded460814e1c0c7dab3ddf6d8f 100644 (file)
@@ -3454,40 +3454,32 @@ formatting.example.link.example=[link label](https://www.domain.com)
 # KEYBOARD SHORTCUTS
 #
 #------------------------------------------------------------------------------
-
-keyboard_shortcuts.title=Keyboard Shortcuts
-keyboard_shortcuts.disable_link=Disable shortcuts
-keyboard_shortcuts.shortcut=Shortcut
-keyboard_shortcuts.action=Action
-keyboard_shortcuts.global.title=Global
-keyboard_shortcuts.global.search=Open the search bar
-keyboard_shortcuts.global.open_shortcuts=Open this panel
-keyboard_shortcuts.code_page.title=Code Page
-keyboard_shortcuts.code_page.select_files=Select files
-keyboard_shortcuts.code_page.open_file=Open the selected file
-keyboard_shortcuts.code_page.back=Return back to the list
-keyboard_shortcuts.issues_page.title=Issues Page
-keyboard_shortcuts.issues_page.navigate=navigate between issues
-keyboard_shortcuts.issues_page.source_code=go from the list of issues to the source code
-keyboard_shortcuts.issues_page.back=return back to the list
-keyboard_shortcuts.issues_page.navigate_locations=to navigate issue locations
-keyboard_shortcuts.issues_page.switch_flows=to switch flows
-keyboard_shortcuts.issues_page.transition=do an issue transition
-keyboard_shortcuts.issues_page.assign=assign issue
-keyboard_shortcuts.issues_page.assign_to_me=assign issue to the current user
-keyboard_shortcuts.issues_page.severity=change severity of issue
-keyboard_shortcuts.issues_page.comment=comment issue
-keyboard_shortcuts.issues_page.submit_comment=submit comment
-keyboard_shortcuts.issues_page.tags=change tags of issue
-keyboard_shortcuts.measures_page.title=Measures Page
-keyboard_shortcuts.measures_page.select_files=Select files
-keyboard_shortcuts.measures_page.open_file=Open the selected file
-keyboard_shortcuts.measures_page.back=Return back to the list
-keyboard_shortcuts.rules_page.title=Rules Page
-keyboard_shortcuts.rules_page.navigate=navigate between rules
-keyboard_shortcuts.rules_page.rule_details=go from the list of rules to the rule details
-keyboard_shortcuts.rules_page.back=Return back to the list
-
+keyboard_shortcuts_modal.title=Keyboard Shortcuts
+keyboard_shortcuts_modal.disable_link=Disable shortcuts
+keyboard_shortcuts_modal.description= You can use the following shortcuts when navigating within SonarCloud
+keyboard_shortcuts_modal.global= Global
+keyboard_shortcuts_modal.global.open_search_bar= Open search bar
+keyboard_shortcuts_modal.global.open_keyboard_shortcuts_modal= Open keyboard shortcuts modal
+keyboard_shortcuts_modal.navigate_between_issues= Navigate between issues
+keyboard_shortcuts_modal.open_issue= Open issue
+keyboard_shortcuts_modal.return_back_to_the_list= Return back to the list
+keyboard_shortcuts_modal.do_issue_transition= Do an issue transition
+keyboard_shortcuts_modal.assign_issue= Assign issue
+keyboard_shortcuts_modal.assign_issue_to_me= Assign issue to me
+keyboard_shortcuts_modal.change_tags_of_issue= Change tags of issue
+keyboard_shortcuts_modal.select_an_issue= Select an issue
+keyboard_shortcuts_modal.issues_page= Issues page
+keyboard_shortcuts_modal.issue_details_page.navigate_issue_locations= To navigate issue locations
+keyboard_shortcuts_modal.issue_details_page.switch_flows= To switch flows
+keyboard_shortcuts_modal.issue_details_page.comment_an_issue= Comment an issue
+keyboard_shortcuts_modal.issue_details_page.submit_comment= Submit comment
+keyboard_shortcuts_modal.code_page= Code page
+keyboard_shortcuts_modal.code_page.select_files= Select files
+keyboard_shortcuts_modal.code_page.open_file= Open file
+keyboard_shortcuts_modal.measures_page= Measures page
+keyboard_shortcuts_modal.rules_page= Rules page
+keyboard_shortcuts_modal.rules_page.navigate_between_rule= Navigate between rules
+keyboard_shortcuts_modal.rules_page.open_rule= Open rule
 
 #------------------------------------------------------------------------------
 #