diff options
author | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-08-05 14:42:08 +0200 |
---|---|---|
committer | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-09-03 09:03:10 +0200 |
commit | f04b182b9447231b32cd5b5f8060314dee4fd801 (patch) | |
tree | 56b068420cc230af0ab6d7d036bdddc482b2cf52 /core/src | |
parent | f359529555fbf6fa344869d01f0b3c17ecce46c8 (diff) | |
download | nextcloud-server-f04b182b9447231b32cd5b5f8060314dee4fd801.tar.gz nextcloud-server-f04b182b9447231b32cd5b5f8060314dee4fd801.zip |
Fix search placeholder animation & dark theme compatibility
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'core/src')
-rw-r--r-- | core/src/components/HeaderMenu.vue | 11 | ||||
-rw-r--r-- | core/src/components/UnifiedSearch/SearchFilter.vue | 77 | ||||
-rw-r--r-- | core/src/components/UnifiedSearch/SearchResultPlaceholder.vue | 68 | ||||
-rw-r--r-- | core/src/components/UnifiedSearch/SearchResultPlaceholders.vue | 100 | ||||
-rw-r--r-- | core/src/views/UnifiedSearch.vue | 49 |
5 files changed, 138 insertions, 167 deletions
diff --git a/core/src/components/HeaderMenu.vue b/core/src/components/HeaderMenu.vue index 2cc5b79d6dd..9848dc45e38 100644 --- a/core/src/components/HeaderMenu.vue +++ b/core/src/components/HeaderMenu.vue @@ -20,7 +20,7 @@ - --> <template> - <div v-click-outside="closeMenu" :class="{ 'header-menu--opened': opened }" class="header-menu"> + <div v-click-outside="clickOutsideConfig" :class="{ 'header-menu--opened': opened }" class="header-menu"> <a class="header-menu__trigger" href="#" :aria-controls="`header-menu-${id}`" @@ -44,6 +44,7 @@ <script> import { directive as ClickOutside } from 'v-click-outside' import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus' +import excludeClickOutsideClasses from '@nextcloud/vue/dist/Mixins/excludeClickOutsideClasses' export default { name: 'HeaderMenu', @@ -52,6 +53,10 @@ export default { ClickOutside, }, + mixins: [ + excludeClickOutsideClasses, + ], + props: { id: { type: String, @@ -66,6 +71,10 @@ export default { data() { return { opened: this.open, + clickOutsideConfig: { + handler: this.closeMenu, + middleware: this.clickOutsideMiddleware, + }, } }, diff --git a/core/src/components/UnifiedSearch/SearchFilter.vue b/core/src/components/UnifiedSearch/SearchFilter.vue deleted file mode 100644 index f3c06a24528..00000000000 --- a/core/src/components/UnifiedSearch/SearchFilter.vue +++ /dev/null @@ -1,77 +0,0 @@ - <!-- - - @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com> - - - - @author John Molakvoæ <skjnldsv@protonmail.com> - - - - @license GNU AGPL version 3 or any later version - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU Affero General Public License as - - published by the Free Software Foundation, either version 3 of the - - License, or (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU Affero General Public License for more details. - - - - You should have received a copy of the GNU Affero General Public License - - along with this program. If not, see <http://www.gnu.org/licenses/>. - - - --> -<template> - <li> - <a :title="t('core', 'Search for {name} only', { name })" - class="unified-search__filter" - href="#" - @click.prevent="onClick"> - {{ filter }} - </a> - </li> -</template> - -<script> -export default { - name: 'SearchFilter', - - props: { - type: { - type: String, - required: true, - }, - name: { - type: String, - required: true, - }, - }, - computed: { - filter() { - return `in:${this.type}` - }, - }, - - methods: { - onClick() { - this.$emit('click', this.filter) - }, - }, - -} -</script> - -<style lang="scss" scoped> -.unified-search__filter { - height: 1em; - margin-right: 5px; - padding: 3px 8px; - border-radius: 1em; - background-color: var(--color-background-darker); - - &:active, - &:focus, - &:hover { - background-color: var(--color-background-hover); - } -} - -</style> diff --git a/core/src/components/UnifiedSearch/SearchResultPlaceholder.vue b/core/src/components/UnifiedSearch/SearchResultPlaceholder.vue deleted file mode 100644 index 7eb0915b359..00000000000 --- a/core/src/components/UnifiedSearch/SearchResultPlaceholder.vue +++ /dev/null @@ -1,68 +0,0 @@ -<template> - <svg - class="unified-search__result-placeholder" - xmlns="http://www.w3.org/2000/svg" - fill="url(#unified-search__result-placeholder-gradient)"> - <defs> - <linearGradient id="unified-search__result-placeholder-gradient"> - <stop offset="0%" stop-color="#ededed"><animate attributeName="stop-color" - values="#ededed; #ededed; #cccccc; #cccccc; #ededed" - dur="2s" - repeatCount="indefinite" /></stop> - <stop offset="100%" stop-color="#cccccc"><animate attributeName="stop-color" - values="#cccccc; #ededed; #ededed; #cccccc; #cccccc" - dur="2s" - repeatCount="indefinite" /></stop> - </linearGradient> - </defs> - <rect class="unified-search__result-placeholder-icon" /> - <rect class="unified-search__result-placeholder-line-one" /> - <rect class="unified-search__result-placeholder-line-two" :style="{width: `calc(${randWidth}%)`}" /> - </svg> -</template> - -<script> -export default { - name: 'SearchResultPlaceholder', - - data() { - return { - randWidth: Math.floor(Math.random() * 20) + 30, - } - }, -} -</script> - -<style lang="scss" scoped> -$clickable-area: 44px; -$margin: 10px; - -.unified-search__result-placeholder { - width: calc(100% - 2 * #{$margin}); - height: $clickable-area; - margin: $margin; - - &-icon { - width: $clickable-area; - height: $clickable-area; - rx: var(--border-radius); - ry: var(--border-radius); - } - - &-line-one, - &-line-two { - width: calc(100% - #{$margin + $clickable-area}); - height: 1em; - x: $margin + $clickable-area; - } - - &-line-one { - y: 5px; - } - - &-line-two { - y: 25px; - } -} - -</style> diff --git a/core/src/components/UnifiedSearch/SearchResultPlaceholders.vue b/core/src/components/UnifiedSearch/SearchResultPlaceholders.vue new file mode 100644 index 00000000000..31f85f413d3 --- /dev/null +++ b/core/src/components/UnifiedSearch/SearchResultPlaceholders.vue @@ -0,0 +1,100 @@ +<template> + <ul> + <!-- Placeholder animation --> + <svg class="unified-search__result-placeholder-gradient"> + <defs> + <linearGradient id="unified-search__result-placeholder-gradient"> + <stop offset="0%" :stop-color="light"> + <animate attributeName="stop-color" + :values="`${light}; ${light}; ${dark}; ${dark}; ${light}`" + dur="2s" + repeatCount="indefinite" /> + </stop> + <stop offset="100%" :stop-color="dark"> + <animate attributeName="stop-color" + :values="`${dark}; ${light}; ${light}; ${dark}; ${dark}`" + dur="2s" + repeatCount="indefinite" /> + </stop> + </linearGradient> + </defs> + </svg> + + <!-- Placeholders --> + <li v-for="placeholder in [1, 2, 3]" :key="placeholder"> + <svg + class="unified-search__result-placeholder" + xmlns="http://www.w3.org/2000/svg" + fill="url(#unified-search__result-placeholder-gradient)"> + <rect class="unified-search__result-placeholder-icon" /> + <rect class="unified-search__result-placeholder-line-one" /> + <rect class="unified-search__result-placeholder-line-two" :style="{width: `calc(${randWidth()}%)`}" /> + </svg> + </li> + </ul> +</template> + +<script> +export default { + name: 'SearchResultPlaceholders', + + data() { + return { + light: null, + dark: null, + } + }, + mounted() { + const styles = getComputedStyle(document.documentElement) + this.dark = styles.getPropertyValue('--color-placeholder-dark') + this.light = styles.getPropertyValue('--color-placeholder-light') + }, + + methods: { + randWidth() { + return Math.floor(Math.random() * 20) + 30 + }, + }, +} +</script> + +<style lang="scss" scoped> +$clickable-area: 44px; +$margin: 10px; + +.unified-search__result-placeholder-gradient { + position: fixed; + height: 0; + width: 0; + z-index: -1; +} + +.unified-search__result-placeholder { + width: calc(100% - 2 * #{$margin}); + height: $clickable-area; + margin: $margin; + + &-icon { + width: $clickable-area; + height: $clickable-area; + rx: var(--border-radius); + ry: var(--border-radius); + } + + &-line-one, + &-line-two { + width: calc(100% - #{$margin + $clickable-area}); + height: 1em; + x: $margin + $clickable-area; + } + + &-line-one { + y: 5px; + } + + &-line-two { + y: 25px; + } +} + +</style> diff --git a/core/src/views/UnifiedSearch.vue b/core/src/views/UnifiedSearch.vue index 2d0b59db5dc..2251ac2dd39 100644 --- a/core/src/views/UnifiedSearch.vue +++ b/core/src/views/UnifiedSearch.vue @@ -22,6 +22,7 @@ <template> <HeaderMenu id="unified-search" class="unified-search" + exclude-click-outside-classes="popover" :open.sync="open" @open="onOpen" @close="onClose"> @@ -39,26 +40,21 @@ :placeholder="t('core', 'Search {types} …', { types: typesNames.join(', ').toLowerCase() })" @input="onInputDebounced" @keypress.enter.prevent.stop="onInputEnter"> - </div> - - <!-- Search filters --> - <div v-if="availableFilters.length > 1" class="unified-search__filters"> - <ul> - <SearchFilter v-for="type in availableFilters" + <!-- Search filters --> + <Actions v-if="availableFilters.length > 1" class="unified-search__filters" placement="bottom"> + <ActionButton v-for="type in availableFilters" :key="type" - :type="type" - :name="typesMap[type]" - @click="onClickFilter" /> - </ul> + icon="icon-filter" + :title="t('core', 'Search for {name} only', { name: typesMap[type] })" + @click="onClickFilter(`in:${type}`)"> + {{ `in:${type}` }} + </ActionButton> + </Actions> </div> <template v-if="!hasResults"> <!-- Loading placeholders --> - <ul v-if="isLoading"> - <li v-for="placeholder in [1, 2, 3]" :key="placeholder"> - <SearchResultPlaceholder /> - </li> - </ul> + <SearchResultPlaceholders v-if="isLoading" /> <EmptyContent v-else-if="isValidQuery && isDoneSearching" icon="icon-search"> {{ t('core', 'No results for {query}', {query}) }} @@ -109,6 +105,9 @@ <script> import { minSearchLength, getTypes, search, defaultLimit, regexFilterIn, regexFilterNot } from '../services/UnifiedSearchService' +import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' +import Actions from '@nextcloud/vue/dist/Components/Actions' +import debounce from 'debounce' import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' import Magnify from 'vue-material-design-icons/Magnify' import debounce from 'debounce' @@ -117,18 +116,20 @@ import { emit } from '@nextcloud/event-bus' import HeaderMenu from '../components/HeaderMenu' import SearchFilter from '../components/UnifiedSearch/SearchFilter' import SearchResult from '../components/UnifiedSearch/SearchResult' -import SearchResultPlaceholder from '../components/UnifiedSearch/SearchResultPlaceholder' +import SearchResultPlaceholders from '../components/UnifiedSearch/SearchResultPlaceholders' export default { name: 'UnifiedSearch', components: { + ActionButton, + Actions, EmptyContent, HeaderMenu, Magnify, SearchFilter, SearchResult, - SearchResultPlaceholder, + SearchResultPlaceholders, }, data() { @@ -352,12 +353,12 @@ export default { types = this.typesIDs.filter(type => this.usedFiltersIn.indexOf(type) > -1) } - // remove any filters from the query + // Remove any filters from the query query = query.replace(regexFilterIn, '').replace(regexFilterNot, '') console.debug('Searching', query, 'in', types) - // reset search if the query changed + // Reset search if the query changed this.resetState() types.forEach(async type => { @@ -566,10 +567,13 @@ $input-padding: 6px; } &__input-wrapper { + width: 100%; position: sticky; // above search results z-index: 2; top: 0; + display: inline-flex; + align-items: center; background-color: var(--color-main-background); } @@ -582,8 +586,7 @@ $input-padding: 6px; } &__input { - // Minus margins - width: calc(100% - 2 * #{$margin}); + width: 100%; height: 34px; margin: $margin; padding: $input-padding; @@ -596,6 +599,10 @@ $input-padding: 6px; } } + &__filters { + margin-right: $margin / 2; + } + &__results { &::before { display: block; |