Repository: inovex/scrumlr.io
Pull request #2324 (2022-11-04 12:33:44)

Add hotkey combination as tooltip to action buttons
Added tooltips, displaying the corresponding hotkey, when hovering over an action button.

NNKKOO: Please tell me if you think the show-delay is okay. I'm afraid I got a little impatient during the testing process and might have set it too short.

Patch:
diff --git a/package.json b/package.json
index 7eb72b02..88b67055 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
     "react-router-dom": "^6.4.3",
     "react-to-print": "^2.14.8",
     "react-toastify": "^9.1.1",
+    "react-tooltip": "^4.4.3",
     "redux": "^4.2.0",
     "redux-thunk": "^2.4.2",
     "sockette": "^2.0.6",
diff --git a/src/components/Board/Board.tsx b/src/components/Board/Board.tsx
index 87dd5cfe..ddd04e04 100644
--- a/src/components/Board/Board.tsx
+++ b/src/components/Board/Board.tsx
@@ -5,6 +5,7 @@ import {MenuBars} from "components/MenuBars";
 import {BoardHeader} from "components/BoardHeader";
 import "./Board.scss";
 import {HotkeyAnchor} from "components/HotkeyAnchor";
+import ReactTooltip from "react-tooltip";
 
 export interface BoardProps {
   children: React.ReactElement<ColumnProps> | React.ReactElement<ColumnProps>[];
@@ -147,7 +148,7 @@ export const BoardComponent = ({children, currentUserIsModerator, moderating}: B
   return (
     <>
       <style>{`.board { --board__columns: ${columnsCount} }`}</style>
-
+      <ReactTooltip type={document.documentElement.getAttribute("theme") === "dark" ? "dark" : "light"} place="bottom" delayShow={500} multiline />
       <BoardHeader currentUserIsModerator={currentUserIsModerator} />
       <MenuBars showPreviousColumn={state.showPreviousButton} showNextColumn={state.showNextButton} onPreviousColumn={handlePreviousClick} onNextColumn={handleNextClick} />
       <HotkeyAnchor />
diff --git a/src/components/Column/Column.tsx b/src/components/Column/Column.tsx
index 648da4b9..68162996 100644
--- a/src/components/Column/Column.tsx
+++ b/src/components/Column/Column.tsx
@@ -13,10 +13,12 @@ import {ReactComponent as DotsIcon} from "assets/icon-dots.svg";
 import _ from "underscore";
 import {useDispatch} from "react-redux";
 import {useTranslation} from "react-i18next";
+import {hotkeyMap} from "constants/hotkeys";
 import {Note} from "../Note";
 import {ColumnSettings} from "./ColumnSettings";
 
 const MAX_NOTE_LENGTH = 1024;
+const {SELECT_NOTE_INPUT_FIRST_KEY} = hotkeyMap;
 
 export interface ColumnProps {
   id: string;
@@ -166,7 +168,14 @@ export const Column = ({id, name, color, visible, index}: ColumnProps) => {
     <section className={classNames("column", {"column__moderation-isActive": isModerator && state.moderating}, getColorClassName(color))} ref={columnRef}>
       <div className="column__content">
         <div className="column__header">
-          <NoteInput columnIndex={index} columnId={id} maxNoteLength={MAX_NOTE_LENGTH} columnIsVisible={visible} toggleColumnVisibility={toggleVisibilityHandler} />
+          <NoteInput
+            columnIndex={index}
+            columnId={id}
+            maxNoteLength={MAX_NOTE_LENGTH}
+            columnIsVisible={visible}
+            toggleColumnVisibility={toggleVisibilityHandler}
+            hotkeyKey={`${SELECT_NOTE_INPUT_FIRST_KEY.map((key, i) => (i === 0 ? `${key.toUpperCase()}/` : key.toUpperCase())).join("")} + ${index + 1}`}
+          />
           <div className="column__header-title">
             {renderColumnName()}
             {columnNameMode === "VIEW" && <span className="column__header-card-number">{state.notes.length}</span>}
diff --git a/src/components/MenuBars/MenuBars.tsx b/src/components/MenuBars/MenuBars.tsx
index 73115084..5f666804 100644
--- a/src/components/MenuBars/MenuBars.tsx
+++ b/src/components/MenuBars/MenuBars.tsx
@@ -46,7 +46,7 @@ export const MenuBars = ({showPreviousColumn, showNextColumn, onPreviousColumn,
     return () => document.removeEventListener("click", handleClickOutside);
   }, [menuBarsMobileRef, fabIsExpanded]);
 
-  const {SHOW_TIMER_MENU, SHOW_VOTING_MENU} = hotkeyMap;
+  const {SHOW_TIMER_MENU, SHOW_VOTING_MENU, SHOW_SETTINGS, TOGGLE_RAISED_HAND, TOGGLE_READY_STATE, TOGGLE_MODERATION} = hotkeyMap;
 
   const state = useAppSelector(
     (rootState) => ({
@@ -99,6 +99,7 @@ export const MenuBars = ({showPreviousColumn, showNextColumn, onPreviousColumn,
                 label={isReady ? t("MenuBars.unmarkAsDone") : t("MenuBars.markAsDone")}
                 icon={CheckIcon}
                 active={isReady}
+                hotkeyKey={TOGGLE_READY_STATE.toUpperCase()}
               />
             </li>
             <li>
@@ -108,10 +109,11 @@ export const MenuBars = ({showPreviousColumn, showNextColumn, onPreviousColumn,
                 icon={RaiseHand}
                 onClick={toggleRaiseHand}
                 active={raisedHand}
+                hotkeyKey={TOGGLE_RAISED_HAND.toUpperCase()}
               />
             </li>
             <li>
-              <TooltipButton direction="right" label={t("MenuBars.settings")} onClick={showSettings} icon={SettingsIcon} />
+              <TooltipButton direction="right" label={t("MenuBars.settings")} onClick={showSettings} icon={SettingsIcon} hotkeyKey={SHOW_SETTINGS.toUpperCase()} />
             </li>
           </ul>
 
@@ -129,10 +131,10 @@ export const MenuBars = ({showPreviousColumn, showNextColumn, onPreviousColumn,
           {isAdmin && (
             <ul className="menu__items">
               <li>
-                <TooltipButton direction="left" label="Timer" onClick={showTimerMenu} icon={TimerIcon} />
+                <TooltipButton direction="left" label="Timer" onClick={showTimerMenu} icon={TimerIcon} hotkeyKey={SHOW_TIMER_MENU.toUpperCase()} />
               </li>
               <li>
-                <TooltipButton direction="left" label="Voting" onClick={showVotingMenu} icon={VoteIcon} />
+                <TooltipButton direction="left" label="Voting" onClick={showVotingMenu} icon={VoteIcon} hotkeyKey={SHOW_VOTING_MENU.toUpperCase()} />
               </li>
               <li>
                 <TooltipButton
@@ -141,6 +143,7 @@ export const MenuBars = ({showPreviousColumn, showNextColumn, onPreviousColumn,
                   label={state.moderation ? t("MenuBars.stopFocusMode") : t("MenuBars.startFocusMode")}
                   icon={FocusIcon}
                   onClick={toggleModeration}
+                  hotkeyKey={TOGGLE_MODERATION.toUpperCase()}
                 />
               </li>
             </ul>
diff --git a/src/components/NoteInput/NoteInput.tsx b/src/components/NoteInput/NoteInput.tsx
index 1a8ec804..fd191c10 100644
--- a/src/components/NoteInput/NoteInput.tsx
+++ b/src/components/NoteInput/NoteInput.tsx
@@ -14,9 +14,10 @@ export interface NoteInputProps {
   columnIndex: number;
   columnIsVisible: boolean;
   toggleColumnVisibility: () => void;
+  hotkeyKey?: string;
 }
 
-export const NoteInput = ({columnIndex, columnId, maxNoteLength, columnIsVisible, toggleColumnVisibility}: NoteInputProps) => {
+export const NoteInput = ({columnIndex, columnId, maxNoteLength, columnIsVisible, toggleColumnVisibility, hotkeyKey}: NoteInputProps) => {
   const {t} = useTranslation();
   const dispatch = useDispatch();
   const [value, setValue] = useState("");
@@ -75,6 +76,7 @@ export const NoteInput = ({columnIndex, columnId, maxNoteLength, columnIsVisible
           }
         }}
         maxLength={maxNoteLength}
+        data-tip={hotkeyKey}
       />
       <button
         type="submit"
diff --git a/src/components/TooltipButton/TooltipButton.scss b/src/components/TooltipButton/TooltipButton.scss
index ed1bb343..10991e82 100644
--- a/src/components/TooltipButton/TooltipButton.scss
+++ b/src/components/TooltipButton/TooltipButton.scss
@@ -97,6 +97,10 @@ $tooltip-button-size: 42px;
   }
 }
 
+.tooltip-button__hotkey {
+  color: $color-dark-gray;
+}
+
 @media #{$tablet} {
   .tooltip-button {
     .tooltip-button__tooltip {
diff --git a/src/components/TooltipButton/TooltipButton.tsx b/src/components/TooltipButton/TooltipButton.tsx
index 902ac92b..4c4c2306 100644
--- a/src/components/TooltipButton/TooltipButton.tsx
+++ b/src/components/TooltipButton/TooltipButton.tsx
@@ -10,6 +10,7 @@ type TooltipButtonProps = {
   icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
   className?: string;
   active?: boolean;
+  hotkeyKey?: string;
 };
 
 export const TooltipButton = (props: TooltipButtonProps) => {
@@ -23,7 +24,10 @@ export const TooltipButton = (props: TooltipButtonProps) => {
       aria-label={props.label}
     >
       <div className="tooltip-button__tooltip" aria-hidden>
-        <span className="tooltip-button__tooltip-text">{props.label}</span>
+        <span className="tooltip-button__tooltip-text">
+          {props.label}
+          <span className="tooltip-button__hotkey">{` [${props.hotkeyKey}]`}</span>
+        </span>
       </div>
       <Icon className="tooltip-button__icon" aria-hidden />
       <CloseIcon className="tooltip-button__icon" aria-hidden />
diff --git a/yarn.lock b/yarn.lock
index 786bd68f..088ca7ed 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8515,6 +8515,14 @@ react-toastify@^9.1.1:
   dependencies:
     clsx "^1.1.1"
 
+react-tooltip@^4.4.3:
+  version "4.4.3"
+  resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.4.3.tgz#44cbb6c8bdb3281c2819d16c92e3c419ebd98582"
+  integrity sha512-l7/TDBwq3JtuLBtq6FvIs7wsqcHjvoHFT8AvNGpf0JhHwzh+ZhCDN25wM/+gxelj8i1ngw/ULFv53XRQ/wCMzQ==
+  dependencies:
+    prop-types "^15.8.1"
+    uuid "^7.0.3"
+
 react@^17.0.2:
   version "17.0.2"
   resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz"
@@ -9872,6 +9880,11 @@ utils-merge@1.0.1:
   resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz"
   integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
 
+uuid@^7.0.3:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
+  integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
+
 uuid@^8.3.2:
   version "8.3.2"
   resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"


Test patch:
diff --git a/src/components/Column/__tests__/__snapshots__/Column.test.tsx.snap b/src/components/Column/__tests__/__snapshots__/Column.test.tsx.snap
index 0962c4c7..f61b3020 100644
--- a/src/components/Column/__tests__/__snapshots__/Column.test.tsx.snap
+++ b/src/components/Column/__tests__/__snapshots__/Column.test.tsx.snap
@@ -15,6 +15,7 @@ exports[`Column should have correct style show column with correct style 1`] = `
       >
         <input
           class="note-input__input"
+          data-tip="CMD/CTRL + 1"
           maxlength="1024"
           placeholder="Add your note..."
           type="text"
diff --git a/src/components/MenuBars/__tests__/__snapshots__/MenuBars.test.tsx.snap b/src/components/MenuBars/__tests__/__snapshots__/MenuBars.test.tsx.snap
index c1c6aeff..64607800 100644
--- a/src/components/MenuBars/__tests__/__snapshots__/MenuBars.test.tsx.snap
+++ b/src/components/MenuBars/__tests__/__snapshots__/MenuBars.test.tsx.snap
@@ -23,6 +23,11 @@ exports[`MenuBars should match snapshot 1`] = `
               class="tooltip-button__tooltip-text"
             >
               MenuBars.markAsDone
+              <span
+                class="tooltip-button__hotkey"
+              >
+                 [D]
+              </span>
             </span>
           </div>
           <svg
@@ -52,6 +57,11 @@ exports[`MenuBars should match snapshot 1`] = `
               class="tooltip-button__tooltip-text"
             >
               MenuBars.raiseHand
+              <span
+                class="tooltip-button__hotkey"
+              >
+                 [H]
+              </span>
             </span>
           </div>
           <svg
@@ -81,6 +91,11 @@ exports[`MenuBars should match snapshot 1`] = `
               class="tooltip-button__tooltip-text"
             >
               MenuBars.settings
+              <span
+                class="tooltip-button__hotkey"
+              >
+                 [S]
+              </span>
             </span>
           </div>
           <svg
@@ -129,6 +144,11 @@ exports[`MenuBars should match snapshot 1`] = `
               class="tooltip-button__tooltip-text"
             >
               Timer
+              <span
+                class="tooltip-button__hotkey"
+              >
+                 [T]
+              </span>
             </span>
           </div>
           <svg
@@ -158,6 +178,11 @@ exports[`MenuBars should match snapshot 1`] = `
               class="tooltip-button__tooltip-text"
             >
               Voting
+              <span
+                class="tooltip-button__hotkey"
+              >
+                 [V]
+              </span>
             </span>
           </div>
           <svg
@@ -187,6 +212,11 @@ exports[`MenuBars should match snapshot 1`] = `
               class="tooltip-button__tooltip-text"
             >
               MenuBars.startFocusMode
+              <span
+                class="tooltip-button__hotkey"
+              >
+                 [F]
+              </span>
             </span>
           </div>
           <svg