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:
@@ -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)}
|
||||||
/>
|
/>
|
||||||
|
@@ -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>
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user