Skip to content

Commit

Permalink
fix(stats): Move CSS classes definition to widget from component
Browse files Browse the repository at this point in the history
We can now easily spot in default.css which widget are not yet
BEMified (yeah, that's a verb now).

- Moved the BEM definition to widget instead of component
- Updated the tests
- This widget is using the headerFooter decorator, I updated the docs
- The initial root class was added twice because we already used it in the default template. I removed the wrapping div in the template.

BREAKING CHANGE: `cssClasses.root` now applies to the main root
element (above header and footer) and no longer to the template
wrapper. To style the template wrapper, use `cssClasses.body`
  • Loading branch information
pixelastic committed Oct 20, 2015
1 parent 3f72635 commit 99073cd
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 36 deletions.
42 changes: 32 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,26 +352,22 @@ search.addWidget(
/**
* Display various stats about the current search state
* @param {String|DOMElement} options.container CSS Selector or DOMElement to insert the widget
* @param {Object} [options.cssClasses] CSS classes to add to the default template
* @param {Object} [options.cssClasses] CSS classes to add
* @param {String} [options.cssClasses.root] CSS class to add to the root element
* @param {String} [options.cssClasses.header] CSS class to add to the header element
* @param {String} [options.cssClasses.body] CSS class to add to the body element
* @param {String} [options.cssClasses.footer] CSS class to add to the footer element
* @param {String} [options.cssClasses.time] CSS class to add to the element wrapping the time processingTimeMs
* @param {Object} [options.templates] Templates to use for the widget
* @param {String|Function} [options.templates.header=''] Header template
* @param {String|Function} [options.templates.body='<div class="{{cssClasses.root}}">
{{#hasNoResults}}No results{{/hasNoResults}}
{{#hasOneResult}}1 result{{/hasOneResult}}
{{#hasManyResults}}{{#helpers.formatNumber}}{{nbHits}}{{/helpers.formatNumber}} results{{/hasManyResults}}
<span class="{{cssClasses.time}}">found in {{processingTimeMS}}ms</span>
</div>'] Body template
* @param {String|Function} [options.templates.body] Body template
* @param {String|Function} [options.templates.footer=''] Footer template
* @param {Function} [options.transformData] Function to change the object passed to the `body` template
* @param {boolean} [hideWhenNoResults=true] Hide the container when there's no results
* @return {Object}
*/
```



#### Usage

```html
Expand All @@ -389,6 +385,32 @@ search.addWidget(
);
```

### Styling

```html
<div class="ais-stats">
<div class="ais-stats--header ais-header">[custom header template]</div>
<div class="ais-stats--body">
42 results found in <span class="ais-stats--time">42ms</span>
</div>
<div class="ais-stats--footer ais-footer">[custom footer template]</div>
</div>
```

```css
.ais-stats {
}
.ais-stats--header {
}
.ais-stats--body {
}
.ais-stats--time {
font-size: small;
}
.ais-stats--footer {
}
```

### indexSelector

![Example of the indexSelector widget][indexSelector]
Expand Down Expand Up @@ -623,7 +645,7 @@ search.addWidget(
* @param {String} [options.operator='or'] How to apply refinements. Possible values: `or`, `and`
* @param {String[]} [options.sortBy=['count:desc']] How to sort refinements. Possible values: `count|isRefined|name:asc|desc`
* @param {String} [options.limit=1000] How much facet values to get
* @param {Object} [options.cssClasses] CSS classes to add to the wrapping elements: root, list, item
* @param {Object} [options.cssClasses] CSS classes to add
* @param {String|String[]} [options.cssClasses.root] CSS class to add to the root element
* @param {String|String[]} [options.cssClasses.header] CSS class to add to the header element
* @param {String|String[]} [options.cssClasses.body] CSS class to add to the body element
Expand Down
13 changes: 7 additions & 6 deletions components/Stats/Stats.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
var React = require('react');

var Template = require('../Template');
var bem = require('../../lib/utils').bemHelper('ais-stats');
var cx = require('classnames');

class Stats extends React.Component {
render() {
Expand All @@ -16,10 +14,7 @@ class Stats extends React.Component {
page: this.props.page,
processingTimeMS: this.props.processingTimeMS,
query: this.props.query,
cssClasses: {
root: cx(bem(null), this.props.cssClasses.root),
time: cx(bem('time'), this.props.cssClasses.time)
}
cssClasses: this.props.cssClasses
};

return (
Expand All @@ -29,6 +24,12 @@ class Stats extends React.Component {
}

Stats.propTypes = {
cssClasses: React.PropTypes.shape({
time: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.arrayOf(React.PropTypes.string)
])
}),
hitsPerPage: React.PropTypes.number,
nbHits: React.PropTypes.number,
nbPages: React.PropTypes.number,
Expand Down
8 changes: 1 addition & 7 deletions components/Stats/__tests__/Stats-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import TestUtils from 'react-addons-test-utils';
import Stats from '../Stats';
import Template from '../../Template';

var bem = require('../../../lib/utils').bemHelper('ais-stats');
var cx = require('classnames');

describe('Stats', () => {
var renderer;

Expand All @@ -21,10 +18,7 @@ describe('Stats', () => {
it('should render <Template data= />', () => {
var out = render();
var defaultProps = {
cssClasses: {
root: cx(bem(null)),
time: cx(bem('time'))
},
cssClasses: {},
hasManyResults: true,
hasNoResults: false,
hasOneResult: false
Expand Down
8 changes: 7 additions & 1 deletion themes/default/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@
/* STATS */
.ais-stats {
}
.ais-stats--header {
}
.ais-stats--body {
}
.ais-stats--time {
font-size: small;
}
.ais-stats--footer {
}

/* INDEX SELECTOR */
.ais-index-selector {
Expand All @@ -51,7 +57,7 @@
}
.ais-pagination--item-previous {
}
.ais-pagination--item-page {
.ais-pagination--item-page {
}
.ais-pagination--item-page__active {
}
Expand Down
8 changes: 7 additions & 1 deletion widgets/stats/__tests__/stats-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ describe('stats()', () => {
expect(headerFooter.calledOnce).toBe(true, 'headerFooter called once');
expect(ReactDOM.render.firstCall.args[0]).toEqualJSX(
<Stats
cssClasses={{}}
cssClasses={{
body: 'ais-stats--body',
header: 'ais-stats--header',
footer: 'ais-stats--footer',
root: 'ais-stats',
time: 'ais-stats--time'
}}
hasResults={true}
hideWhenNoResults={true}
hitsPerPage={2}
Expand Down
6 changes: 2 additions & 4 deletions widgets/stats/defaultTemplates.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
module.exports = {
header: '',
body: `<div class="{{cssClasses.root}}">
{{#hasNoResults}}No results{{/hasNoResults}}
body: `{{#hasNoResults}}No results{{/hasNoResults}}
{{#hasOneResult}}1 result{{/hasOneResult}}
{{#hasManyResults}}{{#helpers.formatNumber}}{{nbHits}}{{/helpers.formatNumber}} results{{/hasManyResults}}
<span class="{{cssClasses.time}}">found in {{processingTimeMS}}ms</span>
</div>`,
<span class="{{cssClasses.time}}">found in {{processingTimeMS}}ms</span>`,
footer: ''
};
23 changes: 16 additions & 7 deletions widgets/stats/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ var ReactDOM = require('react-dom');
var utils = require('../../lib/utils.js');
var autoHide = require('../../decorators/autoHide');
var headerFooter = require('../../decorators/headerFooter');
var bem = require('../../lib/utils').bemHelper('ais-stats');
var cx = require('classnames/dedupe');

var defaultTemplates = require('./defaultTemplates.js');

/**
* Display various stats about the current search state
* @param {String|DOMElement} options.container CSS Selector or DOMElement to insert the widget
* @param {Object} [options.cssClasses] CSS classes to add to the default template
* @param {Object} [options.cssClasses] CSS classes to add
* @param {String} [options.cssClasses.root] CSS class to add to the root element
* @param {String} [options.cssClasses.header] CSS class to add to the header element
* @param {String} [options.cssClasses.body] CSS class to add to the body element
* @param {String} [options.cssClasses.footer] CSS class to add to the footer element
* @param {String} [options.cssClasses.time] CSS class to add to the element wrapping the time processingTimeMs
* @param {Object} [options.templates] Templates to use for the widget
* @param {String|Function} [options.templates.header=''] Header template
* @param {String|Function} [options.templates.body='<div class="{{cssClasses.root}}">
{{#hasNoResults}}No results{{/hasNoResults}}
{{#hasOneResult}}1 result{{/hasOneResult}}
{{#hasManyResults}}{{#helpers.formatNumber}}{{nbHits}}{{/helpers.formatNumber}} results{{/hasManyResults}}
<span class="{{cssClasses.time}}">found in {{processingTimeMS}}ms</span>
</div>'] Body template
* @param {String|Function} [options.templates.body] Body template
* @param {String|Function} [options.templates.footer=''] Footer template
* @param {Function} [options.transformData] Function to change the object passed to the `body` template
* @param {boolean} [hideWhenNoResults=true] Hide the container when there's no results
Expand Down Expand Up @@ -49,6 +49,15 @@ function stats({
});

var Stats = autoHide(headerFooter(require('../../components/Stats/Stats.js')));

cssClasses = {
body: cx(bem('body'), cssClasses.body),
footer: cx(bem('footer'), cssClasses.footer),
header: cx(bem('header'), cssClasses.header),
root: cx(bem(null), cssClasses.root),
time: cx(bem('time'), cssClasses.time)
};

ReactDOM.render(
<Stats
cssClasses={cssClasses}
Expand Down

0 comments on commit 99073cd

Please sign in to comment.