]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19167 - Create KeyboardHint component in DS
authorKevin Silva <kevin.silva@sonarsource.com>
Thu, 11 May 2023 12:53:21 +0000 (14:53 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 12 May 2023 20:02:40 +0000 (20:02 +0000)
14 files changed:
server/sonar-web/design-system/src/components/KeyboardHint.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/KeyboardHintKeys.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/KeyboardHint-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/KeyboardHintKeys-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHint-test.tsx.snap [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHintKeys-test.tsx.snap [new file with mode: 0644]
server/sonar-web/design-system/src/components/icons/TriangleDownIcon.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/icons/TriangleLeftIcon.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/icons/TriangleRightIcon.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/icons/TriangleUpIcon.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/icons/index.ts
server/sonar-web/design-system/src/components/index.ts
server/sonar-web/design-system/src/helpers/keyboard.ts
server/sonar-web/design-system/src/theme/light.ts

diff --git a/server/sonar-web/design-system/src/components/KeyboardHint.tsx b/server/sonar-web/design-system/src/components/KeyboardHint.tsx
new file mode 100644 (file)
index 0000000..cbfa574
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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 styled from '@emotion/styled';
+import tw from 'twin.macro';
+import { themeContrast } from '../helpers';
+import { Key } from '../helpers/keyboard';
+import { KeyboardHintKeys } from './KeyboardHintKeys';
+
+interface Props {
+  command: string;
+  title?: string;
+}
+
+export function KeyboardHint({ title, command }: Props) {
+  const normalizedCommand = command
+    .replace(Key.Control, isMacOS() ? 'Command' : 'Control')
+    .replace(Key.Alt, isMacOS() ? 'Option' : 'Alt');
+
+  return (
+    <Body>
+      {title && <span className="sw-truncate">{title}</span>}
+      <KeyboardHintKeys command={normalizedCommand} />
+    </Body>
+  );
+}
+
+const Body = styled.div`
+  ${tw`sw-flex sw-gap-2 sw-justify-center`}
+  flex-wrap: wrap;
+  color: ${themeContrast('pageContentLight')};
+`;
+
+function isMacOS() {
+  return navigator.userAgent.toLocaleLowerCase().includes('mac os');
+}
diff --git a/server/sonar-web/design-system/src/components/KeyboardHintKeys.tsx b/server/sonar-web/design-system/src/components/KeyboardHintKeys.tsx
new file mode 100644 (file)
index 0000000..7aa37f1
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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 styled from '@emotion/styled';
+import tw from 'twin.macro';
+import { themeColor, themeContrast } from '../helpers';
+import { Key } from '../helpers/keyboard';
+import { TriangleDownIcon, TriangleLeftIcon, TriangleRightIcon, TriangleUpIcon } from './icons';
+
+const COMMAND = '⌘';
+const CTRL = 'Ctrl';
+const OPTION = '⌥';
+const ALT = 'Alt';
+const NON_KEY_SYMBOLS = ['+', ' '];
+
+export function KeyboardHintKeys({ command }: { command: string }) {
+  const keys = command
+    .trim()
+    .split(' ')
+    .map((key, index) => {
+      const uniqueKey = `${key}-${index}`;
+      if (NON_KEY_SYMBOLS.includes(key)) {
+        return <span key={uniqueKey}>{key}</span>;
+      }
+
+      return <KeyBox key={uniqueKey}>{getKey(key)}</KeyBox>;
+    });
+
+  return <div className="sw-flex sw-gap-1">{keys}</div>;
+}
+
+export const KeyBox = styled.span`
+  ${tw`sw-flex sw-items-center sw-justify-center`}
+  ${tw`sw-px-1/2`}
+  ${tw`sw-rounded-1/2`}
+
+  color: ${themeContrast('keyboardHintKey')};
+  background-color: ${themeColor('keyboardHintKey')};
+`;
+
+function getKey(key: string) {
+  switch (key) {
+    case Key.Control:
+      return CTRL;
+    case Key.Command:
+      return COMMAND;
+    case Key.Alt:
+      return ALT;
+    case Key.Option:
+      return OPTION;
+    case Key.ArrowUp:
+      return <TriangleUpIcon />;
+    case Key.ArrowDown:
+      return <TriangleDownIcon />;
+    case Key.ArrowLeft:
+      return <TriangleLeftIcon />;
+    case Key.ArrowRight:
+      return <TriangleRightIcon />;
+    default:
+      return key;
+  }
+}
diff --git a/server/sonar-web/design-system/src/components/__tests__/KeyboardHint-test.tsx b/server/sonar-web/design-system/src/components/__tests__/KeyboardHint-test.tsx
new file mode 100644 (file)
index 0000000..95afb94
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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 { Key } from '../../helpers/keyboard';
+import { render } from '../../helpers/testUtils';
+import { FCProps } from '../../types/misc';
+import { KeyboardHint } from '../KeyboardHint';
+
+afterEach(() => {
+  jest.clearAllMocks();
+});
+
+it('renders without title', () => {
+  const { container } = setupWithProps();
+  expect(container).toMatchSnapshot();
+});
+
+it('renders with title', () => {
+  const { container } = setupWithProps({ title: 'title' });
+  expect(container).toMatchSnapshot();
+});
+
+it('renders with command', () => {
+  const { container } = setupWithProps({ command: 'command' });
+  expect(container).toMatchSnapshot();
+});
+
+it('renders on mac', () => {
+  Object.defineProperty(navigator, 'userAgent', {
+    configurable: true,
+    value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4)',
+  });
+  const { container } = setupWithProps({ command: `${Key.Control} ${Key.Alt}` });
+  expect(container).toMatchSnapshot();
+});
+
+it('renders on windows', () => {
+  Object.defineProperty(navigator, 'userAgent', {
+    configurable: true,
+    value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
+  });
+  const { container } = setupWithProps({ command: `${Key.Control} ${Key.Alt}` });
+  expect(container).toMatchSnapshot();
+});
+
+function setupWithProps(props: Partial<FCProps<typeof KeyboardHint>> = {}) {
+  return render(<KeyboardHint command="click" {...props} />);
+}
diff --git a/server/sonar-web/design-system/src/components/__tests__/KeyboardHintKeys-test.tsx b/server/sonar-web/design-system/src/components/__tests__/KeyboardHintKeys-test.tsx
new file mode 100644 (file)
index 0000000..4d1ff44
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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 { Key } from '../../helpers/keyboard';
+import { render } from '../../helpers/testUtils';
+import { FCProps } from '../../types/misc';
+import { KeyboardHintKeys } from '../KeyboardHintKeys';
+
+it.each([
+  Key.Control,
+  Key.Command,
+  Key.Alt,
+  Key.Option,
+  Key.ArrowUp,
+  Key.ArrowDown,
+  Key.ArrowLeft,
+  Key.ArrowRight,
+])('should render %s', (key) => {
+  const { container } = setupWithProps({ command: key });
+  expect(container).toMatchSnapshot();
+});
+
+it('should render multiple keys', () => {
+  const { container } = setupWithProps({ command: `${Key.ArrowUp} ${Key.ArrowDown}` });
+  expect(container).toMatchSnapshot();
+});
+
+it('should render multiple keys with non-key symbols', () => {
+  const { container } = setupWithProps({
+    command: `${Key.Control} + ${Key.ArrowDown} ${Key.ArrowUp}`,
+  });
+  expect(container).toMatchSnapshot();
+});
+
+it('should render a default text if no keys match', () => {
+  const { container } = setupWithProps({ command: `${Key.Control} + click` });
+  expect(container).toMatchSnapshot();
+});
+
+function setupWithProps(props: Partial<FCProps<typeof KeyboardHintKeys>> = {}) {
+  return render(<KeyboardHintKeys command={`${Key.ArrowUp}`} {...props} />);
+}
diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHint-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHint-test.tsx.snap
new file mode 100644 (file)
index 0000000..081af38
--- /dev/null
@@ -0,0 +1,291 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders on mac 1`] = `
+.emotion-0 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-pack: center;
+  -ms-flex-pack: center;
+  -webkit-justify-content: center;
+  justify-content: center;
+  gap: 0.5rem;
+  -webkit-box-flex-wrap: wrap;
+  -webkit-flex-wrap: wrap;
+  -ms-flex-wrap: wrap;
+  flex-wrap: wrap;
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="emotion-0 emotion-1"
+  >
+    <div
+      class="sw-flex sw-gap-1"
+    >
+      <span
+        class="emotion-2 emotion-3"
+      >
+        ⌘
+      </span>
+      <span
+        class="emotion-2 emotion-3"
+      >
+        ⌥
+      </span>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`renders on windows 1`] = `
+.emotion-0 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-pack: center;
+  -ms-flex-pack: center;
+  -webkit-justify-content: center;
+  justify-content: center;
+  gap: 0.5rem;
+  -webkit-box-flex-wrap: wrap;
+  -webkit-flex-wrap: wrap;
+  -ms-flex-wrap: wrap;
+  flex-wrap: wrap;
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="emotion-0 emotion-1"
+  >
+    <div
+      class="sw-flex sw-gap-1"
+    >
+      <span
+        class="emotion-2 emotion-3"
+      >
+        Ctrl
+      </span>
+      <span
+        class="emotion-2 emotion-3"
+      >
+        Alt
+      </span>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`renders with command 1`] = `
+.emotion-0 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-pack: center;
+  -ms-flex-pack: center;
+  -webkit-justify-content: center;
+  justify-content: center;
+  gap: 0.5rem;
+  -webkit-box-flex-wrap: wrap;
+  -webkit-flex-wrap: wrap;
+  -ms-flex-wrap: wrap;
+  flex-wrap: wrap;
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="emotion-0 emotion-1"
+  >
+    <div
+      class="sw-flex sw-gap-1"
+    >
+      <span
+        class="emotion-2 emotion-3"
+      >
+        command
+      </span>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`renders with title 1`] = `
+.emotion-0 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-pack: center;
+  -ms-flex-pack: center;
+  -webkit-justify-content: center;
+  justify-content: center;
+  gap: 0.5rem;
+  -webkit-box-flex-wrap: wrap;
+  -webkit-flex-wrap: wrap;
+  -ms-flex-wrap: wrap;
+  flex-wrap: wrap;
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="emotion-0 emotion-1"
+  >
+    <span
+      class="sw-truncate"
+    >
+      title
+    </span>
+    <div
+      class="sw-flex sw-gap-1"
+    >
+      <span
+        class="emotion-2 emotion-3"
+      >
+        click
+      </span>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`renders without title 1`] = `
+.emotion-0 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-pack: center;
+  -ms-flex-pack: center;
+  -webkit-justify-content: center;
+  justify-content: center;
+  gap: 0.5rem;
+  -webkit-box-flex-wrap: wrap;
+  -webkit-flex-wrap: wrap;
+  -ms-flex-wrap: wrap;
+  flex-wrap: wrap;
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="emotion-0 emotion-1"
+  >
+    <div
+      class="sw-flex sw-gap-1"
+    >
+      <span
+        class="emotion-2 emotion-3"
+      >
+        click
+      </span>
+    </div>
+  </div>
+</div>
+`;
diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHintKeys-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHintKeys-test.tsx.snap
new file mode 100644 (file)
index 0000000..907e399
--- /dev/null
@@ -0,0 +1,513 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render Alt 1`] = `
+.emotion-0 {
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="sw-flex sw-gap-1"
+  >
+    <span
+      class="emotion-0 emotion-1"
+    >
+      Alt
+    </span>
+  </div>
+</div>
+`;
+
+exports[`should render ArrowDown 1`] = `
+.emotion-0 {
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="sw-flex sw-gap-1"
+  >
+    <span
+      class="emotion-0 emotion-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="octicon octicon-triangle-down"
+        fill="currentColor"
+        focusable="false"
+        height="16"
+        role="img"
+        style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;"
+        viewBox="0 0 16 16"
+        width="16"
+      >
+        <path
+          d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"
+        />
+      </svg>
+    </span>
+  </div>
+</div>
+`;
+
+exports[`should render ArrowLeft 1`] = `
+.emotion-0 {
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="sw-flex sw-gap-1"
+  >
+    <span
+      class="emotion-0 emotion-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="octicon octicon-triangle-left"
+        fill="currentColor"
+        focusable="false"
+        height="16"
+        role="img"
+        style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;"
+        viewBox="0 0 16 16"
+        width="16"
+      >
+        <path
+          d="M9.573 4.427 6.177 7.823a.25.25 0 0 0 0 .354l3.396 3.396a.25.25 0 0 0 .427-.177V4.604a.25.25 0 0 0-.427-.177Z"
+        />
+      </svg>
+    </span>
+  </div>
+</div>
+`;
+
+exports[`should render ArrowRight 1`] = `
+.emotion-0 {
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="sw-flex sw-gap-1"
+  >
+    <span
+      class="emotion-0 emotion-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="octicon octicon-triangle-right"
+        fill="currentColor"
+        focusable="false"
+        height="16"
+        role="img"
+        style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;"
+        viewBox="0 0 16 16"
+        width="16"
+      >
+        <path
+          d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"
+        />
+      </svg>
+    </span>
+  </div>
+</div>
+`;
+
+exports[`should render ArrowUp 1`] = `
+.emotion-0 {
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="sw-flex sw-gap-1"
+  >
+    <span
+      class="emotion-0 emotion-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="octicon octicon-triangle-up"
+        fill="currentColor"
+        focusable="false"
+        height="16"
+        role="img"
+        style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;"
+        viewBox="0 0 16 16"
+        width="16"
+      >
+        <path
+          d="m4.427 9.573 3.396-3.396a.25.25 0 0 1 .354 0l3.396 3.396a.25.25 0 0 1-.177.427H4.604a.25.25 0 0 1-.177-.427Z"
+        />
+      </svg>
+    </span>
+  </div>
+</div>
+`;
+
+exports[`should render Command 1`] = `
+.emotion-0 {
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="sw-flex sw-gap-1"
+  >
+    <span
+      class="emotion-0 emotion-1"
+    >
+      ⌘
+    </span>
+  </div>
+</div>
+`;
+
+exports[`should render Control 1`] = `
+.emotion-0 {
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="sw-flex sw-gap-1"
+  >
+    <span
+      class="emotion-0 emotion-1"
+    >
+      Ctrl
+    </span>
+  </div>
+</div>
+`;
+
+exports[`should render Option 1`] = `
+.emotion-0 {
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="sw-flex sw-gap-1"
+  >
+    <span
+      class="emotion-0 emotion-1"
+    >
+      ⌥
+    </span>
+  </div>
+</div>
+`;
+
+exports[`should render a default text if no keys match 1`] = `
+.emotion-0 {
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="sw-flex sw-gap-1"
+  >
+    <span
+      class="emotion-0 emotion-1"
+    >
+      Ctrl
+    </span>
+    <span>
+      +
+    </span>
+    <span
+      class="emotion-0 emotion-1"
+    >
+      click
+    </span>
+  </div>
+</div>
+`;
+
+exports[`should render multiple keys 1`] = `
+.emotion-0 {
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="sw-flex sw-gap-1"
+  >
+    <span
+      class="emotion-0 emotion-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="octicon octicon-triangle-up"
+        fill="currentColor"
+        focusable="false"
+        height="16"
+        role="img"
+        style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;"
+        viewBox="0 0 16 16"
+        width="16"
+      >
+        <path
+          d="m4.427 9.573 3.396-3.396a.25.25 0 0 1 .354 0l3.396 3.396a.25.25 0 0 1-.177.427H4.604a.25.25 0 0 1-.177-.427Z"
+        />
+      </svg>
+    </span>
+    <span
+      class="emotion-0 emotion-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="octicon octicon-triangle-down"
+        fill="currentColor"
+        focusable="false"
+        height="16"
+        role="img"
+        style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;"
+        viewBox="0 0 16 16"
+        width="16"
+      >
+        <path
+          d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"
+        />
+      </svg>
+    </span>
+  </div>
+</div>
+`;
+
+exports[`should render multiple keys with non-key symbols 1`] = `
+.emotion-0 {
+  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;
+  color: rgb(62,67,87);
+  background-color: rgb(225,230,243);
+}
+
+<div>
+  <div
+    class="sw-flex sw-gap-1"
+  >
+    <span
+      class="emotion-0 emotion-1"
+    >
+      Ctrl
+    </span>
+    <span>
+      +
+    </span>
+    <span
+      class="emotion-0 emotion-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="octicon octicon-triangle-down"
+        fill="currentColor"
+        focusable="false"
+        height="16"
+        role="img"
+        style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;"
+        viewBox="0 0 16 16"
+        width="16"
+      >
+        <path
+          d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"
+        />
+      </svg>
+    </span>
+    <span
+      class="emotion-0 emotion-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="octicon octicon-triangle-up"
+        fill="currentColor"
+        focusable="false"
+        height="16"
+        role="img"
+        style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;"
+        viewBox="0 0 16 16"
+        width="16"
+      >
+        <path
+          d="m4.427 9.573 3.396-3.396a.25.25 0 0 1 .354 0l3.396 3.396a.25.25 0 0 1-.177.427H4.604a.25.25 0 0 1-.177-.427Z"
+        />
+      </svg>
+    </span>
+  </div>
+</div>
+`;
diff --git a/server/sonar-web/design-system/src/components/icons/TriangleDownIcon.tsx b/server/sonar-web/design-system/src/components/icons/TriangleDownIcon.tsx
new file mode 100644 (file)
index 0000000..44c8cc5
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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 { TriangleDownIcon as Octicon } from '@primer/octicons-react';
+import { OcticonHoc } from './Icon';
+
+export const TriangleDownIcon = OcticonHoc(Octicon);
diff --git a/server/sonar-web/design-system/src/components/icons/TriangleLeftIcon.tsx b/server/sonar-web/design-system/src/components/icons/TriangleLeftIcon.tsx
new file mode 100644 (file)
index 0000000..2b2e0f2
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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 { TriangleLeftIcon as Octicon } from '@primer/octicons-react';
+import { OcticonHoc } from './Icon';
+
+export const TriangleLeftIcon = OcticonHoc(Octicon);
diff --git a/server/sonar-web/design-system/src/components/icons/TriangleRightIcon.tsx b/server/sonar-web/design-system/src/components/icons/TriangleRightIcon.tsx
new file mode 100644 (file)
index 0000000..fd8dc00
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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 { TriangleRightIcon as Octicon } from '@primer/octicons-react';
+import { OcticonHoc } from './Icon';
+
+export const TriangleRightIcon = OcticonHoc(Octicon);
diff --git a/server/sonar-web/design-system/src/components/icons/TriangleUpIcon.tsx b/server/sonar-web/design-system/src/components/icons/TriangleUpIcon.tsx
new file mode 100644 (file)
index 0000000..9f192da
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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 { TriangleUpIcon as Octicon } from '@primer/octicons-react';
+import { OcticonHoc } from './Icon';
+
+export const TriangleUpIcon = OcticonHoc(Octicon);
index 114132d47103ad0134bf339986afc6b26659b7bd..3b23d108df4717889a1ac7949d04f050341ec224 100644 (file)
@@ -61,6 +61,10 @@ export { StatusConfirmedIcon } from './StatusConfirmedIcon';
 export { StatusOpenIcon } from './StatusOpenIcon';
 export { StatusReopenedIcon } from './StatusReopenedIcon';
 export { StatusResolvedIcon } from './StatusResolvedIcon';
