Commit a0da303f by Johannes Schill

Change KeyboardNavigation from hoc to render prop component

parent 07ce88f6
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, { KeyboardNavigationProps } from './withKeyboardNavigation'; import KeyboardNavigation, { KeyboardNavigationProps } from './KeyboardNavigation';
import { DataSourceSelectItem } from 'app/types'; import { DataSourceSelectItem } from 'app/types';
export interface Props { export interface Props {
...@@ -13,109 +13,105 @@ interface State { ...@@ -13,109 +13,105 @@ interface State {
searchQuery: string; searchQuery: string;
} }
export const DataSourcePicker = withKeyboardNavigation( export class DataSourcePicker extends PureComponent<Props, State> {
class DataSourcePicker extends PureComponent<Props & KeyboardNavigationProps, State> { searchInput: HTMLElement;
searchInput: HTMLElement;
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
searchQuery: '', searchQuery: '',
}; };
} }
getDataSources() { getDataSources() {
const { searchQuery } = this.state; const { searchQuery } = this.state;
const regex = new RegExp(searchQuery, 'i'); const regex = new RegExp(searchQuery, 'i');
const { datasources } = this.props; const { datasources } = this.props;
const filtered = datasources.filter(item => { const filtered = datasources.filter(item => {
return regex.test(item.name) || regex.test(item.meta.name); return regex.test(item.name) || regex.test(item.meta.name);
}); });
return filtered; return filtered;
} }
get maxSelectedIndex() { get maxSelectedIndex() {
const filtered = this.getDataSources(); const filtered = this.getDataSources();
return filtered.length - 1; return filtered.length - 1;
} }
renderDataSource = (ds: DataSourceSelectItem, index: number) => { renderDataSource = (ds: DataSourceSelectItem, index: number, keyNavProps: KeyboardNavigationProps) => {
const { onChangeDataSource, selected, onMouseEnter } = this.props; const { onChangeDataSource } = this.props;
const onClick = () => onChangeDataSource(ds); const { selected, onMouseEnter } = keyNavProps;
const isSelected = selected === index; const onClick = () => onChangeDataSource(ds);
const cssClass = classNames({ const isSelected = selected === index;
'ds-picker-list__item': true, const cssClass = classNames({
'ds-picker-list__item--selected': isSelected, 'ds-picker-list__item': true,
}); 'ds-picker-list__item--selected': isSelected,
return ( });
<div return (
key={index} <div key={index} className={cssClass} title={ds.name} onClick={onClick} onMouseEnter={() => onMouseEnter(index)}>
className={cssClass} <img className="ds-picker-list__img" src={ds.meta.info.logos.small} />
title={ds.name} <div className="ds-picker-list__name">{ds.name}</div>
onClick={onClick} </div>
onMouseEnter={() => onMouseEnter(index)} );
> };
<img className="ds-picker-list__img" src={ds.meta.info.logos.small} />
<div className="ds-picker-list__name">{ds.name}</div>
</div>
);
};
componentDidMount() { componentDidMount() {
setTimeout(() => { setTimeout(() => {
this.searchInput.focus(); this.searchInput.focus();
}, 300); }, 300);
} }
onSearchQueryChange = evt => { onSearchQueryChange = evt => {
const value = evt.target.value; const value = evt.target.value;
this.setState(prevState => ({ this.setState(prevState => ({
...prevState, ...prevState,
searchQuery: value, searchQuery: value,
})); }));
}; };
renderFilters() { renderFilters({ onKeyDown, selected }: KeyboardNavigationProps) {
const { searchQuery } = this.state; const { searchQuery } = this.state;
const { onKeyDown } = this.props; return (
return ( <label className="gf-form--has-input-icon">
<> <input
<label className="gf-form--has-input-icon"> type="text"
<input className="gf-form-input width-13"
type="text" placeholder=""
className="gf-form-input width-13" ref={elem => (this.searchInput = elem)}
placeholder="" onChange={this.onSearchQueryChange}
ref={elem => (this.searchInput = elem)} value={searchQuery}
onChange={this.onSearchQueryChange} onKeyDown={evt => {
value={searchQuery} onKeyDown(evt, this.maxSelectedIndex, () => {
onKeyDown={evt => { const { onChangeDataSource } = this.props;
onKeyDown(evt, this.maxSelectedIndex, () => { const ds = this.getDataSources()[selected];
const { onChangeDataSource, selected } = this.props; onChangeDataSource(ds);
const ds = this.getDataSources()[selected]; });
onChangeDataSource(ds); }}
}); />
}} <i className="gf-form-input-icon fa fa-search" />
/> </label>
<i className="gf-form-input-icon fa fa-search" /> );
</label> }
</>
);
}
render() { render() {
return ( return (
<> <KeyboardNavigation
<div className="cta-form__bar"> render={(keyNavProps: KeyboardNavigationProps) => (
{this.renderFilters()} <>
<div className="gf-form--grow" /> <div className="cta-form__bar">
</div> {this.renderFilters(keyNavProps)}
<div className="ds-picker-list">{this.getDataSources().map(this.renderDataSource)}</div> <div className="gf-form--grow" />
</> </div>
); <div className="ds-picker-list">
} {this.getDataSources().map((ds, index) => this.renderDataSource(ds, index, keyNavProps))}
</div>
</>
)}
/>
);
} }
); }
export default DataSourcePicker; export default DataSourcePicker;
import React, { KeyboardEvent, ComponentType, Component } from 'react'; import React, { KeyboardEvent, Component } from 'react';
interface State { interface State {
selected: number; selected: number;
...@@ -10,56 +10,62 @@ export interface KeyboardNavigationProps { ...@@ -10,56 +10,62 @@ export interface KeyboardNavigationProps {
selected: number; selected: number;
} }
const withKeyboardNavigation = <P extends object>(WrappedComponent: ComponentType<P & KeyboardNavigationProps>) => { interface Props {
return class WithKeyboardNavigation extends Component<P, State> { render: (injectProps: any) => void;
constructor(props) { }
super(props);
this.state = {
selected: 0,
};
}
goToNext = (maxSelectedIndex: number) => { class KeyboardNavigation extends Component<Props, State> {
const nextIndex = this.state.selected >= maxSelectedIndex ? 0 : this.state.selected + 1; constructor(props) {
this.setState({ super(props);
selected: nextIndex,
});
};
goToPrev = (maxSelectedIndex: number) => { this.state = {
const nextIndex = this.state.selected <= 0 ? maxSelectedIndex : this.state.selected - 1; selected: 0,
this.setState({
selected: nextIndex,
});
}; };
}
onKeyDown = (evt: KeyboardEvent, maxSelectedIndex: number, onEnterAction: any) => { goToNext = (maxSelectedIndex: number) => {
if (evt.key === 'ArrowDown') { const nextIndex = this.state.selected >= maxSelectedIndex ? 0 : this.state.selected + 1;
evt.preventDefault(); this.setState({
this.goToNext(maxSelectedIndex); selected: nextIndex,
} });
if (evt.key === 'ArrowUp') { };
evt.preventDefault();
this.goToPrev(maxSelectedIndex);
}
if (evt.key === 'Enter' && onEnterAction) {
onEnterAction();
}
};
onMouseEnter = (mouseEnterIndex: number) => { goToPrev = (maxSelectedIndex: number) => {
this.setState({ const nextIndex = this.state.selected <= 0 ? maxSelectedIndex : this.state.selected - 1;
selected: mouseEnterIndex, this.setState({
}); selected: nextIndex,
}; });
};
render() { onKeyDown = (evt: KeyboardEvent, maxSelectedIndex: number, onEnterAction: any) => {
return ( if (evt.key === 'ArrowDown') {
<WrappedComponent {...this.state} {...this.props} onKeyDown={this.onKeyDown} onMouseEnter={this.onMouseEnter} /> 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,
});
}; };
};
export default withKeyboardNavigation; render() {
const injectProps = {
onKeyDown: this.onKeyDown,
onMouseEnter: this.onMouseEnter,
selected: this.state.selected,
};
return <>{this.props.render({ ...injectProps })}</>;
}
}
export default KeyboardNavigation;
...@@ -4,7 +4,7 @@ import _ from 'lodash'; ...@@ -4,7 +4,7 @@ import _ from 'lodash';
import config from 'app/core/config'; import config from 'app/core/config';
import { PanelPlugin } from 'app/types/plugins'; import { PanelPlugin } from 'app/types/plugins';
import VizTypePickerPlugin from './VizTypePickerPlugin'; import VizTypePickerPlugin from './VizTypePickerPlugin';
import withKeyboardNavigation, { KeyboardNavigationProps } from './withKeyboardNavigation'; import KeyboardNavigation, { KeyboardNavigationProps } from './KeyboardNavigation';
export interface Props { export interface Props {
current: PanelPlugin; current: PanelPlugin;
...@@ -15,118 +15,121 @@ interface State { ...@@ -15,118 +15,121 @@ interface State {
searchQuery: string; searchQuery: string;
} }
export const VizTypePicker = withKeyboardNavigation( export class VizTypePicker extends PureComponent<Props, State> {
class VizTypePicker extends PureComponent<Props & KeyboardNavigationProps, State> { searchInput: HTMLElement;
searchInput: HTMLElement; pluginList = this.getPanelPlugins('');
pluginList = this.getPanelPlugins('');
constructor(props) {
super(props);
this.state = {
searchQuery: '',
};
}
get maxSelectedIndex() {
const filteredPluginList = this.getFilteredPluginList();
return filteredPluginList.length - 1;
}
componentDidMount() {
setTimeout(() => {
this.searchInput.focus();
}, 300);
}
getPanelPlugins(filter): PanelPlugin[] {
const panels = _.chain(config.panels)
.filter({ hideFromList: false })
.map(item => item)
.value();
// add sort by sort property
return _.sortBy(panels, 'sort');
}
renderVizPlugin = (plugin: PanelPlugin, index: number) => {
const { onTypeChanged, selected, onMouseEnter } = this.props;
const isSelected = selected === index;
const isCurrent = plugin.id === this.props.current.id;
return (
<VizTypePickerPlugin
key={plugin.id}
isSelected={isSelected}
isCurrent={isCurrent}
plugin={plugin}
onMouseEnter={() => {
onMouseEnter(index);
}}
onClick={() => onTypeChanged(plugin)}
/>
);
};
getFilteredPluginList = (): PanelPlugin[] => {
const { searchQuery } = this.state;
const regex = new RegExp(searchQuery, 'i');
const pluginList = this.pluginList;
const filtered = pluginList.filter(item => { constructor(props) {
return regex.test(item.name); super(props);
});
return filtered; this.state = {
searchQuery: '',
}; };
}
onSearchQueryChange = evt => { get maxSelectedIndex() {
const value = evt.target.value; const filteredPluginList = this.getFilteredPluginList();
this.setState(prevState => ({ return filteredPluginList.length - 1;
...prevState, }
searchQuery: value,
}));
};
renderFilters = () => { componentDidMount() {
const { searchQuery } = this.state; setTimeout(() => {
const { onKeyDown } = this.props; this.searchInput.focus();
return ( }, 300);
<> }
<label className="gf-form--has-input-icon">
<input getPanelPlugins(filter): PanelPlugin[] {
type="text" const panels = _.chain(config.panels)
className="gf-form-input width-13" .filter({ hideFromList: false })
placeholder="" .map(item => item)
ref={elem => (this.searchInput = elem)} .value();
onChange={this.onSearchQueryChange}
value={searchQuery}
// onKeyDown={this.props.onKeyDown}
onKeyDown={evt => {
onKeyDown(evt, this.maxSelectedIndex, () => {
const { onTypeChanged, selected } = this.props;
const vizType = this.getFilteredPluginList()[selected];
onTypeChanged(vizType);
});
}}
/>
<i className="gf-form-input-icon fa fa-search" />
</label>
</>
);
};
render() { // add sort by sort property
const filteredPluginList = this.getFilteredPluginList(); return _.sortBy(panels, 'sort');
return (
<>
<div className="cta-form__bar">
{this.renderFilters()}
<div className="gf-form--grow" />
</div>
<div className="viz-picker">{filteredPluginList.map(this.renderVizPlugin)}</div>
</>
);
}
} }
);
renderVizPlugin = (plugin: PanelPlugin, index: number, keyNavProps: KeyboardNavigationProps) => {
const { onTypeChanged } = this.props;
const { selected, onMouseEnter } = keyNavProps;
const isSelected = selected === index;
const isCurrent = plugin.id === this.props.current.id;
return (
<VizTypePickerPlugin
key={plugin.id}
isSelected={isSelected}
isCurrent={isCurrent}
plugin={plugin}
onMouseEnter={() => {
onMouseEnter(index);
}}
onClick={() => onTypeChanged(plugin)}
/>
);
};
getFilteredPluginList = (): PanelPlugin[] => {
const { searchQuery } = this.state;
const regex = new RegExp(searchQuery, 'i');
const pluginList = this.pluginList;
const filtered = pluginList.filter(item => {
return regex.test(item.name);
});
return filtered;
};
onSearchQueryChange = evt => {
const value = evt.target.value;
this.setState(prevState => ({
...prevState,
searchQuery: value,
}));
};
renderFilters = ({ onKeyDown, selected }: KeyboardNavigationProps) => {
const { searchQuery } = this.state;
return (
<>
<label className="gf-form--has-input-icon">
<input
type="text"
className="gf-form-input width-13"
placeholder=""
ref={elem => (this.searchInput = elem)}
onChange={this.onSearchQueryChange}
value={searchQuery}
onKeyDown={evt => {
onKeyDown(evt, this.maxSelectedIndex, () => {
const { onTypeChanged } = this.props;
const vizType = this.getFilteredPluginList()[selected];
onTypeChanged(vizType);
});
}}
/>
<i className="gf-form-input-icon fa fa-search" />
</label>
</>
);
};
render() {
const filteredPluginList = this.getFilteredPluginList();
return (
<KeyboardNavigation
render={(keyNavProps: KeyboardNavigationProps) => (
<>
<div className="cta-form__bar">
{this.renderFilters(keyNavProps)}
<div className="gf-form--grow" />
</div>
<div className="viz-picker">
{filteredPluginList.map((plugin, index) => this.renderVizPlugin(plugin, index, keyNavProps))}
</div>
</>
)}
/>
);
}
}
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