Fix search placeholder animation & dark theme compatibility

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
John Molakvoæ (skjnldsv) 2020-08-05 14:42:08 +02:00
parent f359529555
commit f04b182b94
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF
11 changed files with 152 additions and 167 deletions

View 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%);

View 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;

View 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;

View 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 {

View 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;

View File

@ -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>

After

Width:  |  Height:  |  Size: 207 B

View 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,
},
}
},

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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">
@ -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;