Skip to content
This repository has been archived by the owner on Feb 7, 2019. It is now read-only.

Commit

Permalink
Automatically focus the filter input when the page loads; resolves #573
Browse files Browse the repository at this point in the history
  • Loading branch information
Jim Porter committed Mar 1, 2018
1 parent dc46a1a commit ff4e80c
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 52 deletions.
9 changes: 7 additions & 2 deletions src/webextension/list/containers/item-filter.js
Expand Up @@ -3,20 +3,25 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Localized } from "fluent-react";
import PropTypes from "prop-types";
import React from "react";
import { connect } from "react-redux";

import { filterItems } from "../actions";
import FilterInput from "../../widgets/filter-input";

function ItemFilter(props) {
function ItemFilter({inputRef, ...props}) {
return (
<Localized id="item-filter">
<FilterInput placeholder="fILTEr…" {...props}/>
<FilterInput placeholder="fILTEr…" ref={inputRef} {...props}/>
</Localized>
);
}

ItemFilter.propTypes = {
inputRef: PropTypes.func,
};

export default connect(
(state) => ({
value: state.filter,
Expand Down
63 changes: 36 additions & 27 deletions src/webextension/list/manage/components/app.js
Expand Up @@ -19,31 +19,40 @@ import Toolbar, { ToolbarSpace } from "../../../widgets/toolbar";

import styles from "./app.css";

export default function App() {
return (
<Localized id="document">
<DocumentTitle title="lOCKBOx eNTRIEs">
<div className={styles.app}>
<section className={styles.appMain}>
<Toolbar className={styles.navigation}>
<AddItem/>
<GoHome/>
<ToolbarSpace/>
<OpenFAQ/>
<SendFeedback/>
<CurrentAccountSummary/>
</Toolbar>
<aside>
<ItemFilter className={styles.filter}/>
<AllItems/>
</aside>
<article>
<CurrentSelection/>
</article>
</section>
<ModalRoot/>
</div>
</DocumentTitle>
</Localized>
);
export default class App extends React.Component {
componentDidMount() {
this._filterField.focus();
}

render() {
return (
<Localized id="document">
<DocumentTitle title="lOCKBOx eNTRIEs">
<div className={styles.app}>
<section className={styles.appMain}>
<Toolbar className={styles.navigation}>
<AddItem/>
<GoHome/>
<ToolbarSpace/>
<OpenFAQ/>
<SendFeedback/>
<CurrentAccountSummary/>
</Toolbar>
<aside>
<ItemFilter className={styles.filter}
inputRef={(element) => {
this._filterField = element;
}}/>
<AllItems/>
</aside>
<article>
<CurrentSelection/>
</article>
</section>
<ModalRoot/>
</div>
</DocumentTitle>
</Localized>
);
}
}
28 changes: 18 additions & 10 deletions src/webextension/list/popup/components/app.js
Expand Up @@ -10,14 +10,22 @@ import CurrentSelection from "../containers/current-selection";

import styles from "./app.css";

export default function App() {
return (
<Localized id="document">
<DocumentTitle title="lOCKBOx eNTRIEs">
<div className={styles.app}>
<CurrentSelection/>
</div>
</DocumentTitle>
</Localized>
);
export default class App extends React.Component {
componentDidMount() {
this._filterField.focus();
}

render() {
return (
<Localized id="document">
<DocumentTitle title="lOCKBOx eNTRIEs">
<div className={styles.app}>
<CurrentSelection inputRef={(element) => {
this._filterField = element;
}}/>
</div>
</DocumentTitle>
</Localized>
);
}
}
9 changes: 7 additions & 2 deletions src/webextension/list/popup/components/item-list-panel.js
Expand Up @@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Localized } from "fluent-react";
import PropTypes from "prop-types";
import React from "react";

import Button from "../../../widgets/button";
Expand All @@ -11,7 +12,7 @@ import Panel, { PanelHeader, PanelBody, PanelFooter } from
import ItemFilter from "../../containers/item-filter";
import AllItems from "../containers/all-items";

export default function ItemListPanel() {
export default function ItemListPanel({inputRef}) {
const openManager = () => {
browser.runtime.sendMessage({
type: "open_view",
Expand All @@ -23,7 +24,7 @@ export default function ItemListPanel() {
return (
<Panel>
<PanelHeader>
<ItemFilter/>
<ItemFilter inputRef={inputRef}/>
</PanelHeader>

<PanelBody>
Expand All @@ -40,3 +41,7 @@ export default function ItemListPanel() {
</Panel>
);
}

ItemListPanel.propTypes = {
inputRef: PropTypes.func,
};
5 changes: 3 additions & 2 deletions src/webextension/list/popup/containers/current-selection.js
Expand Up @@ -21,15 +21,16 @@ const ConnectedItemDetailsPanel = connect(
})
)(ItemDetailsPanel);

function CurrentSelection({item}) {
function CurrentSelection({item, inputRef}) {
if (item) {
return <ConnectedItemDetailsPanel item={item}/>;
}
return <ItemListPanel/>;
return <ItemListPanel inputRef={inputRef}/>;
}

CurrentSelection.propTypes = {
item: PropTypes.object,
inputRef: PropTypes.func,
};

export default connect(
Expand Down
10 changes: 7 additions & 3 deletions src/webextension/widgets/filter-input.js
Expand Up @@ -43,6 +43,10 @@ export default class FilterInput extends React.Component {
}
}

focus() {
this.inputElement.focus();
}

render() {
// eslint-disable-next-line no-unused-vars
const {className, onChange, value, disabled, ...props} = this.props;
Expand All @@ -53,9 +57,9 @@ export default class FilterInput extends React.Component {

return (
<div className={finalClassName}>
<input type="search" {...props} disabled={disabled}
value={this.state.value}
onChange={(e) => this.updateValue(e.target.value)}/>
<input type="search" disabled={disabled} value={this.state.value}
onChange={(e) => this.updateValue(e.target.value)}
ref={(element) => this.inputElement = element} {...props}/>
<Localized id="filter-input-clear">
<button type="button" disabled={disabled}
onClick={() => this.updateValue("")}>
Expand Down
17 changes: 15 additions & 2 deletions test/unit/list/manage/components/app-test.js
Expand Up @@ -9,8 +9,9 @@ import React from "react";
import configureStore from "redux-mock-store";
import { Provider } from "react-redux";

import { initialState } from "../mock-redux-state";
import mountWithL10n from "test/mocks/l10n";
import { initialState, filledState } from "../mock-redux-state";
import chaiFocus from "test/chai-focus";
import mountWithL10n, { mountWithL10nIntoDOM } from "test/mocks/l10n";
import App from "src/webextension/list/manage/components/app";
import AddItem from "src/webextension/list/manage/containers/add-item";
import AllItems from "src/webextension/list/manage/containers/all-items";
Expand All @@ -19,6 +20,7 @@ import CurrentSelection from
import ModalRootWidget from "src/webextension/widgets/modal-root";

chai.use(chaiEnzyme());
chai.use(chaiFocus);

const middlewares = [];
const mockStore = configureStore(middlewares);
Expand Down Expand Up @@ -58,4 +60,15 @@ describe("list > manage > components > <App/>", () => {
expect(wrapper).to.contain(<AllItems/>);
expect(wrapper).to.contain(<CurrentSelection/>);
});

it("filter input focused", () => {
const store = mockStore(filledState);
const wrapper = mountWithL10nIntoDOM(
<Provider store={store}>
<App/>
</Provider>
);

expect(wrapper.find("input")).to.be.focused();
});
});
22 changes: 19 additions & 3 deletions test/unit/list/popup/components/app-test.js
Expand Up @@ -8,13 +8,15 @@ import React from "react";
import configureStore from "redux-mock-store";
import { Provider } from "react-redux";

import { initialState } from "../mock-redux-state";
import mountWithL10n from "test/mocks/l10n";
import { initialState, filledState } from "../mock-redux-state";
import chaiFocus from "test/chai-focus";
import mountWithL10n, { mountWithL10nIntoDOM } from "test/mocks/l10n";
import App from "src/webextension/list/popup/components/app";
import CurrentSelection from
"src/webextension/list/popup/containers/current-selection";

chai.use(chaiEnzyme());
chai.use(chaiFocus);

const middlewares = [];
const mockStore = configureStore(middlewares);
Expand All @@ -28,6 +30,20 @@ describe("list > popup > components > <App/>", () => {
</Provider>
);

expect(wrapper).to.contain(<CurrentSelection/>);
expect(wrapper).to.have.descendants(CurrentSelection);
});

it("filter input focused", () => {
const store = mockStore({
cache: {...filledState.cache, currentItem: null},
list: {...filledState.list, selectedItemid: null},
});
const wrapper = mountWithL10nIntoDOM(
<Provider store={store}>
<App/>
</Provider>
);

expect(wrapper.find("input")).to.be.focused();
});
});
12 changes: 11 additions & 1 deletion test/unit/widgets/filter-input-test.js
Expand Up @@ -8,11 +8,13 @@ import React from "react";
import sinon from "sinon";
import sinonChai from "sinon-chai";

import chaiFocus from "test/chai-focus";
import { simulateTyping } from "test/common";
import mountWithL10n from "test/mocks/l10n";
import mountWithL10n, { mountWithL10nIntoDOM } from "test/mocks/l10n";
import FilterInput from "src/webextension/widgets/filter-input";

chai.use(chaiEnzyme());
chai.use(chaiFocus);
chai.use(sinonChai);

describe("widgets > <FilterInput/>", () => {
Expand Down Expand Up @@ -76,5 +78,13 @@ describe("widgets > <FilterInput/>", () => {

expect(onChange).to.have.callCount(0);
});

it("focus() focuses input", () => {
const wrapper = mountWithL10nIntoDOM(
<FilterInput onChange={() => {}}/>
);
wrapper.instance().focus();
expect(wrapper.find("input")).to.be.focused();
});
});

0 comments on commit ff4e80c

Please sign in to comment.