Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

renderSectionContainer #789

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ class Example extends React.Component {
| [`getSectionSuggestions`](#get-section-suggestions-prop) | Function | ✓<br>when `multiSection={true}` | Implement it to teach Autosuggest where to find the suggestions for every section. |
| [`renderInputComponent`](#render-input-component-prop) | Function | | Use it only if you need to customize the rendering of the input. |
| [`renderSuggestionsContainer`](#render-suggestions-container-prop) | Function | | Use it if you want to customize things inside the suggestions container beyond rendering the suggestions themselves. |
| [`renderSectionContainer`](#render-sections-container-prop) | Function | | Use it if you want to customize things inside the sections container beyond rendering the sections themselves. |
| [`theme`](#theme-prop) | Object | | Use your imagination to style the Autosuggest. |
| [`id`](#id-prop) | String | | Use it only if you have multiple Autosuggest components on a page. |

Expand Down Expand Up @@ -602,6 +603,39 @@ function renderSuggestionsContainer({ containerProps, children }) {
}
```

<a name="render-sections-container-prop"></a>

#### renderSectionContainer (optional)

You shouldn't specify `renderSectionContainer` unless you want to customize the content or behaviour of the sections container beyond rendering the sections themselves. For example, you might want to add a custom text before/after the sections list.

The signature is:

```js
function renderSectionContainer({ containerProps, children, query })
```

where:

- `containerProps` - props that you MUST pass to the topmost element that is returned from `renderSectionContainer`.
- `children` - the sections themselves. It's up to you where to render them.
- `query` - Same as `query` in [`renderSuggestion`](#render-suggestion-prop).

For example:

```js
function renderSectionContainer({ containerProps, children, query }) {
return (
<div {...containerProps}>
{children}
<div>
Press Enter to search <strong>{query}</strong>
</div>
</div>
);
}
```

<a name="theme-prop"></a>

#### theme (optional)
Expand Down
17 changes: 17 additions & 0 deletions src/Autosuggest.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ const defaultShouldRenderSuggestions = (value) => value.trim().length > 0;
const defaultRenderSuggestionsContainer = ({ containerProps, children }) => (
<div {...containerProps}>{children}</div>
);
const defaultRenderSectionContainer = ({ containerProps, children }) => (
<div {...containerProps}>{children}</div>
);

const REASON_SUGGESTIONS_REVEALED = 'suggestions-revealed';
const REASON_SUGGESTIONS_UPDATED = 'suggestions-updated';
Expand Down Expand Up @@ -47,6 +50,7 @@ export default class Autosuggest extends Component {
onSuggestionHighlighted: PropTypes.func,
renderInputComponent: PropTypes.func,
renderSuggestionsContainer: PropTypes.func,
renderSectionContainer: PropTypes.func,
getSuggestionValue: PropTypes.func.isRequired,
renderSuggestion: PropTypes.func.isRequired,
inputProps: (props, propName) => {
Expand Down Expand Up @@ -100,6 +104,7 @@ export default class Autosuggest extends Component {

static defaultProps = {
renderSuggestionsContainer: defaultRenderSuggestionsContainer,
renderSectionContainer: defaultRenderSectionContainer,
shouldRenderSuggestions: defaultShouldRenderSuggestions,
alwaysRenderSuggestions: false,
multiSection: false,
Expand Down Expand Up @@ -545,6 +550,17 @@ export default class Autosuggest extends Component {
});
};

renderSectionContainer = ({ containerProps, children, section }) => {
const { renderSectionContainer } = this.props;

return renderSectionContainer({
containerProps,
children,
query: this.getQuery(),
section,
});
};

render() {
const {
suggestions,
Expand Down Expand Up @@ -803,6 +819,7 @@ export default class Autosuggest extends Component {
items={items}
renderInputComponent={renderInputComponent}
renderItemsContainer={this.renderSuggestionsContainer}
renderSectionContainer={this.renderSectionContainer}
renderItem={renderSuggestion}
renderItemData={renderSuggestionData}
renderSectionTitle={renderSectionTitle}
Expand Down
76 changes: 41 additions & 35 deletions src/Autowhatever.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const defaultRenderInputComponent = (props) => <input {...props} />;
const defaultRenderItemsContainer = ({ containerProps, children }) => (
<div {...containerProps}>{children}</div>
);
const defaultRenderSectionContainer = ({ containerProps, children }) => (
<div {...containerProps}>{children}</div>
);
const defaultTheme = {
container: 'react-autowhatever__container',
containerOpen: 'react-autowhatever__container--open',
Expand All @@ -33,6 +36,7 @@ export default class Autowhatever extends Component {
multiSection: PropTypes.bool, // Indicates whether a multi section layout should be rendered.
renderInputComponent: PropTypes.func, // When specified, it is used to render the input element.
renderItemsContainer: PropTypes.func, // Renders the items container.
renderSectionContainer: PropTypes.func, // Renders the section container.
items: PropTypes.array.isRequired, // Array of items or sections to render.
renderItem: PropTypes.func, // This function renders a single item.
renderItemData: PropTypes.object, // Arbitrary data that will be passed to renderItem()
Expand All @@ -59,6 +63,7 @@ export default class Autowhatever extends Component {
multiSection: false,
renderInputComponent: defaultRenderInputComponent,
renderItemsContainer: defaultRenderItemsContainer,
renderSectionContainer: defaultRenderSectionContainer,
renderItem: () => {
throw new Error('`renderItem` must be provided');
},
Expand Down Expand Up @@ -191,6 +196,7 @@ export default class Autowhatever extends Component {
items,
renderItem,
renderItemData,
renderSectionContainer,
renderSectionTitle,
highlightedSectionIndex,
highlightedItemIndex,
Expand All @@ -203,41 +209,41 @@ export default class Autowhatever extends Component {
const isFirstSection = sectionIndex === 0;

// `key` is provided by theme()
/* eslint-disable react/jsx-key */
return (
<div
{...theme(
`${sectionKeyPrefix}container`,
'sectionContainer',
isFirstSection && 'sectionContainerFirst'
)}
>
<SectionTitle
section={section}
renderSectionTitle={renderSectionTitle}
theme={theme}
sectionKeyPrefix={sectionKeyPrefix}
/>
<ItemList
items={this.sectionsItems[sectionIndex]}
itemProps={itemProps}
renderItem={renderItem}
renderItemData={renderItemData}
sectionIndex={sectionIndex}
highlightedItemIndex={
highlightedSectionIndex === sectionIndex
? highlightedItemIndex
: null
}
onHighlightedItemChange={this.onHighlightedItemChange}
getItemId={this.getItemId}
theme={theme}
keyPrefix={keyPrefix}
ref={this.storeItemsListReference}
/>
</div>
);
/* eslint-enable react/jsx-key */
return renderSectionContainer({
section,
containerProps: theme(
`${sectionKeyPrefix}container`,
'sectionContainer',
isFirstSection && 'sectionContainerFirst'
),
children: (
<React.Fragment>
<SectionTitle
section={section}
renderSectionTitle={renderSectionTitle}
theme={theme}
sectionKeyPrefix={sectionKeyPrefix}
/>
<ItemList
items={this.sectionsItems[sectionIndex]}
itemProps={itemProps}
renderItem={renderItem}
renderItemData={renderItemData}
sectionIndex={sectionIndex}
highlightedItemIndex={
highlightedSectionIndex === sectionIndex
? highlightedItemIndex
: null
}
onHighlightedItemChange={this.onHighlightedItemChange}
getItemId={this.getItemId}
theme={theme}
keyPrefix={keyPrefix}
ref={this.storeItemsListReference}
/>
</React.Fragment>
),
});
});
}

Expand Down
95 changes: 95 additions & 0 deletions test/render-section-container/AutosuggestApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { Component } from 'react';
import sinon from 'sinon';
import Autosuggest from '../../src/Autosuggest';
import languages from '../plain-list/languages';
import { escapeRegexCharacters } from '../../demo/src/components/utils/utils.js';

const getMatchingLanguages = value => {
const escapedValue = escapeRegexCharacters(value.trim());
const regex = new RegExp('^' + escapedValue, 'i');

return languages.filter(language => regex.test(language.name));
};

let app = null;

const onChange = (event, { newValue }) => {
app.setState({
value: newValue
});
};

const onSuggestionsFetchRequested = ({ value }) => {
app.setState({
suggestions: [{ title: 'languages', languages: getMatchingLanguages(value) }]
});
};

const onSuggestionsClearRequested = () => {
app.setState({
suggestions: []
});
};

const getSuggestionValue = suggestion => suggestion.name;

const renderSuggestion = suggestion => suggestion.name;

export const renderSectionContainer = sinon.spy(
({ containerProps, children, query, section }) => <div {...containerProps}>
<div className="my-section-container-header">
Showing results for <strong className="my-query">{query}</strong> in {section.title}
</div>
{children}
</div>
);

const renderSectionTitle = () => null;

const getSectionSuggestions = section => section.languages;

export default class AutosuggestApp extends Component {
constructor() {
super();

app = this;

this.state = {
value: '',
suggestions: []
};
}

storeAutosuggestReference = autosuggest => {
if (autosuggest !== null) {
this.input = autosuggest.input;
}
};

alwaysRenderSuggestions = () => true;

render() {
const { value, suggestions } = this.state;
const inputProps = {
value,
onChange
};

return (
<Autosuggest
multiSection={true}
renderSectionTitle={renderSectionTitle}
getSectionSuggestions={getSectionSuggestions}
alwaysRenderSuggestions={this.alwaysRenderSuggestions}
suggestions={suggestions}
onSuggestionsFetchRequested={onSuggestionsFetchRequested}
onSuggestionsClearRequested={onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
renderSectionContainer={renderSectionContainer}
inputProps={inputProps}
ref={this.storeAutosuggestReference}
/>
);
}
}
46 changes: 46 additions & 0 deletions test/render-section-container/AutosuggestApp.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import sinon from 'sinon';
import React from 'react';
import TestUtils from 'react-dom/test-utils';
import { expect } from 'chai';
import {
init,
childrenMatcher,
getInnerHTML,
getElementWithClass,
setInputValue
} from '../helpers';
import AutosuggestApp, { renderSectionContainer } from './AutosuggestApp';

describe('Autosuggest with renderSectionContainer', () => {
beforeEach(() => {
init(TestUtils.renderIntoDocument(<AutosuggestApp />));
renderSectionContainer.resetHistory();
setInputValue('c ');
});

it('should render whatever renderSectionContainer returns', () => {
expect(getElementWithClass('my-section-container-header')).not.to.equal(
null
);
expect(getInnerHTML(getElementWithClass('my-query'))).to.equal('c');
});

it('should call renderSectionContainer once with the right parameters', () => {
expect(renderSectionContainer).to.have.been.calledOnce;
expect(renderSectionContainer).to.be.calledWith({
containerProps: sinon.match({
key: sinon.match.string,
className: sinon.match.string,
}),
children: childrenMatcher,
query: 'c',
section: sinon.match({
title: sinon.match.string,
languages: sinon.match.every(sinon.match({
name: sinon.match.string,
year: sinon.match.number
}))
})
});
});
});