From 5c948e1a68d93dac97e922f54a706350240dfdcc Mon Sep 17 00:00:00 2001
From: unknown <pawel999@icloud.com>
Date: Sat, 22 May 2021 18:10:12 +0200
Subject: [PATCH] Support for keyboard navigation for Apps. Small refactor to
 pinApp action

---
 .../src/components/Apps/AppForm/AppForm.tsx   | 11 +++++++-
 .../src/components/Apps/AppTable/AppTable.tsx | 28 +++++++++++++++----
 client/src/components/Apps/Apps.tsx           | 11 ++++++--
 client/src/store/actions/app.ts               |  3 +-
 4 files changed, 43 insertions(+), 10 deletions(-)

diff --git a/client/src/components/Apps/AppForm/AppForm.tsx b/client/src/components/Apps/AppForm/AppForm.tsx
index f46c7e7..8a71b4e 100644
--- a/client/src/components/Apps/AppForm/AppForm.tsx
+++ b/client/src/components/Apps/AppForm/AppForm.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, ChangeEvent, SyntheticEvent } from 'react';
+import { useState, useEffect, useRef, ChangeEvent, SyntheticEvent } from 'react';
 import { connect } from 'react-redux';
 import { addApp, updateApp } from '../../../store/actions';
 import { App, NewApp } from '../../../interfaces/App';
@@ -20,6 +20,14 @@ const AppForm = (props: ComponentProps): JSX.Element => {
     icon: ''
   });
 
+  const inputRef = useRef<HTMLInputElement>(null);
+
+  useEffect(() => {
+    if (inputRef.current) {
+      inputRef.current.focus();
+    }
+  }, [inputRef])
+
   useEffect(() => {
     if (props.app) {
       console.log('app');
@@ -75,6 +83,7 @@ const AppForm = (props: ComponentProps): JSX.Element => {
             required
             value={formData.name}
             onChange={(e) => inputChangeHandler(e)}
+            ref={inputRef}
           />
         </div>
         <div className={classes.InputGroup}>
diff --git a/client/src/components/Apps/AppTable/AppTable.tsx b/client/src/components/Apps/AppTable/AppTable.tsx
index 072da58..ec0a11c 100644
--- a/client/src/components/Apps/AppTable/AppTable.tsx
+++ b/client/src/components/Apps/AppTable/AppTable.tsx
@@ -1,3 +1,4 @@
+import { KeyboardEvent } from 'react';
 import { connect } from 'react-redux';
 import { App, GlobalState } from '../../../interfaces';
 import { pinApp, deleteApp } from '../../../store/actions';
@@ -7,7 +8,7 @@ import Icon from '../../UI/Icons/Icon/Icon';
 
 interface ComponentProps {
   apps: App[];
-  pinApp: (id: number, isPinned: boolean) => void;
+  pinApp: (app: App) => void;
   deleteApp: (id: number) => void;
   updateAppHandler: (app: App) => void;
 }
@@ -21,6 +22,12 @@ const AppTable = (props: ComponentProps): JSX.Element => {
     }
   }
 
+  const keyboardActionHandler = (e: KeyboardEvent, app: App, handler: Function) => {
+    if (e.key === 'Enter') {
+      handler(app);
+    }
+  }
+
   return (
     <div className={classes.TableContainer}>
       <table className={classes.Table}>
@@ -42,16 +49,27 @@ const AppTable = (props: ComponentProps): JSX.Element => {
                 <td className={classes.TableActions}>
                   <div
                     className={classes.TableAction}
-                    onClick={() => deleteAppHandler(app)}>
+                    onClick={() => deleteAppHandler(app)}
+                    onKeyDown={(e) => keyboardActionHandler(e, app, deleteAppHandler)}
+                    tabIndex={0}>
                     <Icon icon='mdiDelete' />
                   </div>
                   <div
                     className={classes.TableAction}
-                    onClick={() => props.updateAppHandler(app)}>
+                    onClick={() => props.updateAppHandler(app)}
+                    onKeyDown={(e) => keyboardActionHandler(e, app, props.updateAppHandler)}
+                    tabIndex={0}>
                     <Icon icon='mdiPencil' />
                   </div>
-                  <div className={classes.TableAction} onClick={() => props.pinApp(app.id, app.isPinned)}>
-                    {app.isPinned? <Icon icon='mdiPinOff' color='var(--color-accent)' /> : <Icon icon='mdiPin' />}
+                  <div
+                    className={classes.TableAction}
+                    onClick={() => props.pinApp(app)}
+                    onKeyDown={(e) => keyboardActionHandler(e, app, props.pinApp)}
+                    tabIndex={0}>
+                    {app.isPinned
+                      ? <Icon icon='mdiPinOff' color='var(--color-accent)' />
+                      : <Icon icon='mdiPin' />
+                    }
                   </div>
                 </td>
               </tr>
diff --git a/client/src/components/Apps/Apps.tsx b/client/src/components/Apps/Apps.tsx
index e79696c..9b26111 100644
--- a/client/src/components/Apps/Apps.tsx
+++ b/client/src/components/Apps/Apps.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react';
+import { Fragment, useEffect, useState } from 'react';
 import { Link } from 'react-router-dom';
 
 // Redux
@@ -25,7 +25,6 @@ import AppTable from './AppTable/AppTable';
 
 interface ComponentProps {
   getApps: Function;
-  pinApp: (id: number, isPinned: boolean) => void;
   addApp: (formData: NewApp) => void;
   apps: App[];
   loading: boolean;
@@ -92,6 +91,12 @@ const Apps = (props: ComponentProps): JSX.Element => {
           icon='mdiPencil'
           handler={toggleEdit}
         />
+        {isInEdit && <Fragment>
+          <ActionButton
+            name='Pin All'
+            icon='mdiPin'
+          />
+        </Fragment>}
       </div>
 
       <div className={classes.Apps}>
@@ -115,4 +120,4 @@ const mapStateToProps = (state: GlobalState) => {
   }
 }
 
-export default connect(mapStateToProps, { getApps, pinApp, addApp })(Apps);
\ No newline at end of file
+export default connect(mapStateToProps, { getApps, addApp })(Apps);
\ No newline at end of file
diff --git a/client/src/store/actions/app.ts b/client/src/store/actions/app.ts
index d66f325..0ba2c3f 100644
--- a/client/src/store/actions/app.ts
+++ b/client/src/store/actions/app.ts
@@ -34,8 +34,9 @@ export interface PinAppAction {
   payload: App;
 }
 
-export const pinApp = (id: number, isPinned: boolean) => async (dispatch: Dispatch) => {
+export const pinApp = (app: App) => async (dispatch: Dispatch) => {
   try {
+    const { id, isPinned} = app;
     const res = await axios.put<ApiResponse<App>>(`/api/apps/${id}`, { isPinned: !isPinned });
 
     dispatch<PinAppAction>({