search-results.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. <template lang="pug">
  2. .search-results(v-if='searchIsFocused || search.length > 1')
  3. .search-results-container
  4. .search-results-help(v-if='search.length < 2')
  5. img(src='/svg/icon-search-alt.svg')
  6. .mt-4 Type at least 2 characters to start searching...
  7. .search-results-loader(v-else-if='searchIsLoading && results.length < 1')
  8. orbit-spinner(
  9. :animation-duration='1000'
  10. :size='100'
  11. color='#FFF'
  12. )
  13. .headline.mt-5 Searching...
  14. .search-results-none(v-else-if='!searchIsLoading && results.length < 1')
  15. img(src='/svg/icon-no-results.svg', alt='No Results')
  16. .subheading No pages matching your query.
  17. template(v-if='results.length > 0')
  18. v-subheader.white--text Found {{response.totalHits}} results
  19. v-list.radius-7(two-line)
  20. template(v-for='(item, idx) of results')
  21. v-list-tile(@click='', :key='item.id')
  22. v-list-tile-avatar(tile)
  23. img(src='/svg/icon-selective-highlighting.svg')
  24. v-list-tile-content
  25. v-list-tile-title(v-html='item.title')
  26. v-list-tile-sub-title(v-html='item.description')
  27. .caption.grey--text.mt-1(v-html='item.path')
  28. v-list-tile-action
  29. v-chip(label) {{item.locale.toUpperCase()}}
  30. v-divider(v-if='idx < results.length - 1')
  31. v-pagination.mt-3(
  32. v-if='paginationLength > 1'
  33. dark
  34. v-model='pagination'
  35. :length='paginationLength'
  36. )
  37. template(v-if='suggestions.length > 0')
  38. v-subheader.white--text.mt-3 Did you mean...
  39. v-list.radius-7(dense, dark)
  40. template(v-for='(term, idx) of suggestions')
  41. v-list-tile(:key='term', @click='setSearchTerm(term)')
  42. v-list-tile-avatar
  43. v-icon search
  44. v-list-tile-content
  45. v-list-tile-title(v-html='term')
  46. v-divider(v-if='idx < suggestions.length - 1')
  47. .text-xs-center.pt-5(v-if='search.length > 1')
  48. v-btn(outline, color='orange', @click='search = ``', v-if='results.length > 0')
  49. v-icon(left) save
  50. span Copy Search Link
  51. v-btn(outline, color='pink', @click='search = ``')
  52. v-icon(left) clear
  53. span Close
  54. </template>
  55. <script>
  56. import _ from 'lodash'
  57. import { get, sync } from 'vuex-pathify'
  58. import { OrbitSpinner } from 'epic-spinners'
  59. import searchPagesQuery from 'gql/common/common-pages-query-search.gql'
  60. export default {
  61. components: {
  62. OrbitSpinner
  63. },
  64. data() {
  65. return {
  66. pagination: 1,
  67. response: {
  68. results: [],
  69. suggestions: [],
  70. totalHits: 0
  71. }
  72. }
  73. },
  74. computed: {
  75. search: sync('site/search'),
  76. searchIsFocused: sync('site/searchIsFocused'),
  77. searchIsLoading: sync('site/searchIsLoading'),
  78. searchRestrictLocale: sync('site/searchRestrictLocale'),
  79. searchRestrictPath: sync('site/searchRestrictPath'),
  80. results() {
  81. return this.response.results ? this.response.results : []
  82. },
  83. hits() {
  84. return this.response.totalHits ? this.response.totalHits : 0
  85. },
  86. suggestions() {
  87. return this.response.suggestions ? this.response.suggestions : []
  88. },
  89. paginationLength() {
  90. return this.response.totalHits > 0 ? 0 : Math.ceil(this.response.totalHits / 10)
  91. }
  92. },
  93. watch: {
  94. search(newValue, oldValue) {
  95. if (newValue.length < 2) {
  96. this.response.results = []
  97. }
  98. }
  99. },
  100. methods: {
  101. setSearchTerm(term) {
  102. this.search = term
  103. }
  104. },
  105. apollo: {
  106. response: {
  107. query: searchPagesQuery,
  108. variables() {
  109. return {
  110. query: this.search
  111. }
  112. },
  113. fetchPolicy: 'network-only',
  114. debounce: 300,
  115. throttle: 1000,
  116. skip() {
  117. return !this.search || this.search.length < 2
  118. },
  119. update: (data) => _.get(data, 'pages.search', {}),
  120. watchLoading (isLoading) {
  121. this.searchIsLoading = isLoading
  122. }
  123. }
  124. }
  125. }
  126. </script>
  127. <style lang="scss">
  128. .search-results {
  129. position: fixed;
  130. top: 64px;
  131. left: 0;
  132. width: 100%;
  133. height: calc(100% - 64px);
  134. background-color: rgba(0,0,0,.9);
  135. z-index: 100;
  136. text-align: center;
  137. animation: searchResultsReveal .6s ease;
  138. @media #{map-get($display-breakpoints, 'sm-and-down')} {
  139. top: 112px;
  140. }
  141. &-container {
  142. margin: 12px auto;
  143. width: 90vw;
  144. max-width: 1024px;
  145. }
  146. &-help {
  147. text-align: center;
  148. padding: 32px 0;
  149. font-size: 18px;
  150. font-weight: 300;
  151. color: #FFF;
  152. img {
  153. width: 104px;
  154. }
  155. }
  156. &-loader {
  157. display: flex;
  158. justify-content: center;
  159. align-items: center;
  160. flex-direction: column;
  161. padding: 32px 0;
  162. color: #FFF;
  163. }
  164. &-none {
  165. color: #FFF;
  166. img {
  167. width: 200px;
  168. }
  169. }
  170. }
  171. @keyframes searchResultsReveal {
  172. 0% {
  173. background-color: rgba(0,0,0,0);
  174. padding-top: 32px;
  175. }
  176. 100% {
  177. background-color: rgba(0,0,0,.9);
  178. padding-top: 0;
  179. }
  180. }
  181. </style>