Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions demo/src/components/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import Example9 from 'Example9/Example9';
import Example10 from 'Example10/Example10';
import Example11 from 'Example11/Example11';
import Example12 from 'Example12/Example12';
import Example13 from 'Example13/Example13';
import Example14 from 'Example14/Example14';

export default function App() {
return (
Expand Down Expand Up @@ -67,6 +69,12 @@ export default function App() {
<div className={styles.exampleContainer}>
<Example12 />
</div>
<div className={styles.exampleContainer}>
<Example13 />
</div>
<div className={styles.exampleContainer}>
<Example14 />
</div>
</div>
<ForkMeOnGitHub user="moroshko" repo="react-autowhatever" />
</div>
Expand Down
114 changes: 114 additions & 0 deletions demo/src/components/App/components/Example13/Example13.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import theme from '../theme.less';

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { updateInputValue, updateHighlightedItem } from '../../redux';
import Autowhatever from 'Autowhatever';
import SourceCodeLink from 'SourceCodeLink/SourceCodeLink';

const exampleId = '13';
const file = `demo/src/components/App/components/Example${exampleId}/Example${exampleId}.js`;

const items = [{
title: 'A',
items: [{
text: 'Apple'
}, {
text: 'Apricot'
}]
}, {
title: 'B',
items: [{
text: 'Banana'
}]
}, {
title: 'C',
items: [{
text: 'Cherry'
}]
}];

function renderSectionTitle(section) {
return (
<strong>{section.title}</strong>
);
}

function getSectionItems(section) {
return section.items;
}

function renderItem(item) {
return (
<span>{item.text}</span>
);
}

function mapStateToProps(state) {
return {
value: state[exampleId].value,
highlightedSectionIndex: state[exampleId].highlightedSectionIndex,
highlightedItemIndex: state[exampleId].highlightedItemIndex
};
}

function mapDispatchToProps(dispatch) {
return {
onChange: event => {
dispatch(updateInputValue(exampleId, event.target.value));
},
onKeyDown: (event, { highlightedSectionIndex, highlightedItemIndex, newHighlightedSectionIndex, newHighlightedItemIndex }) => {
switch (event.key) {
case 'ArrowDown':
case 'ArrowUp':
event.preventDefault(); // Don't move the cursor to start/end
dispatch(updateHighlightedItem(exampleId, newHighlightedSectionIndex, newHighlightedItemIndex));
break;

case 'Enter':
if (highlightedItemIndex !== null) {
dispatch(updateInputValue(exampleId, items[highlightedSectionIndex].items[highlightedItemIndex].text + ' selected'));
}
break;
}
}
};
}

function Example(props) {
const { value, highlightedSectionIndex, highlightedItemIndex, onChange, onKeyDown } = props;
const inputProps = { value, onChange, onKeyDown };

return (
<div>
<Autowhatever
id={exampleId}
multiSection={true}
isItemDisabled={function (item, index, sectionIndex) {
return index === 1 && sectionIndex === 0
}}
items={items}
renderSectionTitle={renderSectionTitle}
getSectionItems={getSectionItems}
renderItem={renderItem}
inputProps={inputProps}
highlightedSectionIndex={highlightedSectionIndex}
highlightedItemIndex={highlightedItemIndex}
theme={theme}
/>
<SourceCodeLink file={file} />
</div>
);
}

Example.propTypes = {
value: PropTypes.string.isRequired,
highlightedSectionIndex: PropTypes.number,
highlightedItemIndex: PropTypes.number,

onChange: PropTypes.func.isRequired,
onKeyDown: PropTypes.func.isRequired
};

export default connect(mapStateToProps, mapDispatchToProps)(Example);
86 changes: 86 additions & 0 deletions demo/src/components/App/components/Example14/Example14.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import theme from '../theme.less';

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { updateInputValue, updateHighlightedItem } from '../../redux';
import Autowhatever from 'Autowhatever';
import SourceCodeLink from 'SourceCodeLink/SourceCodeLink';

const exampleId = '14';
const file = `demo/src/components/App/components/Example${exampleId}/Example${exampleId}.js`;

const items = [{
text: 'Apple'
}, {
text: 'Banana'
}, {
text: 'Cherry'
}, {
text: 'Grapefruit'
}, {
text: 'Lemon'
}];

function mapStateToProps(state) {
return {
value: state[exampleId].value,
highlightedSectionIndex: state[exampleId].highlightedSectionIndex,
highlightedItemIndex: state[exampleId].highlightedItemIndex
};
}

function mapDispatchToProps(dispatch) {
return {
onChange: event => {
dispatch(updateInputValue(exampleId, event.target.value));
},
onKeyDown: (event, { newHighlightedSectionIndex, newHighlightedItemIndex }) => {
event.preventDefault(); // Don't move the cursor to start/end

if (typeof newHighlightedItemIndex !== 'undefined') {
dispatch(updateHighlightedItem(exampleId, newHighlightedSectionIndex, newHighlightedItemIndex));
}
}
};
}

function renderItem(item) {
return (
<span>{item.text}</span>
);
}

function Example(props) {
const { value, highlightedSectionIndex, highlightedItemIndex, onChange, onKeyDown } = props;
const inputProps = { value, onChange, onKeyDown };

return (
<div>
<Autowhatever
id={exampleId}
items={items}
isItemDisabled={function (item, index) {
return index === 1 || index === 3
}}
renderItem={renderItem}
inputProps={inputProps}
highlightedSectionIndex={highlightedSectionIndex}
highlightedItemIndex={highlightedItemIndex}
theme={theme}
/>
<SourceCodeLink file={file} />
</div>
);
}

Example.propTypes = {
value: PropTypes.string.isRequired,
highlightedSectionIndex: PropTypes.number,
highlightedItemIndex: PropTypes.number,

onChange: PropTypes.func.isRequired,
onKeyDown: PropTypes.func.isRequired
};

export default connect(mapStateToProps, mapDispatchToProps)(Example);
4 changes: 4 additions & 0 deletions demo/src/components/App/components/theme.less
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@
background-color: #ddd;
}

.itemDisabled {
color: #ddd;
}

.sectionContainer {
border-top: 1px dashed #ccc;
}
Expand Down
10 changes: 10 additions & 0 deletions demo/src/components/App/redux.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ const initialState = {
value: '',
highlightedSectionIndex: null,
highlightedItemIndex: null
},
13: {
value: 'Multi section - Disabled items',
highlightedSectionIndex: null,
highlightedItemIndex: null
},
14: {
value: 'Disabled items',
highlightedSectionIndex: null,
highlightedItemIndex: null
}
};

Expand Down
45 changes: 42 additions & 3 deletions src/Autowhatever.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const defaultTheme = {
item: 'react-autowhatever__item',
itemFirst: 'react-autowhatever__item--first',
itemHighlighted: 'react-autowhatever__item--highlighted',
itemDisabled: 'react-autowhatever__item--disabled',
sectionContainer: 'react-autowhatever__section-container',
sectionContainerFirst: 'react-autowhatever__section-container--first',
sectionTitle: 'react-autowhatever__section-title'
Expand All @@ -44,6 +45,7 @@ export default class Autowhatever extends Component {
]),
highlightedSectionIndex: PropTypes.number, // Section index of the highlighted item
highlightedItemIndex: PropTypes.number, // Highlighted item index (within a section)
isItemDisabled: PropTypes.func,
theme: PropTypes.oneOfType([ // Styles. See: https://github.com/markdalgleish/react-themeable
PropTypes.object,
PropTypes.array
Expand All @@ -69,6 +71,7 @@ export default class Autowhatever extends Component {
itemProps: emptyObject,
highlightedSectionIndex: null,
highlightedItemIndex: null,
isItemDisabled: () => false,
theme: defaultTheme
};

Expand Down Expand Up @@ -162,7 +165,8 @@ export default class Autowhatever extends Component {
const { theme } = this;
const {
id, items, renderItem, renderItemData, renderSectionTitle,
highlightedSectionIndex, highlightedItemIndex, itemProps
highlightedSectionIndex, highlightedItemIndex, itemProps,
isItemDisabled
} = this.props;

return items.map((section, sectionIndex) => {
Expand All @@ -187,6 +191,9 @@ export default class Autowhatever extends Component {
renderItemData={renderItemData}
sectionIndex={sectionIndex}
highlightedItemIndex={highlightedSectionIndex === sectionIndex ? highlightedItemIndex : null}
isItemDisabled={function(item, itemIndex) {
return isItemDisabled(item, itemIndex, sectionIndex);
}}
onHighlightedItemChange={this.onHighlightedItemChange}
getItemId={this.getItemId}
theme={theme}
Expand All @@ -209,7 +216,7 @@ export default class Autowhatever extends Component {
const { theme } = this;
const {
id, renderItem, renderItemData, highlightedSectionIndex,
highlightedItemIndex, itemProps
highlightedItemIndex, itemProps, isItemDisabled
} = this.props;

return (
Expand All @@ -219,6 +226,9 @@ export default class Autowhatever extends Component {
renderItem={renderItem}
renderItemData={renderItemData}
highlightedItemIndex={highlightedSectionIndex === null ? highlightedItemIndex : null}
isItemDisabled={function(item, itemIndex) {
return isItemDisabled(item, itemIndex, null);
}}
onHighlightedItemChange={this.onHighlightedItemChange}
getItemId={this.getItemId}
theme={theme}
Expand Down Expand Up @@ -255,7 +265,7 @@ export default class Autowhatever extends Component {
case 'ArrowUp': {
const nextPrev = (event.key === 'ArrowDown' ? 'next' : 'prev');
const [newHighlightedSectionIndex, newHighlightedItemIndex] =
this.sectionIterator[nextPrev]([highlightedSectionIndex, highlightedItemIndex]);
this.closestNotDisabled(nextPrev, highlightedSectionIndex, highlightedItemIndex);

inputProps.onKeyDown(event, { newHighlightedSectionIndex, newHighlightedItemIndex });
break;
Expand All @@ -266,6 +276,35 @@ export default class Autowhatever extends Component {
}
};

closestNotDisabled = (direction, highlightedSectionIndex, highlightedItemIndex) => {
const { multiSection, getSectionItems, isItemDisabled, items } = this.props;
let [newHighlightedSectionIndex, newHighlightedItemIndex] =
this.sectionIterator[direction]([highlightedSectionIndex, highlightedItemIndex]);

for (; newHighlightedItemIndex !== null;
[newHighlightedSectionIndex, newHighlightedItemIndex] =
this.sectionIterator[direction](
[newHighlightedSectionIndex, newHighlightedItemIndex]
)
) {
let highlightedItem;

if (multiSection) {
const sectionItems = getSectionItems(items[newHighlightedSectionIndex]);

highlightedItem = sectionItems[newHighlightedItemIndex];
} else {
highlightedItem = items[newHighlightedItemIndex];
}

if (!isItemDisabled(highlightedItem, newHighlightedItemIndex, newHighlightedSectionIndex)) {
break;
}
}

return [newHighlightedSectionIndex, newHighlightedItemIndex];
}

ensureHighlightedItemIsVisible() {
const { highlightedItem } = this;

Expand Down
Loading