Commit 20134c90 by Johannes Schill

Add keyboard navigation to datasource picker via a hoc.

parent 1ffac5a3
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import _ from 'lodash'; import _ from 'lodash';
import withKeyboardNavigation from './withKeyboardNavigation';
import { DataSourceSelectItem } from 'app/types'; import { DataSourceSelectItem } from 'app/types';
interface Props { export interface Props {
onChangeDataSource: (ds: any) => void; onChangeDataSource: (ds: any) => void;
datasources: DataSourceSelectItem[]; datasources: DataSourceSelectItem[];
selected?: number;
onKeyDown?: (evt: any, maxSelectedIndex: number, onEnterAction: () => void) => void;
onMouseEnter?: (select: number) => void;
} }
interface State { interface State {
searchQuery: string; searchQuery: string;
} }
export class DataSourcePicker extends PureComponent<Props, State> { export const DataSourcePicker = withKeyboardNavigation(
class DataSourcePicker extends PureComponent<Props, State> {
searchInput: HTMLElement; searchInput: HTMLElement;
constructor(props) { constructor(props) {
...@@ -35,15 +39,27 @@ export class DataSourcePicker extends PureComponent<Props, State> { ...@@ -35,15 +39,27 @@ export class DataSourcePicker extends PureComponent<Props, State> {
return filtered; return filtered;
} }
get maxSelectedIndex() {
const filtered = this.getDataSources();
return filtered.length - 1;
}
renderDataSource = (ds: DataSourceSelectItem, index: number) => { renderDataSource = (ds: DataSourceSelectItem, index: number) => {
const { onChangeDataSource } = this.props; const { onChangeDataSource, selected, onMouseEnter } = this.props;
const onClick = () => onChangeDataSource(ds); const onClick = () => onChangeDataSource(ds);
const isSelected = selected === index;
const cssClass = classNames({ const cssClass = classNames({
'ds-picker-list__item': true, 'ds-picker-list__item': true,
'ds-picker-list__item--selected': isSelected,
}); });
return ( return (
<div key={index} className={cssClass} title={ds.name} onClick={onClick}> <div
key={index}
className={cssClass}
title={ds.name}
onClick={onClick}
onMouseEnter={() => onMouseEnter(index)}
>
<img className="ds-picker-list__img" src={ds.meta.info.logos.small} /> <img className="ds-picker-list__img" src={ds.meta.info.logos.small} />
<div className="ds-picker-list__name">{ds.name}</div> <div className="ds-picker-list__name">{ds.name}</div>
</div> </div>
...@@ -66,6 +82,7 @@ export class DataSourcePicker extends PureComponent<Props, State> { ...@@ -66,6 +82,7 @@ export class DataSourcePicker extends PureComponent<Props, State> {
renderFilters() { renderFilters() {
const { searchQuery } = this.state; const { searchQuery } = this.state;
const { onKeyDown } = this.props;
return ( return (
<> <>
<label className="gf-form--has-input-icon"> <label className="gf-form--has-input-icon">
...@@ -76,6 +93,13 @@ export class DataSourcePicker extends PureComponent<Props, State> { ...@@ -76,6 +93,13 @@ export class DataSourcePicker extends PureComponent<Props, State> {
ref={elem => (this.searchInput = elem)} ref={elem => (this.searchInput = elem)}
onChange={this.onSearchQueryChange} onChange={this.onSearchQueryChange}
value={searchQuery} value={searchQuery}
onKeyDown={evt => {
onKeyDown(evt, this.maxSelectedIndex, () => {
const { onChangeDataSource, selected } = this.props;
const ds = this.getDataSources()[selected];
onChangeDataSource(ds);
});
}}
/> />
<i className="gf-form-input-icon fa fa-search" /> <i className="gf-form-input-icon fa fa-search" />
</label> </label>
...@@ -94,4 +118,7 @@ export class DataSourcePicker extends PureComponent<Props, State> { ...@@ -94,4 +118,7 @@ export class DataSourcePicker extends PureComponent<Props, State> {
</> </>
); );
} }
} }
);
export default DataSourcePicker;
import React from 'react';
import { Props } from './DataSourcePicker';
interface State {
selected: number;
}
const withKeyboardNavigation = WrappedComponent => {
return class extends React.Component<Props, State> {
constructor(props) {
super(props);
this.state = {
selected: 0,
};
}
goToNext = (maxSelectedIndex: number) => {
const nextIndex = this.state.selected >= maxSelectedIndex ? 0 : this.state.selected + 1;
this.setState({
selected: nextIndex,
});
};
goToPrev = (maxSelectedIndex: number) => {
const nextIndex = this.state.selected <= 0 ? maxSelectedIndex : this.state.selected - 1;
this.setState({
selected: nextIndex,
});
};
onKeyDown = (evt: KeyboardEvent, maxSelectedIndex: number, onEnterAction: any) => {
if (evt.key === 'ArrowDown') {
evt.preventDefault();
this.goToNext(maxSelectedIndex);
}
if (evt.key === 'ArrowUp') {
evt.preventDefault();
this.goToPrev(maxSelectedIndex);
}
if (evt.key === 'Enter' && onEnterAction) {
onEnterAction();
}
};
onMouseEnter = (mouseEnterIndex: number) => {
this.setState({
selected: mouseEnterIndex,
});
};
render() {
return (
<WrappedComponent
selected={this.state.selected}
onKeyDown={this.onKeyDown}
onMouseEnter={this.onMouseEnter}
{...this.props}
/>
);
}
};
};
export default withKeyboardNavigation;
...@@ -257,13 +257,13 @@ ...@@ -257,13 +257,13 @@
align-items: center; align-items: center;
height: 44px; height: 44px;
&:hover { &--selected {
background: $panel-editor-viz-item-bg-hover; background: $panel-editor-viz-item-bg-hover;
border: $panel-editor-viz-item-border-hover; border: $panel-editor-viz-item-border-hover;
box-shadow: $panel-editor-viz-item-shadow-hover; box-shadow: $panel-editor-viz-item-shadow-hover;
} }
&--selected { &--active {
box-shadow: 0 0 6px $orange; box-shadow: 0 0 6px $orange;
border: 1px solid $orange; border: 1px solid $orange;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment