Files
shields/frontend/components/suggestion-and-search.js
Paul Melnikow 5c7d07f5be Rework styling using styled-components (#2517)
The CSS in the project is relatively difficult to change. While it is very DRY, it relies heavily on inheritance. It's difficult to make changes in the markup modal without it also affecting styles elsewhere.
 
[styled-components](https://www.styled-components.com/) is one of the leading CSS-in-JS libraries. By reducing dependency on global state and CSS inheritance, styles become explicit and are easier to inspect and change. It's also convenient that styles can be embedded with the components they modify.

At runtime, the library creates CSS classes, so it's pretty efficient.

We were using a little bit of [styled-jsx](https://github.com/zeit/styled-jsx) before, which ships with Next.js, though styled-components is more widely used and I've had good experiences with it all around.

In a few cases I've duplicated styles where it feels more natural to do that: for example, `text-align: center` is duplicated in `Main` and `MarkupModal`.

Much of this is a refactor, though there are a few visual changes, particularly in the markup modal and the style examples.
2018-12-18 16:44:47 -05:00

117 lines
2.9 KiB
JavaScript

import React from 'react'
import PropTypes from 'prop-types'
import fetchPonyfill from 'fetch-ponyfill'
import debounce from 'lodash.debounce'
import resolveUrl from '../lib/resolve-url'
import BadgeExamples from './badge-examples'
import { BlockInput } from './common'
export default class SuggestionAndSearch extends React.Component {
static propTypes = {
queryChanged: PropTypes.func.isRequired,
onBadgeClick: PropTypes.func.isRequired,
baseUrl: PropTypes.string.isRequired,
longCache: PropTypes.bool.isRequired,
}
constructor(props) {
super(props)
this.queryChangedDebounced = debounce(props.queryChanged, 50, {
leading: true,
})
}
state = {
isUrl: false,
inProgress: false,
projectUrl: null,
suggestions: [],
}
queryChanged(query) {
const isUrl = query.startsWith('https://') || query.startsWith('http://')
this.setState({
isUrl,
projectUrl: isUrl ? query : null,
})
this.queryChangedDebounced(query)
}
getSuggestions() {
this.setState({ inProgress: true }, async () => {
const { baseUrl } = this.props
const { projectUrl } = this.state
const url = resolveUrl('/$suggest/v1', baseUrl, { url: projectUrl })
const fetch = window.fetch || fetchPonyfill
const res = await fetch(url)
let suggestions
try {
const json = await res.json()
// This doesn't validate the response. The default value here prevents
// a crash if the server returns {"err":"Disallowed"}.
suggestions = json.suggestions || []
} catch (e) {
suggestions = []
}
this.setState({ inProgress: false, suggestions })
})
}
renderSuggestions() {
const { baseUrl, longCache } = this.props
const { suggestions } = this.state
if (suggestions.length === 0) {
return null
}
const transformed = [
{
examples: suggestions.map(({ title, path, link, queryParams }) => ({
title,
preview: { path, queryParams },
example: { path, queryParams },
link,
})),
},
]
return (
<BadgeExamples
definitions={transformed}
baseUrl={baseUrl}
longCache={longCache}
onClick={this.props.onBadgeClick}
/>
)
}
render() {
return (
<section>
<form action="javascript:void 0" autoComplete="off">
<BlockInput
onChange={event => this.queryChanged(event.target.value)}
autofill="off"
autoFocus
placeholder="search / project URL"
/>
<br />
<button
onClick={event => this.getSuggestions(event.target.value)}
disabled={this.state.inProgress}
hidden={!this.state.isUrl}
>
Suggest badges
</button>
</form>
{this.renderSuggestions()}
</section>
)
}
}