From 3801056d3c24cbe67424f1f4e89cb25029bb8dd8 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Thu, 9 Apr 2020 08:03:21 +1000 Subject: [PATCH] invite-search: allow keyboard nav + selection Allows the user to navigate potential invitees with the arrow keys and with shift+tab and tab. Enter adds the currently selected invitee. --- .../src/js/components/lib/invite-search.js | 101 ++++++++++++++++-- .../src/js/components/lib/invite-search.js | 101 ++++++++++++++++-- .../src/js/components/lib/invite-search.js | 101 ++++++++++++++++-- .../src/js/components/lib/invite-search.js | 101 ++++++++++++++++-- 4 files changed, 360 insertions(+), 44 deletions(-) diff --git a/pkg/interface/chat/src/js/components/lib/invite-search.js b/pkg/interface/chat/src/js/components/lib/invite-search.js index f1340d08e3..c5f59b201b 100644 --- a/pkg/interface/chat/src/js/components/lib/invite-search.js +++ b/pkg/interface/chat/src/js/components/lib/invite-search.js @@ -1,4 +1,6 @@ import React, { Component } from "react"; +import _ from 'lodash'; +import Mousetrap from 'mousetrap'; import urbitOb from "urbit-ob"; import { Sigil } from "../lib/icons/sigil"; @@ -14,13 +16,17 @@ export class InviteSearch extends Component { groups: [], ships: [] }, + selected: null, inviteError: false }; this.search = this.search.bind(this); + + this.textarea = React.createRef(); } componentDidMount() { this.peerUpdate(); + this.bindShortcuts(); } componentDidUpdate(prevProps) { @@ -126,12 +132,91 @@ export class InviteSearch extends Component { } } + let { selected } = this.state; + let groupIdx = groupMatches.findIndex(([path]) => path === selected); + let shipIdx = shipMatches.findIndex(ship => ship === selected); + let staleSelection = groupIdx < 0 && shipIdx < 0; + if(!selected || staleSelection) { + const newSelection = _.get(groupMatches, '[0][0]') || shipMatches[0]; + this.setState({ selected: newSelection }) + } + this.setState({ searchResults: { groups: groupMatches, ships: shipMatches } }); } } + bindShortcuts() { + let mousetrap = Mousetrap(this.textarea.current); + mousetrap.bind(['down', 'tab'], e => { + e.preventDefault(); + e.stopPropagation(); + this.nextSelection(); + }); + + mousetrap.bind(['up', 'shift+tab'], e => { + e.preventDefault(); + e.stopPropagation(); + this.nextSelection('backward'); + }); + + mousetrap.bind('enter', e => { + e.preventDefault(); + e.stopPropagation(); + const { selected } = this.state; + if(selected.startsWith('/')) { + this.addGroup(selected) + } else { + this.addShip(selected); + } + this.setState({ selected: null }) + }) + } + nextSelection(backward = false) { + let { selected, searchResults } = this.state; + const { ships, groups } = searchResults; + if(!selected) { + return; + } + let groupIdx = groups.findIndex(([path]) => path === selected); + let shipIdx = ships.findIndex(ship => ship === selected); + if(groupIdx >= 0) { + backward ? groupIdx-- : groupIdx++; + let selected = _.get(groups,[groupIdx], '[0]'); + if(groupIdx === groups.length) { + selected = ships.length === 0 + ? groups[0][0] + : ships[0]; + + } + if(groupIdx < 0) { + selected = ships.length === 0 + ? groups[groups.length - 1][0] + : ships[ships.length - 1]; + } + this.setState({ selected }); + return; + } + if(shipIdx >= 0) { + backward ? shipIdx-- : shipIdx++; + let selected = ships[shipIdx]; + if(shipIdx === ships.length) { + selected = groups.length === 0 + ? ships[0] + : groups[0][0]; + } + + if(shipIdx < 0) { + selected = groups.length === 0 + ? ships[ships.length - 1] + : groups[groups.length - 1][0]; + } + + this.setState({ selected }); + } + + } deleteGroup() { let { ships } = this.props.invites; this.setState({ @@ -251,7 +336,8 @@ export class InviteSearch extends Component { className={ "list white-d f8 pv2 ph3 pointer" + " hover-bg-gray4 hover-bg-gray1-d " + - ((group[1]) ? "inter" : "mono") + ((group[1]) ? "inter" : "mono") + + ( group[0] === state.selected ? ' bg-gray1-d bg-gray4' : '') } onClick={() => this.addGroup(group[0])}> {(group[1]) ? group[1] : group[0]} @@ -270,7 +356,8 @@ export class InviteSearch extends Component { key={ship} className={ "list mono white-d f8 pv1 ph3 pointer" + - " hover-bg-gray4 hover-bg-gray1-d relative" + " hover-bg-gray4 hover-bg-gray1-d relative" + + ( ship === state.selected ? ' bg-gray1-d bg-gray4' : '') } onClick={e => this.addShip(ship)}>