Browse Source

SONAR-21566 Migrate keyboard shortcut modal to new UI

tags/10.5.0.89998
Revanshu Paliwal 3 months ago
parent
commit
4a2eeb85b6

+ 13
- 2
server/sonar-web/design-system/src/components/KeyboardHintKeys.tsx View 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>;

+ 29
- 7
server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHint-test.tsx.snap View 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>

+ 20
- 18
server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHintKeys-test.tsx.snap View 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"

+ 1
- 0
server/sonar-web/design-system/src/helpers/index.ts View File

@@ -19,6 +19,7 @@
*/
export * from './colors';
export * from './constants';
export * from './keyboard';
export * from './positioning';
export * from './tabs';
export * from './theme';

+ 156
- 125
server/sonar-web/src/main/js/app/components/KeyboardShortcutsModal.tsx View File

@@ -18,115 +18,148 @@
* 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')}
/>
);
}

+ 1
- 1
server/sonar-web/src/main/js/app/components/__tests__/KeyboardShortcutsModal-test.tsx View 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'),

+ 26
- 34
sonar-core/src/main/resources/org/sonar/l10n/core.properties View 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

#------------------------------------------------------------------------------
#

Loading…
Cancel
Save