diff options
-rw-r--r-- | apps/accessibility/css/dark.scss | 3 | ||||
-rw-r--r-- | apps/accessibility/css/highcontrast.scss | 3 | ||||
-rw-r--r-- | core/css/css-variables.scss | 3 | ||||
-rw-r--r-- | core/css/icons.scss | 1 | ||||
-rw-r--r-- | core/css/variables.scss | 3 | ||||
-rw-r--r-- | core/img/actions/filter.svg | 1 | ||||
-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 |
11 files changed, 152 insertions, 167 deletions
diff --git a/apps/accessibility/css/dark.scss b/apps/accessibility/css/dark.scss index c779f64ffd8..78203eae9f3 100644 --- a/apps/accessibility/css/dark.scss +++ b/apps/accessibility/css/dark.scss @@ -5,6 +5,9 @@ $color-main-background: #181818; $color-background-dark: lighten($color-main-background, 4%); $color-background-darker: lighten($color-main-background, 8%); +$color-placeholder-light: lighten($color-main-background, 10%); +$color-placeholder-dark: lighten($color-main-background, 20%); + $color-text-maxcontrast: darken($color-main-text, 30%); $color-text-light: darken($color-main-text, 10%); $color-text-lighter: darken($color-main-text, 20%); diff --git a/apps/accessibility/css/highcontrast.scss b/apps/accessibility/css/highcontrast.scss index 10ccdb47da6..707e34a2b42 100644 --- a/apps/accessibility/css/highcontrast.scss +++ b/apps/accessibility/css/highcontrast.scss @@ -5,6 +5,9 @@ $color-main-background: #fff; $color-background-dark: darken($color-main-background, 30%); $color-background-darker: darken($color-main-background, 30%); +$color-placeholder-light: darken($color-main-background, 30%); +$color-placeholder-dark: darken($color-main-background, 45%); + $color-text-maxcontrast: $color-main-text; $color-text-light: $color-main-text; $color-text-lighter: $color-main-text; diff --git a/core/css/css-variables.scss b/core/css/css-variables.scss index cf69a281237..8b09b006939 100644 --- a/core/css/css-variables.scss +++ b/core/css/css-variables.scss @@ -11,6 +11,9 @@ --color-background-dark: $color-background-dark; --color-background-darker: $color-background-darker; + --color-placeholder-light: $color-placeholder-light; + --color-placeholder-dark: $color-placeholder-dark; + --color-primary: $color-primary; --color-primary-light: $color-primary-light; --color-primary-text: $color-primary-text; diff --git a/core/css/icons.scss b/core/css/icons.scss index 16ffc4a5874..71aa8214df0 100644 --- a/core/css/icons.scss +++ b/core/css/icons.scss @@ -307,6 +307,7 @@ audio, canvas, embed, iframe, img, input, object, video { @include icon-black-white('upload', 'actions', 1, true); @include icon-black-white('user', 'actions', 1, true); @include icon-black-white('group', 'actions', 1, true); +@include icon-black-white('filter', 'actions', 1, true); @include icon-black-white('video', 'actions', 2, true); .icon-video-white { diff --git a/core/css/variables.scss b/core/css/variables.scss index 796b5ffe3e2..6f2d19e8723 100644 --- a/core/css/variables.scss +++ b/core/css/variables.scss @@ -40,6 +40,9 @@ $color-background-hover: nc-darken($color-main-background, 4%) !default; $color-background-dark: nc-darken($color-main-background, 7%) !default; $color-background-darker: nc-darken($color-main-background, 14%) !default; +$color-placeholder-light: nc-darken($color-main-background, 10%) !default; +$color-placeholder-dark: nc-darken($color-main-background, 20%) !default; + $color-primary: #0082c9 !default; $color-primary-light: mix($color-primary, $color-main-background, 10%) !default; $color-primary-text: #ffffff !default; diff --git a/core/img/actions/filter.svg b/core/img/actions/filter.svg new file mode 100644 index 00000000000..34c1dd2181c --- /dev/null +++ b/core/img/actions/filter.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M1.19 2.4l5.05 6.48v5.24c0 .49.4.88.88.88h1.76c.49 0 .88-.4.88-.88V8.88l5.05-6.47a.87.87 0 00-.7-1.41H1.89a.87.87 0 00-.7 1.4z"/></svg>
\ No newline at end of file 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; |