+export { TriangleDownIcon } from './TriangleDownIcon';
+export { TriangleLeftIcon } from './TriangleLeftIcon';
+export { TriangleRightIcon } from './TriangleRightIcon';
+export { TriangleUpIcon } from './TriangleUpIcon';
 export { UnfoldDownIcon } from './UnfoldDownIcon';
 export { UnfoldIcon } from './UnfoldIcon';
 export { UnfoldUpIcon } from './UnfoldUpIcon';
index 83f428113018cdff9fd086b30d47d4fe991fc973..1859acdfabadf904a4a3c6ffb9424723bb960328 100644 (file)
@@ -40,6 +40,7 @@ export { HotspotRating } from './HotspotRating';
 export { InputSearch } from './InputSearch';
 export * from './InputSelect';
 export * from './InteractiveIcon';
+export * from './KeyboardHint';
 export * from './Link';
 export { StandoutLink as Link } from './Link';
 export * from './MainAppBar';
index 42bc6bdf52ef7d82316c3999dcd8af4fd0bd6f81..37b26e3cb2e238ecf3a8d424d38087b3c1ccd193 100644 (file)
@@ -24,10 +24,12 @@ export enum Key {
   ArrowDown = 'ArrowDown',
 
   Alt = 'Alt',
+  Option = 'Option',
   Backspace = 'Backspace',
   CapsLock = 'CapsLock',
   Meta = 'Meta',
   Control = 'Control',
+  Command = 'Command',
   Delete = 'Delete',
   End = 'End',
   Enter = 'Enter',
index 6a2f8bd5e9597da34e55dcd9058f57922677fdef..24cd71f3ea33d7c816b20ab36d7888066e4e66b1 100644 (file)
@@ -472,6 +472,9 @@ export const lightTheme = {
 
     // project analyse page
     almCardBorder: COLORS.grey[100],
+
+    // Keyboard hint
+    keyboardHintKey: COLORS.blueGrey[100],
   },
 
   // contrast colors to be used for text when using a color background with the same name
@@ -657,6 +660,9 @@ export const lightTheme = {
     newsTag: COLORS.blueGrey[500],
     roadmap: COLORS.blueGrey[600],
     roadmapContent: COLORS.blueGrey[500],
+
+    // Keyboard hint
+    keyboardHintKey: COLORS.blueGrey[500],
   },
 
   // predefined shadows