import * as React from "react" import { InputSingleLine } from "./Input" import styled from "styled-components" import bp from "styled-components-breakpoint" import Router from "next/router" import withTranslate from "jamplay-common/i18n/withTranslate" import { withState, lifecycle } from "recompose" import getConfig from "jamplay-common/remote-config" import { provider } from "jamplay-common/client" import { getCategory } from "jamplay-common/enum/book-categories" import { Icon } from ".." import { compose, Query, QueryResult } from "react-apollo" import Analytic, { getInstance } from "jamplay-common/client/analytic" import { Link } from "jamplay-common/routing" import { BookThumbnail } from "./Book" import gql from "graphql-tag" const SuggestionResultContainer = styled.div` position: absolute; top: 100%; right: 0; left: 0; z-index: 9; display: none; .SuggestionResultDialog__wrapper { border-radius: 0 0 5px 5px; border: 1px solid ${(props) => props.theme.borderGrey}; background-color: ${(props) => props.theme.matteWhite}; } .SuggestionResultDialog__type-label { background-color: ${(props) => props.theme.pumpkin}; color: ${(props) => props.theme.white}; padding: 3px 5px; display: inline-block; border-radius: 5px; } .SuggestionResultDialog__see-all-wrapper { text-align: right; border-top: solid 1px ${(props) => props.theme.borderGrey}; padding: 5px 13px 8px 5px; a { font-weight: bold; color: ${(props) => props.theme.matteBlack}; } } .SuggestionResultDialog__no-content-wrapper { text-align: center; margin-top: 21px; } .SugestionResultDialog__loading-wrap { background: ${(props) => props.theme.matteWhite}; position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: 2; margin: 8px; } .SuggestionResultDialog__scroll { ${bp("mobile")` -webkit-overflow-scrolling: touch; overflow: auto; `} ${bp("tablet")` max-height: 999999px; `}; } .AuthorContainer { .AuthorInfo__avatar-image { width: 40px; height: 40px; border: 1px solid ${(props) => props.theme.borderGrey}; } .AuthorInfo__info-wrapper p { font-size: 1rem; font-weight: bold; color: ${(props) => props.theme.matteBlack}; } } h6 { ${bp("mobile")` font-size: 16px; `} ${bp("tablet")` font-size: inherit; `}; } transition: 0.222s ease-in-out all; transform: translate(0, 8px); opacity: 0; pointer-events: none; display: block; ${bp("mobile")` &.visible { opacity: 1; pointer-events: all; transform: translate(0, 0px); } `} ${bp("tablet")` `}; ` const BookSuggestionResultItemContainer = styled.div` cursor: pointer; a { text-decoration: none; } a label { cursor: pointer; color: #333; } ${bp("mobile")` padding: 5px 8px 5px 8px; .AuthorInfo { } `} ${bp("tablet")` .AuthorInfo { display: block; } `} &:hover { background-color: ${(props) => props.theme.white}; } &:first-child { } &:last-child { border-bottom: none; } .BookSuggestionResultItem__cover { width: 35px; } .BookSuggestionResultItem__info-wrapper { width: 100%; display: flex; align-items: center; } .BookSuggestionResultItem__label { margin-left: 8px; font-weight: bold; word-break: break-all; } .BookSuggestionResultItem__label--highlight { background-color: ${(props) => props.theme.gold}; } .AuthorInfo { clear: both; } .AuthorInfo__avatar-image { width: 35px; height: 35px; ${bp("mobile")` display: none; `} ${bp("tablet")` display: block; `}; } .BookSuggestionResultItem__cover-wrapper { width: 35px; margin-right: 8px; ${bp("mobile")` display: none; `} ${bp("tablet")` display: block; `}; &.author { border: none; .thumbnail { width: 35px; height: 35px; border-radius: 50%; } .thumbnail:before { padding-top: 0; } .thumbnail:after { border: none; } } } .BookSuggestionResultItem__tags-container { font-size: 0.6em; ${bp("mobile")` display: none; `} ${bp("tablet")` display: block; `}; } .BookSuggestionResultItem__tag { border: 1px solid ${(props) => props.theme.borderGrey}; color: ${(props) => props.theme.darkGrey}; padding: 0px 5px; margin: 2px 1px; border-radius: 8px; float: left; &:first-child { margin-left: 0; } &:last-child { margin-right: 0; } } ` function navigateToResultPage(value: string, type: string = "book") { Link.Search.go(Router, { category: "ALL", v: value, sort: "SCORE", type }) } function preventDefault(e) { e.preventDefault() } export const BookSuggestionResultItem = ( props: { _id: string thumbnailImage?: string category?: string onClearSearchText?: () => void } & { title?: string highlightText: string __typename: "Author" | "Book" } & withTranslatePropType ) => { let url = `/search?v=${props.title}&category=ALL&sort=SCORE` let type = "book" if (props.__typename === "Author") { url = `${url}&type=author` type = "author" } function SendEventAndNavigate(sendEvent) { return (e) => { e.preventDefault() sendEvent("search_input", "click-suggestion", `${props.title}`, 0) navigateToResultPage(props.title || "", type) } } function categoryLowerCase(cat) { if (!cat) { return "all" } return getCategory(cat).prefix } function dismissSearchSuggestion() { if (props.onClearSearchText) { props.onClearSearchText() } const activeElement: any = document.activeElement activeElement.blur() } const Routing = type === "book" ? Link.BookDetail : Link.Author const paramsCustom = type === "book" ? { _id: props._id, category: categoryLowerCase(props.category) } : { id: props._id } return ( {({ sendEvent }) => (
)}
) } export const SEARCH_SUGGESTION_QUERY = gql` query($value: String!) { books: searchBook(value: $value, limit: 5) { pageInfo { total } data { _id title thumbnailImage categories tags } } authors: searchAuthor(value: $value, limit: 3) { pageInfo { total } data { name _id profilePicture } } } ` type SearchSuggestionTData = { autoSuggestion: { books: BookItemData[] authors: AuthorItemData[] } } type SearchSuggestionQueryPropTypes = { variables: { value: string } children: ( results: SearchSuggestionTData["autoSuggestion"], loading ) => JSX.Element | JSX.Element[] } const SearchSuggestionQuery: React.SFC = ({ children, variables }) => { return ( {({ data, loading }: QueryResult<{ books: any; authors: any }, { value: string }>) => { if (data) { if (data.books && data.authors) { return children( { books: data.books.data, authors: data.authors.data }, loading ) } else { return children({ books: [], authors: [] }, loading) } } else { return children({ books: [], authors: [] }, loading) } }} ) } export interface SuggestionResultDialogPropTypes extends withTranslatePropType { visible: boolean value: string onClearSearchText?: () => void } interface ISuggestionResultContainer extends React.SFC { fragments: { author: any book: any } } export const SuggestionResultDialog: ISuggestionResultContainer = Object.assign( (props: SuggestionResultDialogPropTypes & withTranslatePropType) => { if (!props.t) { return (
) } return (
{(results) => { const BookItemResult = results.books.map((book) => ( )) const AuthorItemResult = results.authors.map((author) => ( )) return [...BookItemResult, ...AuthorItemResult] }} {(results) => [...results.authors, ...results.books].length <= 0 ? (

) : (
) }
) } ) export interface SearchTextInputPropTypes extends withTranslatePropType, React.InputHTMLAttributes { value: string isLoading: { [key: string]: boolean } forceFocus?: boolean onChange: (s: any) => void } const SearchTextInputContainer = styled.div.attrs({ className: "SearchTextInput" })` display: block; position: relative; .SearchTextInputContainer__input-wrap { display: flex; .jamplay-icon { padding: 8px 18px 8px 18px; color: white; font-size: 21px; } } .SeachTextInputContainer__input-wrap__input--result-active { border-radius: 5px 0px 0 0 !important; } .SeachTextInputContainer__search-button--result-active { border-radius: 0px 5px 0 0 !important; } ` export class SearchTextInput extends React.Component< SearchTextInputPropTypes, { isFocus: boolean } > { public static defaultProps = {} constructor(props) { super(props) this.state = { isFocus: props.initFocus || false } } public onInputFocus = (value: boolean) => { return (e) => { this.setState({ isFocus: value }) } } public pushResult = () => { Link.Search.go(Router, { category: "ALL", v: this.props.value, sort: "SCORE" }) } public onKeyPress = (e) => { if (e.key === "Enter") { console.log("[search] confirm...") const ga = getInstance() ga.sendEvent("search_input", "press_key", this.props.value, 0) this.pushResult() } } public onSearchIconClick = (e) => { const ga = getInstance() ga.sendEvent("search_input", "click-icon", this.props.value, 0) this.pushResult() } public onChange = (e) => { if (this.props.onChange) { this.props.onChange(e.target.value) } } public onClearSearchText = () => { if (this.props.onChange) { this.props.onChange("") } } public render() { if (!this.props.t) { throw new Error("1i8n component not provide") } const isResultVisible = (this.state.isFocus || !!this.props.forceFocus) && this.props.value.length > 0 return (
) } } const _providers = {} function getProvider(uri: string) { if (_providers[uri]) { return _providers[uri] } _providers[uri] = provider({ uri })(({ children }) => children as any) return _providers[uri] } export const SearchTextInputStandalone = compose( withTranslate, withState("isLoading", "setIsLoading", {}), withState("value", "onChange", (props) => { return props.initValue || "" }), withState("result", "setResult", { "": [], "books": [], "authors": [] }), lifecycle({ componentWillReceiveProps(nextProps) { if (this.props.initValue !== nextProps.initValue) { this.props.onChange(nextProps.initValue) } }, componentDidMount() { this.props.onChange(this.props.value) } }), (Component) => { return (props) => { // if not provide coreGraphQLUrl or server side will not create provider if (!props.coreGraphQLUrl) { return } const Provider = getProvider(props.coreGraphQLUrl) return ( ) } } )(SearchTextInput as any) as React.ComponentClass<{ initValue?: string forceFocus?: boolean t?: any coreGraphQLUrl?: string }> export interface SearchInput extends React.ComponentClass<{ autoFocus?: boolean forceFocus?: boolean initValue?: string, coreGraphQLUrl?: string, }> {} export default withTranslate(SearchTextInputStandalone) as SearchInput