diff --git a/plugin-hrm-form/src/HrmFormPlugin.js b/plugin-hrm-form/src/HrmFormPlugin.js index 80e06aea54..dddbdef13e 100644 --- a/plugin-hrm-form/src/HrmFormPlugin.js +++ b/plugin-hrm-form/src/HrmFormPlugin.js @@ -13,7 +13,7 @@ import setUpMonitoring from './utils/setUpMonitoring'; import * as TransferHelpers from './utils/transfer'; import { changeLanguage } from './states/configuration/actions'; import { issueSyncToken } from './services/ServerlessService'; -import CannedResponses from './components/CannedResponses'; +import KeyboardShortcutManager from './components/KeyboardShortcut/KeyboardShortcutManager'; const PLUGIN_NAME = 'HrmFormPlugin'; export const PLUGIN_VERSION = '0.10.0'; @@ -26,6 +26,8 @@ export const DEFAULT_TRANSFER_MODE = transferModes.cold; */ let sharedStateClient; +let shortcutManager; + export const getConfig = () => { const manager = Flex.Manager.getInstance(); @@ -74,6 +76,7 @@ export const getConfig = () => { pdfImagesSource, multipleOfficeSupport, permissionConfig, + shortcutManager, }; }; @@ -175,6 +178,7 @@ const setUpComponents = setupObject => { const { helpline, featureFlags } = setupObject; // setUp (add) dynamic components + Components.setUpShortcuts(); Components.setUpQueuesStatusWriter(setupObject); Components.setUpQueuesStatus(); Components.setUpAddButtons(setupObject); @@ -255,9 +259,9 @@ export default class HrmFormPlugin extends FlexPlugin { console.log(`Welcome to ${PLUGIN_NAME} Version ${PLUGIN_VERSION}`); this.registerReducers(manager); + shortcutManager = new KeyboardShortcutManager(manager); const config = getConfig(); - /* * localization setup (translates the UI if necessary) * WARNING: the way this is done right now is "hacky". More info in initLocalization declaration diff --git a/plugin-hrm-form/src/___tests__/mockStyled.js b/plugin-hrm-form/src/___tests__/mockStyled.js index 47d5fd3bd5..ab1b9e22b0 100644 --- a/plugin-hrm-form/src/___tests__/mockStyled.js +++ b/plugin-hrm-form/src/___tests__/mockStyled.js @@ -68,6 +68,7 @@ jest.mock('../styles/HrmStyles', () => ({ PopoverText: 'PopoverText', CannedResponsesContainer: 'CannedResponsesContainer', Bold: 'Bold', + StyledBackButton: 'StyledBackButton', })); jest.mock('../styles/search', () => ({ @@ -94,6 +95,8 @@ jest.mock('../styles/search', () => ({ ContactDetailsIcon: () => 'ContactDetailsIcon', DetailsContainer: 'DetailsContainer', SectionTitleContainer: 'SectionTitleContainer', + SectionTitleButton: 'SectionTitleButton', + SectionCollapse: 'SectionCollapse', NameContainer: 'NameContainer', BackText: 'BackText', DetNameText: 'DetNameText', diff --git a/plugin-hrm-form/src/components/KeyboardShortcut/KeyboardShortcut.Container.tsx b/plugin-hrm-form/src/components/KeyboardShortcut/KeyboardShortcut.Container.tsx new file mode 100644 index 0000000000..73bf2e25ae --- /dev/null +++ b/plugin-hrm-form/src/components/KeyboardShortcut/KeyboardShortcut.Container.tsx @@ -0,0 +1,24 @@ +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; + +import { namespace, shortcutBase, RootState } from '../../states'; +import { updatePressedKeys, openGuideModal, closeGuideModal } from '../../states/shortcuts/actions'; +import KeyboardShortcut from './KeyboardShortcut'; + +/** + * We're not using the pattern Container/Component in the rest of the app. + * So I suggest we don't do this here. We should unify KeyboarShortcut.Container and + * KeyboardShortcut to be a single component/file. + */ +const mapStateToProps = (state: RootState) => ({ + pressedKeys: state[namespace][shortcutBase].pressedKeys, + isGuideModalOpen: state[namespace][shortcutBase].isGuideModalOpen, +}); + +const mapDispatchToProps = dispatch => ({ + updatePressedKeys: bindActionCreators(updatePressedKeys, dispatch), + openGuideModal: bindActionCreators(openGuideModal, dispatch), + closeGuideModal: bindActionCreators(closeGuideModal, dispatch), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(KeyboardShortcut); diff --git a/plugin-hrm-form/src/components/KeyboardShortcut/KeyboardShortcut.tsx b/plugin-hrm-form/src/components/KeyboardShortcut/KeyboardShortcut.tsx new file mode 100644 index 0000000000..4f5bc14a32 --- /dev/null +++ b/plugin-hrm-form/src/components/KeyboardShortcut/KeyboardShortcut.tsx @@ -0,0 +1,127 @@ +import React, { Component } from 'react'; +import { Dialog, DialogContent, DialogTitle, List, ListItem, ListItemText, Typography } from '@material-ui/core'; + +import { KeyBoardShortcutRule } from './KeyboardShortcutManager'; +import { keyboardGuide } from './KeyboardShortcutGuide'; +import { PressedKeys } from '../../states/shortcuts/types'; +import { getConfig } from '../../HrmFormPlugin'; + +type OwnProps = { + shortcuts: KeyBoardShortcutRule[]; +}; + +type ReduxProps = { + shortcuts: KeyBoardShortcutRule[]; + pressedKeys: PressedKeys; + isGuideModalOpen: boolean; + updatePressedKeys: (pressedKeys: PressedKeys) => void; + closeGuideModal: () => void; +}; + +type Props = OwnProps & ReduxProps; + +// TODO: Make this a pure function component +class KeyboardShortcut extends Component { + static displayName = 'KeyboardShortcut'; + + componentDidMount() { + window.addEventListener('keydown', this.keyboardEventListener, true); + window.addEventListener('keyup', this.keyboardEventListener, true); + + const initialKeys = this.props.shortcuts.reduce<{ [key: string]: boolean }>((result, curr) => { + curr.keys.forEach(key => (result[key] = false)); + return result; + }, {}); + + this.props.updatePressedKeys(initialKeys); + } + + componentWillUnmount() { + window.removeEventListener('keydown', this.keyboardEventListener); + window.removeEventListener('keyup', this.keyboardEventListener); + } + + keyboardEventListener = (e: KeyboardEvent) => { + /** + * Here we use event.pressedKey to determine the key the user has typed. + * If our shortcuts contains Shift or Alt, this pressed key value will be secondary + * or terciary value of that key. Example: 3, #, £. I'm not sure if those secondary + * and terciary values are consistent between different keyboards. + * + * One alternative to investigate is to use event.keyCode instead, that keeps the + * same value, regardless if we're pressing Shift or Alt with it. + */ + const { key: pressedKey, type: eventType, repeat } = e; + const { tagName } = e.target as Element; + console.log({ e, pressedKey }); + + const { shortcutManager } = getConfig(); + + /** + * Shortcuts should not be single key. When we change them to be like 'Control + Command + Letter', + * we can remove the bellow condition. + */ + // Check user is not inputting text + if (['INPUT', 'TEXTAREA', 'SELECT'].includes(tagName)) { + return; + } + + // Check that this is a non-repeating key event + if (repeat) return; + + if (eventType === 'keydown') { + for (const { keys, action } of shortcutManager.shortcuts) { + if (keys.every(key => key === pressedKey || this.props.pressedKeys[key])) { + action(); + break; + } + } + } + + /** + * This part has an issue. + * How it should work? + * After a keydown event is detected, the related key is marked as TRUE on redux. + * After the key is released, the keyup event for this key should go into this if condition, and thus, + * this key will become FALSE on redux. + * + * What's the issue? + * Sometimes the keyup event is not detected. For single key shortcuts, it looks like this issue never arises. + * But for multiple keys short, such as 'Control + Command + M', if the action triggers things like an alert or a dialog, + * the keyup event for the letter 'M' is not detected, and the 'M' key remains TRUE on redux, making the app behave wrong. + * For some reason, the Control and Command keyup events seem to be always detected, but not the keyup events for letters. + */ + if (this.props.pressedKeys[pressedKey] !== undefined) { + const updatedPressedKeys = { ...this.props.pressedKeys, [pressedKey]: eventType === 'keydown' }; + this.props.updatePressedKeys(updatedPressedKeys); + } + }; + + render() { + return ( + + Keyboard Shortcuts Help + + + {keyboardGuide.map((shortcut, i) => { + return ( + + {shortcut.description} + {shortcut.keys} + + ); + })} + + + + ); + } +} + +export default KeyboardShortcut; diff --git a/plugin-hrm-form/src/components/KeyboardShortcut/KeyboardShortcutGuide.ts b/plugin-hrm-form/src/components/KeyboardShortcut/KeyboardShortcutGuide.ts new file mode 100644 index 0000000000..b8fdd92ed6 --- /dev/null +++ b/plugin-hrm-form/src/components/KeyboardShortcut/KeyboardShortcutGuide.ts @@ -0,0 +1,17 @@ +/** + * This part is completely hardcoded and centralized. + * Should we make it more dynamically, in a way that we wouldn't + * need this file at all? + * + * For example: When calling the addShorcut() method, we could pass its description as a parameter. + * That way it will be easier to keep the keyboard guide up to date. And the 'keys' part could be + * built automatically filled (or overwritten if necessary for accessibility reasons). + */ +export const keyboardGuide = [ + { description: 'Toggle keyboard guide', keys: '?' }, + { description: 'Toggle sidebar', keys: 'M' }, + { description: 'Toggle availability', keys: 'O' }, + { description: 'Open standalone search', keys: 'S' }, + { description: 'Open agent desktop', keys: 'A' }, + { description: 'Choose Tab', keys: '0/1/2/3/4 (tab number)' }, +]; diff --git a/plugin-hrm-form/src/components/KeyboardShortcut/KeyboardShortcutManager.ts b/plugin-hrm-form/src/components/KeyboardShortcut/KeyboardShortcutManager.ts new file mode 100644 index 0000000000..2d274c59c6 --- /dev/null +++ b/plugin-hrm-form/src/components/KeyboardShortcut/KeyboardShortcutManager.ts @@ -0,0 +1,63 @@ +import * as Flex from '@twilio/flex-ui'; + +import { openGuideModal, closeGuideModal } from '../../states/shortcuts/actions'; +import { namespace, shortcutBase } from '../../states'; + +export interface KeyBoardShortcutRule { + keys: string[]; + action: () => void; +} + +/** + * Suggestion: we should make the methods of this class to be arrow functions, + * so that we don't need to bind the instance to use the correct 'this' value. + * + * Right now, we're using these methods like this: shortcutManager.toggleGuide.bind(shortcutManager). + * Notice the '.bind(shortcutManager)'. We can avoid that if we make these methods arrow functions. + */ +class KeyboardShortcutManager { + private manager: Flex.Manager; + + public shortcuts: KeyBoardShortcutRule[]; + + constructor(manager: Flex.Manager) { + this.manager = manager; + this.shortcuts = []; + } + + public addShortcut(keys: string[], action: () => void) { + this.shortcuts = [...this.shortcuts, { keys, action }]; + } + + public toggleGuide() { + if (this.manager.store.getState()[namespace][shortcutBase].isGuideModalOpen) { + this.manager.store.dispatch(closeGuideModal()); + } else { + this.manager.store.dispatch(openGuideModal()); + } + } + + public toggleSidebar() { + Flex.Actions.invokeAction('ToggleSidebar'); + } + + public toggleAvailability() { + const { activity } = this.manager.store.getState().flex.worker; + if (activity.name === 'Offline' || activity.name === 'Unavailable') { + Flex.Actions.invokeAction('SetActivity', { activityAvailable: true, activityName: 'Available' }); + } + if (activity.name === 'Available') { + Flex.Actions.invokeAction('SetActivity', { activityAvailable: false, activityName: 'Unavailable' }); + } + } + + public openStandaloneSearch() { + Flex.Actions.invokeAction('NavigateToView', { viewName: 'search' }); + } + + public openAgentDesktop() { + Flex.Actions.invokeAction('NavigateToView', { viewName: 'agent-desktop' }); + } +} + +export default KeyboardShortcutManager; diff --git a/plugin-hrm-form/src/components/Section.jsx b/plugin-hrm-form/src/components/Section.jsx index 97a88ce4cb..bc31b4b6a2 100644 --- a/plugin-hrm-form/src/components/Section.jsx +++ b/plugin-hrm-form/src/components/Section.jsx @@ -1,9 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { ButtonBase, Collapse } from '@material-ui/core'; import { ArrowDropDownTwoTone, ArrowDropUpTwoTone } from '@material-ui/icons'; -import { SectionTitleContainer, SectionTitleText, ContactDetailsIcon } from '../styles/search'; +import { + SectionTitleContainer, + SectionTitleButton, + SectionTitleText, + SectionCollapse, + ContactDetailsIcon, +} from '../styles/search'; const ArrowDownIcon = ContactDetailsIcon(ArrowDropDownTwoTone); const ArrowUpIcon = ContactDetailsIcon(ArrowDropUpTwoTone); @@ -11,14 +16,14 @@ const ArrowUpIcon = ContactDetailsIcon(ArrowDropUpTwoTone); const Section = ({ color, sectionTitle, expanded, hideIcon, children, handleExpandClick, buttonDataTestid }) => ( <> - + {sectionTitle} {!hideIcon && (expanded ? : )} - + - + {children} - + ); diff --git a/plugin-hrm-form/src/components/search/SearchResults/SearchResultsBackButton.tsx b/plugin-hrm-form/src/components/search/SearchResults/SearchResultsBackButton.tsx index ad709fbad7..7ab7a4a6da 100644 --- a/plugin-hrm-form/src/components/search/SearchResults/SearchResultsBackButton.tsx +++ b/plugin-hrm-form/src/components/search/SearchResults/SearchResultsBackButton.tsx @@ -1,8 +1,7 @@ /* eslint-disable react/prop-types */ import React from 'react'; -import { ButtonBase } from '@material-ui/core'; -import { Row } from '../../../styles/HrmStyles'; +import { Row, StyledBackButton } from '../../../styles/HrmStyles'; import { BackText, BackIcon } from '../../../styles/search'; type OwnProps = { @@ -15,12 +14,12 @@ type Props = OwnProps; const SearchResultsBackButton: React.FC = ({ text, handleBack }) => { return ( - + {text} - + ); }; diff --git a/plugin-hrm-form/src/components/tabbedForms/TabbedForms.tsx b/plugin-hrm-form/src/components/tabbedForms/TabbedForms.tsx index b1c99ddb62..2cde376871 100644 --- a/plugin-hrm-form/src/components/tabbedForms/TabbedForms.tsx +++ b/plugin-hrm-form/src/components/tabbedForms/TabbedForms.tsx @@ -28,7 +28,7 @@ import BottomBar from './BottomBar'; import { hasTaskControl } from '../../utils/transfer'; import { isNonDataCallType } from '../../states/ValidationRules'; import SearchResultsBackButton from '../search/SearchResults/SearchResultsBackButton'; -import { isStandaloneITask } from '../case/Case'; +import { getConfig } from '../../HrmFormPlugin'; // eslint-disable-next-line react/display-name const mapTabsComponents = (errors: any) => (t: TabbedFormSubroutes) => { @@ -126,6 +126,9 @@ const TabbedForms: React.FC = ({ dispatch, routing, task, contactForm, cu dispatch(changeRoute({ route: 'tabbed-forms', subroute: tab }, taskId)); }; + const { shortcutManager } = getConfig(); + tabsToIndex.forEach((_, index) => shortcutManager.addShortcut([String(index)], () => handleTabsChange(null, index))); + const { subroute } = routing; let tabIndex = tabsToIndex.findIndex(t => t === subroute); diff --git a/plugin-hrm-form/src/states/index.ts b/plugin-hrm-form/src/states/index.ts index ae77032828..5ea3144c9e 100644 --- a/plugin-hrm-form/src/states/index.ts +++ b/plugin-hrm-form/src/states/index.ts @@ -7,6 +7,7 @@ import { reduce as ConnectedCaseReducer } from './case/reducer'; import { reduce as QueuesStatusReducer } from './queuesStatus/reducer'; import { reduce as ConfigurationReducer } from './configuration/reducer'; import { reduce as RoutingReducer } from './routing/reducer'; +import { reduce as ShortcutReducer } from './shortcuts/reducer'; // Register your redux store under a unique namespace export const namespace = 'plugin-hrm-form'; @@ -16,6 +17,7 @@ export const connectedCaseBase = 'connectedCase'; export const queuesStatusBase = 'queuesStatusState'; export const configurationBase = 'configuration'; export const routingBase = 'routing'; +export const shortcutBase = 'shortcut'; const reducers = { [contactFormsBase]: ContactStateReducer, @@ -24,6 +26,7 @@ const reducers = { [connectedCaseBase]: ConnectedCaseReducer, [configurationBase]: ConfigurationReducer, [routingBase]: RoutingReducer, + [shortcutBase]: ShortcutReducer, }; // Combine the reducers diff --git a/plugin-hrm-form/src/states/shortcuts/actions.ts b/plugin-hrm-form/src/states/shortcuts/actions.ts new file mode 100644 index 0000000000..6c44ff968f --- /dev/null +++ b/plugin-hrm-form/src/states/shortcuts/actions.ts @@ -0,0 +1,10 @@ +import * as t from './types'; + +export const updatePressedKeys = (pressedKeys: t.PressedKeys): t.ShortcutActionType => ({ + type: t.UPDATE_KEYS_PRESSED, + pressedKeys, +}); + +export const openGuideModal = (): t.ShortcutActionType => ({ type: t.OPEN_GUIDE_MODAL }); + +export const closeGuideModal = (): t.ShortcutActionType => ({ type: t.CLOSE_GUIDE_MODAL }); diff --git a/plugin-hrm-form/src/states/shortcuts/reducer.ts b/plugin-hrm-form/src/states/shortcuts/reducer.ts new file mode 100644 index 0000000000..81a8123683 --- /dev/null +++ b/plugin-hrm-form/src/states/shortcuts/reducer.ts @@ -0,0 +1,37 @@ +import * as t from './types'; + +type ShortcutState = { + pressedKeys: t.PressedKeys; + isGuideModalOpen: boolean; +}; + +const initialState: ShortcutState = { + pressedKeys: {}, + isGuideModalOpen: false, +}; + +export function reduce(state = initialState, action: t.ShortcutActionType): ShortcutState { + switch (action.type) { + case t.UPDATE_KEYS_PRESSED: + const updatedPressedKeys = { + ...state.pressedKeys, + ...action.pressedKeys, + }; + return { + ...state, + pressedKeys: updatedPressedKeys, + }; + case t.OPEN_GUIDE_MODAL: + return { + ...state, + isGuideModalOpen: true, + }; + case t.CLOSE_GUIDE_MODAL: + return { + ...state, + isGuideModalOpen: false, + }; + default: + return state; + } +} diff --git a/plugin-hrm-form/src/states/shortcuts/types.ts b/plugin-hrm-form/src/states/shortcuts/types.ts new file mode 100644 index 0000000000..c30f25110f --- /dev/null +++ b/plugin-hrm-form/src/states/shortcuts/types.ts @@ -0,0 +1,13 @@ +export const UPDATE_KEYS_PRESSED = 'UPDATE_KEYS_PRESSED'; +export const OPEN_GUIDE_MODAL = 'OPEN_GUIDE_MODAL'; +export const CLOSE_GUIDE_MODAL = 'CLOSE_GUIDE_MODAL'; + +export type PressedKeys = { + [key: string]: boolean; +}; + +type UpdatePressedKeysAction = { type: typeof UPDATE_KEYS_PRESSED; pressedKeys: PressedKeys }; +type OpenGuideModalAction = { type: typeof OPEN_GUIDE_MODAL }; +type CloseGuideModalAction = { type: typeof CLOSE_GUIDE_MODAL }; + +export type ShortcutActionType = UpdatePressedKeysAction | OpenGuideModalAction | CloseGuideModalAction; diff --git a/plugin-hrm-form/src/styles/HrmStyles.tsx b/plugin-hrm-form/src/styles/HrmStyles.tsx index e08800ec3d..86657fa044 100644 --- a/plugin-hrm-form/src/styles/HrmStyles.tsx +++ b/plugin-hrm-form/src/styles/HrmStyles.tsx @@ -263,8 +263,9 @@ export const StyledNextStepButton = styled(Button)` )}; &&:focus { - background-color: rgba(255, 255, 255, 0.2); - background-blend-mode: color; + outline-color: #4d90fe; + outline-style: auto; + outline-width: initial; } &&:active { @@ -306,7 +307,7 @@ TwoColumnLayout.displayName = 'TwoColumnLayout'; type ToggleViewButtonProps = { active?: boolean }; -export const ToggleViewButton = styled('div')` +export const ToggleViewButton = styled('button')` display: inline-flex; width: 37px; height: 37px; @@ -321,6 +322,10 @@ export const ToggleViewButton = styled('div')` background-color: ${({ active }) => (active ? 'initial' : '#a0a8bdcc')}; opacity: ${({ active }) => (active ? 'initial' : '20%')}; + &:focus { + outline: auto; + } + > svg { font-size: 18px; } @@ -377,6 +382,9 @@ export const StyledTab = withStyles({ '&:hover': { backgroundColor: '#c9c9c9', }, + '&:focus': { + outline: 'auto', + }, }, selected: { backgroundColor: '#ffffff', @@ -390,6 +398,9 @@ export const StyledSearchTab = withStyles({ minWidth: 40, width: 40, backgroundColor: 'transparent', + '&:focus': { + outline: 'auto', + }, }, selected: { backgroundColor: '#ffffff', @@ -975,3 +986,11 @@ export const Bold = styled('span')` `; Bold.displayName = 'Bold'; + +export const StyledBackButton = styled(ButtonBase)` + &:focus { + outline: auto; + } +`; + +StyledBackButton.displayName = 'StyledBackButton'; diff --git a/plugin-hrm-form/src/styles/callTypeButtons/index.tsx b/plugin-hrm-form/src/styles/callTypeButtons/index.tsx index 4dca8c159e..8ebebf54db 100644 --- a/plugin-hrm-form/src/styles/callTypeButtons/index.tsx +++ b/plugin-hrm-form/src/styles/callTypeButtons/index.tsx @@ -37,6 +37,10 @@ export const DataCallTypeButton = styled(Button)` &:hover { background-color: #7fa3cb; } + + &:focus { + outline: auto; + } `; DataCallTypeButton.displayName = 'DataCallTypeButton'; @@ -64,6 +68,10 @@ export const NonDataCallTypeButton = styled(Button)` &:hover { background-color: #b1b6c0; } + + &:focus { + outline: auto; + } `; export const CloseTaskDialog = styled(props => )` @@ -95,7 +103,9 @@ export const ConfirmButton = styled(Button)` ${p => getBackgroundWithHoverCSS(p.theme.colors.declineColor, true, false, p.disabled)}; &:focus { - box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 3px 0px; + outline-color: #4d90fe; + outline-style: auto; + outline-width: initial; } `; @@ -103,10 +113,14 @@ export const CancelButton = styled(Button)` text-transform: uppercase; margin-left: 30px; - &:focus { + &:hover { background-color: rgba(0, 0, 0, 0.2); background-blend-mode: color; } + + &:focus { + outline: auto; + } `; export const CloseButton = styled(props => ( @@ -118,7 +132,11 @@ export const CloseButton = styled(props => ( color: ${props => props.theme.colors.defaultButtonColor}; } - &:focus { + &:hover { background-color: rgba(0, 0, 0, 0.08); } + + &:focus { + outline: auto; + } `; diff --git a/plugin-hrm-form/src/styles/menu/index.js b/plugin-hrm-form/src/styles/menu/index.js index f798ca190f..e5ff4a8f97 100644 --- a/plugin-hrm-form/src/styles/menu/index.js +++ b/plugin-hrm-form/src/styles/menu/index.js @@ -30,7 +30,7 @@ export const StyledMenuItem = styled(props => ( text-decoration-color: ${props => (props.underline ? '#1874e1' : 'transparent')}; background-color: ${props => (props.underline ? 'transparent' : props.theme.colors.hyperlinkHoverBackgroundColor)}; } + + &&:focus { + outline: auto; + } `; const Tag = styled('div')` @@ -319,6 +323,27 @@ export const SectionTitleContainer = styled(Row)` padding-left: 18px; border-left: ${({ color }) => (color ? `6px solid ${color}` : 'none')}; `; +SectionTitleContainer.displayName = 'SectionTitleContainer'; + +export const SectionTitleButton = styled(ButtonBase)` + width: 100%; + padding: 0; + &:focus { + outline: auto; + } +`; +SectionTitleButton.displayName = 'SectionTitleButton'; + +type CollapseProps = { + expanded: boolean; +}; + +export const SectionCollapse = styled(({ expanded, ...rest }: CollapseProps) => ( + +))` + visibility: ${props => (props.expanded ? 'visible' : 'collapse')}; +`; +SectionCollapse.displayName = 'SectionCollapse'; export const NameContainer = styled(SectionTitleContainer)` background-color: #000000; diff --git a/plugin-hrm-form/src/utils/setUpComponents.js b/plugin-hrm-form/src/utils/setUpComponents.js index dee184e03f..53ecb5ae86 100644 --- a/plugin-hrm-form/src/utils/setUpComponents.js +++ b/plugin-hrm-form/src/utils/setUpComponents.js @@ -3,6 +3,8 @@ import React from 'react'; import * as Flex from '@twilio/flex-ui'; +import KeyboardShortcut from '../components/KeyboardShortcut/KeyboardShortcut.Container'; +import KeyboardShortcutManager from '../components/KeyboardShortcut/KeyboardShortcutManager'; import { TransferButton, AcceptTransferButton, RejectTransferButton } from '../components/transfer'; import * as TransferHelpers from './transfer'; import CannedResponses from '../components/CannedResponses'; @@ -28,6 +30,7 @@ import TwitterIcon from '../components/common/icons/TwitterIcon'; // eslint-disable-next-line import { getConfig } from '../HrmFormPlugin'; import { isInMyBehalfITask } from '../types/types'; +import { openGuideModal } from '../states/shortcuts/actions'; const voiceColor = { Accepted: Flex.DefaultTaskChannels.Call.colors.main() }; const webColor = Flex.DefaultTaskChannels.Chat.colors.main; @@ -36,6 +39,29 @@ const smsColor = Flex.DefaultTaskChannels.ChatSms.colors.main; const whatsappColor = Flex.DefaultTaskChannels.ChatWhatsApp.colors.main; const twitterColor = '#1DA1F2'; +export const setUpShortcuts = () => { + const { shortcutManager } = getConfig(); + + shortcutManager.addShortcut(['/'], shortcutManager.toggleGuide.bind(shortcutManager)); + shortcutManager.addShortcut(['s'], shortcutManager.openStandaloneSearch.bind(shortcutManager)); + shortcutManager.addShortcut(['a'], shortcutManager.openAgentDesktop.bind(shortcutManager)); + shortcutManager.addShortcut(['m'], shortcutManager.toggleSidebar.bind(shortcutManager)); + shortcutManager.addShortcut(['o'], shortcutManager.toggleAvailability.bind(shortcutManager)); + + Flex.RootContainer.Content.add(); + Flex.MainHeader.Content.add( + manager.store.dispatch(openGuideModal())} + icon="Info" + key="keyboard-shortcut-guide-button" + />, + { + sortOrder: -1, + align: 'end', + }, + ); +}; + /** * Returns the UI for the "Contacts Waiting" section */