Signed-off-by: julia.kirschenheuter <julia.kirschenheuter@nextcloud.com>tags/v28.0.0rc1
@@ -29,13 +29,19 @@ | |||
<Contacts :size="20" /> | |||
</template> | |||
<div class="contactsmenu__menu"> | |||
<label for="contactsmenu__menu__search">{{ t('core', 'Search contacts') }}</label> | |||
<input id="contactsmenu__menu__search" | |||
v-model="searchTerm" | |||
class="contactsmenu__menu__search" | |||
type="search" | |||
:placeholder="t('core', 'Search contacts …')" | |||
@input="onInputDebounced"> | |||
<div class="contactsmenu__menu__input-wrapper"> | |||
<NcTextField :value.sync="searchTerm" | |||
trailing-button-icon="close" | |||
ref="contactsMenuInput" | |||
:label="t('core', 'Search contacts')" | |||
:trailing-button-label="t('core','Reset search')" | |||
:show-trailing-button="searchTerm !== ''" | |||
:placeholder="t('core', 'Search contacts …')" | |||
id="contactsmenu__menu__search" | |||
class="contactsmenu__menu__search" | |||
@input="onInputDebounced" | |||
@trailing-button-click="onReset" /> | |||
</div> | |||
<NcEmptyContent v-if="error" :name="t('core', 'Could not load your contacts')"> | |||
<template #icon> | |||
<Magnify /> | |||
@@ -88,6 +94,7 @@ import { translate as t } from '@nextcloud/l10n' | |||
import Contact from '../components/ContactsMenu/Contact.vue' | |||
import logger from '../logger.js' | |||
import Nextcloud from '../mixins/Nextcloud.js' | |||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' | |||
export default { | |||
name: 'ContactsMenu', | |||
@@ -100,6 +107,7 @@ export default { | |||
NcEmptyContent, | |||
NcHeaderMenu, | |||
NcLoadingIcon, | |||
NcTextField, | |||
}, | |||
mixins: [Nextcloud], | |||
@@ -152,17 +160,40 @@ export default { | |||
onInputDebounced: debounce(function() { | |||
this.getContacts(this.searchTerm) | |||
}, 500), | |||
/** | |||
* Reset the search state | |||
*/ | |||
onReset() { | |||
this.searchTerm = '' | |||
this.contacts = [] | |||
this.focusInput() | |||
}, | |||
/** | |||
* Focus the search input on next tick | |||
*/ | |||
focusInput() { | |||
this.$nextTick(() => { | |||
this.$refs.contactsMenuInput.focus() | |||
this.$refs.contactsMenuInput.select() | |||
}) | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.contactsmenu { | |||
overflow-y: hidden; | |||
&__menu { | |||
/* show 2.5 to 4.5 entries depending on the screen height */ | |||
height: calc(100vh - 50px * 3); | |||
max-height: calc(50px * 6 + 2px + 26px); | |||
min-height: calc(50px * 3.5); | |||
display: flex; | |||
flex-direction: column; | |||
overflow: hidden; | |||
height: calc(50px * 6 + 2px + 26px); | |||
max-height: inherit; | |||
label[for="contactsmenu__menu__search"] { | |||
font-weight: bold; | |||
@@ -170,25 +201,22 @@ export default { | |||
margin-left: 13px; | |||
} | |||
&__input-wrapper { | |||
padding: 10px; | |||
z-index: 2; | |||
top: 0; | |||
} | |||
&__search { | |||
width: 100%; | |||
height: 34px; | |||
margin: 8px 0; | |||
&:focus, | |||
&:focus-visible, | |||
&:active { | |||
border-color: 2px solid var(--color-main-text) !important; | |||
box-shadow: 0 0 0 2px var(--color-main-background) !important; | |||
} | |||
margin-top: 0!important; | |||
} | |||
&__content { | |||
/* fixed max height of the parent container without the search input */ | |||
height: calc(100vh - 50px * 3 - 60px); | |||
max-height: calc(50px * 5); | |||
min-height: calc(50px * 3.5 - 50px); | |||
overflow-y: auto; | |||
margin-top: 10px; | |||
flex: 1 1 auto; | |||
&__footer { | |||
display: flex; |
@@ -35,41 +35,22 @@ | |||
<!-- Search form & filters wrapper --> | |||
<div class="unified-search__input-wrapper"> | |||
<label for="unified-search__input">{{ ariaLabel }}</label> | |||
<div class="unified-search__input-row"> | |||
<form class="unified-search__form" | |||
role="search" | |||
:class="{'icon-loading-small': isLoading}" | |||
@submit.prevent.stop="onInputEnter" | |||
@reset.prevent.stop="onReset"> | |||
<!-- Search input --> | |||
<input id="unified-search__input" | |||
ref="input" | |||
v-model="query" | |||
class="unified-search__form-input" | |||
type="search" | |||
:class="{'unified-search__form-input--with-reset': !!query}" | |||
:placeholder="t('core', 'Search {types} …', { types: typesNames.join(', ') })" | |||
aria-describedby="unified-search-desc" | |||
@input="onInputDebounced" | |||
@keypress.enter.prevent.stop="onInputEnter"> | |||
<p id="unified-search-desc" class="hidden-visually"> | |||
{{ t('core', 'Search starts once you start typing and results may be reached with the arrow keys') }} | |||
</p> | |||
<!-- Reset search button --> | |||
<input v-if="!!query && !isLoading" | |||
type="reset" | |||
class="unified-search__form-reset icon-close" | |||
:aria-label="t('core','Reset search')" | |||
value=""> | |||
<input v-if="!!query && !isLoading && !enableLiveSearch" | |||
type="submit" | |||
class="unified-search__form-submit icon-confirm" | |||
:aria-label="t('core','Start search')" | |||
value=""> | |||
</form> | |||
<NcTextField :value.sync="query" | |||
trailing-button-icon="close" | |||
:label="ariaLabel" | |||
ref="input" | |||
:trailing-button-label="t('core','Reset search')" | |||
:show-trailing-button="query !== ''" | |||
aria-describedby="unified-search-desc" | |||
class="unified-search__form-input" | |||
:class="{'unified-search__form-input--with-reset': !!query}" | |||
:placeholder="t('core', 'Search {types} …', { types: typesNames.join(', ') })" | |||
@trailing-button-click="onReset" | |||
@input="onInputDebounced" /> | |||
<p id="unified-search-desc" class="hidden-visually"> | |||
{{ t('core', 'Search starts once you start typing and results may be reached with the arrow keys') }} | |||
</p> | |||
<!-- Search filters --> | |||
<NcActions v-if="availableFilters.length > 1" | |||
@@ -152,6 +133,7 @@ import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' | |||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' | |||
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js' | |||
import NcHeaderMenu from '@nextcloud/vue/dist/Components/NcHeaderMenu.js' | |||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' | |||
import Magnify from 'vue-material-design-icons/Magnify.vue' | |||
@@ -175,6 +157,7 @@ export default { | |||
NcHeaderMenu, | |||
SearchResult, | |||
SearchResultPlaceholders, | |||
NcTextField, | |||
}, | |||
data() { | |||
@@ -738,7 +721,7 @@ export default { | |||
$margin: 10px; | |||
$input-height: 34px; | |||
$input-padding: 6px; | |||
$input-padding: 10px; | |||
.unified-search { | |||
&__input-wrapper { | |||
@@ -778,6 +761,7 @@ $input-padding: 6px; | |||
&__filters { | |||
margin: $margin 0 $margin math.div($margin, 2); | |||
padding-top: 5px; | |||
ul { | |||
display: inline-flex; | |||
justify-content: space-between; | |||
@@ -820,12 +804,6 @@ $input-padding: 6px; | |||
&::-webkit-search-results-decoration { | |||
-webkit-appearance: none; | |||
} | |||
// Ellipsis earlier if reset button is here | |||
.icon-loading-small &, | |||
&--with-reset { | |||
padding-right: $input-height; | |||
} | |||
} | |||
&-reset, &-submit { |