-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #218 from Financial-Times/x-topic-search
- x-live-blog-wrapper-v0.0.34
- x-gift-article-v0.0.11
- v14.8.9
- v14.8.8
- v14.8.7
- v14.7.7
- v14.7.6
- v14.7.5
- v14.7.4
- v14.7.3
- v14.7.2
- v14.7.1
- v14.7.0
- v14.6.5
- v14.6.4
- v14.6.3
- v14.6.2
- v14.6.1
- v14.6.0
- v14.5.2
- v14.5.1
- v14.5.0
- v14.4.2
- v14.4.1
- v14.4.0
- v14.3.1
- v14.3.0
- v14.2.0
- v14.1.2
- v14.1.1
- v14.1.0
- v14.0.4
- v14.0.3
- v14.0.2
- v14.0.1
- v14.0.0
- v13.0.1
- v13.0.0
- v12.0.2
- v12.0.1
- v12.0.0
- v12.0.0-beta.3
- v12.0.0-beta.2
- v12.0.0-beta.1
- v11.2.0
- v11.1.2
- v11.1.1
- v11.1.0
- v11.0.0
- v10.5.0
- v10.4.0
- v10.3.0
- v10.2.0
- v10.1.2
- v10.1.1
- v10.1.0
- v10.0.0
- v9.0.0
- v8.2.5
- v8.2.4
- v8.2.3
- v8.2.2
- v8.2.1
- v8.2.0
- v8.1.1
- v8.1.0
- v8.0.6
- v8.0.5
- v8.0.4
- v8.0.3
- v8.0.2
- v8.0.1
- v8.0.1-beta.1
- v8.0.0
- v7.0.0
- v6.8.2-beta.1
- v6.8.1
- v6.8.0
- v6.7.1
- v6.7.0
- v6.6.0
- v6.5.0
- v6.4.1
Showing
15 changed files
with
646 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,4 +8,4 @@ | |
**/public-prod/** | ||
**/blueprints/** | ||
web/static/** | ||
/e2e/** | ||
/e2e/** |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"registry": { | ||
"search": [ | ||
"https://origami-bower-registry.ft.com", | ||
"https://registry.bower.io" | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
src/ | ||
stories/ | ||
rollup.js |
128 changes: 128 additions & 0 deletions
128
components/x-topic-search/__tests__/x-topic-search.test.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
const fetchMock = require('fetch-mock') | ||
const { h } = require('@financial-times/x-engine') | ||
const { mount } = require('@financial-times/x-test-utils/enzyme') | ||
const { TopicSearch } = require('../') | ||
|
||
const minSearchLength = 3 | ||
const maxSuggestions = 3 | ||
const apiUrl = 'api-url' | ||
const FOLLOWED_TOPIC_ID1 = 'Cat-House-id' | ||
const FOLLOWED_TOPIC_ID2 = 'Cat-Food-id' | ||
const UNFOLLOWED_TOPIC_ID1 = 'Cat-Toys-id' | ||
|
||
describe('x-topic-search', () => { | ||
const buildSearchUrl = (term) => `${apiUrl}?count=${maxSuggestions}&partial=${term}` | ||
const enterSearchTerm = (searchTerm) => { | ||
target.find('input').simulate('input', { target: { value: searchTerm } }) | ||
|
||
return new Promise((resolve) => { | ||
setTimeout(resolve, 400) | ||
}) | ||
} | ||
let target | ||
|
||
beforeEach(() => { | ||
const props = { | ||
minSearchLength, | ||
maxSuggestions, | ||
apiUrl, | ||
followedTopicIds: [FOLLOWED_TOPIC_ID1, FOLLOWED_TOPIC_ID2] | ||
} | ||
target = mount(<TopicSearch {...props} />) | ||
}) | ||
|
||
afterEach(() => { | ||
fetchMock.reset() | ||
}) | ||
|
||
describe('initial rendering', () => { | ||
it('should render with input box', () => { | ||
expect(target.find('input').exists()).toBe(true) | ||
}) | ||
|
||
it('should not display result container', () => { | ||
expect(target.render().children('div')).toHaveLength(1) | ||
}) | ||
}) | ||
|
||
describe('when input receives focus', () => { | ||
it('selects the text in the input', () => { | ||
const selectMock = jest.fn() | ||
const inputBox = target.find('input') | ||
|
||
inputBox.simulate('blur') | ||
inputBox.simulate('focus', { target: { select: selectMock } }) | ||
|
||
expect(selectMock).toHaveBeenCalledTimes(1) | ||
}) | ||
}) | ||
|
||
describe('given inputted text is shorter than minSearchLength', () => { | ||
const apiUrlWithResults = buildSearchUrl('a') | ||
|
||
beforeEach(() => { | ||
fetchMock.get(apiUrlWithResults, []) | ||
return enterSearchTerm('a') | ||
}) | ||
|
||
it('does not make a request to the api or render any result', () => { | ||
expect(fetchMock.called(apiUrlWithResults)).toBe(false) | ||
expect(target.render().children('div')).toHaveLength(1) | ||
}) | ||
}) | ||
|
||
describe('given searchTerm which has some topic suggestions to follow', () => { | ||
const apiUrlWithResults = buildSearchUrl('Cat') | ||
const results = [ | ||
{ id: FOLLOWED_TOPIC_ID1, prefLabel: 'Cat House', url: 'Cat-House-url' }, | ||
{ id: FOLLOWED_TOPIC_ID2, prefLabel: 'Cat Food', url: 'Cat-Food-url' }, | ||
{ id: UNFOLLOWED_TOPIC_ID1, prefLabel: 'Cat Toys', url: 'Cat-Toys-url' } | ||
] | ||
|
||
beforeEach(() => { | ||
fetchMock.get(apiUrlWithResults, results) | ||
return enterSearchTerm('Cat') | ||
}) | ||
|
||
it('requests the topic suggestions with count set to maxSuggestions', () => { | ||
expect(fetchMock.called(apiUrlWithResults)).toBe(true) | ||
}) | ||
|
||
it('renders no more than the max number of suggestions', () => { | ||
expect(target.render().children('div')).toHaveLength(2) | ||
expect(target.render().find('li')).toHaveLength(maxSuggestions) | ||
}) | ||
|
||
it('renders links and follow buttons for each suggestion', () => { | ||
const suggestionsList = target.render().find('li') | ||
|
||
results.forEach((topic, index) => { | ||
const suggestion = suggestionsList.eq(index) | ||
|
||
expect(suggestion.find('a').text()).toEqual(topic.prefLabel) | ||
expect(suggestion.find('a').attr('href')).toEqual(topic.url) | ||
expect(suggestion.find('button').text()).toEqual( | ||
topic.id === UNFOLLOWED_TOPIC_ID1 ? 'Add to myFT' : 'Added' | ||
) | ||
}) | ||
}) | ||
}) | ||
|
||
describe('given searchTerm which has no topic suggestions to follow', () => { | ||
const apiUrlNoResults = buildSearchUrl('Dog') | ||
|
||
beforeEach(() => { | ||
fetchMock.get(apiUrlNoResults, []) | ||
return enterSearchTerm('Dog') | ||
}) | ||
|
||
it('requests from the api and renders the no matching topics message', () => { | ||
expect(fetchMock.called(apiUrlNoResults)).toBe(true) | ||
|
||
const resultContainer = target.render().children('div').eq(1) | ||
|
||
expect(resultContainer).toHaveLength(1) | ||
expect(resultContainer.find('h2').text()).toMatch('No topics matching') | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"name": "x-topic-search", | ||
"main": "dist/TopicSearch.es5.js", | ||
"private": true, | ||
"dependencies": { | ||
"o-icons": "^6.3.0", | ||
"o-typography": "^6.4.6", | ||
"o-editorial-typography": "^1.2.1", | ||
"o-colors": "^5.4.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
{ | ||
"name": "@financial-times/x-topic-search", | ||
"version": "0.0.0", | ||
"description": "", | ||
"main": "dist/TopicSearch.cjs.js", | ||
"module": "dist/TopicSearch.esm.js", | ||
"browser": "dist/TopicSearch.es5.js", | ||
"style": "dist/TopicSearch.css", | ||
"scripts": { | ||
"prepare": "bower install && npm run build", | ||
"build": "node rollup.js", | ||
"start": "node rollup.js --watch" | ||
}, | ||
"keywords": [ | ||
"x-dash" | ||
], | ||
"author": "", | ||
"license": "ISC", | ||
"dependencies": { | ||
"@financial-times/x-engine": "file:../../packages/x-engine", | ||
"@financial-times/x-follow-button": "file:../x-follow-button", | ||
"classnames": "^2.2.6", | ||
"debounce-promise": "^3.1.0" | ||
}, | ||
"devDependencies": { | ||
"@financial-times/x-rollup": "file:../../packages/x-rollup", | ||
"@financial-times/x-test-utils": "file:../../packages/x-test-utils", | ||
"bower": "^1.7.9", | ||
"node-sass": "^4.9.2" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/Financial-Times/x-dash.git" | ||
}, | ||
"homepage": "https://github.com/Financial-Times/x-dash/tree/master/components/x-topic-search", | ||
"engines": { | ||
"node": ">= 6.0.0" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# x-topic-search | ||
|
||
This module allows a user to search for topics by name, and follow them. If an already-followed topic is returned in the search results, then those topics are indicated as such. | ||
|
||
The search results are fetched from the api whose url is passed as a property. | ||
[next-myft-page](https://github.com/Financial-Times/next-myft-page/blob/master/client/components/topic-search/TopicSearchContainer.jsx#L9) | ||
uses [next-tag-facets-api](https://github.com/Financial-Times/next-tag-facets-api). | ||
|
||
|
||
## Installation | ||
|
||
This module is compatible with Node 6+ and is distributed on npm. | ||
|
||
```bash | ||
npm install --save @financial-times/x-topic-search | ||
``` | ||
|
||
The [`x-engine`][engine] module is used to inject your chosen runtime into the component. Please read the `x-engine` documentation first if you are consuming `x-` components for the first time in your application. | ||
|
||
[engine]: https://github.com/Financial-Times/x-dash/tree/master/packages/x-engine | ||
|
||
|
||
## Usage | ||
|
||
The components provided by this module are all functions that expect a map of [properties](#properties). They can be used with vanilla JavaScript or JSX (If you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this: | ||
|
||
```jsx | ||
import React from 'react'; | ||
import { TopicSearch } from '@financial-times/x-topic-search'; | ||
|
||
// A == B == C | ||
const a = TopicSearch(props); | ||
const b = <TopicSearch {...props} />; | ||
const c = React.createElement(TopicSearch, props); | ||
``` | ||
|
||
All `x-` components are designed to be compatible with a variety of runtimes, not just React. Check out the [`x-engine`][engine] documentation for a list of recommended libraries and frameworks. | ||
|
||
[jsx-wtf]: https://jasonformat.com/wtf-is-jsx/ | ||
|
||
|
||
The consumer of this component needs to update `followedTopicIds` every time when users follow or unfollow topics. | ||
|
||
|
||
### Properties | ||
|
||
Property | Type | Required | Note | ||
---------------------|----------|----------|------------------ | ||
`minSearchLength` | Number | No | Minimum chars to start search. Default is 2 | ||
`maxSuggestions` | Number | No | Maximum number to display suggestions. Default is 5 | ||
`apiUrl` | String | Yes | The url to use when making requests to get topics | ||
`followedTopicIds` | Array | Yes | Array of followed topic `id`s. | ||
`csrfToken` | String | Yes | Value included in a hidden form field for x-follow-button | ||
`renderFollowButton` | Function | No | Optional render prop for the follow button |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
const xRollup = require('@financial-times/x-rollup'); | ||
const pkg = require('./package.json'); | ||
|
||
xRollup({ input: './src/TopicSearch.jsx', pkg }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { h } from '@financial-times/x-engine'; | ||
import styles from './TopicSearch.scss'; | ||
import classNames from 'classnames'; | ||
|
||
export default ({ searchTerm }) => ( | ||
<div className={classNames(styles["no-suggestions"])} aria-live="polite"> | ||
|
||
<h2 className={classNames(styles["no-suggestions__title"])}> | ||
No topics matching <b>{searchTerm}</b> | ||
</h2> | ||
|
||
<p>Suggestions:</p> | ||
|
||
<ul className={classNames(styles["no-suggestions__message"])}> | ||
<li>Make sure that all words are spelled correctly.</li> | ||
<li>Try different keywords.</li> | ||
<li>Try more general keywords.</li> | ||
<li>Try fewer keywords.</li> | ||
</ul> | ||
|
||
</div> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { h } from '@financial-times/x-engine'; | ||
import { FollowButton } from '@financial-times/x-follow-button'; | ||
import styles from './TopicSearch.scss'; | ||
import classNames from 'classnames'; | ||
|
||
const defaultFollowButtonRender = (concept, csrfToken, followedTopicIds) => ( | ||
<FollowButton | ||
conceptId={concept.id} | ||
conceptName={concept.prefLabel} | ||
csrfToken={csrfToken} | ||
isFollowed={followedTopicIds.includes(concept.id)} | ||
/> | ||
); | ||
|
||
export default ({ suggestions, renderFollowButton, searchTerm, csrfToken, followedTopicIds = [] }) => { | ||
renderFollowButton = typeof renderFollowButton === 'function' ? renderFollowButton : defaultFollowButtonRender; | ||
|
||
return ( | ||
<ul className={classNames(styles["suggestions"])} aria-live="polite"> | ||
|
||
{suggestions.map(suggestion => ( | ||
<li className={classNames(styles["suggestion"])} | ||
key={suggestion.id} | ||
data-trackable="myft-topic" | ||
data-concept-id={suggestion.id} | ||
data-trackable-meta={'{"search-term":"' + searchTerm + '"}'}> | ||
|
||
<a data-trackable="topic-link" | ||
className={classNames(styles["suggestion__name"])} | ||
href={suggestion.url || `/stream/${suggestion.id}`}> | ||
{suggestion.prefLabel} | ||
</a> | ||
|
||
{renderFollowButton(suggestion, csrfToken, followedTopicIds)} | ||
</li> | ||
))} | ||
|
||
</ul> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import { h, Component } from '@financial-times/x-engine'; | ||
import styles from './TopicSearch.scss'; | ||
import classNames from 'classnames'; | ||
import getSuggestions from './lib/get-suggestions.js'; | ||
import debounce from 'debounce-promise'; | ||
import SuggestionList from './SuggestionList'; | ||
import NoSuggestions from './NoSuggestions'; | ||
|
||
class TopicSearch extends Component { | ||
constructor(props) { | ||
super(props); | ||
|
||
this.minSearchLength = props.minSearchLength || 2; | ||
this.maxSuggestions = props.maxSuggestions || 5; | ||
this.apiUrl = props.apiUrl; | ||
this.getSuggestions = debounce(getSuggestions, 150); | ||
this.outsideEvents = ['focusout', 'focusin', 'click']; | ||
this.handleInputChange = this.handleInputChange.bind(this); | ||
this.handleInputClick = this.handleInputClick.bind(this); | ||
this.handleInputFocus = this.handleInputFocus.bind(this); | ||
this.handleInteractionOutside = this.handleInteractionOutside.bind(this); | ||
|
||
this.state = { | ||
followedTopicIds: props.followedTopicIds || [], | ||
searchTerm: '', | ||
showResult: false | ||
}; | ||
} | ||
|
||
componentDidMount() { | ||
this.outsideEvents.forEach(action => { | ||
document.body.addEventListener(action, this.handleInteractionOutside); | ||
}); | ||
} | ||
|
||
componentWillUnmount() { | ||
this.outsideEvents.forEach(action => { | ||
document.body.removeEventListener(action, this.handleInteractionOutside); | ||
}); | ||
} | ||
|
||
handleInputChange(event) { | ||
const searchTerm = event.target.value.trim(); | ||
|
||
this.setState({ searchTerm }); | ||
|
||
if (searchTerm.length >= this.minSearchLength) { | ||
this.getSuggestions(searchTerm, this.maxSuggestions, this.apiUrl) | ||
.then(({ suggestions }) => { | ||
this.setState({ | ||
suggestions, | ||
showResult: true | ||
}); | ||
}) | ||
.catch(() => { | ||
this.setState({ | ||
showResult: false | ||
}); | ||
}); | ||
} else { | ||
this.setState({ | ||
showResult: false | ||
}); | ||
} | ||
} | ||
|
||
handleInteractionOutside(event) { | ||
if (!this.rootEl.contains(event.target)) { | ||
this.setState({ | ||
showResult: false | ||
}); | ||
} | ||
} | ||
|
||
handleInputClick() { | ||
if (this.state.searchTerm.length >= this.minSearchLength) { | ||
this.setState({ | ||
showResult: true | ||
}); | ||
} | ||
} | ||
|
||
handleInputFocus(event) { | ||
event.target.select(); | ||
this.handleInputClick(); | ||
} | ||
|
||
render() { | ||
const { csrfToken, followedTopicIds, renderFollowButton } = this.props; | ||
const { searchTerm, showResult, suggestions } = this.state; | ||
|
||
return ( | ||
<div className={classNames(styles['container'])} ref={el => this.rootEl = el}> | ||
<h2 className="o-normalise-visually-hidden"> | ||
Search for topics, authors, companies, or other areas of interest | ||
</h2> | ||
|
||
<label className="o-normalise-visually-hidden" htmlFor="topic-search-input">Search and add topics</label> | ||
<div className={classNames(styles["input-wrapper"])}> | ||
<i className={classNames(styles["search-icon"])}/> | ||
<input | ||
type="search" | ||
id="topic-search-input" | ||
placeholder="Search and add topics" | ||
className={classNames(styles["input"])} | ||
data-trackable="topic-search" | ||
autoComplete="off" | ||
onInput={this.handleInputChange} | ||
onClick={this.handleInputClick} | ||
onFocus={this.handleInputFocus} | ||
/> | ||
</div> | ||
|
||
{showResult && searchTerm.length >= this.minSearchLength && | ||
<div className={classNames(styles['result-container'])}> | ||
{suggestions.length > 0 ? | ||
<SuggestionList | ||
csrfToken={csrfToken} | ||
followedTopicIds={followedTopicIds} | ||
searchTerm={searchTerm} | ||
suggestions={suggestions} | ||
renderFollowButton={renderFollowButton} | ||
/> : | ||
<NoSuggestions searchTerm={searchTerm}/>} | ||
</div>} | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export { TopicSearch }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
$system-code:'github:Financial-Times/x-dash' !default; | ||
|
||
@import 'o-icons/main'; | ||
@import 'o-colors/main'; | ||
@import 'o-typography/main'; | ||
@import 'o-editorial-typography/main'; | ||
|
||
:global { | ||
@import "~@financial-times/x-follow-button/dist/FollowButton"; | ||
} | ||
|
||
.container { | ||
position: relative; | ||
text-align: center; | ||
background-color: oColorsByName('claret-70'); | ||
color: oColorsByName('white'); | ||
width: 100%; | ||
} | ||
|
||
.input-wrapper { | ||
position: relative; | ||
} | ||
|
||
.search-icon { | ||
@include oIconsContent($icon-name: 'search', $color: oColorsByName('white'), $size: 32); | ||
position: absolute; | ||
top: 4px; | ||
left: -7px; | ||
} | ||
|
||
.input { | ||
@include oTypographySans($scale: 0); | ||
-webkit-appearance: none; | ||
width: 100%; | ||
min-height: 40px; | ||
margin: 0; | ||
border: none; | ||
border-bottom: 2px solid oColorsByName('white'); | ||
padding: 0 9px 0 24px; | ||
max-width: none; | ||
color: oColorsByName('white'); | ||
background: transparent; | ||
|
||
&::placeholder { | ||
color: oColorsByName('white'); | ||
} | ||
|
||
&::-webkit-search-cancel-button { | ||
@include oIconsContent($icon-name: 'cross', $color: oColorsByName('white'), $size: 26); | ||
-webkit-appearance: none; | ||
} | ||
|
||
&::-webkit-search-decoration, | ||
&::-webkit-search-results-button, | ||
&::-webkit-search-results-decoration { | ||
display: none; | ||
} | ||
} | ||
|
||
.result-container { | ||
position: absolute; | ||
background: oColorsByName('white'); | ||
top: 48px; | ||
padding: 10px; | ||
z-index: 1; | ||
width: calc(100% - 20px); | ||
} | ||
|
||
.suggestions { | ||
list-style: none; | ||
padding: 0; | ||
text-align: left; | ||
margin: 0; | ||
} | ||
|
||
.suggestion { | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
clear: right; | ||
padding: 5px 0; | ||
border-bottom: 1px solid oColorsByName('black-5'); | ||
|
||
&:last-child { | ||
border-bottom: 0; | ||
padding-bottom: 0; | ||
} | ||
|
||
&:first-child { | ||
padding-top: 0; | ||
} | ||
|
||
} | ||
|
||
.suggestion__name { | ||
@include oTypographySans($scale: -2, $weight: 'semibold'); | ||
@include oEditorialTypographyTag($type: 'topic'); | ||
padding: 5px 0; | ||
max-width: 50%; | ||
word-wrap: break-word; | ||
overflow-wrap: break-word; | ||
} | ||
|
||
.no-suggestions { | ||
@include oTypographySans($scale: 1); | ||
color: oColorsByName('black-70'); | ||
text-align: left; | ||
padding: 0 0 5px 0; | ||
margin: 0; | ||
p { | ||
margin: 0; | ||
} | ||
} | ||
|
||
.no-suggestions__title { | ||
@include oTypographySans($scale: 3); | ||
font-weight: normal; | ||
overflow-wrap: break-word; | ||
margin: 0; | ||
padding: 0 0 20px; | ||
} | ||
|
||
.no-suggestions__message { | ||
margin-top: 12px; | ||
margin-bottom: 0; | ||
padding-left: 20px; | ||
list-style-type: disc; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
const addQueryParamToUrl = (name, value, url, append = true) => { | ||
const queryParam = `${name}=${value}`; | ||
|
||
return append === true ? `${url}&${queryParam}` : `${url}?${queryParam}`; | ||
}; | ||
|
||
export default (searchTerm, maxSuggestions, apiUrl) => { | ||
const dataSrc = addQueryParamToUrl('count', maxSuggestions, apiUrl, false); | ||
const url = addQueryParamToUrl('partial', searchTerm.replace(' ', '+'), dataSrc); | ||
|
||
return fetch(url) | ||
.then(response => { | ||
if (!response.ok) { | ||
throw new Error(response.statusText); | ||
} | ||
|
||
return response.json(); | ||
}) | ||
.then(suggestions => ({ suggestions })); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import React from 'react' | ||
import { TopicSearch } from '../src/TopicSearch' | ||
import BuildService from '../../../.storybook/build-service' | ||
|
||
// Set up basic document styling using the Origami build service | ||
const dependencies = { | ||
'o-normalise': '^2.0.0', | ||
'o-typography': '^6.0.0', | ||
'o-colors': '^5.0.0', | ||
'o-icons': '^6.0.0' | ||
} | ||
|
||
export default { | ||
title: 'x-topic-search' | ||
} | ||
|
||
export const _TopicSearchBar = (args) => { | ||
return ( | ||
<div className="story-container"> | ||
<BuildService dependencies={dependencies} /> | ||
<TopicSearch {...args} /> | ||
</div> | ||
) | ||
} | ||
|
||
_TopicSearchBar.args = { | ||
minSearchLength: 2, | ||
maxSuggestions: 10, | ||
apiUrl: '//tag-facets-api.ft.com/annotations', | ||
followedTopicIds: ['f95d1e16-2307-4feb-b3ff-6f224798aa49'], | ||
csrfToken: 'csrfToken' | ||
} | ||
|
||
_TopicSearchBar.argTypes = { | ||
minSearchLength: { name: 'Minimum search start length' }, | ||
maxSuggestions: { name: 'Maximum sugggestions to show' }, | ||
apiUrl: { name: 'URL of the API to use' }, | ||
followedTopicIds: { | ||
type: 'select', | ||
name: 'Followed Topics', | ||
options: { | ||
None: [], | ||
'World Elephant Water Polo': ['f95d1e16-2307-4feb-b3ff-6f224798aa49'], | ||
'Brexit, Britain after Brexit, Brexit Unspun Podcast': [ | ||
'19b95057-4614-45fb-9306-4d54049354db', | ||
'464cc2f2-395e-4c36-bb29-01727fc95558', | ||
'c4e899ed-157e-4446-86f0-5a65803dc07a' | ||
] | ||
} | ||
}, | ||
csrfToken: { name: 'CSRF Token' } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters