1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-07-29 09:40:10 +02:00

Add keyboard navigation to command palette.

This commit is contained in:
Kushagra Gour
2018-10-27 18:56:38 +05:30
parent 78468f8a5d
commit 021f1390ab
3 changed files with 98 additions and 45 deletions

View File

@@ -7,6 +7,7 @@ import {
SWITCH_FILE_EVENT SWITCH_FILE_EVENT
} from '../commandPaletteService'; } from '../commandPaletteService';
import { FileIcon } from './FileIcon'; import { FileIcon } from './FileIcon';
import { UP_KEY, DOWN_KEY, ENTER_KEY } from '../keyboardKeys';
function getFolder(filePath) { function getFolder(filePath) {
const split = filePath.split('/'); const split = filePath.split('/');
@@ -16,14 +17,19 @@ function getFolder(filePath) {
} }
return ''; return '';
} }
function Row({ item, onClick }) { function Row({ item, onClick, isSelected }) {
return ( return (
<li> <li>
<button style="background:0;border:0;" onClick={onClick}> <button
class={`command-palette__option-row ${
isSelected ? 'command-palette__option-row--selected' : ''
}`}
onClick={onClick}
>
{item.path ? <FileIcon file={item} /> : null} {item.path ? <FileIcon file={item} /> : null}
{item.name} {item.name}
{item.path ? ( {item.path ? (
<span style="color:#ccc;margin-left:10px;font-size:0.8em;"> <span class="command-palette__option-subtitle">
{getFolder(item.path)} {getFolder(item.path)}
</span> </span>
) : null} ) : null}
@@ -32,7 +38,7 @@ function Row({ item, onClick }) {
); );
} }
export class CommandPalette extends Component { export class CommandPalette extends Component {
state = { list: [], search: '' }; state = { list: [], search: '', selectedIndex: 0 };
componentDidUpdate(previousProps) { componentDidUpdate(previousProps) {
if (this.props.show && !previousProps.show) { if (this.props.show && !previousProps.show) {
this.state.search = ''; this.state.search = '';
@@ -58,17 +64,32 @@ export class CommandPalette extends Component {
); );
} }
keyDownHandler(e) {
const diff = { [UP_KEY]: -1, [DOWN_KEY]: 1 }[e.which];
if (diff) {
this.setState({
selectedIndex:
(this.state.selectedIndex + diff) % this.state.list.length
});
return;
}
if (e.which === ENTER_KEY) {
this.selectOption(this.state.list[this.state.selectedIndex]);
}
}
inputHandler(e) { inputHandler(e) {
const search = e.target.value; const search = e.target.value;
this.setState({ search }); this.setState({ search });
if (search.indexOf('>') === 0) { this.isCommandMode = search.indexOf('>') === 0;
this.isCommandMode = true;
}
this.setState({ this.setState({
list: this.getFilteredList(search) list: this.getFilteredList(search),
selectedIndex: 0
}); });
} }
optionClickHandler(option) { optionClickHandler(option) {
this.selectOption(option);
}
selectOption(option) {
commandPaletteService.publish( commandPaletteService.publish(
option.path ? SWITCH_FILE_EVENT : option.event, option.path ? SWITCH_FILE_EVENT : option.event,
option option
@@ -77,14 +98,21 @@ export class CommandPalette extends Component {
} }
render() { render() {
return ( return (
<Modal show={this.props.show} closeHandler={this.props.closeHandler}> <Modal
show={this.props.show}
closeHandler={this.props.closeHandler}
noOverlay
hideCloseButton
>
<AutoFocusInput <AutoFocusInput
value={this.state.search} value={this.state.search}
onInput={this.inputHandler.bind(this)} onInput={this.inputHandler.bind(this)}
onKeyUp={this.keyDownHandler.bind(this)}
/> />
<ul style="padding:0;list-style:none;"> <ul style="padding:0;list-style:none;">
{this.state.list.map(item => ( {this.state.list.map((item, index) => (
<Row <Row
isSelected={this.state.selectedIndex === index}
item={item} item={item}
onClick={this.optionClickHandler.bind(this, item)} onClick={this.optionClickHandler.bind(this, item)}
/> />

View File

@@ -24,9 +24,11 @@ export default class Modal extends Component {
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (this.props.show !== prevProps.show) { if (this.props.show !== prevProps.show) {
document.body.classList[this.props.show ? 'add' : 'remove']( if (!this.props.noOverlay) {
'overlay-visible' document.body.classList[this.props.show ? 'add' : 'remove'](
); 'overlay-visible'
);
}
if (this.props.show) { if (this.props.show) {
// HACK: refs will evaluate on next tick due to portals // HACK: refs will evaluate on next tick due to portals
setTimeout(() => { setTimeout(() => {
@@ -63,15 +65,17 @@ export default class Modal extends Component {
onClick={this.onOverlayClick.bind(this)} onClick={this.onOverlayClick.bind(this)}
> >
<div class="modal__content"> <div class="modal__content">
<button {this.props.hideCloseButton ? null : (
type="button" <button
onClick={this.props.closeHandler} type="button"
aria-label="Close modal" onClick={this.props.closeHandler}
title="Close" aria-label="Close modal"
class="js-modal__close-btn modal__close-btn" title="Close"
> class="js-modal__close-btn modal__close-btn"
Close >
</button> Close
</button>
)}
{this.props.children} {this.props.children}
</div> </div>
</div> </div>

View File

@@ -1,5 +1,6 @@
:root { :root {
--color-text: #d4cde9; --color-text: #e2daf9;
--color-text-dark-1: #b3aec4;
--color-bg: #252637; --color-bg: #252637;
--color-popup: #3a2b63; --color-popup: #3a2b63;
--code-font-size: 16px; --code-font-size: 16px;
@@ -74,6 +75,10 @@ button {
.d-i { .d-i {
display: inline; display: inline;
} }
.d-b {
display: block;
}
.flex { .flex {
display: flex; display: flex;
} }
@@ -1688,28 +1693,6 @@ body:not(.is-app) .show-when-app {
.is-file-mode .hide-in-file-mode { .is-file-mode .hide-in-file-mode {
display: none !important; display: none !important;
} }
@media screen and (max-width: 600px) {
body {
font-size: 70%;
}
.main-header {
overflow-x: auto;
}
.main-header__btn-wrap {
flex-shrink: 0;
}
.modal__content {
padding: 1em;
}
.saved-items-pane {
width: 77vw;
padding: 10px 20px;
}
}
/* Codemirror themes basic bg styles. This is here so that there is no big FOUC /* Codemirror themes basic bg styles. This is here so that there is no big FOUC
while the theme CSS file is loading */ while the theme CSS file is loading */
@@ -1807,3 +1790,41 @@ while the theme CSS file is loading */
.cm-s-midnight .CodeMirror-activeline-background { .cm-s-midnight .CodeMirror-activeline-background {
background: #253540; background: #253540;
} }
.command-palette__option-row {
padding: 4px 5px;
width: 100%;
text-align: left;
border: 0;
background: transparent;
color: var(--color-text);
}
.command-palette__option-row--selected {
background: rgba(0, 0, 0, 0.2);
}
.command-palette__option-subtitle {
color: var(--color-text-dark-1);
margin-left: 10px;
font-size: 0.8em;
}
@media screen and (max-width: 600px) {
body {
font-size: 70%;
}
.main-header {
overflow-x: auto;
}
.main-header__btn-wrap {
flex-shrink: 0;
}
.modal__content {
padding: 1em;
}
.saved-items-pane {
width: 77vw;
padding: 10px 20px;
}
}