You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SearchableList.vue 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <!--
  2. - @copyright 2023 Marco Ambrosini <marcoambrosini@proton.me>
  3. -
  4. - @author Marco Ambrosini <marcoambrosini@proton.me>
  5. -
  6. - @license AGPL-3.0-or-later
  7. -
  8. - This program is free software: you can redistribute it and/or modify
  9. - it under the terms of the GNU Affero General Public License as
  10. - published by the Free Software Foundation, either version 3 of the
  11. - License, or (at your option) any later version.
  12. -
  13. - This program is distributed in the hope that it will be useful,
  14. - but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. - GNU Affero General Public License for more details.
  17. -
  18. - You should have received a copy of the GNU Affero General Public License
  19. - along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. -
  21. -->
  22. <template>
  23. <NcPopover :shown="opened"
  24. @show="opened = true"
  25. @hide="opened = false">
  26. <template #trigger>
  27. <slot ref="popoverTrigger" name="trigger" />
  28. </template>
  29. <div class="searchable-list__wrapper">
  30. <NcTextField :value.sync="searchTerm"
  31. :label="labelText"
  32. trailing-button-icon="close"
  33. :show-trailing-button="searchTerm !== ''"
  34. @trailing-button-click="clearSearch">
  35. <Magnify :size="20" />
  36. </NcTextField>
  37. <ul v-if="filteredList.length > 0" class="searchable-list__list">
  38. <li v-for="element in filteredList"
  39. :key="element.id"
  40. :title="element.displayName"
  41. role="button">
  42. <NcButton alignment="start"
  43. type="tertiary"
  44. :wide="true"
  45. @click="itemSelected(element)">
  46. <template #icon>
  47. <NcAvatar :user="element.user" :show-user-status="false" :hide-favorite="false" />
  48. </template>
  49. {{ element.displayName }}
  50. </NcButton>
  51. </li>
  52. </ul>
  53. <div v-else class="searchable-list__empty-content">
  54. <NcEmptyContent :name="emptyContentText">
  55. <template #icon>
  56. <AlertCircleOutline />
  57. </template>
  58. </NcEmptyContent>
  59. </div>
  60. </div>
  61. </NcPopover>
  62. </template>
  63. <script>
  64. import { NcPopover, NcTextField, NcAvatar, NcEmptyContent, NcButton } from '@nextcloud/vue'
  65. import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
  66. import Magnify from 'vue-material-design-icons/Magnify.vue'
  67. export default {
  68. name: 'SearchableList',
  69. components: {
  70. NcPopover,
  71. NcTextField,
  72. Magnify,
  73. AlertCircleOutline,
  74. NcAvatar,
  75. NcEmptyContent,
  76. NcButton,
  77. },
  78. props: {
  79. labelText: {
  80. type: String,
  81. default: 'this is a label',
  82. },
  83. searchList: {
  84. type: Array,
  85. required: true,
  86. },
  87. emptyContentText: {
  88. type: String,
  89. required: true,
  90. },
  91. },
  92. data() {
  93. return {
  94. opened: false,
  95. error: false,
  96. searchTerm: '',
  97. }
  98. },
  99. computed: {
  100. filteredList() {
  101. return this.searchList.filter((element) => {
  102. if (!this.searchTerm.toLowerCase().length) {
  103. return true
  104. }
  105. return ['displayName'].some(prop => element[prop].toLowerCase().includes(this.searchTerm.toLowerCase()))
  106. })
  107. },
  108. },
  109. methods: {
  110. clearSearch() {
  111. this.searchTerm = ''
  112. },
  113. itemSelected(element) {
  114. this.$emit('item-selected', element)
  115. this.clearSearch()
  116. this.opened = false
  117. },
  118. },
  119. }
  120. </script>
  121. <style lang="scss" scoped>
  122. .searchable-list {
  123. &__wrapper {
  124. padding: calc(var(--default-grid-baseline) * 3);
  125. display: flex;
  126. flex-direction: column;
  127. align-items: center;
  128. width: 250px;
  129. }
  130. &__list {
  131. width: 100%;
  132. max-height: 284px;
  133. overflow-y: auto;
  134. margin-top: var(--default-grid-baseline);
  135. padding: var(--default-grid-baseline);
  136. :deep(.button-vue) {
  137. border-radius: var(--border-radius-large) !important;
  138. span {
  139. font-weight: initial;
  140. }
  141. }
  142. }
  143. &__empty-content {
  144. margin-top: calc(var(--default-grid-baseline) * 3);
  145. }
  146. }
  147. </style>