Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

Commit

Permalink
feat(Highlighter): allow rendering to custom tag (#11)
Browse files Browse the repository at this point in the history
* feat(Highlighter): allow rendering to custom tag

* add jsdoc
  • Loading branch information
Haroenv authored and mthuret committed Apr 12, 2017
1 parent 785cf5c commit 52a1212
Show file tree
Hide file tree
Showing 14 changed files with 347 additions and 4 deletions.
8 changes: 6 additions & 2 deletions docgen/src/guide/Highlighting_results.md
Expand Up @@ -31,14 +31,18 @@ to use the Highlighter. The Highlight and the Snippet widgets takes two props:
Here is an example in which we create a custom Hit widget for results that have a
`description` field that is highlighted.

In these examples we use the [`mark`](https://developer.mozilla.org/en/docs/Web/HTML/Element/mark)
tag to highlight. This is a tag specifically made for highlighting pieces of text. The default
tag is `em`, mostly for legacy reasons.

```jsx
import React from 'react';

import {InstantSearch, Hits, Highlight} from 'InstantSearch';

const Hit = ({hit}) =>
<p>
<Highlight attributeName="description" hit={hit}/>
<Highlight attributeName="description" hit={hit} tagName="mark"/>
</p>;

export default function App() {
Expand Down Expand Up @@ -72,7 +76,7 @@ way as the [widgets](guide/Highlighting_results.html#highlight-and-snippet-widge
const CustomHighlight = connectHighlight(({highlight, attributeName, hit}) => {
const parsedHit = highlight({attributeName, hit, highlightProperty: 'highlightProperty'});
return parsedHit.map(part => {
if(part.isHighlighted) return <em>{part.value}</em>;
if(part.isHighlighted) return <mark>{part.value}</mark>;
return part.value;
});
});
Expand Down
1 change: 1 addition & 0 deletions packages/react-instantsearch/src/components/Highlight.js
Expand Up @@ -9,4 +9,5 @@ Highlight.propTypes = {
hit: React.PropTypes.object.isRequired,
attributeName: React.PropTypes.string.isRequired,
highlight: React.PropTypes.func.isRequired,
tagName: React.PropTypes.string,
};
38 changes: 38 additions & 0 deletions packages/react-instantsearch/src/components/Highlight.test.js
Expand Up @@ -43,4 +43,42 @@ describe('Highlight', () => {
);
expect(tree.toJSON()).toMatchSnapshot();
});

it('renders a hit with a custom tag correctly', () => {
const hitFromAPI = {
objectID: 0,
deep: { attribute: { value: 'awesome highlighted hit!' } },
_highlightResult: {
deep: {
attribute: {
value: {
value: 'awesome <ais-highlight>hi</ais-highlight>ghlighted <ais-highlight>hi</ais-highlight>t!',
fullyHighlighted: true,
matchLevel: 'full',
matchedWords: [''],
},
},
},
},
};

const highlight = ({ hit, attributeName, highlightProperty }) =>
parseAlgoliaHit({
preTag: '<ais-highlight>',
postTag: '</ais-highlight>',
attributeName,
hit,
highlightProperty,
});

const tree = renderer.create(
<Highlight
attributeName="deep.attribute.value"
hit={hitFromAPI}
highlight={highlight}
tagName="marquee"
/>
);
expect(tree.toJSON()).toMatchSnapshot();
});
});
10 changes: 8 additions & 2 deletions packages/react-instantsearch/src/components/Highlighter.js
@@ -1,7 +1,7 @@
import React from 'react';

export default function Highlighter(
{ hit, attributeName, highlight, highlightProperty }
{ hit, attributeName, highlight, highlightProperty, tagName }
) {
const parsedHighlightedValue = highlight({
hit,
Expand All @@ -17,7 +17,12 @@ export default function Highlighter(
</span>
);
}
return <em key={key} className="ais-Highlight__highlighted">{v.value}</em>;
const HighlightedTag = tagName ? tagName : 'em';
return (
<HighlightedTag key={key} className="ais-Highlight__highlighted">
{v.value}
</HighlightedTag>
);
});
return <span className="ais-Highlight">{reactHighlighted}</span>;
}
Expand All @@ -27,4 +32,5 @@ Highlighter.propTypes = {
attributeName: React.PropTypes.string.isRequired,
highlight: React.PropTypes.func.isRequired,
highlightProperty: React.PropTypes.string.isRequired,
tagName: React.PropTypes.string,
};
39 changes: 39 additions & 0 deletions packages/react-instantsearch/src/components/Highlighter.test.js
Expand Up @@ -44,4 +44,43 @@ describe('Highlighter', () => {
);
expect(tree.toJSON()).toMatchSnapshot();
});

it('renders a hit with a custom tag correctly', () => {
const hitFromAPI = {
objectID: 0,
deep: { attribute: { value: 'awesome highlighted hit!' } },
_highlightProperty: {
deep: {
attribute: {
value: {
value: 'awesome <ais-highlight>hi</ais-highlight>ghlighted <ais-highlight>hi</ais-highlight>t!',
fullyHighlighted: true,
matchLevel: 'full',
matchedWords: [''],
},
},
},
},
};

const highlight = ({ hit, attributeName, highlightProperty }) =>
parseAlgoliaHit({
preTag: '<ais-highlight>',
postTag: '</ais-highlight>',
attributeName,
hit,
highlightProperty,
});

const tree = renderer.create(
<Highlighter
attributeName="deep.attribute.value"
hit={hitFromAPI}
highlight={highlight}
highlightProperty="_highlightProperty"
tagName="mark"
/>
);
expect(tree.toJSON()).toMatchSnapshot();
});
});
1 change: 1 addition & 0 deletions packages/react-instantsearch/src/components/Snippet.js
Expand Up @@ -10,4 +10,5 @@ Snippet.propTypes = {
hit: React.PropTypes.object.isRequired,
attributeName: React.PropTypes.string.isRequired,
highlight: React.PropTypes.func.isRequired,
tagName: React.PropTypes.string,
};
38 changes: 38 additions & 0 deletions packages/react-instantsearch/src/components/Snippet.test.js
Expand Up @@ -43,4 +43,42 @@ describe('Snippet', () => {
);
expect(tree.toJSON()).toMatchSnapshot();
});

it('renders a hit with a custom tag correctly', () => {
const hitFromAPI = {
objectID: 0,
deep: { attribute: { value: 'awesome highlighted hit!' } },
_snippetResult: {
deep: {
attribute: {
value: {
value: 'awesome <ais-highlight>hi</ais-highlight>ghlighted <ais-highlight>hi</ais-highlight>t!',
fullyHighlighted: true,
matchLevel: 'full',
matchedWords: [''],
},
},
},
},
};

const highlight = ({ hit, attributeName, highlightProperty }) =>
parseAlgoliaHit({
preTag: '<ais-highlight>',
postTag: '</ais-highlight>',
attributeName,
hit,
highlightProperty,
});

const tree = renderer.create(
<Snippet
attributeName="deep.attribute.value"
hit={hitFromAPI}
highlight={highlight}
tagName="strong"
/>
);
expect(tree.toJSON()).toMatchSnapshot();
});
});
Expand Up @@ -31,3 +31,35 @@ exports[`Highlight parses an highlighted attribute of hit object 1`] = `
</span>
</span>
`;

exports[`Highlight renders a hit with a custom tag correctly 1`] = `
<span
className="ais-Highlight"
>
<span
className="ais-Highlight__nonHighlighted"
>
awesome
</span>
<marquee
className="ais-Highlight__highlighted"
>
hi
</marquee>
<span
className="ais-Highlight__nonHighlighted"
>
ghlighted
</span>
<marquee
className="ais-Highlight__highlighted"
>
hi
</marquee>
<span
className="ais-Highlight__nonHighlighted"
>
t!
</span>
</span>
`;
Expand Up @@ -31,3 +31,35 @@ exports[`Highlighter parses an highlighted attribute of hit object 1`] = `
</span>
</span>
`;

exports[`Highlighter renders a hit with a custom tag correctly 1`] = `
<span
className="ais-Highlight"
>
<span
className="ais-Highlight__nonHighlighted"
>
awesome
</span>
<mark
className="ais-Highlight__highlighted"
>
hi
</mark>
<span
className="ais-Highlight__nonHighlighted"
>
ghlighted
</span>
<mark
className="ais-Highlight__highlighted"
>
hi
</mark>
<span
className="ais-Highlight__nonHighlighted"
>
t!
</span>
</span>
`;
Expand Up @@ -31,3 +31,35 @@ exports[`Snippet parses an highlighted snippet attribute of hit object 1`] = `
</span>
</span>
`;

exports[`Snippet renders a hit with a custom tag correctly 1`] = `
<span
className="ais-Highlight"
>
<span
className="ais-Highlight__nonHighlighted"
>
awesome
</span>
<strong
className="ais-Highlight__highlighted"
>
hi
</strong>
<span
className="ais-Highlight__nonHighlighted"
>
ghlighted
</span>
<strong
className="ais-Highlight__highlighted"
>
hi
</strong>
<span
className="ais-Highlight__nonHighlighted"
>
t!
</span>
</span>
`;
1 change: 1 addition & 0 deletions packages/react-instantsearch/src/widgets/Highlight.js
Expand Up @@ -9,6 +9,7 @@ import HighlightComponent from '../components/Highlight.js';
* @kind widget
* @propType {string} attributeName - the location of the highlighted attribute in the hit
* @propType {object} hit - the hit object containing the highlighted attribute
* @propType {string} [tagName='em'] - the tag to be used for highlighted parts of the hit
* @example
* import React from 'react';
*
Expand Down
1 change: 1 addition & 0 deletions packages/react-instantsearch/src/widgets/Snippet.js
Expand Up @@ -9,6 +9,7 @@ import SnippetComponent from '../components/Snippet.js';
* @kind widget
* @propType {string} attributeName - the location of the highlighted snippet attribute in the hit
* @propType {object} hit - the hit object containing the highlighted snippet attribute
* @propType {string} [tagName='em'] - the tag to be used for highlighted parts of the attribute
* @example
* import React from 'react';
*
Expand Down
59 changes: 59 additions & 0 deletions stories/Highlight.stories.js
@@ -0,0 +1,59 @@
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { Highlight, Hits } from '../packages/react-instantsearch/dom';
import { withKnobs, text } from '@kadira/storybook-addon-knobs';
import { WrapWithHits } from './util';

const stories = storiesOf('Highlight', module);

stories.addDecorator(withKnobs);

const Default = ({ hit }) => (
<article>
<p>
<Highlight attributeName="name" hit={hit} />
</p>
<p>
<Highlight attributeName="description" hit={hit} />
</p>
</article>
);

Default.propTypes = {
hit: React.PropTypes.object.isRequired,
};

const StrongHits = ({ hit }) => (
<article>
<p>
<Highlight
attributeName="name"
tagName={text('tag name (title)', 'strong')}
hit={hit}
/>
</p>
<p>
<Highlight
attributeName="description"
tagName={text('tag name (description)', 'strong')}
hit={hit}
/>
</p>
</article>
);

StrongHits.propTypes = {
hit: React.PropTypes.object.isRequired,
};

stories
.add('default', () => (
<WrapWithHits hasPlayground={true} linkedStoryGroup="Highlight">
<Hits hitComponent={Default} />
</WrapWithHits>
))
.add('playground', () => (
<WrapWithHits linkedStoryGroup="Highlight">
<Hits hitComponent={StrongHits} />
</WrapWithHits>
));

0 comments on commit 52a1212

Please sign in to comment.