]> source.dussan.org Git - nextcloud-server.git/commitdiff
Fix search placeholder animation & dark theme compatibility
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Wed, 5 Aug 2020 12:42:08 +0000 (14:42 +0200)
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Thu, 3 Sep 2020 07:03:10 +0000 (09:03 +0200)
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
apps/accessibility/css/dark.scss
apps/accessibility/css/highcontrast.scss
core/css/css-variables.scss
core/css/icons.scss
core/css/variables.scss
core/img/actions/filter.svg [new file with mode: 0644]
core/src/components/HeaderMenu.vue
core/src/components/UnifiedSearch/SearchFilter.vue [deleted file]
core/src/components/UnifiedSearch/SearchResultPlaceholder.vue [deleted file]
core/src/components/UnifiedSearch/SearchResultPlaceholders.vue [new file with mode: 0644]
core/src/views/UnifiedSearch.vue

index c779f64ffd82f37e7a0dfef57aec37495a9ff2ad..78203eae9f337c6764d00991a082ce77298c65d3 100644 (file)
@@ -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%);
index 10ccdb47da629bf0df03d3b846dec74bd6ed752f..707e34a2b42832b955eeb9759b7bcd9e9ad66185 100644 (file)
@@ -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;
index cf69a28123749857cf6819cb387fe4bfb98a9603..8b09b006939caef90108f4273a8955b96e0a08c5 100644 (file)
@@ -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;
index 16ffc4a58746739cffba045bd35a5663c55a5de1..71aa8214df0286e19d860fa6402727c22e909542 100644 (file)
@@ -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 {
index 796b5ffe3e2e1dfd08216a8390370ab11620441e..6f2d19e87238087f464cd578401fccf003e0862c 100644 (file)
@@ -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 (file)
index 0000000..34c1dd2
--- /dev/null
@@ -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
index 2cc5b79d6ddb7904b8fda3399b76cb17e042855d..9848dc45e38df89ff4a01b2df6983d78e3cc7af2 100644 (file)
@@ -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 (file)
index f3c06a2..0000000
+++ /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 (file)
index 7eb0915..0000000
+++ /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 (file)
index 0000000..31f85f4
--- /dev/null
@@ -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>
index 2d0b59db5dcb55f34bf8f257febf1c3dc1608d74..2251ac2dd39739ae79d35b347af2a8007fdaa9f3 100644 (file)
@@ -22,6 +22,7 @@
 <template>
        <HeaderMenu id="unified-search"
                class="unified-search"
+               exclude-click-outside-classes="popover"
                :open.sync="open"
                @open="onOpen"
                @close="onClose">
                                :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}) }}
 
 <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;